diff --git a/src/bmm/data/workflows.csv b/src/bmm/data/workflows.csv deleted file mode 100644 index 4e703272..00000000 --- a/src/bmm/data/workflows.csv +++ /dev/null @@ -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 diff --git a/src/bmm/tasks/whats-after.md b/src/bmm/tasks/whats-after.md index ec18704e..97274cd0 100644 --- a/src/bmm/tasks/whats-after.md +++ b/src/bmm/tasks/whats-after.md @@ -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 diff --git a/src/bmm/tasks/workflows.csv b/src/bmm/tasks/workflows.csv new file mode 100644 index 00000000..e2bad0c3 --- /dev/null +++ b/src/bmm/tasks/workflows.csv @@ -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 diff --git a/tools/cli/installers/lib/core/manifest-generator.js b/tools/cli/installers/lib/core/manifest-generator.js index f9c8c401..5aff792a 100644 --- a/tools/cli/installers/lib/core/manifest-generator.js +++ b/tools/cli/installers/lib/core/manifest-generator.js @@ -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 element - const descMatch = content.match(/description="([^"]+)"/); - const objMatch = content.match(/([^<]+)<\/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 tag (default: false) - const standaloneMatch = content.match(/]+standalone="true"/); - const standalone = !!standaloneMatch; + const descMatch = content.match(/description="([^"]+)"/); + const objMatch = content.match(/([^<]+)<\/objective>/); + description = descMatch ? descMatch[1] : objMatch ? objMatch[1].trim() : ''; + + const standaloneMatch = content.match(/]+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 element - const descMatch = content.match(/description="([^"]+)"/); - const objMatch = content.match(/([^<]+)<\/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 tag (default: false) - const standaloneMatch = content.match(/]+standalone="true"/); - const standalone = !!standaloneMatch; + const descMatch = content.match(/description="([^"]+)"/); + const objMatch = content.match(/([^<]+)<\/objective>/); + description = descMatch ? descMatch[1] : objMatch ? objMatch[1].trim() : ''; + + const standaloneMatch = content.match(/]+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, }); diff --git a/tools/cli/installers/lib/ide/STANDARDIZATION_PLAN.md b/tools/cli/installers/lib/ide/STANDARDIZATION_PLAN.md new file mode 100644 index 00000000..1dd39746 --- /dev/null +++ b/tools/cli/installers/lib/ide/STANDARDIZATION_PLAN.md @@ -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 diff --git a/tools/cli/installers/lib/ide/_base-ide.js b/tools/cli/installers/lib/ide/_base-ide.js index b53eb977..b16ee518 100644 --- a/tools/cli/installers/lib/ide/_base-ide.js +++ b/tools/cli/installers/lib/ide/_base-ide.js @@ -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 diff --git a/tools/cli/installers/lib/ide/antigravity.js b/tools/cli/installers/lib/ide/antigravity.js index 1cb5abcc..71fbd4b7 100644 --- a/tools/cli/installers/lib/ide/antigravity.js +++ b/tools/cli/installers/lib/ide/antigravity.js @@ -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', }; } diff --git a/tools/cli/installers/lib/ide/claude-code.js b/tools/cli/installers/lib/ide/claude-code.js index 82a4d540..9de97794 100644 --- a/tools/cli/installers/lib/ide/claude-code.js +++ b/tools/cli/installers/lib/ide/claude-code.js @@ -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 `; - 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', '')}`, }; } } diff --git a/tools/cli/installers/lib/ide/cline.js b/tools/cli/installers/lib/ide/cline.js index 9a9ceba6..07e674ba 100644 --- a/tools/cli/installers/lib/ide/cline.js +++ b/tools/cli/installers/lib/ide/cline.js @@ -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', }; } diff --git a/tools/cli/installers/lib/ide/codex.js b/tools/cli/installers/lib/ide/codex.js index e037a779..0002b5ed 100644 --- a/tools/cli/installers/lib/ide/codex.js +++ b/tools/cli/installers/lib/ide/codex.js @@ -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 `; - 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'); diff --git a/tools/cli/installers/lib/ide/crush.js b/tools/cli/installers/lib/ide/crush.js index 0bef6952..7bf04164 100644 --- a/tools/cli/installers/lib/ide/crush.js +++ b/tools/cli/installers/lib/ide/crush.js @@ -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', }; } diff --git a/tools/cli/installers/lib/ide/cursor.js b/tools/cli/installers/lib/ide/cursor.js index 61f374a4..53113e05 100644 --- a/tools/cli/installers/lib/ide/cursor.js +++ b/tools/cli/installers/lib/ide/cursor.js @@ -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', '')}`, }; } } diff --git a/tools/cli/installers/lib/ide/roo.js b/tools/cli/installers/lib/ide/roo.js index 26370100..b8a1dd24 100644 --- a/tools/cli/installers/lib/ide/roo.js +++ b/tools/cli/installers/lib/ide/roo.js @@ -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 diff --git a/tools/cli/installers/lib/ide/shared/agent-command-generator.js b/tools/cli/installers/lib/ide/shared/agent-command-generator.js index 20b89304..6d610b6b 100644 --- a/tools/cli/installers/lib/ide/shared/agent-command-generator.js +++ b/tools/cli/installers/lib/ide/shared/agent-command-generator.js @@ -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 }; diff --git a/tools/cli/installers/lib/ide/shared/path-utils.js b/tools/cli/installers/lib/ide/shared/path-utils.js new file mode 100644 index 00000000..d0b06f67 --- /dev/null +++ b/tools/cli/installers/lib/ide/shared/path-utils.js @@ -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, +}; diff --git a/tools/cli/installers/lib/ide/shared/task-tool-command-generator.js b/tools/cli/installers/lib/ide/shared/task-tool-command-generator.js index 315df80e..2b1bfe9a 100644 --- a/tools/cli/installers/lib/ide/shared/task-tool-command-generator.js +++ b/tools/cli/installers/lib/ide/shared/task-tool-command-generator.js @@ -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 }; diff --git a/tools/cli/installers/lib/ide/shared/workflow-command-generator.js b/tools/cli/installers/lib/ide/shared/workflow-command-generator.js index e10d3fe1..a10a2b95 100644 --- a/tools/cli/installers/lib/ide/shared/workflow-command-generator.js +++ b/tools/cli/installers/lib/ide/shared/workflow-command-generator.js @@ -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 };