mirror of
https://github.com/bmad-code-org/BMAD-METHOD.git
synced 2026-01-30 04:32:02 +00:00
standardize installer flat command list with naming convention standardization to make workflow update
This commit is contained in:
@@ -1,18 +0,0 @@
|
||||
phase,name,code,sequence,workflow-file,command,required,agent,options,description
|
||||
0,Document Project,DP,10,_bmad/bmm/workflows/document-project/workflow.yaml,bmm:document-project,false,analyst,Create Mode,Analyze an existing project to produce useful documentation
|
||||
1,Brainstorm Project,BP,10,_bmad/core/workflows/brainstorming/workflow.md,bmm:brainstorming,false,analyst,data=_bmad/bmm/data/project-context-template.md,Expert Guided Facilitation through a single or multiple techniques
|
||||
1,Research,RS,20,_bmad/bmm/workflows/1-analysis/research/workflow.md,bmm:research,false,analyst,Create Mode,Choose from or specify market domain competitive analysis or technical research
|
||||
1,Create Brief,CB,30,_bmad/bmm/workflows/1-analysis/create-product-brief/workflow.md,bmm:create-brief,false,analyst,Create Mode,A guided experience to nail down your product idea
|
||||
1,Validate Brief,VB,40,_bmad/bmm/workflows/1-analysis/create-product-brief/workflow.md,bmm:validate-brief,false,analyst,Validate Mode,Validates product brief completeness
|
||||
2,Create PRD,CP,10,_bmad/bmm/workflows/2-plan-workflows/prd/workflow.md,bmm:create-prd,true,pm,Create Mode,Expert led facilitation to produce your Product Requirements Document
|
||||
2,Validate PRD,VP,20,_bmad/bmm/workflows/2-plan-workflows/prd/workflow.md,bmm:validate-prd,false,pm,Validate Mode,Validate PRD is comprehensive lean well organized and cohesive
|
||||
2,Create UX,CU,30,_bmad/bmm/workflows/2-plan-workflows/create-ux-design/workflow.md,bmm:create-ux,false,ux-designer,Create Mode,Guidance through realizing the plan for your UX
|
||||
2,Validate UX,VU,40,_bmad/bmm/workflows/2-plan-workflows/create-ux-design/workflow.md,bmm:validate-ux,false,ux-designer,Validate Mode,Validates UX design deliverables
|
||||
3,Create Architecture,CA,10,_bmad/bmm/workflows/3-solutioning/create-architecture/workflow.md,bmm:create-architecture,true,architect,Create Mode,Guided Workflow to document technical decisions
|
||||
3,Validate Architecture,VA,20,_bmad/bmm/workflows/3-solutioning/create-architecture/workflow.md,bmm:validate-architecture,false,architect,Validate Mode,Validates architecture completeness
|
||||
3,Create Epics and Stories,CE,30,_bmad/bmm/workflows/3-solutioning/create-epics-and-stories/workflow.md,bmm:create-epics-and-stories,true,pm,Create Mode,Create the Epics and Stories Listing
|
||||
3,Validate Epics and Stories,VE,40,_bmad/bmm/workflows/3-solutioning/create-epics-and-stories/workflow.md,bmm:validate-epics-and-stories,false,pm,Validate Mode,Validates epics and stories completeness
|
||||
3,Test Design,TD,50,_bmad/bmm/workflows/testarch/test-design/workflow.yaml,bmm:test-design,false,tea,Create Mode,Create comprehensive test scenarios ahead of development
|
||||
3,Validate Test Design,VT,60,_bmad/bmm/workflows/testarch/test-design/workflow.yaml,bmm:validate-test-design,false,tea,Validate Mode,Validates test design coverage
|
||||
3,Implementation Readiness,IR,70,_bmad/bmm/workflows/3-solutioning/check-implementation-readiness/workflow.md,bmm:implementation-readiness,true,architect,Validate Mode,Ensure PRD UX Architecture and Epics Stories are aligned
|
||||
4,Sprint Planning,SP,10,_bmad/bmm/workflows/4-implementation/sprint-planning/workflow.yaml,bmm:sprint-planning,true,sm,Create Mode,Generate sprint plan for development tasks
|
||||
|
@@ -1,6 +1,7 @@
|
||||
---
|
||||
name: whats-next
|
||||
name: whats-after
|
||||
description: Show what workflow steps come next in the BMad Method based on what's been completed
|
||||
standalone: true
|
||||
---
|
||||
|
||||
# Task: What's Next?
|
||||
@@ -21,9 +22,9 @@ If no explicit workflow is provided, check the conversation context:
|
||||
|
||||
## EXECUTION
|
||||
|
||||
Load `{project-root}/_bmad/bmm/data/workflows.csv` and `{project-root}/_bmad/_config/agent-manifest.csv`. Find all workflow items after the completed row. Present these in a clear, conversational format.
|
||||
Load `./workflows.csv` and `{project-root}/_bmad/_config/agent-manifest.csv`. Find all workflow items after the completed row. Present these in a clear, conversational format.
|
||||
|
||||
**Phases reference:** Phase 0 (Any Time), Phase 1 (Analysis), Phase 2 (Planning), Phase 3 (Solutioning), Phase 4 (Implementation)
|
||||
**Phases number to name reference:** Phase 0 (Any Time), Phase 1 (Analysis), Phase 2 (Planning), Phase 3 (Solutioning), Phase 4 (Implementation)
|
||||
|
||||
**Present the next steps as follows:**
|
||||
|
||||
@@ -31,7 +32,7 @@ Load `{project-root}/_bmad/bmm/data/workflows.csv` and `{project-root}/_bmad/_co
|
||||
2. **Required items next** - List the next required workflow
|
||||
3. For each item, show:
|
||||
- The workflow **name**
|
||||
- The **command** (prefixed with `/`, e.g., `/bmm:create-architecture`)
|
||||
- The **command** (prefixed with `/`, e.g., `/bmad:bmm:create-architecture`)
|
||||
- The **agent** displayName and title from the loaded agent-manifest that corresponds with the agent value in each row who can help, e.g., `Winston the Architect`
|
||||
- A brief **description** so the user can decide easily
|
||||
|
||||
|
||||
18
src/bmm/tasks/workflows.csv
Normal file
18
src/bmm/tasks/workflows.csv
Normal file
@@ -0,0 +1,18 @@
|
||||
phase,name,code,sequence,workflow-file,command,required,agent,options,description
|
||||
0,Document Project,DP,10,_bmad/bmm/workflows/document-project/workflow.yaml,bmad:bmm:document-project,false,analyst,Create Mode,Analyze an existing project to produce useful documentation
|
||||
1,Brainstorm Project,BP,10,_bmad/core/workflows/brainstorming/workflow.md,bmad:bmm:brainstorming,false,analyst,data=_bmad/bmm/data/project-context-template.md,Expert Guided Facilitation through a single or multiple techniques
|
||||
1,Research,RS,20,_bmad/bmm/workflows/1-analysis/research/workflow.md,bmad:bmm:research,false,analyst,Create Mode,Choose from or specify market domain competitive analysis or technical research
|
||||
1,Create Brief,CB,30,_bmad/bmm/workflows/1-analysis/create-product-brief/workflow.md,bmad:bmm:create-brief,false,analyst,Create Mode,A guided experience to nail down your product idea
|
||||
1,Validate Brief,VB,40,_bmad/bmm/workflows/1-analysis/create-product-brief/workflow.md,bmad:bmm:validate-brief,false,analyst,Validate Mode,Validates product brief completeness
|
||||
2,Create PRD,CP,10,_bmad/bmm/workflows/2-plan-workflows/prd/workflow.md,bmad:bmm:create-prd,true,pm,Create Mode,Expert led facilitation to produce your Product Requirements Document
|
||||
2,Validate PRD,VP,20,_bmad/bmm/workflows/2-plan-workflows/prd/workflow.md,bmad:bmm:validate-prd,false,pm,Validate Mode,Validate PRD is comprehensive lean well organized and cohesive
|
||||
2,Create UX,CU,30,_bmad/bmm/workflows/2-plan-workflows/create-ux-design/workflow.md,bmad:bmm:create-ux,false,ux-designer,Create Mode,Guidance through realizing the plan for your UX
|
||||
2,Validate UX,VU,40,_bmad/bmm/workflows/2-plan-workflows/create-ux-design/workflow.md,bmad:bmm:validate-ux,false,ux-designer,Validate Mode,Validates UX design deliverables
|
||||
3,Create Architecture,CA,10,_bmad/bmm/workflows/3-solutioning/create-architecture/workflow.md,bmad:bmm:create-architecture,true,architect,Create Mode,Guided Workflow to document technical decisions
|
||||
3,Validate Architecture,VA,20,_bmad/bmm/workflows/3-solutioning/create-architecture/workflow.md,bmad:bmm:validate-architecture,false,architect,Validate Mode,Validates architecture completeness
|
||||
3,Create Epics and Stories,CE,30,_bmad/bmm/workflows/3-solutioning/create-epics-and-stories/workflow.md,bmad:bmm:create-epics-and-stories,true,pm,Create Mode,Create the Epics and Stories Listing
|
||||
3,Validate Epics and Stories,VE,40,_bmad/bmm/workflows/3-solutioning/create-epics-and-stories/workflow.md,bmad:bmm:validate-epics-and-stories,false,pm,Validate Mode,Validates epics and stories completeness
|
||||
3,Test Design,TD,50,_bmad/bmm/workflows/testarch/test-design/workflow.yaml,bmad:bmm:test-design,false,tea,Create Mode,Create comprehensive test scenarios ahead of development
|
||||
3,Validate Test Design,VT,60,_bmad/bmm/workflows/testarch/test-design/workflow.yaml,bmad:bmm:validate-test-design,false,tea,Validate Mode,Validates test design coverage
|
||||
3,Implementation Readiness,IR,70,_bmad/bmm/workflows/3-solutioning/check-implementation-readiness/workflow.md,bmad:bmm:implementation-readiness,true,architect,Validate Mode,Ensure PRD UX Architecture and Epics Stories are aligned
|
||||
4,Sprint Planning,SP,10,_bmad/bmm/workflows/4-implementation/sprint-planning/workflow.yaml,bmad:bmm:sprint-planning,true,sm,Create Mode,Generate sprint plan for development tasks
|
||||
|
@@ -385,26 +385,45 @@ class ManifestGenerator {
|
||||
const filePath = path.join(dirPath, file);
|
||||
const content = await fs.readFile(filePath, 'utf8');
|
||||
|
||||
// Extract task metadata from content if possible
|
||||
const nameMatch = content.match(/name="([^"]+)"/);
|
||||
let name = file.replace(/\.(xml|md)$/, '');
|
||||
let displayName = name;
|
||||
let description = '';
|
||||
let standalone = false;
|
||||
|
||||
// Try description attribute first, fall back to <objective> element
|
||||
const descMatch = content.match(/description="([^"]+)"/);
|
||||
const objMatch = content.match(/<objective>([^<]+)<\/objective>/);
|
||||
const description = descMatch ? descMatch[1] : objMatch ? objMatch[1].trim() : '';
|
||||
if (file.endsWith('.md')) {
|
||||
// Parse YAML frontmatter for .md tasks
|
||||
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
||||
if (frontmatterMatch) {
|
||||
try {
|
||||
const frontmatter = yaml.parse(frontmatterMatch[1]);
|
||||
name = frontmatter.name || name;
|
||||
displayName = frontmatter.displayName || frontmatter.name || name;
|
||||
description = frontmatter.description || '';
|
||||
standalone = frontmatter.standalone === true || frontmatter.standalone === 'true';
|
||||
} catch {
|
||||
// If YAML parsing fails, use defaults
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// For .xml tasks, extract from tag attributes
|
||||
const nameMatch = content.match(/name="([^"]+)"/);
|
||||
displayName = nameMatch ? nameMatch[1] : name;
|
||||
|
||||
// Check for standalone attribute in <task> tag (default: false)
|
||||
const standaloneMatch = content.match(/<task[^>]+standalone="true"/);
|
||||
const standalone = !!standaloneMatch;
|
||||
const descMatch = content.match(/description="([^"]+)"/);
|
||||
const objMatch = content.match(/<objective>([^<]+)<\/objective>/);
|
||||
description = descMatch ? descMatch[1] : objMatch ? objMatch[1].trim() : '';
|
||||
|
||||
const standaloneMatch = content.match(/<task[^>]+standalone="true"/);
|
||||
standalone = !!standaloneMatch;
|
||||
}
|
||||
|
||||
// Build relative path for installation
|
||||
const installPath =
|
||||
moduleName === 'core' ? `${this.bmadFolderName}/core/tasks/${file}` : `${this.bmadFolderName}/${moduleName}/tasks/${file}`;
|
||||
|
||||
const taskName = file.replace(/\.(xml|md)$/, '');
|
||||
tasks.push({
|
||||
name: taskName,
|
||||
displayName: nameMatch ? nameMatch[1] : taskName,
|
||||
name: name,
|
||||
displayName: displayName,
|
||||
description: description.replaceAll('"', '""'),
|
||||
module: moduleName,
|
||||
path: installPath,
|
||||
@@ -414,7 +433,7 @@ class ManifestGenerator {
|
||||
// Add to files list
|
||||
this.files.push({
|
||||
type: 'task',
|
||||
name: taskName,
|
||||
name: name,
|
||||
module: moduleName,
|
||||
path: installPath,
|
||||
});
|
||||
@@ -455,26 +474,45 @@ class ManifestGenerator {
|
||||
const filePath = path.join(dirPath, file);
|
||||
const content = await fs.readFile(filePath, 'utf8');
|
||||
|
||||
// Extract tool metadata from content if possible
|
||||
const nameMatch = content.match(/name="([^"]+)"/);
|
||||
let name = file.replace(/\.(xml|md)$/, '');
|
||||
let displayName = name;
|
||||
let description = '';
|
||||
let standalone = false;
|
||||
|
||||
// Try description attribute first, fall back to <objective> element
|
||||
const descMatch = content.match(/description="([^"]+)"/);
|
||||
const objMatch = content.match(/<objective>([^<]+)<\/objective>/);
|
||||
const description = descMatch ? descMatch[1] : objMatch ? objMatch[1].trim() : '';
|
||||
if (file.endsWith('.md')) {
|
||||
// Parse YAML frontmatter for .md tools
|
||||
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
||||
if (frontmatterMatch) {
|
||||
try {
|
||||
const frontmatter = yaml.parse(frontmatterMatch[1]);
|
||||
name = frontmatter.name || name;
|
||||
displayName = frontmatter.displayName || frontmatter.name || name;
|
||||
description = frontmatter.description || '';
|
||||
standalone = frontmatter.standalone === true || frontmatter.standalone === 'true';
|
||||
} catch {
|
||||
// If YAML parsing fails, use defaults
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// For .xml tools, extract from tag attributes
|
||||
const nameMatch = content.match(/name="([^"]+)"/);
|
||||
displayName = nameMatch ? nameMatch[1] : name;
|
||||
|
||||
// Check for standalone attribute in <tool> tag (default: false)
|
||||
const standaloneMatch = content.match(/<tool[^>]+standalone="true"/);
|
||||
const standalone = !!standaloneMatch;
|
||||
const descMatch = content.match(/description="([^"]+)"/);
|
||||
const objMatch = content.match(/<objective>([^<]+)<\/objective>/);
|
||||
description = descMatch ? descMatch[1] : objMatch ? objMatch[1].trim() : '';
|
||||
|
||||
const standaloneMatch = content.match(/<tool[^>]+standalone="true"/);
|
||||
standalone = !!standaloneMatch;
|
||||
}
|
||||
|
||||
// Build relative path for installation
|
||||
const installPath =
|
||||
moduleName === 'core' ? `${this.bmadFolderName}/core/tools/${file}` : `${this.bmadFolderName}/${moduleName}/tools/${file}`;
|
||||
|
||||
const toolName = file.replace(/\.(xml|md)$/, '');
|
||||
tools.push({
|
||||
name: toolName,
|
||||
displayName: nameMatch ? nameMatch[1] : toolName,
|
||||
name: name,
|
||||
displayName: displayName,
|
||||
description: description.replaceAll('"', '""'),
|
||||
module: moduleName,
|
||||
path: installPath,
|
||||
@@ -484,7 +522,7 @@ class ManifestGenerator {
|
||||
// Add to files list
|
||||
this.files.push({
|
||||
type: 'tool',
|
||||
name: toolName,
|
||||
name: name,
|
||||
module: moduleName,
|
||||
path: installPath,
|
||||
});
|
||||
|
||||
234
tools/cli/installers/lib/ide/STANDARDIZATION_PLAN.md
Normal file
234
tools/cli/installers/lib/ide/STANDARDIZATION_PLAN.md
Normal file
@@ -0,0 +1,234 @@
|
||||
# IDE Installer Standardization Plan
|
||||
|
||||
## Overview
|
||||
|
||||
Standardize IDE installers to use **flat file naming** and centralize duplicated code in shared utilities.
|
||||
|
||||
**Key Rule: Only folder-based IDEs convert to colon format. IDEs already using dashes keep using dashes.**
|
||||
|
||||
## 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 (dashes) | `.agent/workflows/bmad-module-agents-name.md` |
|
||||
| **codex** | Flattened (dashes) | `~/.codex/prompts/bmad-module-agents-name.md` |
|
||||
| **cline** | Flattened (dashes) | `.clinerules/workflows/bmad-module-type-name.md` |
|
||||
| **roo** | Flattened (dashes) | `.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 Folder-Based IDEs (convert to colon format)
|
||||
|
||||
**IDEs affected:** claude-code, cursor, crush
|
||||
|
||||
```
|
||||
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:whats-after.md
|
||||
- Tool: bmad:core:tools:code-review.md
|
||||
- Custom: bmad:custom:agents:fred-commit-poet.md
|
||||
```
|
||||
|
||||
### For Already-Flat IDEs (keep using dashes)
|
||||
|
||||
**IDEs affected:** antigravity, codex, cline, roo
|
||||
|
||||
```
|
||||
Format: bmad-{module}-{type}-{name}.md
|
||||
|
||||
Examples:
|
||||
- Agent: bmad-bmm-agents-pm.md
|
||||
- Workflow: bmad-bmm-workflows-correct-course.md
|
||||
- Task: bmad-bmm-tasks-whats-after.md
|
||||
- Custom: bmad-custom-agents-fred-commit-poet.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 colon-separated name (for folder-based IDEs)
|
||||
* @param {string} module - Module name (e.g., 'bmm', 'core')
|
||||
* @param {string} type - Artifact type ('agents', 'workflows', 'tasks', 'tools')
|
||||
* @param {string} name - Artifact name (e.g., 'pm', 'correct-course')
|
||||
* @returns {string} Flat filename like 'bmad:bmm:agents:pm.md'
|
||||
*/
|
||||
function toColonName(module, type, name) {
|
||||
return `bmad:${module}:${type}:${name}.md`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert relative path to flat colon-separated name (for folder-based IDEs)
|
||||
* @param {string} relativePath - Path like 'bmm/agents/pm.md'
|
||||
* @returns {string} Flat filename like 'bmad:bmm:agents:pm.md'
|
||||
*/
|
||||
function toColonPath(relativePath) {
|
||||
const withoutExt = relativePath.replace('.md', '');
|
||||
const parts = withoutExt.split(/[\/\\]/);
|
||||
return `bmad:${parts.join(':')}.md`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert hierarchical path to flat dash-separated name (for flat IDEs)
|
||||
* @param {string} relativePath - Path like 'bmm/agents/pm.md'
|
||||
* @returns {string} Flat filename like 'bmad-bmm-agents-pm.md'
|
||||
*/
|
||||
function toDashPath(relativePath) {
|
||||
const withoutExt = relativePath.replace('.md', '');
|
||||
const parts = withoutExt.split(/[\/\\]/);
|
||||
return `bmad-${parts.join('-')}.md`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create custom agent colon name
|
||||
* @param {string} agentName - Custom agent name
|
||||
* @returns {string} Flat filename like 'bmad:custom:agents:fred-commit-poet.md'
|
||||
*/
|
||||
function customAgentColonName(agentName) {
|
||||
return `bmad:custom:agents:${agentName}.md`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create custom agent dash name
|
||||
* @param {string} agentName - Custom agent name
|
||||
* @returns {string} Flat filename like 'bmad-custom-agents-fred-commit-poet.md'
|
||||
*/
|
||||
function customAgentDashName(agentName) {
|
||||
return `bmad-custom-agents-${agentName}.md`;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
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
|
||||
4. Add method `writeDashArtifacts()` for flat IDEs
|
||||
|
||||
### Phase 3: Update Folder-Based IDEs
|
||||
|
||||
**Files to modify:**
|
||||
- `claude-code.js`
|
||||
- `cursor.js`
|
||||
- `crush.js`
|
||||
|
||||
**Changes:**
|
||||
1. Import `toColonPath`, `customAgentColonName` from path-utils
|
||||
2. Change from hierarchical to flat colon naming
|
||||
3. Update cleanup to handle flat structure
|
||||
|
||||
### Phase 4: Update Flat IDEs
|
||||
|
||||
**Files to modify:**
|
||||
- `antigravity.js`
|
||||
- `codex.js`
|
||||
- `cline.js`
|
||||
- `roo.js`
|
||||
|
||||
**Changes:**
|
||||
1. Import `toDashPath`, `customAgentDashName` from path-utils
|
||||
2. Replace local `flattenFilename()` with shared `toDashPath()`
|
||||
|
||||
### Phase 5: 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
|
||||
- [ ] Create `shared/path-utils.js`
|
||||
|
||||
### Folder-Based IDEs (convert to colon format)
|
||||
- [ ] Update `shared/agent-command-generator.js` - add `writeColonArtifacts()`
|
||||
- [ ] Update `shared/task-tool-command-generator.js` - add `writeColonArtifacts()`
|
||||
- [ ] Update `shared/workflow-command-generator.js` - add `writeColonArtifacts()`
|
||||
- [ ] Update `claude-code.js` - convert to colon format
|
||||
- [ ] Update `cursor.js` - convert to colon format
|
||||
- [ ] Update `crush.js` - convert to colon format
|
||||
|
||||
### Flat IDEs (standardize dash format)
|
||||
- [ ] Update `shared/agent-command-generator.js` - add `writeDashArtifacts()`
|
||||
- [ ] Update `shared/task-tool-command-generator.js` - add `writeDashArtifacts()`
|
||||
- [ ] Update `shared/workflow-command-generator.js` - add `writeDashArtifacts()`
|
||||
- [ ] Update `antigravity.js` - use shared `toDashPath()`
|
||||
- [ ] Update `codex.js` - use shared `toDashPath()`
|
||||
- [ ] Update `cline.js` - use shared `toDashPath()`
|
||||
- [ ] Update `roo.js` - use shared `toDashPath()`
|
||||
|
||||
### 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. **Keep segments**: agents, workflows, tasks, tools all become part of the flat name
|
||||
2. **Colon vs Dash**: Colons for folder-based IDEs converting to flat, dashes for already-flat IDEs
|
||||
3. **Custom agents**: Follow the same pattern as regular agents
|
||||
4. **Backward compatibility**: Cleanup will remove old folder structure
|
||||
@@ -619,6 +619,7 @@ class BaseIdeSetup {
|
||||
|
||||
/**
|
||||
* Flatten a relative path to a single filename for flat slash command naming
|
||||
* @deprecated Use toColonPath() or toDashPath() from shared/path-utils.js instead
|
||||
* Example: 'module/agents/name.md' -> 'bmad-module-agents-name.md'
|
||||
* Used by IDEs that ignore directory structure for slash commands (e.g., Antigravity, Codex)
|
||||
* @param {string} relativePath - Relative path to flatten
|
||||
|
||||
@@ -13,6 +13,7 @@ const {
|
||||
resolveSubagentFiles,
|
||||
} = require('./shared/module-injections');
|
||||
const { getAgentsFromBmad, getAgentsFromDir } = require('./shared/bmad-artifacts');
|
||||
const { toDashPath, customAgentDashName } = require('./shared/path-utils');
|
||||
const prompts = require('../../../lib/prompts');
|
||||
|
||||
/**
|
||||
@@ -125,16 +126,10 @@ class AntigravitySetup extends BaseIdeSetup {
|
||||
const agentGen = new AgentCommandGenerator(this.bmadFolderName);
|
||||
const { artifacts: agentArtifacts, counts: agentCounts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []);
|
||||
|
||||
// Write agent launcher files with FLATTENED naming
|
||||
// Antigravity ignores directory structure, so we flatten to: bmad-module-agents-name.md
|
||||
// This creates slash commands like /bmad-bmm-agents-dev instead of /dev
|
||||
let agentCount = 0;
|
||||
for (const artifact of agentArtifacts) {
|
||||
const flattenedName = this.flattenFilename(artifact.relativePath);
|
||||
const targetPath = path.join(bmadWorkflowsDir, flattenedName);
|
||||
await this.writeFile(targetPath, artifact.content);
|
||||
agentCount++;
|
||||
}
|
||||
// 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);
|
||||
|
||||
// Process Antigravity specific injections for installed modules
|
||||
// Use pre-collected configuration if available, or skip if already configured
|
||||
@@ -152,16 +147,8 @@ class AntigravitySetup extends BaseIdeSetup {
|
||||
const workflowGen = new WorkflowCommandGenerator(this.bmadFolderName);
|
||||
const { artifacts: workflowArtifacts } = await workflowGen.collectWorkflowArtifacts(bmadDir);
|
||||
|
||||
// Write workflow-command artifacts with FLATTENED naming
|
||||
let workflowCommandCount = 0;
|
||||
for (const artifact of workflowArtifacts) {
|
||||
if (artifact.type === 'workflow-command') {
|
||||
const flattenedName = this.flattenFilename(artifact.relativePath);
|
||||
const targetPath = path.join(bmadWorkflowsDir, flattenedName);
|
||||
await this.writeFile(targetPath, artifact.content);
|
||||
workflowCommandCount++;
|
||||
}
|
||||
}
|
||||
// Write workflow-command artifacts with FLATTENED naming using shared utility
|
||||
const workflowCommandCount = await workflowGen.writeDashArtifacts(bmadWorkflowsDir, workflowArtifacts);
|
||||
|
||||
// Generate task and tool commands from manifests (if they exist)
|
||||
const taskToolGen = new TaskToolCommandGenerator();
|
||||
@@ -468,7 +455,8 @@ usage: |
|
||||
|
||||
⚠️ **IMPORTANT**: Run @${agentPath} to load the complete agent before using this launcher!`;
|
||||
|
||||
const fileName = `bmad-custom-agents-${agentName}.md`;
|
||||
// Use dash format: bmad-custom-agents-fred-commit-poet.md
|
||||
const fileName = customAgentDashName(agentName);
|
||||
const launcherPath = path.join(bmadWorkflowsDir, fileName);
|
||||
|
||||
// Write the launcher file
|
||||
@@ -477,7 +465,7 @@ usage: |
|
||||
return {
|
||||
ide: 'antigravity',
|
||||
path: path.relative(projectDir, launcherPath),
|
||||
command: `/${agentName}`,
|
||||
command: `/${fileName.replace('.md', '')}`,
|
||||
type: 'custom-agent-launcher',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ const {
|
||||
resolveSubagentFiles,
|
||||
} = require('./shared/module-injections');
|
||||
const { getAgentsFromBmad, getAgentsFromDir } = require('./shared/bmad-artifacts');
|
||||
const { customAgentColonName } = require('./shared/path-utils');
|
||||
const prompts = require('../../../lib/prompts');
|
||||
|
||||
/**
|
||||
@@ -89,11 +90,44 @@ class ClaudeCodeSetup extends BaseIdeSetup {
|
||||
* @param {string} projectDir - Project directory
|
||||
*/
|
||||
async cleanup(projectDir) {
|
||||
const bmadCommandsDir = path.join(projectDir, this.configDir, this.commandsDir, 'bmad');
|
||||
const commandsDir = path.join(projectDir, this.configDir, this.commandsDir);
|
||||
|
||||
if (await fs.pathExists(bmadCommandsDir)) {
|
||||
await fs.remove(bmadCommandsDir);
|
||||
console.log(chalk.dim(` Removed old BMAD commands from ${this.name}`));
|
||||
// Remove any bmad:* files from the commands directory
|
||||
if (await fs.pathExists(commandsDir)) {
|
||||
const entries = await fs.readdir(commandsDir);
|
||||
let removedCount = 0;
|
||||
for (const entry of entries) {
|
||||
if (entry.startsWith('bmad:')) {
|
||||
await fs.remove(path.join(commandsDir, entry));
|
||||
removedCount++;
|
||||
}
|
||||
}
|
||||
// Also remove legacy bmad folder if it exists
|
||||
const bmadFolder = path.join(commandsDir, 'bmad');
|
||||
if (await fs.pathExists(bmadFolder)) {
|
||||
await fs.remove(bmadFolder);
|
||||
console.log(chalk.dim(` Removed old BMAD commands from ${this.name}`));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up legacy folder structure (module/type/name.md) if it exists
|
||||
* This can be called after migration to remove old nested directories
|
||||
* @param {string} projectDir - Project directory
|
||||
*/
|
||||
async cleanupLegacyFolders(projectDir) {
|
||||
const commandsDir = path.join(projectDir, this.configDir, this.commandsDir);
|
||||
|
||||
if (!(await fs.pathExists(commandsDir))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove legacy bmad folder if it exists
|
||||
const bmadFolder = path.join(commandsDir, 'bmad');
|
||||
if (await fs.pathExists(bmadFolder)) {
|
||||
await fs.remove(bmadFolder);
|
||||
console.log(chalk.dim(` Removed legacy bmad folder from ${this.name}`));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,28 +149,19 @@ class ClaudeCodeSetup extends BaseIdeSetup {
|
||||
// Create .claude/commands directory structure
|
||||
const claudeDir = path.join(projectDir, this.configDir);
|
||||
const commandsDir = path.join(claudeDir, this.commandsDir);
|
||||
const bmadCommandsDir = path.join(commandsDir, 'bmad');
|
||||
await this.ensureDir(commandsDir);
|
||||
|
||||
await this.ensureDir(bmadCommandsDir);
|
||||
// Use colon format: files written directly to commands dir (no bmad subfolder)
|
||||
// Creates: .claude/commands/bmad:bmm:pm.md
|
||||
|
||||
// Generate agent launchers using AgentCommandGenerator
|
||||
// This creates small launcher files that reference the actual agents in _bmad/
|
||||
const agentGen = new AgentCommandGenerator(this.bmadFolderName);
|
||||
const { artifacts: agentArtifacts, counts: agentCounts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []);
|
||||
|
||||
// Create directories for each module
|
||||
const modules = new Set();
|
||||
for (const artifact of agentArtifacts) {
|
||||
modules.add(artifact.module);
|
||||
}
|
||||
|
||||
for (const module of modules) {
|
||||
await this.ensureDir(path.join(bmadCommandsDir, module));
|
||||
await this.ensureDir(path.join(bmadCommandsDir, module, 'agents'));
|
||||
}
|
||||
|
||||
// Write agent launcher files
|
||||
const agentCount = await agentGen.writeAgentLaunchers(bmadCommandsDir, agentArtifacts);
|
||||
// Write agent launcher files using flat colon naming
|
||||
// Creates files like: bmad:bmm:pm.md
|
||||
const agentCount = await agentGen.writeColonArtifacts(commandsDir, agentArtifacts);
|
||||
|
||||
// Process Claude Code specific injections for installed modules
|
||||
// Use pre-collected configuration if available, or skip if already configured
|
||||
@@ -157,22 +182,13 @@ class ClaudeCodeSetup extends BaseIdeSetup {
|
||||
const workflowGen = new WorkflowCommandGenerator(this.bmadFolderName);
|
||||
const { artifacts: workflowArtifacts } = await workflowGen.collectWorkflowArtifacts(bmadDir);
|
||||
|
||||
// Write only workflow-command artifacts, skip workflow-launcher READMEs
|
||||
let workflowCommandCount = 0;
|
||||
for (const artifact of workflowArtifacts) {
|
||||
if (artifact.type === 'workflow-command') {
|
||||
const moduleWorkflowsDir = path.join(bmadCommandsDir, artifact.module, 'workflows');
|
||||
await this.ensureDir(moduleWorkflowsDir);
|
||||
const commandPath = path.join(moduleWorkflowsDir, path.basename(artifact.relativePath));
|
||||
await this.writeFile(commandPath, artifact.content);
|
||||
workflowCommandCount++;
|
||||
}
|
||||
// Skip workflow-launcher READMEs as they would be treated as slash commands
|
||||
}
|
||||
// Write workflow-command artifacts using flat colon naming
|
||||
// Creates files like: bmad:bmm:correct-course.md
|
||||
const workflowCommandCount = await workflowGen.writeColonArtifacts(commandsDir, workflowArtifacts);
|
||||
|
||||
// Generate task and tool commands from manifests (if they exist)
|
||||
const taskToolGen = new TaskToolCommandGenerator();
|
||||
const taskToolResult = await taskToolGen.generateTaskToolCommands(projectDir, bmadDir);
|
||||
const taskToolResult = await taskToolGen.generateColonTaskToolCommands(projectDir, bmadDir, commandsDir);
|
||||
|
||||
console.log(chalk.green(`✓ ${this.name} configured:`));
|
||||
console.log(chalk.dim(` - ${agentCount} agents installed`));
|
||||
@@ -186,7 +202,7 @@ class ClaudeCodeSetup extends BaseIdeSetup {
|
||||
),
|
||||
);
|
||||
}
|
||||
console.log(chalk.dim(` - Commands directory: ${path.relative(projectDir, bmadCommandsDir)}`));
|
||||
console.log(chalk.dim(` - Commands directory: ${path.relative(projectDir, commandsDir)}`));
|
||||
|
||||
return {
|
||||
success: true,
|
||||
@@ -449,13 +465,13 @@ class ClaudeCodeSetup extends BaseIdeSetup {
|
||||
* @returns {Object|null} Info about created command
|
||||
*/
|
||||
async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) {
|
||||
const customAgentsDir = path.join(projectDir, this.configDir, this.commandsDir, 'bmad', 'custom', 'agents');
|
||||
const commandsDir = path.join(projectDir, this.configDir, this.commandsDir);
|
||||
|
||||
if (!(await this.exists(path.join(projectDir, this.configDir)))) {
|
||||
return null; // IDE not configured for this project
|
||||
}
|
||||
|
||||
await this.ensureDir(customAgentsDir);
|
||||
await this.ensureDir(commandsDir);
|
||||
|
||||
const launcherContent = `---
|
||||
name: '${agentName}'
|
||||
@@ -474,12 +490,15 @@ You must fully embody this agent's persona and follow all activation instruction
|
||||
</agent-activation>
|
||||
`;
|
||||
|
||||
const launcherPath = path.join(customAgentsDir, `${agentName}.md`);
|
||||
// Use colon format: bmad:custom:agents:fred-commit-poet.md
|
||||
// Written directly to commands dir (no bmad subfolder)
|
||||
const launcherName = customAgentColonName(agentName);
|
||||
const launcherPath = path.join(commandsDir, launcherName);
|
||||
await this.writeFile(launcherPath, launcherContent);
|
||||
|
||||
return {
|
||||
path: launcherPath,
|
||||
command: `/bmad:custom:agents:${agentName}`,
|
||||
command: `/${launcherName.replace('.md', '')}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,9 @@ const chalk = require('chalk');
|
||||
const { BaseIdeSetup } = require('./_base-ide');
|
||||
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');
|
||||
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
|
||||
const { TaskToolCommandGenerator } = require('./shared/task-tool-command-generator');
|
||||
const { getAgentsFromBmad, getTasksFromBmad } = require('./shared/bmad-artifacts');
|
||||
const { toDashPath, customAgentDashName } = require('./shared/path-utils');
|
||||
|
||||
/**
|
||||
* Cline IDE setup handler
|
||||
@@ -56,7 +58,7 @@ class ClineSetup extends BaseIdeSetup {
|
||||
console.log(chalk.dim(' Usage:'));
|
||||
console.log(chalk.dim(' - Type / to see available commands'));
|
||||
console.log(chalk.dim(' - All BMAD items start with "bmad-"'));
|
||||
console.log(chalk.dim(' - Example: /bmad-bmm-agents-pm'));
|
||||
console.log(chalk.dim(' - Example: /bmad-bmm-pm'));
|
||||
|
||||
return {
|
||||
success: true,
|
||||
@@ -145,10 +147,10 @@ class ClineSetup extends BaseIdeSetup {
|
||||
|
||||
/**
|
||||
* Flatten file path to bmad-module-type-name.md format
|
||||
* Uses shared toDashPath utility
|
||||
*/
|
||||
flattenFilename(relativePath) {
|
||||
const sanitized = relativePath.replaceAll(/[\\/]/g, '-');
|
||||
return `bmad-${sanitized}`;
|
||||
return toDashPath(relativePath);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -244,7 +246,8 @@ The agent will follow the persona and instructions from the main agent file.
|
||||
|
||||
*Generated by BMAD Method*`;
|
||||
|
||||
const fileName = `bmad-custom-${agentName.toLowerCase()}.md`;
|
||||
// Use dash format: bmad-custom-agents-fred-commit-poet.md
|
||||
const fileName = customAgentDashName(agentName);
|
||||
const launcherPath = path.join(workflowsDir, fileName);
|
||||
|
||||
// Write the launcher file
|
||||
@@ -253,7 +256,7 @@ The agent will follow the persona and instructions from the main agent file.
|
||||
return {
|
||||
ide: 'cline',
|
||||
path: path.relative(projectDir, launcherPath),
|
||||
command: agentName,
|
||||
command: fileName.replace('.md', ''),
|
||||
type: 'custom-agent-launcher',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5,7 +5,9 @@ const chalk = require('chalk');
|
||||
const { BaseIdeSetup } = require('./_base-ide');
|
||||
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');
|
||||
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
|
||||
const { TaskToolCommandGenerator } = require('./shared/task-tool-command-generator');
|
||||
const { getTasksFromBmad } = require('./shared/bmad-artifacts');
|
||||
const { toDashPath, customAgentDashName } = require('./shared/path-utils');
|
||||
const prompts = require('../../../lib/prompts');
|
||||
|
||||
/**
|
||||
@@ -83,7 +85,41 @@ class CodexSetup extends BaseIdeSetup {
|
||||
const destDir = this.getCodexPromptDir(projectDir, installLocation);
|
||||
await fs.ensureDir(destDir);
|
||||
await this.clearOldBmadFiles(destDir);
|
||||
const written = await this.flattenAndWriteArtifacts(artifacts, destDir);
|
||||
|
||||
// Collect artifacts and write using DASH format
|
||||
const agentGen = new AgentCommandGenerator(this.bmadFolderName);
|
||||
const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []);
|
||||
const agentCount = await agentGen.writeDashArtifacts(destDir, agentArtifacts);
|
||||
|
||||
const tasks = await getTasksFromBmad(bmadDir, options.selectedModules || []);
|
||||
const taskArtifacts = [];
|
||||
for (const task of tasks) {
|
||||
const content = await this.readAndProcessWithProject(
|
||||
task.path,
|
||||
{
|
||||
module: task.module,
|
||||
name: task.name,
|
||||
},
|
||||
projectDir,
|
||||
);
|
||||
taskArtifacts.push({
|
||||
type: 'task',
|
||||
module: task.module,
|
||||
sourcePath: task.path,
|
||||
relativePath: path.join(task.module, 'tasks', `${task.name}.md`),
|
||||
content,
|
||||
});
|
||||
}
|
||||
|
||||
const workflowGenerator = new WorkflowCommandGenerator(this.bmadFolderName);
|
||||
const { artifacts: workflowArtifacts } = await workflowGenerator.collectWorkflowArtifacts(bmadDir);
|
||||
const workflowCount = await workflowGenerator.writeDashArtifacts(destDir, workflowArtifacts);
|
||||
|
||||
// Also write tasks using dash format
|
||||
const ttGen = new TaskToolCommandGenerator();
|
||||
const tasksWritten = await ttGen.writeDashArtifacts(destDir, taskArtifacts);
|
||||
|
||||
const written = agentCount + workflowCount + tasksWritten;
|
||||
|
||||
console.log(chalk.green(`✓ ${this.name} configured:`));
|
||||
console.log(chalk.dim(` - Mode: CLI`));
|
||||
@@ -256,7 +292,7 @@ class CodexSetup extends BaseIdeSetup {
|
||||
chalk.dim(" To use with other projects, you'd need to copy the _bmad dir"),
|
||||
'',
|
||||
chalk.green(' ✓ You can now use /commands in Codex CLI'),
|
||||
chalk.dim(' Example: /bmad-bmm-agents-pm'),
|
||||
chalk.dim(' Example: /bmad-bmm-pm'),
|
||||
chalk.dim(' Type / to see all available commands'),
|
||||
'',
|
||||
chalk.bold.cyan('═'.repeat(70)),
|
||||
@@ -361,7 +397,8 @@ You must fully embody this agent's persona and follow all activation instruction
|
||||
</agent-activation>
|
||||
`;
|
||||
|
||||
const fileName = `bmad-custom-agents-${agentName}.md`;
|
||||
// Use dash format: bmad-custom-agents-fred-commit-poet.md
|
||||
const fileName = customAgentDashName(agentName);
|
||||
const launcherPath = path.join(destDir, fileName);
|
||||
await fs.writeFile(launcherPath, launcherContent, 'utf8');
|
||||
|
||||
|
||||
@@ -4,10 +4,12 @@ const { BaseIdeSetup } = require('./_base-ide');
|
||||
const chalk = require('chalk');
|
||||
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
|
||||
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');
|
||||
const { TaskToolCommandGenerator } = require('./shared/task-tool-command-generator');
|
||||
const { customAgentColonName } = require('./shared/path-utils');
|
||||
|
||||
/**
|
||||
* Crush IDE setup handler
|
||||
* Creates commands in .crush/commands/ directory structure
|
||||
* Creates commands in .crush/commands/ directory structure using flat colon naming
|
||||
*/
|
||||
class CrushSetup extends BaseIdeSetup {
|
||||
constructor() {
|
||||
@@ -25,227 +27,73 @@ class CrushSetup extends BaseIdeSetup {
|
||||
async setup(projectDir, bmadDir, options = {}) {
|
||||
console.log(chalk.cyan(`Setting up ${this.name}...`));
|
||||
|
||||
// Create .crush/commands/bmad directory structure
|
||||
const crushDir = path.join(projectDir, this.configDir);
|
||||
const commandsDir = path.join(crushDir, this.commandsDir, 'bmad');
|
||||
// Clean up old BMAD installation first
|
||||
await this.cleanup(projectDir);
|
||||
|
||||
// Create .crush/commands directory
|
||||
const crushDir = path.join(projectDir, this.configDir);
|
||||
const commandsDir = path.join(crushDir, this.commandsDir);
|
||||
await this.ensureDir(commandsDir);
|
||||
|
||||
// Use colon format: files written directly to commands dir (no bmad subfolder)
|
||||
// Creates: .crush/commands/bmad:bmm:pm.md
|
||||
|
||||
// Generate agent launchers
|
||||
const agentGen = new AgentCommandGenerator(this.bmadFolderName);
|
||||
const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []);
|
||||
|
||||
// Get tasks, tools, and workflows (ALL workflows now generate commands)
|
||||
const tasks = await this.getTasks(bmadDir, true);
|
||||
const tools = await this.getTools(bmadDir, true);
|
||||
// Write agent launcher files using flat colon naming
|
||||
// Creates files like: bmad:bmm:pm.md
|
||||
const agentCount = await agentGen.writeColonArtifacts(commandsDir, agentArtifacts);
|
||||
|
||||
// Get ALL workflows using the new workflow command generator
|
||||
const workflowGenerator = new WorkflowCommandGenerator(this.bmadFolderName);
|
||||
const { artifacts: workflowArtifacts, counts: workflowCounts } = await workflowGenerator.collectWorkflowArtifacts(bmadDir);
|
||||
const { artifacts: workflowArtifacts } = await workflowGenerator.collectWorkflowArtifacts(bmadDir);
|
||||
|
||||
// Convert workflow artifacts to expected format for organizeByModule
|
||||
const workflows = workflowArtifacts
|
||||
.filter((artifact) => artifact.type === 'workflow-command')
|
||||
.map((artifact) => ({
|
||||
module: artifact.module,
|
||||
name: path.basename(artifact.relativePath, '.md'),
|
||||
path: artifact.sourcePath,
|
||||
content: artifact.content,
|
||||
}));
|
||||
// Write workflow-command artifacts using flat colon naming
|
||||
// Creates files like: bmad:bmm:correct-course.md
|
||||
const workflowCount = await workflowGenerator.writeColonArtifacts(commandsDir, workflowArtifacts);
|
||||
|
||||
// Organize by module
|
||||
const agentCount = await this.organizeByModule(commandsDir, agentArtifacts, tasks, tools, workflows, projectDir);
|
||||
// Generate task and tool commands using flat colon naming
|
||||
const taskToolGen = new TaskToolCommandGenerator();
|
||||
const taskToolResult = await taskToolGen.generateColonTaskToolCommands(projectDir, bmadDir, commandsDir);
|
||||
|
||||
console.log(chalk.green(`✓ ${this.name} configured:`));
|
||||
console.log(chalk.dim(` - ${agentCount.agents} agent commands created`));
|
||||
console.log(chalk.dim(` - ${agentCount.tasks} task commands created`));
|
||||
console.log(chalk.dim(` - ${agentCount.tools} tool commands created`));
|
||||
console.log(chalk.dim(` - ${agentCount.workflows} workflow commands created`));
|
||||
console.log(chalk.dim(` - ${agentCount} agent commands created`));
|
||||
console.log(chalk.dim(` - ${taskToolResult.tasks} task commands created`));
|
||||
console.log(chalk.dim(` - ${taskToolResult.tools} tool commands created`));
|
||||
console.log(chalk.dim(` - ${workflowCount} workflow commands created`));
|
||||
console.log(chalk.dim(` - Commands directory: ${path.relative(projectDir, commandsDir)}`));
|
||||
console.log(chalk.dim('\n Commands can be accessed via Crush command palette'));
|
||||
|
||||
return {
|
||||
success: true,
|
||||
...agentCount,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Organize commands by module
|
||||
*/
|
||||
async organizeByModule(commandsDir, agentArtifacts, tasks, tools, workflows, projectDir) {
|
||||
// Get unique modules
|
||||
const modules = new Set();
|
||||
for (const artifact of agentArtifacts) modules.add(artifact.module);
|
||||
for (const task of tasks) modules.add(task.module);
|
||||
for (const tool of tools) modules.add(tool.module);
|
||||
for (const workflow of workflows) modules.add(workflow.module);
|
||||
|
||||
let agentCount = 0;
|
||||
let taskCount = 0;
|
||||
let toolCount = 0;
|
||||
let workflowCount = 0;
|
||||
|
||||
// Create module directories
|
||||
for (const module of modules) {
|
||||
const moduleDir = path.join(commandsDir, module);
|
||||
const moduleAgentsDir = path.join(moduleDir, 'agents');
|
||||
const moduleTasksDir = path.join(moduleDir, 'tasks');
|
||||
const moduleToolsDir = path.join(moduleDir, 'tools');
|
||||
const moduleWorkflowsDir = path.join(moduleDir, 'workflows');
|
||||
|
||||
await this.ensureDir(moduleAgentsDir);
|
||||
await this.ensureDir(moduleTasksDir);
|
||||
await this.ensureDir(moduleToolsDir);
|
||||
await this.ensureDir(moduleWorkflowsDir);
|
||||
|
||||
// Write module-specific agent launchers
|
||||
const moduleAgents = agentArtifacts.filter((a) => a.module === module);
|
||||
for (const artifact of moduleAgents) {
|
||||
const targetPath = path.join(moduleAgentsDir, `${artifact.name}.md`);
|
||||
await this.writeFile(targetPath, artifact.content);
|
||||
agentCount++;
|
||||
}
|
||||
|
||||
// Copy module-specific tasks
|
||||
const moduleTasks = tasks.filter((t) => t.module === module);
|
||||
for (const task of moduleTasks) {
|
||||
const content = await this.readFile(task.path);
|
||||
const commandContent = this.createTaskCommand(task, content);
|
||||
const targetPath = path.join(moduleTasksDir, `${task.name}.md`);
|
||||
await this.writeFile(targetPath, commandContent);
|
||||
taskCount++;
|
||||
}
|
||||
|
||||
// Copy module-specific tools
|
||||
const moduleTools = tools.filter((t) => t.module === module);
|
||||
for (const tool of moduleTools) {
|
||||
const content = await this.readFile(tool.path);
|
||||
const commandContent = this.createToolCommand(tool, content);
|
||||
const targetPath = path.join(moduleToolsDir, `${tool.name}.md`);
|
||||
await this.writeFile(targetPath, commandContent);
|
||||
toolCount++;
|
||||
}
|
||||
|
||||
// Copy module-specific workflow commands (already generated)
|
||||
const moduleWorkflows = workflows.filter((w) => w.module === module);
|
||||
for (const workflow of moduleWorkflows) {
|
||||
// Use the pre-generated workflow command content
|
||||
const targetPath = path.join(moduleWorkflowsDir, `${workflow.name}.md`);
|
||||
await this.writeFile(targetPath, workflow.content);
|
||||
workflowCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
agents: agentCount,
|
||||
tasks: taskCount,
|
||||
tools: toolCount,
|
||||
tasks: taskToolResult.tasks || 0,
|
||||
tools: taskToolResult.tools || 0,
|
||||
workflows: workflowCount,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create task command content
|
||||
*/
|
||||
createTaskCommand(task, content) {
|
||||
// Extract task name
|
||||
const nameMatch = content.match(/name="([^"]+)"/);
|
||||
const taskName = nameMatch ? nameMatch[1] : this.formatTitle(task.name);
|
||||
|
||||
let commandContent = `# /task-${task.name} Command
|
||||
|
||||
When this command is used, execute the following task:
|
||||
|
||||
## ${taskName} Task
|
||||
|
||||
${content}
|
||||
|
||||
## Command Usage
|
||||
|
||||
This command executes the ${taskName} task from the BMAD ${task.module.toUpperCase()} module.
|
||||
|
||||
## Module
|
||||
|
||||
Part of the BMAD ${task.module.toUpperCase()} module.
|
||||
`;
|
||||
|
||||
return commandContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create tool command content
|
||||
*/
|
||||
createToolCommand(tool, content) {
|
||||
// Extract tool name
|
||||
const nameMatch = content.match(/name="([^"]+)"/);
|
||||
const toolName = nameMatch ? nameMatch[1] : this.formatTitle(tool.name);
|
||||
|
||||
let commandContent = `# /tool-${tool.name} Command
|
||||
|
||||
When this command is used, execute the following tool:
|
||||
|
||||
## ${toolName} Tool
|
||||
|
||||
${content}
|
||||
|
||||
## Command Usage
|
||||
|
||||
This command executes the ${toolName} tool from the BMAD ${tool.module.toUpperCase()} module.
|
||||
|
||||
## Module
|
||||
|
||||
Part of the BMAD ${tool.module.toUpperCase()} module.
|
||||
`;
|
||||
|
||||
return commandContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create workflow command content
|
||||
*/
|
||||
createWorkflowCommand(workflow, content) {
|
||||
const workflowName = workflow.name ? this.formatTitle(workflow.name) : 'Workflow';
|
||||
|
||||
let commandContent = `# /${workflow.name} Command
|
||||
|
||||
When this command is used, execute the following workflow:
|
||||
|
||||
## ${workflowName} Workflow
|
||||
|
||||
${content}
|
||||
|
||||
## Command Usage
|
||||
|
||||
This command executes the ${workflowName} workflow from the BMAD ${workflow.module.toUpperCase()} module.
|
||||
|
||||
## Module
|
||||
|
||||
Part of the BMAD ${workflow.module.toUpperCase()} module.
|
||||
`;
|
||||
|
||||
return commandContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format name as title
|
||||
*/
|
||||
formatTitle(name) {
|
||||
return name
|
||||
.split('-')
|
||||
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join(' ');
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup Crush 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);
|
||||
|
||||
if (await fs.pathExists(bmadCommandsDir)) {
|
||||
await fs.remove(bmadCommandsDir);
|
||||
// Remove any bmad:* files from the commands directory
|
||||
if (await fs.pathExists(commandsDir)) {
|
||||
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 folder if it exists
|
||||
const bmadFolder = path.join(commandsDir, 'bmad');
|
||||
if (await fs.pathExists(bmadFolder)) {
|
||||
await fs.remove(bmadFolder);
|
||||
console.log(chalk.dim(`Removed BMAD commands from Crush`));
|
||||
}
|
||||
}
|
||||
@@ -259,11 +107,10 @@ Part of the BMAD ${workflow.module.toUpperCase()} module.
|
||||
* @returns {Object} Installation result
|
||||
*/
|
||||
async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) {
|
||||
const crushDir = path.join(projectDir, this.configDir);
|
||||
const bmadCommandsDir = path.join(crushDir, this.commandsDir, 'bmad');
|
||||
const commandsDir = path.join(projectDir, this.configDir, this.commandsDir);
|
||||
|
||||
// Create .crush/commands/bmad directory if it doesn't exist
|
||||
await fs.ensureDir(bmadCommandsDir);
|
||||
// Create .crush/commands directory if it doesn't exist
|
||||
await fs.ensureDir(commandsDir);
|
||||
|
||||
// Create custom agent launcher
|
||||
const launcherContent = `# ${agentName} Custom Agent
|
||||
@@ -282,8 +129,10 @@ 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);
|
||||
// Use colon format: bmad:custom:agents:fred-commit-poet.md
|
||||
// Written directly to commands dir (no bmad subfolder)
|
||||
const launcherName = customAgentColonName(agentName);
|
||||
const launcherPath = path.join(commandsDir, launcherName);
|
||||
|
||||
// Write the launcher file
|
||||
await fs.writeFile(launcherPath, launcherContent, 'utf8');
|
||||
@@ -291,7 +140,7 @@ The agent will follow the persona and instructions from the main agent file.
|
||||
return {
|
||||
ide: 'crush',
|
||||
path: path.relative(projectDir, launcherPath),
|
||||
command: agentName,
|
||||
command: launcherName.replace('.md', ''),
|
||||
type: 'custom-agent-launcher',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ const chalk = require('chalk');
|
||||
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
|
||||
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');
|
||||
const { TaskToolCommandGenerator } = require('./shared/task-tool-command-generator');
|
||||
const { customAgentColonName } = require('./shared/path-utils');
|
||||
|
||||
/**
|
||||
* Cursor IDE setup handler
|
||||
@@ -22,16 +23,21 @@ class CursorSetup extends BaseIdeSetup {
|
||||
*/
|
||||
async cleanup(projectDir) {
|
||||
const fs = require('fs-extra');
|
||||
const bmadRulesDir = path.join(projectDir, this.configDir, this.rulesDir, 'bmad');
|
||||
const bmadCommandsDir = path.join(projectDir, this.configDir, this.commandsDir, 'bmad');
|
||||
const commandsDir = path.join(projectDir, this.configDir, this.commandsDir);
|
||||
|
||||
if (await fs.pathExists(bmadRulesDir)) {
|
||||
await fs.remove(bmadRulesDir);
|
||||
console.log(chalk.dim(` Removed old BMAD rules from ${this.name}`));
|
||||
// Remove any bmad:* files from the commands directory
|
||||
if (await fs.pathExists(commandsDir)) {
|
||||
const entries = await fs.readdir(commandsDir);
|
||||
for (const entry of entries) {
|
||||
if (entry.startsWith('bmad:')) {
|
||||
await fs.remove(path.join(commandsDir, entry));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (await fs.pathExists(bmadCommandsDir)) {
|
||||
await fs.remove(bmadCommandsDir);
|
||||
// Also remove legacy bmad folder if it exists
|
||||
const bmadFolder = path.join(commandsDir, 'bmad');
|
||||
if (await fs.pathExists(bmadFolder)) {
|
||||
await fs.remove(bmadFolder);
|
||||
console.log(chalk.dim(` Removed old BMAD commands from ${this.name}`));
|
||||
}
|
||||
}
|
||||
@@ -51,49 +57,31 @@ class CursorSetup extends BaseIdeSetup {
|
||||
// Create .cursor/commands directory structure
|
||||
const cursorDir = path.join(projectDir, this.configDir);
|
||||
const commandsDir = path.join(cursorDir, this.commandsDir);
|
||||
const bmadCommandsDir = path.join(commandsDir, 'bmad');
|
||||
await this.ensureDir(commandsDir);
|
||||
|
||||
await this.ensureDir(bmadCommandsDir);
|
||||
// Use colon format: files written directly to commands dir (no bmad subfolder)
|
||||
// Creates: .cursor/commands/bmad:bmm:pm.md
|
||||
|
||||
// Generate agent launchers using AgentCommandGenerator
|
||||
// This creates small launcher files that reference the actual agents in _bmad/
|
||||
const agentGen = new AgentCommandGenerator(this.bmadFolderName);
|
||||
const { artifacts: agentArtifacts, counts: agentCounts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []);
|
||||
|
||||
// Create directories for each module
|
||||
const modules = new Set();
|
||||
for (const artifact of agentArtifacts) {
|
||||
modules.add(artifact.module);
|
||||
}
|
||||
|
||||
for (const module of modules) {
|
||||
await this.ensureDir(path.join(bmadCommandsDir, module));
|
||||
await this.ensureDir(path.join(bmadCommandsDir, module, 'agents'));
|
||||
}
|
||||
|
||||
// Write agent launcher files
|
||||
const agentCount = await agentGen.writeAgentLaunchers(bmadCommandsDir, agentArtifacts);
|
||||
// Write agent launcher files using flat colon naming
|
||||
// Creates files like: bmad:bmm:pm.md
|
||||
const agentCount = await agentGen.writeColonArtifacts(commandsDir, agentArtifacts);
|
||||
|
||||
// Generate workflow commands from manifest (if it exists)
|
||||
const workflowGen = new WorkflowCommandGenerator(this.bmadFolderName);
|
||||
const { artifacts: workflowArtifacts } = await workflowGen.collectWorkflowArtifacts(bmadDir);
|
||||
|
||||
// Write only workflow-command artifacts, skip workflow-launcher READMEs
|
||||
let workflowCommandCount = 0;
|
||||
for (const artifact of workflowArtifacts) {
|
||||
if (artifact.type === 'workflow-command') {
|
||||
const moduleWorkflowsDir = path.join(bmadCommandsDir, artifact.module, 'workflows');
|
||||
await this.ensureDir(moduleWorkflowsDir);
|
||||
const commandPath = path.join(moduleWorkflowsDir, path.basename(artifact.relativePath));
|
||||
await this.writeFile(commandPath, artifact.content);
|
||||
workflowCommandCount++;
|
||||
}
|
||||
// Skip workflow-launcher READMEs as they would be treated as slash commands
|
||||
}
|
||||
// Write workflow-command artifacts using flat colon naming
|
||||
// Creates files like: bmad:bmm:correct-course.md
|
||||
const workflowCommandCount = await workflowGen.writeColonArtifacts(commandsDir, workflowArtifacts);
|
||||
|
||||
// Generate task and tool commands from manifests (if they exist)
|
||||
const taskToolGen = new TaskToolCommandGenerator();
|
||||
const taskToolResult = await taskToolGen.generateTaskToolCommands(projectDir, bmadDir, bmadCommandsDir);
|
||||
const taskToolResult = await taskToolGen.generateColonTaskToolCommands(projectDir, bmadDir, commandsDir);
|
||||
|
||||
console.log(chalk.green(`✓ ${this.name} configured:`));
|
||||
console.log(chalk.dim(` - ${agentCount} agents installed`));
|
||||
@@ -107,7 +95,7 @@ class CursorSetup extends BaseIdeSetup {
|
||||
),
|
||||
);
|
||||
}
|
||||
console.log(chalk.dim(` - Commands directory: ${path.relative(projectDir, bmadCommandsDir)}`));
|
||||
console.log(chalk.dim(` - Commands directory: ${path.relative(projectDir, commandsDir)}`));
|
||||
|
||||
return {
|
||||
success: true,
|
||||
@@ -127,13 +115,13 @@ class CursorSetup extends BaseIdeSetup {
|
||||
* @returns {Object|null} Info about created command
|
||||
*/
|
||||
async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) {
|
||||
const customAgentsDir = path.join(projectDir, this.configDir, this.commandsDir, 'bmad', 'custom', 'agents');
|
||||
const commandsDir = path.join(projectDir, this.configDir, this.commandsDir);
|
||||
|
||||
if (!(await this.exists(path.join(projectDir, this.configDir)))) {
|
||||
return null; // IDE not configured for this project
|
||||
}
|
||||
|
||||
await this.ensureDir(customAgentsDir);
|
||||
await this.ensureDir(commandsDir);
|
||||
|
||||
const launcherContent = `You must fully embody this agent's persona and follow all activation instructions exactly as specified. NEVER break character until given an exit command.
|
||||
|
||||
@@ -156,12 +144,15 @@ description: '${agentName} agent'
|
||||
${launcherContent}
|
||||
`;
|
||||
|
||||
const launcherPath = path.join(customAgentsDir, `${agentName}.md`);
|
||||
// Use colon format: bmad:custom:agents:fred-commit-poet.md
|
||||
// Written directly to commands dir (no bmad subfolder)
|
||||
const launcherName = customAgentColonName(agentName);
|
||||
const launcherPath = path.join(commandsDir, launcherName);
|
||||
await this.writeFile(launcherPath, commandContent);
|
||||
|
||||
return {
|
||||
path: launcherPath,
|
||||
command: `/bmad/custom/agents/${agentName}`,
|
||||
command: `/${launcherName.replace('.md', '')}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ const path = require('node:path');
|
||||
const { BaseIdeSetup } = require('./_base-ide');
|
||||
const chalk = require('chalk');
|
||||
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
|
||||
const { toDashPath, customAgentDashName } = require('./shared/path-utils');
|
||||
|
||||
/**
|
||||
* Roo IDE setup handler
|
||||
@@ -35,7 +36,8 @@ class RooSetup extends BaseIdeSetup {
|
||||
let skippedCount = 0;
|
||||
|
||||
for (const artifact of agentArtifacts) {
|
||||
const commandName = `bmad-${artifact.module}-agent-${artifact.name}`;
|
||||
// Use shared toDashPath to get consistent naming: bmad-bmm-name.md
|
||||
const commandName = toDashPath(artifact.relativePath).replace('.md', '');
|
||||
const commandPath = path.join(rooCommandsDir, `${commandName}.md`);
|
||||
|
||||
// Skip if already exists
|
||||
@@ -71,7 +73,7 @@ class RooSetup extends BaseIdeSetup {
|
||||
if (skippedCount > 0) {
|
||||
console.log(chalk.dim(` - ${skippedCount} commands skipped (already exist)`));
|
||||
}
|
||||
console.log(chalk.dim(` - Commands directory: ${this.configDir}/${this.commandsDir}/bmad/`));
|
||||
console.log(chalk.dim(` - Commands directory: ${this.configDir}/${this.commandsDir}/`));
|
||||
console.log(chalk.dim(` Commands will be available when you open this project in Roo Code`));
|
||||
|
||||
return {
|
||||
@@ -222,7 +224,8 @@ class RooSetup extends BaseIdeSetup {
|
||||
const rooCommandsDir = path.join(projectDir, this.configDir, this.commandsDir);
|
||||
await this.ensureDir(rooCommandsDir);
|
||||
|
||||
const commandName = `bmad-custom-agent-${agentName.toLowerCase()}`;
|
||||
// Use dash format: bmad-custom-agents-fred-commit-poet.md
|
||||
const commandName = customAgentDashName(agentName).replace('.md', '');
|
||||
const commandPath = path.join(rooCommandsDir, `${commandName}.md`);
|
||||
|
||||
// Check if command already exists
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const path = require('node:path');
|
||||
const fs = require('fs-extra');
|
||||
const chalk = require('chalk');
|
||||
const { toColonPath, toDashPath, customAgentColonName, customAgentDashName } = require('./path-utils');
|
||||
|
||||
/**
|
||||
* Generates launcher command files for each agent
|
||||
@@ -91,6 +92,74 @@ class AgentCommandGenerator {
|
||||
|
||||
return writtenCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write agent launcher artifacts using COLON format (for folder-based IDEs)
|
||||
* Creates flat files like: bmad:bmm:pm.md
|
||||
*
|
||||
* @param {string} baseCommandsDir - Base commands directory for the IDE
|
||||
* @param {Array} artifacts - Agent launcher artifacts
|
||||
* @returns {number} Count of launchers written
|
||||
*/
|
||||
async writeColonArtifacts(baseCommandsDir, artifacts) {
|
||||
let writtenCount = 0;
|
||||
|
||||
for (const artifact of artifacts) {
|
||||
if (artifact.type === 'agent-launcher') {
|
||||
// Convert relativePath to colon 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.writeFile(launcherPath, artifact.content);
|
||||
writtenCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return writtenCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write agent launcher artifacts using DASH format (for flat IDEs)
|
||||
* Creates flat files like: bmad-bmm-pm.md
|
||||
*
|
||||
* @param {string} baseCommandsDir - Base commands directory for the IDE
|
||||
* @param {Array} artifacts - Agent launcher artifacts
|
||||
* @returns {number} Count of launchers written
|
||||
*/
|
||||
async writeDashArtifacts(baseCommandsDir, artifacts) {
|
||||
let writtenCount = 0;
|
||||
|
||||
for (const artifact of artifacts) {
|
||||
if (artifact.type === 'agent-launcher') {
|
||||
// Convert relativePath to dash format: bmm/agents/pm.md → bmad-bmm-pm.md
|
||||
const flatName = toDashPath(artifact.relativePath);
|
||||
const launcherPath = path.join(baseCommandsDir, flatName);
|
||||
await fs.ensureDir(path.dirname(launcherPath));
|
||||
await fs.writeFile(launcherPath, artifact.content);
|
||||
writtenCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return writtenCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the custom agent name in colon format
|
||||
* @param {string} agentName - Custom agent name
|
||||
* @returns {string} Colon-formatted filename
|
||||
*/
|
||||
getCustomAgentColonName(agentName) {
|
||||
return customAgentColonName(agentName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the custom agent name in dash format
|
||||
* @param {string} agentName - Custom agent name
|
||||
* @returns {string} Dash-formatted filename
|
||||
*/
|
||||
getCustomAgentDashName(agentName) {
|
||||
return customAgentDashName(agentName);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { AgentCommandGenerator };
|
||||
|
||||
153
tools/cli/installers/lib/ide/shared/path-utils.js
Normal file
153
tools/cli/installers/lib/ide/shared/path-utils.js
Normal file
@@ -0,0 +1,153 @@
|
||||
/**
|
||||
* Path transformation utilities for IDE installer standardization
|
||||
*
|
||||
* Provides utilities to convert hierarchical paths to flat naming conventions.
|
||||
* - Colon format (bmad:module:name.md) for folder-based IDEs converting to flat
|
||||
* - Dash format (bmad-module-name.md) for already-flat IDEs
|
||||
*/
|
||||
|
||||
// Type segments to filter out from paths
|
||||
const TYPE_SEGMENTS = ['agents', 'workflows', 'tasks', 'tools'];
|
||||
|
||||
/**
|
||||
* Convert hierarchical path to flat colon-separated name (for folder-based IDEs)
|
||||
* Converts: 'bmm/agents/pm.md' → 'bmad:bmm:pm.md'
|
||||
* Converts: 'bmm/workflows/correct-course.md' → 'bmad:bmm:correct-course.md'
|
||||
*
|
||||
* @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 toColonName(module, type, name) {
|
||||
return `bmad:${module}:${name}.md`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert relative path to flat colon-separated name (for folder-based IDEs)
|
||||
* Converts: 'bmm/agents/pm.md' → 'bmad:bmm:pm.md'
|
||||
* Converts: 'bmm/workflows/correct-course.md' → 'bmad:bmm:correct-course.md'
|
||||
*
|
||||
* @param {string} relativePath - Path like 'bmm/agents/pm.md'
|
||||
* @returns {string} Flat filename like 'bmad:bmm:pm.md'
|
||||
*/
|
||||
function toColonPath(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`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert hierarchical path to flat dash-separated name (for flat IDEs)
|
||||
* Converts: 'bmm/agents/pm.md' → 'bmad-bmm-pm.md'
|
||||
* Converts: 'bmm/workflows/correct-course.md' → 'bmad-bmm-correct-course.md'
|
||||
*
|
||||
* @param {string} relativePath - Path like 'bmm/agents/pm.md'
|
||||
* @returns {string} Flat filename like 'bmad-bmm-pm.md'
|
||||
*/
|
||||
function toDashPath(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 colon name (for folder-based IDEs)
|
||||
* Creates: 'bmad:custom:fred-commit-poet.md'
|
||||
*
|
||||
* @param {string} agentName - Custom agent name
|
||||
* @returns {string} Flat filename like 'bmad:custom:fred-commit-poet.md'
|
||||
*/
|
||||
function customAgentColonName(agentName) {
|
||||
return `bmad:custom:${agentName}.md`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create custom agent dash name (for flat IDEs)
|
||||
* Creates: 'bmad-custom-fred-commit-poet.md'
|
||||
*
|
||||
* @param {string} agentName - Custom agent name
|
||||
* @returns {string} Flat filename like 'bmad-custom-fred-commit-poet.md'
|
||||
*/
|
||||
function customAgentDashName(agentName) {
|
||||
return `bmad-custom-${agentName}.md`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a filename uses colon format
|
||||
* @param {string} filename - Filename to check
|
||||
* @returns {boolean} True if filename uses colon format
|
||||
*/
|
||||
function isColonFormat(filename) {
|
||||
return filename.includes('bmad:') && filename.includes(':');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a filename uses dash format
|
||||
* @param {string} filename - Filename to check
|
||||
* @returns {boolean} True if filename uses dash format
|
||||
*/
|
||||
function isDashFormat(filename) {
|
||||
return filename.startsWith('bmad-') && !filename.includes(':');
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract parts from a colon-formatted filename
|
||||
* Parses: 'bmad:bmm:pm.md' → { prefix: 'bmad', module: 'bmm', name: 'pm' }
|
||||
*
|
||||
* @param {string} filename - Colon-formatted filename
|
||||
* @returns {Object|null} Parsed parts or null if invalid format
|
||||
*/
|
||||
function parseColonName(filename) {
|
||||
const withoutExt = filename.replace('.md', '');
|
||||
const parts = withoutExt.split(':');
|
||||
|
||||
if (parts.length < 3 || parts[0] !== 'bmad') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
prefix: parts[0],
|
||||
module: parts[1],
|
||||
name: parts.slice(2).join(':'), // Handle names that might contain colons
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract parts from a dash-formatted filename
|
||||
* Parses: 'bmad-bmm-pm.md' → { prefix: 'bmad', module: 'bmm', name: 'pm' }
|
||||
*
|
||||
* @param {string} filename - Dash-formatted filename
|
||||
* @returns {Object|null} Parsed parts or null if invalid format
|
||||
*/
|
||||
function parseDashName(filename) {
|
||||
const withoutExt = filename.replace('.md', '');
|
||||
const parts = withoutExt.split('-');
|
||||
|
||||
if (parts.length < 3 || parts[0] !== 'bmad') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
prefix: parts[0],
|
||||
module: parts[1],
|
||||
name: parts.slice(2).join('-'), // Handle names that might contain dashes
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
toColonName,
|
||||
toColonPath,
|
||||
toDashPath,
|
||||
customAgentColonName,
|
||||
customAgentDashName,
|
||||
isColonFormat,
|
||||
isDashFormat,
|
||||
parseColonName,
|
||||
parseDashName,
|
||||
TYPE_SEGMENTS,
|
||||
};
|
||||
@@ -2,6 +2,7 @@ const path = require('node:path');
|
||||
const fs = require('fs-extra');
|
||||
const csv = require('csv-parse/sync');
|
||||
const chalk = require('chalk');
|
||||
const { toColonName, toColonPath, toDashPath } = require('./path-utils');
|
||||
|
||||
/**
|
||||
* Generates command files for standalone tasks and tools
|
||||
@@ -114,6 +115,154 @@ Follow all instructions in the ${type} file exactly as written.
|
||||
skip_empty_lines: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate task and tool commands using COLON format (for folder-based IDEs)
|
||||
* Creates flat files like: bmad:bmm:whats-after.md
|
||||
*
|
||||
* @param {string} projectDir - Project directory
|
||||
* @param {string} bmadDir - BMAD installation directory
|
||||
* @param {string} baseCommandsDir - Base commands directory for the IDE
|
||||
* @returns {Object} Generation results
|
||||
*/
|
||||
async generateColonTaskToolCommands(projectDir, bmadDir, baseCommandsDir) {
|
||||
const tasks = await this.loadTaskManifest(bmadDir);
|
||||
const tools = await this.loadToolManifest(bmadDir);
|
||||
|
||||
// Filter to only standalone items
|
||||
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 generatedCount = 0;
|
||||
|
||||
// Generate command files for tasks
|
||||
for (const task of standaloneTasks) {
|
||||
const commandContent = this.generateCommandContent(task, 'task');
|
||||
// Use colon format: bmad:bmm:name.md
|
||||
const flatName = toColonName(task.module, 'tasks', task.name);
|
||||
const commandPath = path.join(baseCommandsDir, flatName);
|
||||
await fs.ensureDir(path.dirname(commandPath));
|
||||
await fs.writeFile(commandPath, commandContent);
|
||||
generatedCount++;
|
||||
}
|
||||
|
||||
// Generate command files for tools
|
||||
for (const tool of standaloneTools) {
|
||||
const commandContent = this.generateCommandContent(tool, 'tool');
|
||||
// Use colon format: bmad:bmm:name.md
|
||||
const flatName = toColonName(tool.module, 'tools', tool.name);
|
||||
const commandPath = path.join(baseCommandsDir, flatName);
|
||||
await fs.ensureDir(path.dirname(commandPath));
|
||||
await fs.writeFile(commandPath, commandContent);
|
||||
generatedCount++;
|
||||
}
|
||||
|
||||
return {
|
||||
generated: generatedCount,
|
||||
tasks: standaloneTasks.length,
|
||||
tools: standaloneTools.length,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate task and tool commands using DASH format (for flat IDEs)
|
||||
* Creates flat files like: bmad-bmm-whats-after.md
|
||||
*
|
||||
* @param {string} projectDir - Project directory
|
||||
* @param {string} bmadDir - BMAD installation directory
|
||||
* @param {string} baseCommandsDir - Base commands directory for the IDE
|
||||
* @returns {Object} Generation results
|
||||
*/
|
||||
async generateDashTaskToolCommands(projectDir, bmadDir, baseCommandsDir) {
|
||||
const tasks = await this.loadTaskManifest(bmadDir);
|
||||
const tools = await this.loadToolManifest(bmadDir);
|
||||
|
||||
// Filter to only standalone items
|
||||
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 generatedCount = 0;
|
||||
|
||||
// Generate command files for tasks
|
||||
for (const task of standaloneTasks) {
|
||||
const commandContent = this.generateCommandContent(task, 'task');
|
||||
// Use dash format: bmad-bmm-name.md
|
||||
const flatName = toDashPath(`${task.module}/tasks/${task.name}.md`);
|
||||
const commandPath = path.join(baseCommandsDir, flatName);
|
||||
await fs.ensureDir(path.dirname(commandPath));
|
||||
await fs.writeFile(commandPath, commandContent);
|
||||
generatedCount++;
|
||||
}
|
||||
|
||||
// Generate command files for tools
|
||||
for (const tool of standaloneTools) {
|
||||
const commandContent = this.generateCommandContent(tool, 'tool');
|
||||
// Use dash format: bmad-bmm-name.md
|
||||
const flatName = toDashPath(`${tool.module}/tools/${tool.name}.md`);
|
||||
const commandPath = path.join(baseCommandsDir, flatName);
|
||||
await fs.ensureDir(path.dirname(commandPath));
|
||||
await fs.writeFile(commandPath, commandContent);
|
||||
generatedCount++;
|
||||
}
|
||||
|
||||
return {
|
||||
generated: generatedCount,
|
||||
tasks: standaloneTasks.length,
|
||||
tools: standaloneTools.length,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Write task/tool artifacts using COLON format (for folder-based IDEs)
|
||||
* Creates flat files like: bmad:bmm:whats-after.md
|
||||
*
|
||||
* @param {string} baseCommandsDir - Base commands directory for the IDE
|
||||
* @param {Array} artifacts - Task/tool artifacts with relativePath
|
||||
* @returns {number} Count of commands written
|
||||
*/
|
||||
async writeColonArtifacts(baseCommandsDir, artifacts) {
|
||||
let writtenCount = 0;
|
||||
|
||||
for (const artifact of artifacts) {
|
||||
if (artifact.type === 'task' || artifact.type === 'tool') {
|
||||
const commandContent = this.generateCommandContent(artifact, artifact.type);
|
||||
// Use colon format: bmad:module:name.md
|
||||
const flatName = toColonPath(artifact.relativePath);
|
||||
const commandPath = path.join(baseCommandsDir, flatName);
|
||||
await fs.ensureDir(path.dirname(commandPath));
|
||||
await fs.writeFile(commandPath, commandContent);
|
||||
writtenCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return writtenCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write task/tool artifacts using DASH format (for flat IDEs)
|
||||
* Creates flat files like: bmad-bmm-whats-after.md
|
||||
*
|
||||
* @param {string} baseCommandsDir - Base commands directory for the IDE
|
||||
* @param {Array} artifacts - Task/tool artifacts with relativePath
|
||||
* @returns {number} Count of commands written
|
||||
*/
|
||||
async writeDashArtifacts(baseCommandsDir, artifacts) {
|
||||
let writtenCount = 0;
|
||||
|
||||
for (const artifact of artifacts) {
|
||||
if (artifact.type === 'task' || artifact.type === 'tool') {
|
||||
const commandContent = this.generateCommandContent(artifact, artifact.type);
|
||||
// Use dash format: bmad-module-name.md
|
||||
const flatName = toDashPath(artifact.relativePath);
|
||||
const commandPath = path.join(baseCommandsDir, flatName);
|
||||
await fs.ensureDir(path.dirname(commandPath));
|
||||
await fs.writeFile(commandPath, commandContent);
|
||||
writtenCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return writtenCount;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { TaskToolCommandGenerator };
|
||||
|
||||
@@ -2,6 +2,7 @@ const path = require('node:path');
|
||||
const fs = require('fs-extra');
|
||||
const csv = require('csv-parse/sync');
|
||||
const chalk = require('chalk');
|
||||
const { toColonPath, toDashPath, customAgentColonName, customAgentDashName } = require('./path-utils');
|
||||
|
||||
/**
|
||||
* Generates command files for each workflow in the manifest
|
||||
@@ -237,6 +238,56 @@ When running any workflow:
|
||||
skip_empty_lines: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Write workflow command artifacts using COLON format (for folder-based IDEs)
|
||||
* Creates flat files like: bmad:bmm:correct-course.md
|
||||
*
|
||||
* @param {string} baseCommandsDir - Base commands directory for the IDE
|
||||
* @param {Array} artifacts - Workflow artifacts
|
||||
* @returns {number} Count of commands written
|
||||
*/
|
||||
async writeColonArtifacts(baseCommandsDir, artifacts) {
|
||||
let writtenCount = 0;
|
||||
|
||||
for (const artifact of artifacts) {
|
||||
if (artifact.type === 'workflow-command') {
|
||||
// Convert relativePath to colon format: bmm/workflows/correct-course.md → bmad:bmm:correct-course.md
|
||||
const flatName = toColonPath(artifact.relativePath);
|
||||
const commandPath = path.join(baseCommandsDir, flatName);
|
||||
await fs.ensureDir(path.dirname(commandPath));
|
||||
await fs.writeFile(commandPath, artifact.content);
|
||||
writtenCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return writtenCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write workflow command artifacts using DASH format (for flat IDEs)
|
||||
* Creates flat files like: bmad-bmm-correct-course.md
|
||||
*
|
||||
* @param {string} baseCommandsDir - Base commands directory for the IDE
|
||||
* @param {Array} artifacts - Workflow artifacts
|
||||
* @returns {number} Count of commands written
|
||||
*/
|
||||
async writeDashArtifacts(baseCommandsDir, artifacts) {
|
||||
let writtenCount = 0;
|
||||
|
||||
for (const artifact of artifacts) {
|
||||
if (artifact.type === 'workflow-command') {
|
||||
// Convert relativePath to dash format: bmm/workflows/correct-course.md → bmad-bmm-correct-course.md
|
||||
const flatName = toDashPath(artifact.relativePath);
|
||||
const commandPath = path.join(baseCommandsDir, flatName);
|
||||
await fs.ensureDir(path.dirname(commandPath));
|
||||
await fs.writeFile(commandPath, artifact.content);
|
||||
writtenCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return writtenCount;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { WorkflowCommandGenerator };
|
||||
|
||||
Reference in New Issue
Block a user