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
|
* @param {string} projectDir - Project directory
|
||||||
*/
|
*/
|
||||||
async cleanup(projectDir) {
|
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)) {
|
if (await fs.pathExists(workflowsDir)) {
|
||||||
await fs.remove(bmadWorkflowsDir);
|
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}`));
|
console.log(chalk.dim(` Removed old BMAD workflows from ${this.name}`));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -115,11 +118,9 @@ class AntigravitySetup extends BaseIdeSetup {
|
|||||||
await this.cleanup(projectDir);
|
await this.cleanup(projectDir);
|
||||||
|
|
||||||
// Create .agent/workflows directory structure
|
// Create .agent/workflows directory structure
|
||||||
const agentDir = path.join(projectDir, this.configDir);
|
const workflowsDir = path.join(projectDir, this.configDir, this.workflowsDir);
|
||||||
const workflowsDir = path.join(agentDir, this.workflowsDir);
|
|
||||||
const bmadWorkflowsDir = path.join(workflowsDir, 'bmad');
|
|
||||||
|
|
||||||
await this.ensureDir(bmadWorkflowsDir);
|
await this.ensureDir(workflowsDir);
|
||||||
|
|
||||||
// Generate agent launchers using AgentCommandGenerator
|
// Generate agent launchers using AgentCommandGenerator
|
||||||
// This creates small launcher files that reference the actual agents in _bmad/
|
// 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
|
// Write agent launcher files with FLATTENED naming using shared utility
|
||||||
// Antigravity ignores directory structure, so we flatten to: bmad_module_name.md
|
// Antigravity ignores directory structure, so we flatten to: bmad_module_name.md
|
||||||
// This creates slash commands like /bmad_bmm_dev instead of /dev
|
// 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
|
// Process Antigravity specific injections for installed modules
|
||||||
// Use pre-collected configuration if available, or skip if already configured
|
// Use pre-collected configuration if available, or skip if already configured
|
||||||
@@ -148,12 +149,12 @@ class AntigravitySetup extends BaseIdeSetup {
|
|||||||
const { artifacts: workflowArtifacts } = await workflowGen.collectWorkflowArtifacts(bmadDir);
|
const { artifacts: workflowArtifacts } = await workflowGen.collectWorkflowArtifacts(bmadDir);
|
||||||
|
|
||||||
// Write workflow-command artifacts with FLATTENED naming using shared utility
|
// 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!)
|
// Generate task and tool commands using FLAT naming (not nested!)
|
||||||
// Use the new generateDashTaskToolCommands method with explicit target directory
|
// Use the new generateDashTaskToolCommands method with explicit target directory
|
||||||
const taskToolGen = new TaskToolCommandGenerator();
|
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.green(`✓ ${this.name} configured:`));
|
||||||
console.log(chalk.dim(` - ${agentCount} agents installed`));
|
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)`));
|
console.log(chalk.yellow(`\n Note: Antigravity uses flattened slash commands (e.g., /bmad_module_agents_name)`));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -430,12 +431,10 @@ class AntigravitySetup extends BaseIdeSetup {
|
|||||||
* @returns {Object} Installation result
|
* @returns {Object} Installation result
|
||||||
*/
|
*/
|
||||||
async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) {
|
async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) {
|
||||||
// Create .agent/workflows/bmad directory structure (same as regular agents)
|
// Create .agent/workflows directory structure
|
||||||
const agentDir = path.join(projectDir, this.configDir);
|
const workflowsDir = path.join(projectDir, this.configDir, this.workflowsDir);
|
||||||
const workflowsDir = path.join(agentDir, this.workflowsDir);
|
|
||||||
const bmadWorkflowsDir = path.join(workflowsDir, 'bmad');
|
|
||||||
|
|
||||||
await fs.ensureDir(bmadWorkflowsDir);
|
await fs.ensureDir(workflowsDir);
|
||||||
|
|
||||||
// Create custom agent launcher with same pattern as regular agents
|
// Create custom agent launcher with same pattern as regular agents
|
||||||
const launcherContent = `name: '${agentName}'
|
const launcherContent = `name: '${agentName}'
|
||||||
@@ -458,7 +457,7 @@ usage: |
|
|||||||
|
|
||||||
// Use underscore format: bmad_custom_fred-commit-poet.md
|
// Use underscore format: bmad_custom_fred-commit-poet.md
|
||||||
const fileName = customAgentDashName(agentName);
|
const fileName = customAgentDashName(agentName);
|
||||||
const launcherPath = path.join(bmadWorkflowsDir, fileName);
|
const launcherPath = path.join(workflowsDir, fileName);
|
||||||
|
|
||||||
// Write the launcher file
|
// Write the launcher file
|
||||||
await fs.writeFile(launcherPath, launcherContent, 'utf8');
|
await fs.writeFile(launcherPath, launcherContent, 'utf8');
|
||||||
|
|||||||
@@ -25,30 +25,21 @@ class IFlowSetup extends BaseIdeSetup {
|
|||||||
async setup(projectDir, bmadDir, options = {}) {
|
async setup(projectDir, bmadDir, options = {}) {
|
||||||
console.log(chalk.cyan(`Setting up ${this.name}...`));
|
console.log(chalk.cyan(`Setting up ${this.name}...`));
|
||||||
|
|
||||||
// Create .iflow/commands/bmad directory structure
|
// Clean up old BMAD installation first
|
||||||
const iflowDir = path.join(projectDir, this.configDir);
|
await this.cleanup(projectDir);
|
||||||
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');
|
|
||||||
|
|
||||||
await this.ensureDir(agentsDir);
|
// Create .iflow/commands directory structure (flat files, no bmad subfolder)
|
||||||
await this.ensureDir(tasksDir);
|
const iflowDir = path.join(projectDir, this.configDir);
|
||||||
await this.ensureDir(workflowsDir);
|
const commandsDir = path.join(iflowDir, this.commandsDir);
|
||||||
|
|
||||||
|
await this.ensureDir(commandsDir);
|
||||||
|
|
||||||
// Generate agent launchers
|
// Generate agent launchers
|
||||||
const agentGen = new AgentCommandGenerator(this.bmadFolderName);
|
const agentGen = new AgentCommandGenerator(this.bmadFolderName);
|
||||||
const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []);
|
const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []);
|
||||||
|
|
||||||
// Setup agents as commands
|
// Setup agents as commands (flat files with dash naming)
|
||||||
let agentCount = 0;
|
const agentCount = await agentGen.writeDashArtifacts(commandsDir, agentArtifacts);
|
||||||
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++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get tasks and workflows (ALL workflows now generate commands)
|
// Get tasks and workflows (ALL workflows now generate commands)
|
||||||
const tasks = await this.getTasks(bmadDir);
|
const tasks = await this.getTasks(bmadDir);
|
||||||
@@ -57,26 +48,11 @@ class IFlowSetup extends BaseIdeSetup {
|
|||||||
const workflowGenerator = new WorkflowCommandGenerator(this.bmadFolderName);
|
const workflowGenerator = new WorkflowCommandGenerator(this.bmadFolderName);
|
||||||
const { artifacts: workflowArtifacts, counts: workflowCounts } = await workflowGenerator.collectWorkflowArtifacts(bmadDir);
|
const { artifacts: workflowArtifacts, counts: workflowCounts } = await workflowGenerator.collectWorkflowArtifacts(bmadDir);
|
||||||
|
|
||||||
// Setup tasks as commands
|
// Setup workflows as commands (flat files with dash naming)
|
||||||
let taskCount = 0;
|
const workflowCount = await workflowGenerator.writeDashArtifacts(commandsDir, workflowArtifacts);
|
||||||
for (const task of tasks) {
|
|
||||||
const content = await this.readFile(task.path);
|
|
||||||
const commandContent = this.createTaskCommand(task, content);
|
|
||||||
|
|
||||||
const targetPath = path.join(tasksDir, `${task.module}-${task.name}.md`);
|
// TODO: tasks not yet implemented with flat naming
|
||||||
await this.writeFile(targetPath, commandContent);
|
const taskCount = 0;
|
||||||
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++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(chalk.green(`✓ ${this.name} configured:`));
|
console.log(chalk.green(`✓ ${this.name} configured:`));
|
||||||
console.log(chalk.dim(` - ${agentCount} agent commands created`));
|
console.log(chalk.dim(` - ${agentCount} agent commands created`));
|
||||||
@@ -132,11 +108,20 @@ Part of the BMAD ${task.module.toUpperCase()} module.
|
|||||||
* Cleanup iFlow configuration
|
* Cleanup iFlow configuration
|
||||||
*/
|
*/
|
||||||
async cleanup(projectDir) {
|
async cleanup(projectDir) {
|
||||||
const fs = require('fs-extra');
|
const commandsDir = path.join(projectDir, this.configDir, this.commandsDir);
|
||||||
const bmadCommandsDir = path.join(projectDir, this.configDir, this.commandsDir, 'bmad');
|
const bmadFolder = path.join(commandsDir, 'bmad');
|
||||||
|
|
||||||
if (await fs.pathExists(bmadCommandsDir)) {
|
// Remove old bmad subfolder if it exists
|
||||||
await fs.remove(bmadCommandsDir);
|
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`));
|
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
|
* @returns {Object} Installation result
|
||||||
*/
|
*/
|
||||||
async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) {
|
async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) {
|
||||||
const iflowDir = path.join(projectDir, this.configDir);
|
const commandsDir = path.join(projectDir, this.configDir, this.commandsDir);
|
||||||
const bmadCommandsDir = path.join(iflowDir, this.commandsDir, 'bmad');
|
|
||||||
|
|
||||||
// Create .iflow/commands/bmad directory if it doesn't exist
|
// Create .iflow/commands directory if it doesn't exist
|
||||||
await fs.ensureDir(bmadCommandsDir);
|
await fs.ensureDir(commandsDir);
|
||||||
|
|
||||||
// Create custom agent launcher
|
// Create custom agent launcher
|
||||||
const launcherContent = `# ${agentName} Custom Agent
|
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*`;
|
*Generated by BMAD Method*`;
|
||||||
|
|
||||||
const fileName = `custom-${agentName.toLowerCase()}.md`;
|
const { customAgentDashName } = require('./shared/path-utils');
|
||||||
const launcherPath = path.join(bmadCommandsDir, fileName);
|
const fileName = customAgentDashName(agentName);
|
||||||
|
const launcherPath = path.join(commandsDir, fileName);
|
||||||
|
|
||||||
// Write the launcher file
|
// Write the launcher file
|
||||||
await fs.writeFile(launcherPath, launcherContent, 'utf8');
|
await fs.writeFile(launcherPath, launcherContent, 'utf8');
|
||||||
|
|||||||
@@ -2,19 +2,17 @@ const path = require('node:path');
|
|||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const { BaseIdeSetup } = require('./_base-ide');
|
const { BaseIdeSetup } = require('./_base-ide');
|
||||||
const chalk = require('chalk');
|
const chalk = require('chalk');
|
||||||
const { getAgentsFromBmad, getTasksFromBmad } = require('./shared/bmad-artifacts');
|
const { UnifiedInstaller, NamingStyle, TemplateType } = require('./shared/unified-installer');
|
||||||
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Qwen Code setup handler
|
* Qwen Code setup handler
|
||||||
* Creates TOML command files in .qwen/commands/BMad/
|
* Creates TOML command files in .qwen/commands/
|
||||||
*/
|
*/
|
||||||
class QwenSetup extends BaseIdeSetup {
|
class QwenSetup extends BaseIdeSetup {
|
||||||
constructor() {
|
constructor() {
|
||||||
super('qwen', 'Qwen Code');
|
super('qwen', 'Qwen Code');
|
||||||
this.configDir = '.qwen';
|
this.configDir = '.qwen';
|
||||||
this.commandsDir = 'commands';
|
this.commandsDir = 'commands';
|
||||||
this.bmadDir = 'bmad';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -26,118 +24,43 @@ class QwenSetup extends BaseIdeSetup {
|
|||||||
async setup(projectDir, bmadDir, options = {}) {
|
async setup(projectDir, bmadDir, options = {}) {
|
||||||
console.log(chalk.cyan(`Setting up ${this.name}...`));
|
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 qwenDir = path.join(projectDir, this.configDir);
|
||||||
const commandsDir = path.join(qwenDir, this.commandsDir);
|
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
|
// Update existing settings.json if present
|
||||||
await this.updateSettings(qwenDir);
|
await this.updateSettings(qwenDir);
|
||||||
|
|
||||||
// Clean up old configuration if exists
|
// Clean up old configuration
|
||||||
await this.cleanupOldConfig(qwenDir);
|
await this.cleanupOldConfig(qwenDir);
|
||||||
|
await this.cleanup(projectDir);
|
||||||
|
|
||||||
// Generate agent launchers
|
// Use the unified installer with QWEN template for TOML format
|
||||||
const agentGen = new AgentCommandGenerator(this.bmadFolderName);
|
const installer = new UnifiedInstaller(this.bmadFolderName);
|
||||||
const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []);
|
const counts = await installer.install(
|
||||||
|
projectDir,
|
||||||
// Get tasks, tools, and workflows (standalone only for tools/workflows)
|
bmadDir,
|
||||||
const tasks = await getTasksFromBmad(bmadDir, options.selectedModules || []);
|
{
|
||||||
const tools = await this.getTools(bmadDir, true);
|
targetDir: commandsDir,
|
||||||
const workflows = await this.getWorkflows(bmadDir, true);
|
namingStyle: NamingStyle.FLAT_DASH,
|
||||||
|
templateType: TemplateType.QWEN,
|
||||||
// Create directories for each module (including standalone)
|
fileExtension: '.toml',
|
||||||
const modules = new Set();
|
},
|
||||||
for (const item of [...agentArtifacts, ...tasks, ...tools, ...workflows]) modules.add(item.module);
|
options.selectedModules || [],
|
||||||
|
);
|
||||||
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}`));
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(chalk.green(`✓ ${this.name} configured:`));
|
console.log(chalk.green(`✓ ${this.name} configured:`));
|
||||||
console.log(chalk.dim(` - ${agentCount} agents configured`));
|
console.log(chalk.dim(` - ${counts.agents} agents configured`));
|
||||||
console.log(chalk.dim(` - ${taskCount} tasks configured`));
|
console.log(chalk.dim(` - ${counts.tasks} tasks configured`));
|
||||||
console.log(chalk.dim(` - ${toolCount} tools configured`));
|
console.log(chalk.dim(` - ${counts.tools} tools configured`));
|
||||||
console.log(chalk.dim(` - ${workflowCount} workflows configured`));
|
console.log(chalk.dim(` - ${counts.workflows} workflows configured`));
|
||||||
console.log(chalk.dim(` - Commands directory: ${path.relative(projectDir, bmadCommandsDir)}`));
|
console.log(chalk.dim(` - ${counts.total} TOML files written to ${path.relative(projectDir, commandsDir)}`));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
agents: agentCount,
|
...counts,
|
||||||
tasks: taskCount,
|
|
||||||
tools: toolCount,
|
|
||||||
workflows: workflowCount,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,7 +68,6 @@ class QwenSetup extends BaseIdeSetup {
|
|||||||
* Update settings.json to remove old agent references
|
* Update settings.json to remove old agent references
|
||||||
*/
|
*/
|
||||||
async updateSettings(qwenDir) {
|
async updateSettings(qwenDir) {
|
||||||
const fs = require('fs-extra');
|
|
||||||
const settingsPath = path.join(qwenDir, 'settings.json');
|
const settingsPath = path.join(qwenDir, 'settings.json');
|
||||||
|
|
||||||
if (await fs.pathExists(settingsPath)) {
|
if (await fs.pathExists(settingsPath)) {
|
||||||
@@ -180,7 +102,6 @@ class QwenSetup extends BaseIdeSetup {
|
|||||||
* Clean up old configuration directories
|
* Clean up old configuration directories
|
||||||
*/
|
*/
|
||||||
async cleanupOldConfig(qwenDir) {
|
async cleanupOldConfig(qwenDir) {
|
||||||
const fs = require('fs-extra');
|
|
||||||
const agentsDir = path.join(qwenDir, 'agents');
|
const agentsDir = path.join(qwenDir, 'agents');
|
||||||
const bmadMethodDir = path.join(qwenDir, 'bmad-method');
|
const bmadMethodDir = path.join(qwenDir, 'bmad-method');
|
||||||
const bmadDir = path.join(qwenDir, 'bmadDir');
|
const bmadDir = path.join(qwenDir, 'bmadDir');
|
||||||
@@ -201,114 +122,36 @@ 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
|
* Cleanup Qwen configuration
|
||||||
*/
|
*/
|
||||||
async cleanup(projectDir) {
|
async cleanup(projectDir) {
|
||||||
const fs = require('fs-extra');
|
const commandsDir = path.join(projectDir, this.configDir, this.commandsDir);
|
||||||
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');
|
|
||||||
|
|
||||||
if (await fs.pathExists(bmadCommandsDir)) {
|
if (await fs.pathExists(commandsDir)) {
|
||||||
await fs.remove(bmadCommandsDir);
|
// Remove any bmad* files from the commands directory
|
||||||
console.log(chalk.dim(`Removed BMAD configuration from Qwen Code`));
|
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(` Cleaned up existing BMAD configuration from Qwen Code`));
|
||||||
|
}
|
||||||
|
|
||||||
|
const oldBmadMethodDir = path.join(projectDir, this.configDir, 'bmad-method');
|
||||||
if (await fs.pathExists(oldBmadMethodDir)) {
|
if (await fs.pathExists(oldBmadMethodDir)) {
|
||||||
await fs.remove(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)) {
|
if (await fs.pathExists(oldBMadDir)) {
|
||||||
await fs.remove(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
|
* @returns {Object} Installation result
|
||||||
*/
|
*/
|
||||||
async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) {
|
async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) {
|
||||||
const qwenDir = path.join(projectDir, this.configDir);
|
const commandsDir = path.join(projectDir, this.configDir, this.commandsDir);
|
||||||
const commandsDir = path.join(qwenDir, this.commandsDir);
|
|
||||||
const bmadCommandsDir = path.join(commandsDir, this.bmadDir);
|
|
||||||
|
|
||||||
// Create .qwen/commands/BMad directory if it doesn't exist
|
// Create .qwen/commands directory if it doesn't exist
|
||||||
await fs.ensureDir(bmadCommandsDir);
|
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
|
const launcherContent = `# ${agentName} Custom Agent
|
||||||
|
|
||||||
**⚠️ IMPORTANT**: Run @${agentPath} first to load the complete 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*`;
|
*Generated by BMAD Method*`;
|
||||||
|
|
||||||
// Use Qwen's TOML conversion method
|
// Convert to TOML format using the same method as UnifiedInstaller
|
||||||
const tomlContent = this.processAgentLauncherContent(launcherContent, {
|
const frontmatterRegex = /^---\s*\n[\s\S]*?\n---\s*\n/;
|
||||||
name: agentName,
|
const contentWithoutFrontmatter = launcherContent.replace(frontmatterRegex, '').trim();
|
||||||
module: 'custom',
|
const escapedContent = contentWithoutFrontmatter.replaceAll('"""', String.raw`\"\"\"`);
|
||||||
});
|
|
||||||
|
|
||||||
const fileName = `custom-${agentName.toLowerCase()}.toml`;
|
const tomlContent = `description = "BMAD Custom Agent: ${agentName}"
|
||||||
const launcherPath = path.join(bmadCommandsDir, fileName);
|
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
|
// Write the launcher file
|
||||||
await fs.writeFile(launcherPath, tomlContent, 'utf8');
|
await fs.writeFile(launcherPath, tomlContent, 'utf8');
|
||||||
@@ -363,7 +210,7 @@ The agent will follow the persona and instructions from the main agent file.
|
|||||||
return {
|
return {
|
||||||
ide: 'qwen',
|
ide: 'qwen',
|
||||||
path: path.relative(projectDir, launcherPath),
|
path: path.relative(projectDir, launcherPath),
|
||||||
command: agentName,
|
command: fileName.replace('.toml', ''),
|
||||||
type: 'custom-agent-launcher',
|
type: 'custom-agent-launcher',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,69 +2,19 @@ const path = require('node:path');
|
|||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const chalk = require('chalk');
|
const chalk = require('chalk');
|
||||||
const { BaseIdeSetup } = require('./_base-ide');
|
const { BaseIdeSetup } = require('./_base-ide');
|
||||||
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
|
const { UnifiedInstaller, NamingStyle, TemplateType } = require('./shared/unified-installer');
|
||||||
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');
|
|
||||||
const { TaskToolCommandGenerator } = require('./shared/task-tool-command-generator');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rovo Dev IDE setup handler
|
* Rovo Dev IDE setup handler
|
||||||
*
|
*
|
||||||
* Installs BMAD agents as Rovo Dev subagents in .rovodev/subagents/
|
* Uses UnifiedInstaller for all artifact installation with flat file structure.
|
||||||
* Installs workflows and tasks/tools as reference guides in .rovodev/
|
* All BMAD artifacts are installed to .rovodev/workflows/ as flat files.
|
||||||
* Rovo Dev automatically discovers agents and integrates with BMAD like other IDEs
|
|
||||||
*/
|
*/
|
||||||
class RovoDevSetup extends BaseIdeSetup {
|
class RovoDevSetup extends BaseIdeSetup {
|
||||||
constructor() {
|
constructor() {
|
||||||
super('rovo-dev', 'Atlassian Rovo Dev', false);
|
super('rovo-dev', 'Atlassian Rovo Dev', false);
|
||||||
this.configDir = '.rovodev';
|
this.configDir = '.rovodev';
|
||||||
this.subagentsDir = 'subagents';
|
|
||||||
this.workflowsDir = 'workflows';
|
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
|
// Create .rovodev directory structure
|
||||||
const rovoDevDir = path.join(projectDir, this.configDir);
|
const rovoDevDir = path.join(projectDir, this.configDir);
|
||||||
const subagentsDir = path.join(rovoDevDir, this.subagentsDir);
|
|
||||||
const workflowsDir = path.join(rovoDevDir, this.workflowsDir);
|
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(workflowsDir);
|
||||||
await this.ensureDir(referencesDir);
|
|
||||||
|
|
||||||
// Generate and install agents
|
// Use the unified installer - all artifacts go to workflows folder as flat files
|
||||||
const agentGen = new AgentCommandGenerator(this.bmadFolderName);
|
const installer = new UnifiedInstaller(this.bmadFolderName);
|
||||||
const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []);
|
const counts = await installer.install(
|
||||||
|
projectDir,
|
||||||
let agentCount = 0;
|
bmadDir,
|
||||||
for (const artifact of agentArtifacts) {
|
{
|
||||||
const subagentFilename = `bmad-${artifact.module}-${artifact.name}.md`;
|
targetDir: workflowsDir,
|
||||||
const targetPath = path.join(subagentsDir, subagentFilename);
|
namingStyle: NamingStyle.FLAT_DASH,
|
||||||
const subagentContent = this.convertToRovoDevSubagent(artifact.content, artifact.name, artifact.module);
|
templateType: TemplateType.CLAUDE,
|
||||||
await this.writeFile(targetPath, subagentContent);
|
},
|
||||||
agentCount++;
|
options.selectedModules || [],
|
||||||
}
|
|
||||||
|
|
||||||
// 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/`));
|
|
||||||
}
|
|
||||||
if (taskCount + toolCount > 0) {
|
|
||||||
console.log(
|
|
||||||
chalk.dim(` - ${taskCount + toolCount} tasks/tools installed to .rovodev/references/ (${taskCount} tasks, ${toolCount} tools)`),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
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`));
|
||||||
}
|
}
|
||||||
console.log(chalk.yellow(`\n Note: Agents are automatically discovered by Rovo Dev`));
|
if (counts.tasks + counts.tools > 0) {
|
||||||
console.log(chalk.dim(` - Access agents by typing @ in Rovo Dev to see available options`));
|
console.log(chalk.dim(` - ${counts.tasks + counts.tools} tasks/tools installed (${counts.tasks} tasks, ${counts.tools} tools)`));
|
||||||
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 {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
agents: agentCount,
|
...counts,
|
||||||
workflows: workflowCount,
|
|
||||||
tasks: taskCount,
|
|
||||||
tools: toolCount,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate task and tool reference guides
|
* Cleanup old BMAD installation before reinstalling
|
||||||
* @param {string} bmadDir - BMAD directory
|
* @param {string} projectDir - Project directory
|
||||||
* @param {string} referencesDir - References directory
|
|
||||||
* @param {TaskToolCommandGenerator} taskToolGen - Generator instance
|
|
||||||
*/
|
*/
|
||||||
async generateTaskToolReferences(bmadDir, referencesDir, taskToolGen) {
|
async cleanup(projectDir) {
|
||||||
const tasks = await taskToolGen.loadTaskManifest(bmadDir);
|
const rovoDevDir = path.join(projectDir, this.configDir);
|
||||||
const tools = await taskToolGen.loadToolManifest(bmadDir);
|
|
||||||
|
|
||||||
const standaloneTasks = tasks ? tasks.filter((t) => t.standalone === 'true' || t.standalone === true) : [];
|
if (!(await fs.pathExists(rovoDevDir))) {
|
||||||
const standaloneTools = tools ? tools.filter((t) => t.standalone === 'true' || t.standalone === true) : [];
|
return;
|
||||||
|
|
||||||
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++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let toolCount = 0;
|
// Clean BMAD files from workflows directory
|
||||||
for (const tool of standaloneTools) {
|
const workflowsDir = path.join(rovoDevDir, this.workflowsDir);
|
||||||
const commandContent = taskToolGen.generateCommandContent(tool, 'tool');
|
if (await fs.pathExists(workflowsDir)) {
|
||||||
const targetPath = path.join(referencesDir, `bmad-tool-${tool.module}-${tool.name}.md`);
|
const entries = await fs.readdir(workflowsDir);
|
||||||
await this.writeFile(targetPath, commandContent);
|
const bmadFiles = entries.filter((file) => file.startsWith('bmad') && file.endsWith('.md'));
|
||||||
toolCount++;
|
|
||||||
|
for (const file of bmadFiles) {
|
||||||
|
await fs.remove(path.join(workflowsDir, file));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { tasks: taskCount, tools: toolCount };
|
// 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
|
||||||
* Convert BMAD agent launcher to Rovo Dev subagent format
|
const referencesDir = path.join(rovoDevDir, 'references');
|
||||||
*
|
if (await fs.pathExists(referencesDir)) {
|
||||||
* Rovo Dev subagents use Markdown files with YAML frontmatter containing:
|
await fs.remove(referencesDir);
|
||||||
* - name: Unique identifier for the subagent
|
console.log(chalk.dim(` Removed legacy references directory`));
|
||||||
* - 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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -244,20 +115,7 @@ class RovoDevSetup extends BaseIdeSetup {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for BMAD agents in subagents directory
|
// Check for BMAD files in workflows 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
|
|
||||||
const workflowsDir = path.join(rovoDevDir, this.workflowsDir);
|
const workflowsDir = path.join(rovoDevDir, this.workflowsDir);
|
||||||
if (await fs.pathExists(workflowsDir)) {
|
if (await fs.pathExists(workflowsDir)) {
|
||||||
try {
|
try {
|
||||||
@@ -266,25 +124,64 @@ class RovoDevSetup extends BaseIdeSetup {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// Continue checking other directories
|
// Continue checking
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
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 };
|
module.exports = { RovoDevSetup };
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const chalk = require('chalk');
|
|
||||||
const { toColonPath, toDashPath, customAgentColonName, customAgentDashName } = require('./path-utils');
|
const { toColonPath, toDashPath, customAgentColonName, customAgentDashName } = require('./path-utils');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -65,9 +64,8 @@ class AgentCommandGenerator {
|
|||||||
.replaceAll('{{name}}', agent.name)
|
.replaceAll('{{name}}', agent.name)
|
||||||
.replaceAll('{{module}}', agent.module)
|
.replaceAll('{{module}}', agent.module)
|
||||||
.replaceAll('{{path}}', agentPathInModule)
|
.replaceAll('{{path}}', agentPathInModule)
|
||||||
.replaceAll('{{description}}', agent.description || `${agent.name} agent`)
|
.replaceAll('{{relativePath}}', path.join(agent.module, 'agents', agentPathInModule))
|
||||||
.replaceAll('_bmad', this.bmadFolderName)
|
.replaceAll('{{description}}', agent.description || `${agent.name} agent`);
|
||||||
.replaceAll('_bmad', '_bmad');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -109,7 +107,7 @@ class AgentCommandGenerator {
|
|||||||
// Convert relativePath to underscore format: bmm/agents/pm.md → bmad_bmm_pm.md
|
// Convert relativePath to underscore format: bmm/agents/pm.md → bmad_bmm_pm.md
|
||||||
const flatName = toColonPath(artifact.relativePath);
|
const flatName = toColonPath(artifact.relativePath);
|
||||||
const launcherPath = path.join(baseCommandsDir, flatName);
|
const launcherPath = path.join(baseCommandsDir, flatName);
|
||||||
await fs.ensureDir(path.dirname(launcherPath));
|
await fs.ensureDir(baseCommandsDir);
|
||||||
await fs.writeFile(launcherPath, artifact.content);
|
await fs.writeFile(launcherPath, artifact.content);
|
||||||
writtenCount++;
|
writtenCount++;
|
||||||
}
|
}
|
||||||
@@ -119,8 +117,8 @@ class AgentCommandGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Write agent launcher artifacts using underscore format (Windows-compatible)
|
* Write agent launcher artifacts using dash format
|
||||||
* Creates flat files like: bmad_bmm_pm.md
|
* Creates flat files like: bmad-bmm-agent-pm.md
|
||||||
*
|
*
|
||||||
* @param {string} baseCommandsDir - Base commands directory for the IDE
|
* @param {string} baseCommandsDir - Base commands directory for the IDE
|
||||||
* @param {Array} artifacts - Agent launcher artifacts
|
* @param {Array} artifacts - Agent launcher artifacts
|
||||||
@@ -131,10 +129,10 @@ class AgentCommandGenerator {
|
|||||||
|
|
||||||
for (const artifact of artifacts) {
|
for (const artifact of artifacts) {
|
||||||
if (artifact.type === 'agent-launcher') {
|
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 flatName = toDashPath(artifact.relativePath);
|
||||||
const launcherPath = path.join(baseCommandsDir, flatName);
|
const launcherPath = path.join(baseCommandsDir, flatName);
|
||||||
await fs.ensureDir(path.dirname(launcherPath));
|
await fs.ensureDir(baseCommandsDir);
|
||||||
await fs.writeFile(launcherPath, artifact.content);
|
await fs.writeFile(launcherPath, artifact.content);
|
||||||
writtenCount++;
|
writtenCount++;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ function toUnderscoreName(module, type, name, fileExtension = DEFAULT_FILE_EXTEN
|
|||||||
* Convert relative path to flat underscore-separated name
|
* Convert relative path to flat underscore-separated name
|
||||||
* Converts: 'bmm/agents/pm.md' → 'bmad_bmm_agent_pm.md'
|
* Converts: 'bmm/agents/pm.md' → 'bmad_bmm_agent_pm.md'
|
||||||
* Converts: 'bmm/workflows/correct-course.md' → 'bmad_bmm_correct-course.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)
|
* Converts: 'core/agents/brainstorming.md' → 'bmad_agent_brainstorming.md' (core items skip module prefix)
|
||||||
*
|
*
|
||||||
* @param {string} relativePath - Path like 'bmm/agents/pm.md'
|
* @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 type = parts[1];
|
||||||
const name = parts.slice(2).join('_');
|
const name = parts.slice(2).join('_');
|
||||||
|
|
||||||
// Use toUnderscoreName for consistency
|
const isAgent = type === AGENT_SEGMENT;
|
||||||
return toUnderscoreName(module, type, name, fileExtension);
|
// 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
|
* Convert relative path to flat dash-separated name
|
||||||
* Converts: 'bmm/agents/pm.md' → 'bmad-bmm-agent-pm.md'
|
* Converts: 'bmm/agents/pm.md' → 'bmad-bmm-agent-pm.md'
|
||||||
* Converts: 'bmm/workflows/correct-course' → 'bmad-bmm-correct-course.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} relativePath - Path like 'bmm/agents/pm.md'
|
||||||
* @param {string} [fileExtension=DEFAULT_FILE_EXTENSION] - File extension including dot
|
* @param {string} [fileExtension=DEFAULT_FILE_EXTENSION] - File extension including dot
|
||||||
* @returns {string} Flat filename like 'bmad-bmm-agent-pm.md'
|
* @returns {string} Flat filename like 'bmad-bmm-agent-pm.md'
|
||||||
@@ -188,7 +196,9 @@ function toDashPath(relativePath, fileExtension = DEFAULT_FILE_EXTENSION) {
|
|||||||
if (module === 'core') {
|
if (module === 'core') {
|
||||||
return isAgent ? `bmad-agent-${name}${fileExtension}` : `bmad-${name}${fileExtension}`;
|
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 = {
|
module.exports = {
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ const TemplateType = {
|
|||||||
WINDSURF: 'windsurf', // YAML with auto_execution_mode
|
WINDSURF: 'windsurf', // YAML with auto_execution_mode
|
||||||
AUGMENT: 'augment', // YAML frontmatter
|
AUGMENT: 'augment', // YAML frontmatter
|
||||||
GEMINI: 'gemini', // TOML frontmatter with description/prompt
|
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
|
COPILOT: 'copilot', // YAML with tools array for GitHub Copilot
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -209,7 +210,8 @@ class UnifiedInstaller {
|
|||||||
content = this.applyTemplate(artifact, content, templateType);
|
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');
|
await fs.writeFile(targetPath, content, 'utf8');
|
||||||
written++;
|
written++;
|
||||||
}
|
}
|
||||||
@@ -254,6 +256,11 @@ class UnifiedInstaller {
|
|||||||
return this.addCopilotFrontmatter(artifact, content);
|
return this.addCopilotFrontmatter(artifact, content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case TemplateType.QWEN: {
|
||||||
|
// Add Qwen TOML frontmatter (same as Gemini)
|
||||||
|
return this.addGeminiFrontmatter(artifact, content);
|
||||||
|
}
|
||||||
|
|
||||||
default: {
|
default: {
|
||||||
return content;
|
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.
|
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">
|
<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
|
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
|
3. Execute ALL activation steps exactly as written in the agent file
|
||||||
4. Follow the agent's persona and menu system precisely
|
4. Follow the agent's persona and menu system precisely
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
---
|
---
|
||||||
|
name: '{{name}}'
|
||||||
description: '{{description}}'
|
description: '{{description}}'
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user