Compare commits

...

3 Commits

Author SHA1 Message Date
OverlordBaconPants feae58095e
Merge f2a942b2db into 27c18e0020 2026-01-26 17:23:03 -05:00
Brian Madison 27c18e0020 installer fix 2026-01-26 16:06:00 -06:00
OverlordBaconPants f2a942b2db Add Teachers Assistant educational agent and documentation 2026-01-25 08:12:13 -05:00
49 changed files with 2301 additions and 4812 deletions

View File

@ -0,0 +1,371 @@
---
title: "Build a Complete Web App Using BMAD Method Workflows"
---
Use the BMAD Method workflows to build a complete web application from initial idea to deployed code with comprehensive planning and testing. This guide follows the development of a simple Habit Tracker app to illustrate the concepts, but these same workflows apply to any web application project.
## When to Use This
- You have an idea for an app and want proper planning before coding
- You're new to the BMAD Method and want to see all workflows in action
- You want to build something substantial (10+ user stories) with full architecture
- You prefer comprehensive planning over jumping straight to code
- You need documentation and testing as part of your deliverables
## When to Skip This
- Simple features or bug fixes (use Quick Flow instead)
- Rapid prototyping where planning overhead isn't justified
- You already have detailed requirements and architecture (you can skip some but not all of this)
:::note[Prerequisites]
- BMAD Core Platform installed with BMM module
- Basic understanding of web development concepts
- Willingness to invest time in planning before implementation
:::
## Steps
### 1. Initialize Your Project Structure
Start by setting up your BMAD workflow tracking and determining your project path.
**Load the BMad Master agent:**
```
/bmad:core:agents:bmad-master
```
**Run workflow initialization:**
```
5. [LW] → workflow-init
```
**Example inputs (using our Habit Tracker):**
- **Project name:** "Habit Tracker"
- **Project type:** "1. New project (greenfield)" *(or brownfield for existing codebases)*
- **Planning approach:** "1. BMad Method" *(full planning for substantial apps)*
- **Discovery workflows:** "1,2,3" *(brainstorm, research, product brief)*
**What you provide for any project:**
- Clear project vision *(e.g., "solve my productivity problem", "showcase my work", "help local businesses")*
- Technology constraints *(e.g., "React/Node", "vanilla JS", "WordPress theme", though the workflow will guide you to a tech stack if you'd like)*
- Success criteria *(e.g., "increase daily consistency", "get freelance clients", "reduce manual work")*
### 2. Brainstorm Creative Solutions
Explore different approaches to your problem before committing to specific features.
**Load Analyst agent and run:**
```
/bmad:bmm:workflows:brainstorming
```
**Example developer inputs (Habit Tracker):**
- **Problem:** "I start habit streaks but lose motivation after 2-3 weeks"
- **Constraints:** "Must work offline, no user accounts needed"
- **Inspiration:** "Simple, visual progress tracking like GitHub contribution graph"
**Your inputs for any project:**
- **Problem:** *What specific pain point are you solving?*
- **Constraints:** *Technical, budget, timeline, or user limitations*
- **Inspiration:** *Existing solutions, design patterns, or approaches you admire*
**Typical brainstorming results for any app:**
- Core feature variations and alternatives
- User experience approaches
- Technical implementation options
- Unique differentiators and value propositions
### 3. Research Market and Technical Approaches
Understand what works in existing solutions and validate your technical decisions.
**Continue with Analyst agent:**
```
/bmad:bmm:workflows:research
```
**Example research focus areas (Habit Tracker):**
- **Market research:** "What makes habit tracking apps successful vs abandoned?"
- **Technical research:** "Best practices for vanilla JS local storage and data persistence"
- **UX research:** "Psychology of habit formation and visual feedback"
**Research areas for any project:**
- **Market research:** *Who are your competitors? What do users actually need?*
- **Technical research:** *Best practices, libraries, patterns for your tech stack*
- **UX research:** *User psychology, accessibility, design patterns*
**Typical research insights:**
- User behavior patterns that inform feature prioritization
- Technical approaches that prevent common pitfalls
- Competitive landscape gaps your app can fill
### 4. Create Strategic Product Brief
Transform your ideas and research into a focused product strategy.
**Continue with Analyst agent:**
```
/bmad:bmm:workflows:create-product-brief
```
**Example responses (Habit Tracker):**
- **Target user:** "Developers and knowledge workers who struggle with consistency"
- **Core value proposition:** "Dead-simple habit tracking that works offline"
- **Key differentiator:** "No accounts, no sync, just local progress tracking"
**Your responses for any project:**
- **Target user:** *Who specifically will use this? What are their pain points?*
- **Core value proposition:** *What's the main benefit you're delivering?*
- **Key differentiator:** *How is your approach unique or better?*
**Product brief output for any app:**
- Clear user personas and primary use cases
- Prioritized feature list based on user value
- Success metrics and project constraints
- Technical approach rationale and trade-offs
### 5. Define Requirements with PRD
Convert your strategic vision into detailed technical requirements.
**Load PM agent:**
```
/bmad:bmm:agents:pm → /bmad:bmm:workflows:create-prd
```
**Example functional requirements (Habit Tracker):**
- **FR1:** Users can add/remove habit definitions
- **FR2:** Users can mark habits complete for today
- **FR3:** System shows current streak count per habit
- **FR4:** Calendar view displays completion history
**Your functional requirements (any app):**
- **FR1-N:** *What specific actions must users be able to perform?*
- **FR1-N:** *What data must the system track and display?*
- **FR1-N:** *What business logic must the system implement?*
**Example non-functional requirements:**
- **NFR1:** Performance targets *(load times, response times)*
- **NFR2:** Platform support *(browsers, devices, operating systems)*
- **NFR3:** Scalability requirements *(concurrent users, data volume)*
- **NFR4:** Security and privacy constraints
### 6. Design User Experience
Plan the visual and interaction design that supports your users' goals and workflows.
**Load UX Designer agent:**
```
/bmad:bmm:agents:ux-designer → /bmad:bmm:workflows:create-ux-design
```
**Example UX decisions (Habit Tracker):**
- **Layout:** Single-page app with habit list + calendar view
- **Visual feedback:** Green streaks, gentle animations for completions
- **Interaction patterns:** One-click habit completion, easy habit management
- **Mobile approach:** Touch-friendly buttons, responsive grid
**UX considerations for any app:**
- **Information architecture:** *How do you organize features and content?*
- **Visual hierarchy:** *What gets user attention first, second, third?*
- **Interaction patterns:** *How do users accomplish their primary tasks?*
- **Responsive design:** *How does the experience adapt across devices?*
### 7. Create System Architecture
Define technical decisions that guide consistent implementation.
**Load Architect agent:**
```
/bmad:bmm:agents:architect → /bmad:bmm:workflows:create-architecture
```
**Example architecture decisions (Habit Tracker):**
- **Data layer:** Browser localStorage with JSON serialization
- **State management:** Vanilla JS with simple object models
- **UI pattern:** MVC-style separation with modules
- **File structure:** Modular JS files, single HTML entry point
**Architecture decisions for any app:**
- **Data layer:** *Database choice, data modeling, persistence strategy*
- **Application structure:** *Framework selection, design patterns, code organization*
- **Integration points:** *APIs, third-party services, external dependencies*
- **Deployment model:** *Hosting, build process, environment configuration*
**Architecture output for any project:**
- Technology stack rationale with trade-off analysis
- Data flow and system interaction diagrams
- File organization and module structure
- Development and deployment guidelines
### 8. Break Down Into Stories
Transform requirements into implementation-ready development tasks.
**Return to PM agent:**
```
/bmad:bmm:workflows:create-epics-and-stories
```
**Example epic breakdown (Habit Tracker):**
- **Epic 1:** Core Habit Management (add, edit, delete habits)
- **Epic 2:** Daily Completion Tracking (mark complete, streak calculation)
- **Epic 3:** Visual Progress Display (calendar view, statistics)
- **Epic 4:** Data Persistence (localStorage integration, data recovery)
**Epic organization for any app:**
- **Epic 1-N:** *Group related features by user journey or technical domain*
- **Epic 1-N:** *Organize by value delivery - what users accomplish together*
- **Epic 1-N:** *Consider technical dependencies - foundational features first*
**Story examples (any project):**
- **Story X.Y:** As a [user type], I can [action] so that [benefit]
- **Story X.Y:** As a [user type], I can [action] so that [benefit]
- **Story X.Y:** As a [user type], I can [action] so that [benefit]
### 9. Validate Implementation Readiness
Ensure all planning artifacts align before starting development.
**Continue with Architect agent:**
```
/bmad:bmm:workflows:implementation-readiness
```
**Validation checklist:**
- PRD functional requirements map to stories ✓
- Architecture supports all technical requirements ✓
- UX design covers all user journeys ✓
- Stories have clear acceptance criteria ✓
### 10. Plan Development Sprint
Organize your stories into implementation phases with clear tracking.
**Load Scrum Master agent:**
```
/bmad:bmm:agents:sm → /bmad:bmm:workflows:sprint-planning
```
**Example sprint organization (Habit Tracker):**
- **Sprint 1:** Core habit CRUD + basic UI
- **Sprint 2:** Completion tracking + streak logic
- **Sprint 3:** Calendar visualization + data persistence
- **Sprint 4:** Polish, testing, and edge cases
**Sprint organization principles (any app):**
- **Sprint 1:** *Foundational features that other features depend on*
- **Sprint 2-N:** *User-facing features in order of value delivery*
- **Final sprints:** *Polish, edge cases, performance optimization*
### 11. Implement Stories with Testing
Execute each story with proper implementation and validation.
**For each story, use SM agent:**
```
/bmad:bmm:workflows:dev-story
```
**Development process per story:**
- Write failing tests first (TDD approach)
- Implement minimal code to pass tests
- Refactor for code quality
- Validate against acceptance criteria
### 12. Review Code Quality
Get adversarial feedback to catch issues before they compound.
**After each story, use SM agent:**
```
/bmad:bmm:workflows:code-review
```
**Review focuses:**
- Code quality and maintainability
- Test coverage and edge cases
- Architecture compliance
- Security and performance considerations
### 13. Automate Testing Coverage
Ensure comprehensive test coverage for long-term maintainability.
**Load Test Automation Engineer:**
```
/bmad:bmm:agents:tea → /bmad:bmm:workflows:testarch-automate
```
**Testing layers:**
- Unit tests for data models and utilities
- Integration tests for localStorage interactions
- End-to-end tests for user workflows
- Browser compatibility validation
## What You Get
After completing this workflow sequence, you'll have:
**Planning Artifacts:**
- `_bmad-output/product-brief.md` - Strategic product vision
- `_bmad-output/PRD.md` - Detailed requirements document
- `_bmad-output/ux-design.md` - Visual and interaction design
- `_bmad-output/architecture.md` - Technical architecture decisions
**Implementation Artifacts:**
- `_bmad-output/epics/` - Organized user stories with acceptance criteria
- `_bmad-output/implementation-artifacts/sprint-status.yaml` - Development tracking
- Working web application with full test coverage
- Comprehensive documentation
**Project Structure:**
```
your-web-app/ # (example: habit-tracker, portfolio-site, task-manager)
├── _bmad-output/ # All planning documents
├── src/
│ ├── index.html # Main application entry
│ ├── js/
│ │ ├── app.js # Main application logic
│ │ ├── [feature]-manager.js # Core business logic modules
│ │ ├── [component].js # UI components
│ │ └── [utility].js # Helper utilities
│ ├── css/
│ │ └── styles.css # Application styles
│ └── tests/
│ ├── unit/ # Unit test files
│ └── integration/ # Integration tests
└── README.md # Project documentation
```
## Tips
:::tip[Start Small]
If this feels overwhelming, try the Quick Flow approach first with a smaller feature to get familiar with BMAD workflows.
:::
:::tip[Document Decisions]
Each workflow creates artifacts that inform subsequent workflows. Don't skip documentation - it prevents rework later.
:::
:::tip[Iterate on Planning]
Use the `correct-course` workflow if you discover new requirements during implementation.
:::
## Next Steps
- **Deploy your app:** Use the `testarch-ci` workflow to set up deployment automation
- **Add features:** Create new epics using the `create-epics-and-stories` workflow
- **Maintain quality:** Regular code reviews and test automation expansion
- **Scale complexity:** Graduate to Enterprise Method for larger applications
## Getting Help
- **BMad Community:** [Discord community](https://discord.gg/bmad-method)
- **Documentation:** [Complete workflow reference](https://docs.bmad-method.org/)
- **Issues:** [GitHub repository](https://github.com/bmad-method/core)
:::tip[Key Takeaways]
The BMAD Method's strength is comprehensive planning that prevents common development pitfalls. Whether building a simple habit tracker or complex enterprise application, the same workflows scale to provide the right level of planning for your project. While it requires upfront investment, you'll avoid architecture refactoring, scope creep, and incomplete features that plague many projects. The workflow artifacts serve as living documentation that keeps your development focused and consistent.
:::

View File

@ -0,0 +1,532 @@
---
title: "Create a Custom Agent Using BMAD Method Workflows"
---
Use the BMAD Method workflows to design, build, and deploy a custom AI agent from initial concept to a delightful interactive persona that users love engaging with. This guide follows the development of a Teacher's Assistant agent to illustrate the concepts, but these same workflows apply to any custom agent project.
## When to Use This
- You want to create a specialized AI persona for specific domain expertise
- You need an agent that guides users through multi-step processes in your field
- You want to design custom interactive workflows beyond standard BMAD agents
- You need an agent that embodies specific communication styles and knowledge areas
- You're building agents for educational, professional, or specialized use cases
## When to Skip This
- Simple modifications to existing agents (use agent customization instead)
- One-off custom prompts or simple AI interactions
- Complex AI systems requiring custom training (this focuses on persona and workflow design)
:::note[Prerequisites]
- BMAD Core Platform installed with BMM module
- Understanding of your target domain and user needs
- Familiarity with BMAD agent interaction patterns
- Access to Brainstorm Agent and Custom Agent Builder workflows
:::
:::tip[Quick Path]
Initialize project → Brainstorm agent concepts → Define agent persona and capabilities → Build agent structure → Create workflows and menus → Test and refine → Deploy and document. The entire process typically takes 2-4 focused work sessions to go from idea to working custom agent.
:::
## Understanding Custom Agent Creation
The BMAD Method approaches custom agent creation through systematic design and implementation workflows that ensure your agent delivers real value and delightful user experiences.
| Phase | Name | What Happens |
|-------|------|--------------|
| 1 | Ideation | Brainstorm agent purpose, capabilities, personality *(collaborative)* |
| 2 | Design | Define agent persona, communication style, knowledge domains *(structured)* |
| 3 | Architecture | Plan agent workflows, menu systems, interaction patterns *(technical)* |
| 4 | Implementation | Build agent files, configure activation sequences *(systematic)* |
| 5 | Testing | Validate agent behavior, refine personality, test workflows *(iterative)* |
| 6 | Deployment | Integrate agent into BMAD ecosystem, create documentation *(production)* |
## Single Agent Method
**All steps accomplished through one agent:**
```
/bmad:bmb:agents:agent-builder
```
The Agent Builder is a comprehensive Expert Agent that guides you through all phases of agent creation in a single workflow session. It handles brainstorming, discovery, type classification, persona development, menu structure, activation planning, and agent compilation without needing multiple specialized agents.
## Steps
### Step 1: Initialize Your Agent Creation Session
**General Process:**
Start the Agent Builder to begin comprehensive agent development using its step-file architecture.
```
/bmad:bmb:agents:agent-builder
3. [CA] Create a new BMAD agent with best practices and compliance
```
**Teacher's Assistant Example:**
When prompted for initial agent concept:
- **Agent name:** "Teacher's Assistant"
- **Primary domain:** "Education and learning facilitation"
- **Target users:** "Students seeking homework help and concept understanding"
- **Agent scope:** "Socratic questioning, scaffolding techniques, progress tracking"
**Your Application:**
Provide these key elements for any agent:
- Clear agent purpose *(e.g., "help therapists with session planning", "guide developers through code reviews", "assist writers with story development")*
- Target user definition *(e.g., "healthcare professionals", "marketing teams", "creative professionals")*
- Domain expertise scope *(e.g., "financial planning", "project management", "technical documentation")*
### Step 2: Brainstorm Agent Concepts and Capabilities
**General Process:**
The Agent Builder includes optional brainstorming in Step 1. When you choose to brainstorm, select from 4 specialized technique approaches:
1. **User-Selected Techniques** - Browse the complete technique library
2. **AI-Recommended Techniques** - Customized suggestions based on your goals *(includes web research)*
3. **Random Technique Selection** - Discover unexpected creative methods
4. **Progressive Technique Flow** - Start broad, then systematically narrow focus
Each approach helps you discover your agent's essence - the living personality AND the utility it provides.
**Teacher's Assistant Example:**
Using AI-Recommended Techniques approach, we discovered:
- **Core purpose:** "Guide students through understanding using educational best practices without providing direct answers"
- **Key capabilities:** "Socratic questioning, scaffolding techniques, progress tracking, adaptive teaching methods"
- **Personality traits:** "Patient mentor, educationally rigorous, never gives up on student learning"
**Your Application:**
Define these elements for any agent:
- **Core purpose:** What main problem does your agent solve for users?
- **Key capabilities:** What specific tasks should your agent excel at?
- **Personality traits:** How should your agent communicate and behave?
### Step 2a: Discovery Conversation Phase
**General Process:**
After brainstorming, the workflow includes a comprehensive discovery conversation that establishes your agent's scope, context, target users, and special features. This prevents re-asking questions in later development phases and generates a comprehensive agent plan document.
**Teacher's Assistant Example:**
Discovery conversation explored:
- **Target Scope:** All educational interactions across grade levels and subjects
- **Primary Context:** Home tutoring and independent study environments
- **Communication Strategy:** Age-appropriate language complexity while maintaining concept integrity
- **Persistence Philosophy:** "No giving up allowed" - always find alternative approaches
- **Progress Tracking:** Subject-specific learning profiles with technique effectiveness monitoring
- **Validation Approaches:** Student explanation + pop-quiz verification of understanding
**Your Application:**
The discovery phase will explore for any agent:
- **Target Scope:** Who specifically will use your agent and in what contexts?
- **Primary Context:** Where and how will your agent be used most frequently?
- **Communication Strategy:** How should your agent adapt its communication style?
- **Core Philosophy:** What principles guide your agent's decision-making?
- **Special Features:** What unique capabilities set your agent apart?
### Step 3: Determine Agent Type and Define Metadata
**General Process:**
The workflow systematically classifies your agent and defines all required metadata properties:
- **Simple Agent:** Single-purpose, stateless, all-in-one file (~250 lines max)
- **Expert Agent:** Persistent memory, sidecar folder, domain-specific expertise
- **Module Agent:** Extends existing BMAD modules or requires multiple interconnected agents
**Teacher's Assistant Example:**
- **Classification:** Expert Agent (requires persistent memory for learning profiles)
- **Rationale:** Student progress tracking, technique effectiveness monitoring, evolving teaching strategies
- **Metadata Properties:**
- **ID:** `teachers-assistant`
- **Name:** `Sophia Chen`
- **Title:** `Educational Learning Facilitator`
- **Icon:** `🎓`
- **Module:** `stand-alone`
- **Has Sidecar:** `true`
**Your Application:**
For any agent, the system will determine:
- **Agent Type:** Based on memory requirements and complexity needs
- **Technical Properties:** ID (kebab-case), persona name, professional title
- **Visual Identity:** Appropriate emoji icon for your domain
- **Ecosystem Placement:** Stand-alone vs integration with existing modules
### Step 4: Develop Four-Field Persona System
**General Process:**
The workflow uses a sophisticated four-field persona system that creates distinct, non-overlapping personality dimensions:
- **Role:** WHAT they do (capabilities, expertise, knowledge areas)
- **Identity:** WHO they are (background, experience, character)
- **Communication Style:** HOW they speak (tone, patterns, voice)
- **Principles:** WHY they act (decision framework, values, constraints)
**Teacher's Assistant Example:**
```yaml
persona:
role: >
Educational learning facilitator specializing in Socratic questioning, scaffolding techniques,
and progress-based teaching that guides students to discover answers rather than providing direct instruction.
identity: >
Master educator with deep knowledge of educational psychology, constructivist learning theory,
and adaptive teaching methods. Passionate advocate for authentic learning through struggle and self-discovery.
communication_style: >
Speaks like a patient mentor using strategic questioning, encouraging language, and age-appropriate
complexity while maintaining conceptual integrity.
principles:
- Channel expert educational psychology wisdom: draw upon Zone of Proximal Development, scaffolding techniques, metacognitive strategies, and research-backed methods that facilitate genuine understanding
- Never provide direct answers - guide students to discover solutions through strategic questioning and multiple explanation pathways
- Authentic learning requires productive struggle - frustration signals growth, not failure
- Track what works for each student and adapt techniques accordingly - analogies for some, examples for others
- Academic boundaries are sacred - redirect non-educational conversations back to learning focus
```
**Your Application:**
For any agent, you'll develop:
- **Role:** Professional capabilities and expertise your agent provides
- **Identity:** Background, experience, and character that makes them credible
- **Communication Style:** How your agent speaks and interacts with users
- **Principles:** Decision framework and values that guide behavior
The first principle serves as an "expert activator" that tells the AI to access domain-specific knowledge and frameworks.
### Step 5: Commands & Menu Structure Design
**General Process:**
Transform discovered capabilities into structured menu commands following BMAD patterns:
- **Capability Review:** Analyze all capabilities from the discovery phase
- **Command Grouping:** Organize related capabilities under logical command areas
- **Menu Pattern Application:** Follow BMAD Expert Agent menu structure requirements
- **Trigger Design:** Create intuitive 2-letter codes and fuzzy match patterns
- **Handler Definition:** Map commands to specific prompts or actions
**Teacher's Assistant Example:**
Created 9 educational commands with Expert Agent architecture:
```yaml
critical_actions:
- 'Load COMPLETE file {project-root}/_bmad/_memory/teachers-assistant-sidecar/learning-profiles.md'
- 'Load COMPLETE file {project-root}/_bmad/_memory/teachers-assistant-sidecar/technique-tracking.md'
- 'ONLY read/write files in {project-root}/_bmad/_memory/teachers-assistant-sidecar/'
prompts:
- id: socratic-guidance
content: |
<instructions>Guide student through learning using Socratic questioning without giving direct answers</instructions>
<process>1. Ask strategic questions 2. Use student interests for analogies 3. Encourage discovery 4. Validate understanding</process>
menu:
- trigger: LG or fuzzy match on learn-guide
action: '#socratic-guidance'
description: '[LG] Learning guidance through Socratic questioning'
- trigger: QM or fuzzy match on quiz-me
action: 'Generate pop-quiz on recent or struggling concepts from learning profile'
description: '[QM] Quiz me on challenging concepts'
- trigger: SA or fuzzy match on study-aids
action: '#study-aids-generator'
description: '[SA] Generate study aids (flashcards, practice problems, guides)'
```
**Your Application:**
For any agent, you'll create commands organized by:
- **Primary Functions:** Core capabilities users access most frequently
- **Utility Commands:** Support functions like help, settings, progress tracking
- **Advanced Features:** Specialized tools for power users
- **Memory Management:** For Expert agents with persistent data needs
Design principles include 2-letter triggers, fuzzy matching, action handlers, and proper sidecar integration for Expert agents.
### Step 6: Activation Planning
**General Process:**
Define how your agent behaves when it starts up through critical actions and startup sequences:
- **Reference Loading:** Understanding critical action patterns
- **Routing Decision:** Determining build path (Simple/Expert/Module) based on architecture
- **Activation Needs Discussion:** Deciding autonomous vs responsive behavior patterns
- **Critical Actions Definition:** Specifying startup commands for memory loading and boundaries
**Teacher's Assistant Example:**
```yaml
activation:
hasCriticalActions: true
rationale: "Agent needs to auto-load student learning context to provide personalized educational guidance"
criticalActions:
- 'Load COMPLETE file {project-root}/_bmad/_memory/teachers-assistant-sidecar/learning-profiles.md'
- 'Load COMPLETE file {project-root}/_bmad/_memory/teachers-assistant-sidecar/technique-tracking.md'
- 'ONLY read/write files in {project-root}/_bmad/_memory/teachers-assistant-sidecar/'
routing:
destinationBuild: "step-07b-build-expert.md"
rationale: "Expert agent requires sidecar memory for persistent learning profiles"
```
**Your Application:**
For any agent, consider:
- **Startup Needs:** What must your agent load or initialize when it starts?
- **Memory Requirements:** Does your agent need persistent data between sessions?
- **Security Boundaries:** What file access restrictions should be enforced?
- **Operational Philosophy:** Responsive to prompts vs autonomous background tasks?
Routing logic determines the build path based on your agent's architecture needs.
### Step 7: Expert Agent Build and Compilation
**General Process:**
The Agent Builder automatically compiles all phases into the final .agent.yaml file:
1. **Generates Agent YAML:** Combines persona, menu, activation, and metadata
2. **Creates Sidecar Structure:** Sets up memory folders for Expert agents
3. **Validates Configuration:** Ensures BMAD compliance and proper structure
4. **Provides Installation:** Generates installation guidance
**Teacher's Assistant Example:**
Generated complete Expert agent with this structure:
```
agents/
└── teachers-assistant/
├── teachers-assistant.agent.yaml # Complete agent definition
└── teachers-assistant-sidecar/ # Expert agent memory (build location)
├── learning-profiles.md # Student progress and preferences
├── technique-tracking.md # Teaching method effectiveness data
└── README.md # Sidecar documentation
```
Critical actions use proper path variables: `{project-root}/_bmad/_memory/{sidecar-folder}/` for runtime operation.
**Your Application:**
For any agent, this step produces:
- **Agent YAML:** Complete agent definition with proper BMAD compliance
- **Sidecar Structure:** Memory folders and files for Expert agents
- **Path Configuration:** Proper variable usage for portability
- **Documentation:** README files and installation guidance
### Step 8: Celebration and Installation Guidance
**General Process:**
The Agent Builder provides comprehensive installation instructions and celebrates completion. To make any agent installable, create a standalone BMAD content module with:
- Module directory with `module.yaml` containing `unitary: true`
- Agent files in `agents/agent-name/` structure
- Sidecar folder in `_memory/` for Expert agents
**Teacher's Assistant Example:**
Created this installable module structure:
```
my-educational-agents/
├── module.yaml # Contains: unitary: true
├── agents/
│ └── teachers-assistant/
│ ├── teachers-assistant.agent.yaml # Main agent definition
│ └── _memory/ # Expert agent memory
│ └── teachers-assistant-sidecar/
│ ├── learning-profiles.md
│ ├── technique-tracking.md
│ └── README.md
```
Installation methods include new project setup or adding to existing BMAD installations.
**Your Application:**
For any agent, follow these installation principles:
- **Module Structure:** Use `unitary: true` for standalone agent modules
- **File Organization:** Place agent files in proper directory hierarchy
- **Memory Management:** Include `_memory/` structure for Expert agents
- **Distribution:** Package entire module directory for sharing
## Installing and Using Your Custom Agent
After completing agent creation, follow these steps to install and start using your new agent:
### Step 1: Create Module Directory Structure
**General Process:**
Transform your agent output into a BMAD-installable module:
```bash
# Navigate to your project root
cd /your/project/root
# Create module directory
mkdir -p my-custom-agents
# Create module configuration
echo "unitary: true" > my-custom-agents/module.yaml
# Create agents directory structure
mkdir -p my-custom-agents/agents
```
### Step 2: Organize Agent Files
**General Process:**
Move your completed agent files into the proper module structure:
```bash
# Copy agent directory from bmb-creations output
cp -r /path/to/_bmad-output/bmb-creations/your-agent my-custom-agents/agents/
# For Expert agents, organize sidecar structure
mkdir -p my-custom-agents/agents/your-agent/_memory
mv my-custom-agents/agents/your-agent/your-agent-sidecar my-custom-agents/agents/your-agent/_memory/
```
**Teacher's Assistant Example:**
```
my-educational-agents/
├── module.yaml # Contains: unitary: true
├── agents/
│ └── teachers-assistant/
│ ├── teachers-assistant.agent.yaml # Main agent definition
│ └── _memory/ # Expert agent memory
│ └── teachers-assistant-sidecar/
│ ├── learning-profiles.md
│ ├── technique-tracking.md
│ └── README.md
```
### Step 3: Install Module in BMAD
**General Process:**
Add your custom module to an existing or new BMAD project:
**For New Projects:**
1. Run BMAD installer: `npx @bmad-method/cli init`
2. When prompted for local modules, provide the path to your module
3. Installer will automatically integrate your agent
**For Existing Projects:**
1. Run: `npx @bmad-method/cli modify`
2. Select "Add local custom module"
3. Provide path to your module directory
4. Confirm installation
### Step 4: Activate Your Agent
**General Process:**
Once installed, your agent becomes available through BMAD's command system:
```bash
# List available agents (verify your agent appears)
/agents
# Activate your agent using its module path
/bmad:your-module:agents:your-agent
```
**Teacher's Assistant Example:**
```bash
/bmad:my-educational-agents:agents:teachers-assistant
```
### Step 5: Test Agent Functionality
**General Process:**
Start with basic interactions to verify your agent works correctly:
**Initial Activation Test:**
```
/bmad:your-module:agents:your-agent
```
**Basic Conversation Examples:**
- "Hi [Agent Name], what can you help me with?"
- "Show me your available commands"
- "Tell me about your capabilities"
**Teacher's Assistant Example:**
Specific conversation starters that test educational capabilities:
- "Help me understand fractions without giving me the answer"
- "LG" (Learning Guidance command)
- "QM" (Quiz Me command)
- "SA" (Study Aids command)
### Step 6: Verify Expert Agent Memory (If Applicable)
**General Process:**
For Expert agents with sidecar folders, confirm memory persistence:
1. **Start agent and interact with memory commands**
2. **Update profiles or tracking data**
3. **Restart agent and verify data persists**
**Teacher's Assistant Example:**
```
UP (Update Profile command)
LP (Learning Progress command)
```
### Troubleshooting Common Installation Issues
**Agent Not Found:**
- Verify `module.yaml` exists with `unitary: true`
- Check agent file is in `agents/agent-name/agent-name.agent.yaml`
- Confirm BMAD installation included your module
**Sidecar Memory Issues (Expert Agents):**
- Ensure `_memory/agent-sidecar/` structure exists
- Verify critical_actions reference correct file paths
- Check file permissions for read/write access
**Command Not Working:**
- Test basic interaction first before specialized commands
- Verify agent activation completed successfully
- Check for any startup errors in agent logs
### Sharing Your Agent
To share your agent with others:
1. **Package entire module directory:** `my-custom-agents/`
2. **Include installation instructions:** Reference this guide
3. **Provide example interactions:** Show how to use key features
4. **Document dependencies:** Any special requirements or modules
**Distribution Options:**
- **Git Repository:** Push module directory to version control
- **Archive File:** Zip module directory for direct sharing
- **BMAD Community:** Submit to community agent library (if available)
Your custom agent is now ready for production use and can be shared across BMAD installations!
## Summary
The BMAD Agent Builder provides a comprehensive, single-agent solution for creating production-ready BMAD agents. Through its step-file architecture, it guides you through the complete end-to-end process:
1. **Brainstorming** (optional) - Creative exploration using 4 specialized technique approaches
2. **Discovery** - Comprehensive capability and context definition with agent-plan documentation
3. **Type Classification** - Automatic Simple/Expert/Module architecture determination
4. **Four-Field Persona** - Role, identity, communication style, and principles development
5. **Commands & Menu** - Structured command interface with BMAD compliance
6. **Activation Planning** - Critical actions definition and routing determination
7. **Agent Build** - Complete YAML file generation with sidecar structure
8. **Installation Guidance** - Module packaging instructions and testing recommendations
**Complete Agent Creation in One Session:**
```
/bmad:bmb:agents:agent-builder → [CA] Create a new BMAD agent → Continue through all steps
```
**Process Results:**
- **Production-Ready Files:** Complete `.agent.yaml` with proper BMAD compliance
- **Expert Architecture:** Sidecar folder structure with memory files and security boundaries
- **Installation Package:** Module structure with `module.yaml` for BMAD integration
- **Testing Guidelines:** Conversation starters and command validation approaches
- **Documentation:** Comprehensive agent plan and sidecar README for maintenance
**Key Advantages:**
- **Single Agent Workflow:** Complete process without switching between multiple agents
- **BMAD Compliance:** Automatic adherence to all standards, patterns, and architectural requirements
- **Expert Memory Management:** Proper sidecar setup with runtime path variables and file boundaries
- **Specialized Domain Integration:** Research-backed methodology incorporation
- **Production Installation:** Ready-to-install module structure with proper configuration
**Teacher's Assistant Case Study Achievement:**
- **Expert Agent:** 9 educational commands with persistent memory architecture
- **Educational Psychology Integration:** Socratic method, scaffolding techniques, ZPD assessment
- **Complete Workflow:** From concept to installable module in single session
- **Memory Architecture:** Student learning profiles and technique effectiveness tracking
- **BMAD Compliance:** Full validation and proper sidecar configuration
**Time Investment:**
Typically 2-4 focused work sessions to go from initial idea to production-ready, installable custom agent with comprehensive capabilities and professional-quality implementation.
The BMAD Agent Creation Method transforms agent concepts into production-ready implementations efficiently and systematically, handling all technical complexity while maintaining focus on agent personality and user value delivery.

View File

@ -0,0 +1,26 @@
# Teachers-Assistant-Sidecar
This folder stores persistent memory for the **Teachers Assistant** Expert agent.
## Purpose
Maintains individual student learning profiles and tracks teaching technique effectiveness to provide personalized educational guidance that adapts to each student's learning style and progress.
## Files
- **learning-profiles.md**: Student interests, grade level, subject progress tracking, and learning preferences
- **technique-tracking.md**: Record of teaching methods and their effectiveness per student for continuous improvement
## Runtime Access
After BMAD installation, this folder will be accessible at:
`{project-root}/_bmad/_memory/teachers-assistant-sidecar/`
## Educational Philosophy
This agent implements research-backed educational psychology principles:
- **Zone of Proximal Development**: Scaffolding within student's learning capability
- **Socratic Method**: Strategic questioning to guide discovery rather than provide answers
- **Constructivist Learning**: Student-centered knowledge building through active engagement
- **Metacognitive Strategies**: Building student awareness of their own learning processes
## Security & Privacy
- Agent file access is restricted to this sidecar folder only
- Student learning data remains private and is not shared beyond educational interactions
- Progress tracking serves to improve teaching effectiveness, not for external assessment

View File

@ -0,0 +1,27 @@
# Learning Profiles
## Student Information
- **Name**: [To be filled during first interaction]
- **Grade Level**: [To be determined]
- **Primary Subjects**: [To be identified]
## Learning Preferences
- **Communication Style**: [Age-appropriate complexity level]
- **Preferred Analogies**: [Student interests for concept explanations]
- **Learning Modalities**: [Visual, verbal, kinesthetic preferences]
- **Study Aid Formats**: [Flashcards, concept maps, practice problems, guides]
## Subject Progress Tracking
### [Subject Name]
- **Mastered Concepts**: [List concepts student has demonstrated understanding]
- **Struggling Areas**: [Concepts requiring additional focus and practice]
- **Effective Techniques**: [Methods that work well for this student in this subject]
- **Recent Sessions**: [Brief notes on last few learning interactions]
## Progress Notes
- **Overall Learning Patterns**: [What consistently helps this student learn]
- **Motivation Strategies**: [What keeps student engaged]
- **Challenge Areas**: [Topics or approaches that consistently cause difficulty]
*This file is updated automatically during learning sessions and can be manually edited through the [UP] command.*

View File

@ -0,0 +1,43 @@
# Technique Effectiveness Tracking
## Teaching Method Analysis
### Socratic Questioning
- **Success Rate**: [Percentage of times this leads to understanding]
- **Best Used For**: [Types of concepts where this works well]
- **Student Response**: [How student typically responds to questioning approach]
- **Notes**: [Specific questioning patterns that work for this student]
### Analogies & Examples
- **Preferred Analogy Types**: [Student interests that make good analogies]
- **Successful Analogies**: [Record of analogies that clicked]
- **Failed Analogies**: [Analogies that confused rather than clarified]
- **Interest Areas**: [Student hobbies/interests available for analogies]
### Study Aid Generation
- **Most Effective Formats**: [Flashcards, concept maps, practice problems, guides]
- **Usage Patterns**: [How student uses generated materials]
- **Revision Frequency**: [How often student reviews generated aids]
- **Success Metrics**: [Improvement after using specific aid types]
### Alternative Explanation Methods
- **Visual Methods**: [Diagrams, charts, illustrations effectiveness]
- **Narrative Methods**: [Story-based learning success rate]
- **Step-by-Step Guides**: [Procedural learning effectiveness]
- **Peer Explanation**: [Student teaching back concepts success]
## Session Effectiveness Tracking
- **Date**: [Session date]
- **Subject**: [Topic covered]
- **Methods Used**: [Teaching techniques employed]
- **Student Engagement**: [High/Medium/Low and why]
- **Understanding Achieved**: [Yes/Partial/No and next steps]
- **Follow-up Needed**: [Additional practice or review required]
## Adaptation Insights
- **Learning Style Preferences**: [What consistently works]
- **Avoidance Patterns**: [Methods that consistently fail]
- **Optimal Session Length**: [How long before fatigue sets in]
- **Best Time Patterns**: [When student is most receptive to learning]
*This file tracks which educational techniques work best for individual students and is updated after each learning session.*

View File

@ -0,0 +1,84 @@
agent:
metadata:
id: teachers-assistant
name: Sophia Chen
title: Educational Learning Facilitator
icon: 🎓
module: stand-alone
hasSidecar: true
persona:
role: |
Educational learning facilitator specializing in Socratic questioning, scaffolding techniques, and progress-based teaching that guides students to discover answers rather than providing direct instruction.
identity: |
Master educator with deep knowledge of educational psychology, constructivist learning theory, and adaptive teaching methods. Passionate advocate for authentic learning through struggle and self-discovery.
communication_style: |
Speaks like a patient mentor using strategic questioning, encouraging language, and age-appropriate complexity while maintaining conceptual integrity.
principles:
- Channel expert educational psychology wisdom: draw upon Zone of Proximal Development, scaffolding techniques, metacognitive strategies, and research-backed methods that facilitate genuine understanding
- Never provide direct answers - guide students to discover solutions through strategic questioning and multiple explanation pathways
- Authentic learning requires productive struggle - frustration signals growth, not failure
- Track what works for each student and adapt techniques accordingly - analogies for some, examples for others
- Academic boundaries are sacred - redirect non-educational conversations back to learning focus
critical_actions:
- 'Load COMPLETE file {project-root}/_bmad/_memory/teachers-assistant-sidecar/learning-profiles.md'
- 'Load COMPLETE file {project-root}/_bmad/_memory/teachers-assistant-sidecar/technique-tracking.md'
- 'ONLY read/write files in {project-root}/_bmad/_memory/teachers-assistant-sidecar/'
prompts:
- id: socratic-guidance
content: |
<instructions>Guide student through learning using Socratic questioning without giving direct answers</instructions>
<process>1. Ask strategic questions 2. Use student interests for analogies 3. Encourage discovery 4. Validate understanding</process>
- id: alternative-explanation
content: |
<instructions>Try different teaching approach when student struggles with current method</instructions>
<process>1. Assess why current approach failed 2. Select alternative method 3. Use different modality or analogy 4. Check for understanding</process>
- id: study-aids-generator
content: |
<instructions>Generate study materials based on student needs and preferences</instructions>
<options>Flashcards | Practice Problems | Concept Maps | Step-by-step Guides</options>
<process>1. Assess learning gaps 2. Choose appropriate format 3. Create targeted materials 4. Save to learning profile</process>
menu:
- trigger: LG or fuzzy match on learn-guide
action: '#socratic-guidance'
description: '[LG] Learning guidance through Socratic questioning'
- trigger: QM or fuzzy match on quiz-me
action: 'Generate pop-quiz on recent or struggling concepts from learning profile'
description: '[QM] Quiz me on challenging concepts'
- trigger: VC or fuzzy match on validate-concepts
action: 'Test retention of previously learned material to ensure long-term understanding'
description: '[VC] Validate concept retention'
- trigger: SA or fuzzy match on study-aids
action: '#study-aids-generator'
description: '[SA] Generate study aids (flashcards, practice problems, guides)'
- trigger: ED or fuzzy match on explain-differently
action: '#alternative-explanation'
description: '[ED] Try different explanation method'
- trigger: ST or fuzzy match on story-time
action: 'Tell engaging stories that exemplify concepts being learned'
description: '[ST] Story time - learn through narratives'
- trigger: UP or fuzzy match on update-profile
action: 'Update {project-root}/_bmad/_memory/teachers-assistant-sidecar/learning-profiles.md with interests and preferences'
description: '[UP] Update learning profile and interests'
- trigger: LP or fuzzy match on learning-progress
action: 'Review progress from {project-root}/_bmad/_memory/teachers-assistant-sidecar/technique-tracking.md and provide insights'
description: '[LP] View learning progress and technique effectiveness'
- trigger: HC or fuzzy match on help-commands
action: 'Display all available educational commands and their usage with examples'
description: '[HC] Help - show all available commands'

View File

@ -0,0 +1 @@
unitary: true

View File

@ -161,56 +161,39 @@ class Installer {
}
if (!toolConfig.skipIde && toolConfig.ides && toolConfig.ides.length > 0) {
// Ensure IDE manager is initialized
await this.ideManager.ensureInitialized();
// Determine which IDEs are newly selected (not previously configured)
const newlySelectedIdes = toolConfig.ides.filter((ide) => !previouslyConfiguredIdes.includes(ide));
if (newlySelectedIdes.length > 0) {
console.log('\n'); // Add spacing before IDE questions
// Collect configuration for IDEs that support it
for (const ide of newlySelectedIdes) {
// List of IDEs that have interactive prompts
//TODO: Why is this here, hardcoding this list here is bad, fix me!
const needsPrompts = ['claude-code', 'github-copilot', 'roo', 'cline', 'auggie', 'codex', 'qwen', 'gemini', 'rovo-dev'].includes(
ide,
);
try {
const handler = this.ideManager.handlers.get(ide);
if (needsPrompts) {
// Get IDE handler and collect configuration
try {
// Dynamically load the IDE setup module
const ideModule = require(`../ide/${ide}`);
// Get the setup class (handle different export formats)
let SetupClass;
const className =
ide
.split('-')
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
.join('') + 'Setup';
if (ideModule[className]) {
SetupClass = ideModule[className];
} else if (ideModule.default) {
SetupClass = ideModule.default;
} else {
continue;
}
const ideSetup = new SetupClass();
// Check if this IDE has a collectConfiguration method
if (typeof ideSetup.collectConfiguration === 'function') {
console.log(chalk.cyan(`\nConfiguring ${ide}...`));
ideConfigurations[ide] = await ideSetup.collectConfiguration({
selectedModules: selectedModules || [],
projectDir,
bmadDir,
});
}
} catch {
// IDE doesn't have a setup file or collectConfiguration method
console.warn(chalk.yellow(`Warning: Could not load configuration for ${ide}`));
if (!handler) {
console.warn(chalk.yellow(`Warning: IDE '${ide}' handler not found`));
continue;
}
// Check if this IDE handler has a collectConfiguration method
// (custom installers like Codex, Kilo, Kiro-cli may have this)
if (typeof handler.collectConfiguration === 'function') {
console.log(chalk.cyan(`\nConfiguring ${ide}...`));
ideConfigurations[ide] = await handler.collectConfiguration({
selectedModules: selectedModules || [],
projectDir,
bmadDir,
});
}
// Most config-driven IDEs don't need configuration - silently skip
} catch (error) {
// IDE doesn't support configuration or has an error
console.warn(chalk.yellow(`Warning: Could not load configuration for ${ide}: ${error.message}`));
}
}
}

View File

@ -1,208 +0,0 @@
# IDE Installer Standardization Plan
## Overview
Standardize IDE installers to use **flat file naming** with **underscores** (Windows-compatible) and centralize duplicated code in shared utilities.
**Key Rule: All IDEs use underscore format for Windows compatibility (colons don't work on Windows).**
## Current State Analysis
### File Structure Patterns
| IDE | Current Pattern | Path Format |
|-----|-----------------|-------------|
| **claude-code** | Hierarchical | `.claude/commands/bmad/{module}/agents/{name}.md` |
| **cursor** | Hierarchical | `.cursor/commands/bmad/{module}/agents/{name}.md` |
| **crush** | Hierarchical | `.crush/commands/bmad/{module}/agents/{name}.md` |
| **antigravity** | Flattened (underscores) | `.agent/workflows/bmad_module_agents_name.md` |
| **codex** | Flattened (underscores) | `~/.codex/prompts/bmad_module_agents_name.md` |
| **cline** | Flattened (underscores) | `.clinerules/workflows/bmad_module_type_name.md` |
| **roo** | Flattened (underscores) | `.roo/commands/bmad_module_agent_name.md` |
| **auggie** | Hybrid | `.augment/commands/bmad/agents/{module}-{name}.md` |
| **iflow** | Hybrid | `.iflow/commands/bmad/agents/{module}-{name}.md` |
| **trae** | Different (rules) | `.trae/rules/bmad-agent-{module}-{name}.md` |
| **github-copilot** | Different (agents) | `.github/agents/bmd-custom-{module}-{name}.agent.md` |
### Shared Generators (in `/shared`)
1. `agent-command-generator.js` - generates agent launchers
2. `task-tool-command-generator.js` - generates task/tool commands
3. `workflow-command-generator.js` - generates workflow commands
All currently create artifacts with **nested relative paths** like `{module}/agents/{name}.md`
### Code Duplication Issues
1. **Flattening logic** duplicated in multiple IDEs
2. **Agent launcher content creation** duplicated
3. **Path transformation** duplicated
## Target Standardization
### For All IDEs (underscore format - Windows-compatible)
**IDEs affected:** claude-code, cursor, crush, antigravity, codex, cline, roo
```
Format: bmad_{module}_{type}_{name}.md
Examples:
- Agent: bmad_bmm_agents_pm.md
- Agent: bmad_core_agents_dev.md
- Workflow: bmad_bmm_workflows_correct-course.md
- Task: bmad_bmm_tasks_bmad-help.md
- Tool: bmad_core_tools_code-review.md
- Custom: bmad_custom_agents_fred-commit-poet.md
```
**Note:** Type segments (agents, workflows, tasks, tools) are filtered out from names:
- `bmm/agents/pm.md``bmad_bmm_pm.md` (not `bmad_bmm_agents_pm.md`)
### For Hybrid IDEs (keep as-is)
**IDEs affected:** auggie, iflow
These use `{module}-{name}.md` format within subdirectories - keep as-is.
### Skip (drastically different)
**IDEs affected:** trae, github-copilot
## Implementation Plan
### Phase 1: Create Shared Utility
**File:** `shared/path-utils.js`
```javascript
/**
* Convert hierarchical path to flat underscore-separated name (Windows-compatible)
* @param {string} module - Module name (e.g., 'bmm', 'core')
* @param {string} type - Artifact type ('agents', 'workflows', 'tasks', 'tools') - filtered out
* @param {string} name - Artifact name (e.g., 'pm', 'correct-course')
* @returns {string} Flat filename like 'bmad_bmm_pm.md'
*/
function toUnderscoreName(module, type, name) {
return `bmad_${module}_${name}.md`;
}
/**
* Convert relative path to flat underscore-separated name (Windows-compatible)
* @param {string} relativePath - Path like 'bmm/agents/pm.md'
* @returns {string} Flat filename like 'bmad_bmm_pm.md'
*/
function toUnderscorePath(relativePath) {
const withoutExt = relativePath.replace('.md', '');
const parts = withoutExt.split(/[\/\\]/);
// Filter out type segments (agents, workflows, tasks, tools)
const filtered = parts.filter((p) => !TYPE_SEGMENTS.includes(p));
return `bmad_${filtered.join('_')}.md`;
}
/**
* Create custom agent underscore name
* @param {string} agentName - Custom agent name
* @returns {string} Flat filename like 'bmad_custom_fred-commit-poet.md'
*/
function customAgentUnderscoreName(agentName) {
return `bmad_custom_${agentName}.md`;
}
// Backward compatibility aliases
const toColonName = toUnderscoreName;
const toColonPath = toUnderscorePath;
const toDashPath = toUnderscorePath;
const customAgentColonName = customAgentUnderscoreName;
const customAgentDashName = customAgentUnderscoreName;
module.exports = {
toUnderscoreName,
toUnderscorePath,
customAgentUnderscoreName,
// Backward compatibility
toColonName,
toColonPath,
toDashPath,
customAgentColonName,
customAgentDashName,
};
```
### Phase 2: Update Shared Generators
**Files to modify:**
- `shared/agent-command-generator.js`
- `shared/task-tool-command-generator.js`
- `shared/workflow-command-generator.js`
**Changes:**
1. Import path utilities
2. Change `relativePath` to use flat format
3. Add method `writeColonArtifacts()` for folder-based IDEs (uses underscore)
4. Add method `writeDashArtifacts()` for flat IDEs (uses underscore)
### Phase 3: Update All IDEs
**Files to modify:**
- `claude-code.js`
- `cursor.js`
- `crush.js`
- `antigravity.js`
- `codex.js`
- `cline.js`
- `roo.js`
**Changes:**
1. Import utilities from path-utils
2. Change from hierarchical to flat underscore naming
3. Update cleanup to handle flat structure (`startsWith('bmad')`)
### Phase 4: Update Base Class
**File:** `_base-ide.js`
**Changes:**
1. Mark `flattenFilename()` as `@deprecated`
2. Add comment pointing to new path-utils
## Migration Checklist
### New Files
- [x] Create `shared/path-utils.js`
### All IDEs (convert to underscore format)
- [x] Update `shared/agent-command-generator.js` - update for underscore
- [x] Update `shared/task-tool-command-generator.js` - update for underscore
- [x] Update `shared/workflow-command-generator.js` - update for underscore
- [x] Update `claude-code.js` - convert to underscore format
- [x] Update `cursor.js` - convert to underscore format
- [x] Update `crush.js` - convert to underscore format
- [ ] Update `antigravity.js` - use underscore format
- [ ] Update `codex.js` - use underscore format
- [ ] Update `cline.js` - use underscore format
- [ ] Update `roo.js` - use underscore format
### CSV Command Files
- [x] Update `src/core/module-help.csv` - change colons to underscores
- [x] Update `src/bmm/module-help.csv` - change colons to underscores
### Base Class
- [ ] Update `_base-ide.js` - add deprecation notice
### Testing
- [ ] Test claude-code installation
- [ ] Test cursor installation
- [ ] Test crush installation
- [ ] Test antigravity installation
- [ ] Test codex installation
- [ ] Test cline installation
- [ ] Test roo installation
## Notes
1. **Filter type segments**: agents, workflows, tasks, tools are filtered out from flat names
2. **Underscore format**: Universal underscore format for Windows compatibility
3. **Custom agents**: Follow the same pattern as regular agents
4. **Backward compatibility**: Old function names kept as aliases
5. **Cleanup**: Will remove old `bmad:` format files on next install

View File

@ -0,0 +1,423 @@
const path = require('node:path');
const fs = require('fs-extra');
const chalk = require('chalk');
const { BaseIdeSetup } = require('./_base-ide');
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');
const { TaskToolCommandGenerator } = require('./shared/task-tool-command-generator');
/**
* Config-driven IDE setup handler
*
* This class provides a standardized way to install BMAD artifacts to IDEs
* based on configuration in platform-codes.yaml. It eliminates the need for
* individual installer files for each IDE.
*
* Features:
* - Config-driven from platform-codes.yaml
* - Template-based content generation
* - Multi-target installation support (e.g., GitHub Copilot)
* - Artifact type filtering (agents, workflows, tasks, tools)
*/
class ConfigDrivenIdeSetup extends BaseIdeSetup {
constructor(platformCode, platformConfig) {
super(platformCode, platformConfig.name, platformConfig.preferred);
this.platformConfig = platformConfig;
this.installerConfig = platformConfig.installer || null;
}
/**
* Main setup method - called by IdeManager
* @param {string} projectDir - Project directory
* @param {string} bmadDir - BMAD installation directory
* @param {Object} options - Setup options
* @returns {Promise<Object>} Setup result
*/
async setup(projectDir, bmadDir, options = {}) {
console.log(chalk.cyan(`Setting up ${this.name}...`));
// Clean up any old BMAD installation first
await this.cleanup(projectDir);
if (!this.installerConfig) {
return { success: false, reason: 'no-config' };
}
// Handle multi-target installations (e.g., GitHub Copilot)
if (this.installerConfig.targets) {
return this.installToMultipleTargets(projectDir, bmadDir, this.installerConfig.targets, options);
}
// Handle single-target installations
if (this.installerConfig.target_dir) {
return this.installToTarget(projectDir, bmadDir, this.installerConfig, options);
}
return { success: false, reason: 'invalid-config' };
}
/**
* Install to a single target directory
* @param {string} projectDir - Project directory
* @param {string} bmadDir - BMAD installation directory
* @param {Object} config - Installation configuration
* @param {Object} options - Setup options
* @returns {Promise<Object>} Installation result
*/
async installToTarget(projectDir, bmadDir, config, options) {
const { target_dir, template_type, artifact_types } = config;
const targetPath = path.join(projectDir, target_dir);
await this.ensureDir(targetPath);
const selectedModules = options.selectedModules || [];
const results = { agents: 0, workflows: 0, tasks: 0, tools: 0 };
// Install agents
if (!artifact_types || artifact_types.includes('agents')) {
const agentGen = new AgentCommandGenerator(this.bmadFolderName);
const { artifacts } = await agentGen.collectAgentArtifacts(bmadDir, selectedModules);
results.agents = await this.writeAgentArtifacts(targetPath, artifacts, template_type, config);
}
// Install workflows
if (!artifact_types || artifact_types.includes('workflows')) {
const workflowGen = new WorkflowCommandGenerator(this.bmadFolderName);
const { artifacts } = await workflowGen.collectWorkflowArtifacts(bmadDir);
results.workflows = await this.writeWorkflowArtifacts(targetPath, artifacts, template_type, config);
}
// Install tasks and tools
if (!artifact_types || artifact_types.includes('tasks') || artifact_types.includes('tools')) {
const taskToolGen = new TaskToolCommandGenerator();
const taskToolResult = await taskToolGen.generateDashTaskToolCommands(projectDir, bmadDir, targetPath);
results.tasks = taskToolResult.tasks || 0;
results.tools = taskToolResult.tools || 0;
}
this.printSummary(results, target_dir);
return { success: true, results };
}
/**
* Install to multiple target directories
* @param {string} projectDir - Project directory
* @param {string} bmadDir - BMAD installation directory
* @param {Array} targets - Array of target configurations
* @param {Object} options - Setup options
* @returns {Promise<Object>} Installation result
*/
async installToMultipleTargets(projectDir, bmadDir, targets, options) {
const allResults = { agents: 0, workflows: 0, tasks: 0, tools: 0 };
for (const target of targets) {
const result = await this.installToTarget(projectDir, bmadDir, target, options);
if (result.success) {
allResults.agents += result.results.agents || 0;
allResults.workflows += result.results.workflows || 0;
allResults.tasks += result.results.tasks || 0;
allResults.tools += result.results.tools || 0;
}
}
return { success: true, results: allResults };
}
/**
* Write agent artifacts to target directory
* @param {string} targetPath - Target directory path
* @param {Array} artifacts - Agent artifacts
* @param {string} templateType - Template type to use
* @param {Object} config - Installation configuration
* @returns {Promise<number>} Count of artifacts written
*/
async writeAgentArtifacts(targetPath, artifacts, templateType, config = {}) {
// Try to load platform-specific template, fall back to default-agent
const template = await this.loadTemplate(templateType, 'agent', config, 'default-agent');
let count = 0;
for (const artifact of artifacts) {
const content = this.renderTemplate(template, artifact);
const filename = this.generateFilename(artifact, 'agent');
const filePath = path.join(targetPath, filename);
await this.writeFile(filePath, content);
count++;
}
return count;
}
/**
* Write workflow artifacts to target directory
* @param {string} targetPath - Target directory path
* @param {Array} artifacts - Workflow artifacts
* @param {string} templateType - Template type to use
* @param {Object} config - Installation configuration
* @returns {Promise<number>} Count of artifacts written
*/
async writeWorkflowArtifacts(targetPath, artifacts, templateType, config = {}) {
let count = 0;
for (const artifact of artifacts) {
if (artifact.type === 'workflow-command') {
// Use different template based on workflow type (YAML vs MD)
// Default to 'default' template type, but allow override via config
const workflowTemplateType = artifact.isYamlWorkflow
? config.yaml_workflow_template || `${templateType}-workflow-yaml`
: config.md_workflow_template || `${templateType}-workflow`;
// Fall back to default templates if specific ones don't exist
const finalTemplateType = artifact.isYamlWorkflow ? 'default-workflow-yaml' : 'default-workflow';
const template = await this.loadTemplate(workflowTemplateType, 'workflow', config, finalTemplateType);
const content = this.renderTemplate(template, artifact);
const filename = this.generateFilename(artifact, 'workflow');
const filePath = path.join(targetPath, filename);
await this.writeFile(filePath, content);
count++;
}
}
return count;
}
/**
* Load template based on type and configuration
* @param {string} templateType - Template type (claude, windsurf, etc.)
* @param {string} artifactType - Artifact type (agent, workflow, task, tool)
* @param {Object} config - Installation configuration
* @param {string} fallbackTemplateType - Fallback template type if requested template not found
* @returns {Promise<string>} Template content
*/
async loadTemplate(templateType, artifactType, config = {}, fallbackTemplateType = null) {
const { header_template, body_template } = config;
// Check for separate header/body templates
if (header_template || body_template) {
return await this.loadSplitTemplates(templateType, artifactType, header_template, body_template);
}
// Load combined template
const templateName = `${templateType}-${artifactType}.md`;
const templatePath = path.join(__dirname, 'templates', 'combined', templateName);
if (await fs.pathExists(templatePath)) {
return await fs.readFile(templatePath, 'utf8');
}
// Fall back to default template (if provided)
if (fallbackTemplateType) {
const fallbackPath = path.join(__dirname, 'templates', 'combined', `${fallbackTemplateType}.md`);
if (await fs.pathExists(fallbackPath)) {
return await fs.readFile(fallbackPath, 'utf8');
}
}
// Ultimate fallback - minimal template
return this.getDefaultTemplate(artifactType);
}
/**
* Load split templates (header + body)
* @param {string} templateType - Template type
* @param {string} artifactType - Artifact type
* @param {string} headerTpl - Header template name
* @param {string} bodyTpl - Body template name
* @returns {Promise<string>} Combined template content
*/
async loadSplitTemplates(templateType, artifactType, headerTpl, bodyTpl) {
let header = '';
let body = '';
// Load header template
if (headerTpl) {
const headerPath = path.join(__dirname, 'templates', 'split', headerTpl);
if (await fs.pathExists(headerPath)) {
header = await fs.readFile(headerPath, 'utf8');
}
} else {
// Use default header for template type
const defaultHeaderPath = path.join(__dirname, 'templates', 'split', templateType, 'header.md');
if (await fs.pathExists(defaultHeaderPath)) {
header = await fs.readFile(defaultHeaderPath, 'utf8');
}
}
// Load body template
if (bodyTpl) {
const bodyPath = path.join(__dirname, 'templates', 'split', bodyTpl);
if (await fs.pathExists(bodyPath)) {
body = await fs.readFile(bodyPath, 'utf8');
}
} else {
// Use default body for template type
const defaultBodyPath = path.join(__dirname, 'templates', 'split', templateType, 'body.md');
if (await fs.pathExists(defaultBodyPath)) {
body = await fs.readFile(defaultBodyPath, 'utf8');
}
}
// Combine header and body
return `${header}\n${body}`;
}
/**
* Get default minimal template
* @param {string} artifactType - Artifact type
* @returns {string} Default template
*/
getDefaultTemplate(artifactType) {
if (artifactType === 'agent') {
return `---
name: '{{name}}'
description: '{{description}}'
---
You must fully embody this agent's persona and follow all activation instructions exactly as specified.
<agent-activation CRITICAL="TRUE">
1. LOAD the FULL agent file from {project-root}/{{bmadFolderName}}/{{path}}
2. READ its entire contents - this contains the complete agent persona, menu, and instructions
3. FOLLOW every step in the <activation> section precisely
</agent-activation>
`;
}
return `---
name: '{{name}}'
description: '{{description}}'
---
# {{name}}
LOAD and execute from: {project-root}/{{bmadFolderName}}/{{path}}
`;
}
/**
* Render template with artifact data
* @param {string} template - Template content
* @param {Object} artifact - Artifact data
* @returns {string} Rendered content
*/
renderTemplate(template, artifact) {
// Use the appropriate path property based on artifact type
let pathToUse = artifact.relativePath || '';
if (artifact.type === 'agent-launcher') {
pathToUse = artifact.agentPath || artifact.relativePath || '';
} else if (artifact.type === 'workflow-command') {
pathToUse = artifact.workflowPath || artifact.relativePath || '';
}
let rendered = template
.replaceAll('{{name}}', artifact.name || '')
.replaceAll('{{module}}', artifact.module || 'core')
.replaceAll('{{path}}', pathToUse)
.replaceAll('{{description}}', artifact.description || `${artifact.name} ${artifact.type || ''}`)
.replaceAll('{{workflow_path}}', pathToUse);
// Replace _bmad placeholder with actual folder name
rendered = rendered.replaceAll('_bmad', this.bmadFolderName);
// Replace {{bmadFolderName}} placeholder if present
rendered = rendered.replaceAll('{{bmadFolderName}}', this.bmadFolderName);
return rendered;
}
/**
* Generate filename for artifact
* @param {Object} artifact - Artifact data
* @param {string} artifactType - Artifact type (agent, workflow, task, tool)
* @returns {string} Generated filename
*/
generateFilename(artifact, artifactType) {
const { toDashPath } = require('./shared/path-utils');
// toDashPath already handles the .agent.md suffix for agents correctly
// No need to add it again here
return toDashPath(artifact.relativePath);
}
/**
* Print installation summary
* @param {Object} results - Installation results
* @param {string} targetDir - Target directory (relative)
*/
printSummary(results, targetDir) {
console.log(chalk.green(`\n${this.name} configured:`));
if (results.agents > 0) {
console.log(chalk.dim(` - ${results.agents} agents installed`));
}
if (results.workflows > 0) {
console.log(chalk.dim(` - ${results.workflows} workflow commands generated`));
}
if (results.tasks > 0 || results.tools > 0) {
console.log(chalk.dim(` - ${results.tasks + results.tools} task/tool commands generated`));
}
console.log(chalk.dim(` - Destination: ${targetDir}`));
}
/**
* Cleanup IDE configuration
* @param {string} projectDir - Project directory
*/
async cleanup(projectDir) {
// Clean all target directories
if (this.installerConfig?.targets) {
for (const target of this.installerConfig.targets) {
await this.cleanupTarget(projectDir, target.target_dir);
}
} else if (this.installerConfig?.target_dir) {
await this.cleanupTarget(projectDir, this.installerConfig.target_dir);
}
}
/**
* Cleanup a specific target directory
* @param {string} projectDir - Project directory
* @param {string} targetDir - Target directory to clean
*/
async cleanupTarget(projectDir, targetDir) {
const targetPath = path.join(projectDir, targetDir);
if (!(await fs.pathExists(targetPath))) {
return;
}
// Remove all bmad* files
let entries;
try {
entries = await fs.readdir(targetPath);
} catch {
// Directory exists but can't be read - skip cleanup
return;
}
if (!entries || !Array.isArray(entries)) {
return;
}
let removedCount = 0;
for (const entry of entries) {
// Skip non-strings or undefined entries
if (!entry || typeof entry !== 'string') {
continue;
}
if (entry.startsWith('bmad')) {
const entryPath = path.join(targetPath, entry);
const stat = await fs.stat(entryPath);
if (stat.isFile()) {
await fs.remove(entryPath);
removedCount++;
} else if (stat.isDirectory()) {
await fs.remove(entryPath);
removedCount++;
}
}
}
if (removedCount > 0) {
console.log(chalk.dim(` Cleaned ${removedCount} BMAD files from ${targetDir}`));
}
}
}
module.exports = { ConfigDrivenIdeSetup };

View File

@ -1,474 +0,0 @@
const path = require('node:path');
const fs = require('fs-extra');
const { BaseIdeSetup } = require('./_base-ide');
const chalk = require('chalk');
const { getProjectRoot, getSourcePath, getModulePath } = require('../../../lib/project-root');
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');
const { TaskToolCommandGenerator } = require('./shared/task-tool-command-generator');
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
const {
loadModuleInjectionConfig,
shouldApplyInjection,
filterAgentInstructions,
resolveSubagentFiles,
} = require('./shared/module-injections');
const { getAgentsFromBmad, getAgentsFromDir } = require('./shared/bmad-artifacts');
const { toDashPath, customAgentDashName } = require('./shared/path-utils');
const prompts = require('../../../lib/prompts');
/**
* Google Antigravity IDE setup handler
*
* Uses .agent/workflows/ directory for slash commands
*/
class AntigravitySetup extends BaseIdeSetup {
constructor() {
super('antigravity', 'Google Antigravity', true);
this.configDir = '.agent';
this.workflowsDir = 'workflows';
}
/**
* Prompt for subagent installation location
* @returns {Promise<string>} Selected location ('project' or 'user')
*/
async _promptInstallLocation() {
return prompts.select({
message: 'Where would you like to install Antigravity subagents?',
choices: [
{ name: 'Project level (.agent/agents/)', value: 'project' },
{ name: 'User level (~/.agent/agents/)', value: 'user' },
],
default: 'project',
});
}
/**
* Collect configuration choices before installation
* @param {Object} options - Configuration options
* @returns {Object} Collected configuration
*/
async collectConfiguration(options = {}) {
// const config = {
// subagentChoices: null,
// installLocation: null,
// };
// const sourceModulesPath = getSourcePath('modules');
// const modules = options.selectedModules || [];
// for (const moduleName of modules) {
// // Check for Antigravity sub-module injection config in SOURCE directory
// const injectionConfigPath = path.join(sourceModulesPath, moduleName, 'sub-modules', 'antigravity', 'injections.yaml');
// if (await this.exists(injectionConfigPath)) {
// const yaml = require('yaml');
// try {
// // Load injection configuration
// const configContent = await fs.readFile(injectionConfigPath, 'utf8');
// const injectionConfig = yaml.parse(configContent);
// // Ask about subagents if they exist and we haven't asked yet
// if (injectionConfig.subagents && !config.subagentChoices) {
// config.subagentChoices = await this.promptSubagentInstallation(injectionConfig.subagents);
// if (config.subagentChoices.install !== 'none') {
// config.installLocation = await this._promptInstallLocation();
// }
// }
// } catch (error) {
// console.log(chalk.yellow(` Warning: Failed to process ${moduleName} features: ${error.message}`));
// }
// }
// }
return config;
}
/**
* Cleanup old BMAD installation before reinstalling
* @param {string} projectDir - Project directory
*/
async cleanup(projectDir) {
const bmadWorkflowsDir = path.join(projectDir, this.configDir, this.workflowsDir, 'bmad');
if (await fs.pathExists(bmadWorkflowsDir)) {
await fs.remove(bmadWorkflowsDir);
console.log(chalk.dim(` Removed old BMAD workflows from ${this.name}`));
}
}
/**
* Setup Antigravity IDE configuration
* @param {string} projectDir - Project directory
* @param {string} bmadDir - BMAD installation directory
* @param {Object} options - Setup options
*/
async setup(projectDir, bmadDir, options = {}) {
// Store project directory for use in processContent
this.projectDir = projectDir;
console.log(chalk.cyan(`Setting up ${this.name}...`));
// Clean up old BMAD installation first
await this.cleanup(projectDir);
// Create .agent/workflows directory structure
const agentDir = path.join(projectDir, this.configDir);
const workflowsDir = path.join(agentDir, this.workflowsDir);
const bmadWorkflowsDir = path.join(workflowsDir, 'bmad');
await this.ensureDir(bmadWorkflowsDir);
// 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 || []);
// 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
if (options.preCollectedConfig && options.preCollectedConfig._alreadyConfigured) {
// IDE is already configured from previous installation, skip prompting
// Just process with default/existing configuration
await this.processModuleInjectionsWithConfig(projectDir, bmadDir, options, {});
} else if (options.preCollectedConfig) {
await this.processModuleInjectionsWithConfig(projectDir, bmadDir, options, options.preCollectedConfig);
} else {
await this.processModuleInjections(projectDir, bmadDir, options);
}
// Generate workflow commands from manifest (if it exists)
const workflowGen = new WorkflowCommandGenerator(this.bmadFolderName);
const { artifacts: workflowArtifacts } = await workflowGen.collectWorkflowArtifacts(bmadDir);
// 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();
const taskToolResult = await taskToolGen.generateTaskToolCommands(projectDir, bmadDir);
console.log(chalk.green(`${this.name} configured:`));
console.log(chalk.dim(` - ${agentCount} agents installed`));
if (workflowCommandCount > 0) {
console.log(chalk.dim(` - ${workflowCommandCount} workflow commands generated`));
}
if (taskToolResult.generated > 0) {
console.log(
chalk.dim(
` - ${taskToolResult.generated} task/tool commands generated (${taskToolResult.tasks} tasks, ${taskToolResult.tools} tools)`,
),
);
}
console.log(chalk.dim(` - Workflows directory: ${path.relative(projectDir, bmadWorkflowsDir)}`));
console.log(chalk.yellow(`\n Note: Antigravity uses flattened slash commands (e.g., /bmad_module_agents_name)`));
return {
success: true,
agents: agentCount,
};
}
/**
* Read and process file content
*/
async readAndProcess(filePath, metadata) {
const content = await fs.readFile(filePath, 'utf8');
return this.processContent(content, metadata);
}
/**
* Override processContent to keep {project-root} placeholder
*/
processContent(content, metadata = {}) {
// Use the base class method WITHOUT projectDir to preserve {project-root} placeholder
return super.processContent(content, metadata);
}
/**
* Get agents from source modules (not installed location)
*/
async getAgentsFromSource(sourceDir, selectedModules) {
const agents = [];
// Add core agents
const corePath = getModulePath('core');
if (await fs.pathExists(path.join(corePath, 'agents'))) {
const coreAgents = await getAgentsFromDir(path.join(corePath, 'agents'), 'core');
agents.push(...coreAgents);
}
// Add module agents
for (const moduleName of selectedModules) {
const modulePath = path.join(sourceDir, moduleName);
const agentsPath = path.join(modulePath, 'agents');
if (await fs.pathExists(agentsPath)) {
const moduleAgents = await getAgentsFromDir(agentsPath, moduleName);
agents.push(...moduleAgents);
}
}
return agents;
}
/**
* Process module injections with pre-collected configuration
*/
async processModuleInjectionsWithConfig(projectDir, bmadDir, options, preCollectedConfig) {
// Get list of installed modules
const modules = options.selectedModules || [];
const { subagentChoices, installLocation } = preCollectedConfig;
// Get the actual source directory (not the installation directory)
await this.processModuleInjectionsInternal({
projectDir,
modules,
handler: 'antigravity',
subagentChoices,
installLocation,
interactive: false,
});
}
/**
* Process Antigravity specific injections for installed modules
* Looks for injections.yaml in each module's antigravity sub-module
*/
async processModuleInjections(projectDir, bmadDir, options) {
// Get list of installed modules
const modules = options.selectedModules || [];
let subagentChoices = null;
let installLocation = null;
// Get the actual source directory (not the installation directory)
const { subagentChoices: updatedChoices, installLocation: updatedLocation } = await this.processModuleInjectionsInternal({
projectDir,
modules,
handler: 'antigravity',
subagentChoices,
installLocation,
interactive: true,
});
if (updatedChoices) {
subagentChoices = updatedChoices;
}
if (updatedLocation) {
installLocation = updatedLocation;
}
}
async processModuleInjectionsInternal({ projectDir, modules, handler, subagentChoices, installLocation, interactive = false }) {
let choices = subagentChoices;
let location = installLocation;
for (const moduleName of modules) {
const configData = await loadModuleInjectionConfig(handler, moduleName);
if (!configData) {
continue;
}
const { config, handlerBaseDir } = configData;
if (interactive) {
console.log(chalk.cyan(`\nConfiguring ${moduleName} ${handler} features...`));
}
// if (interactive && config.subagents && !choices) {
// choices = await this.promptSubagentInstallation(config.subagents);
// if (choices.install !== 'none') {
// location = await this._promptInstallLocation();
// }
// }
if (config.injections && choices && choices.install !== 'none') {
for (const injection of config.injections) {
if (shouldApplyInjection(injection, choices)) {
await this.injectContent(projectDir, injection, choices);
}
}
}
if (config.subagents && choices && choices.install !== 'none') {
await this.copySelectedSubagents(projectDir, handlerBaseDir, config.subagents, choices, location || 'project');
}
}
return { subagentChoices: choices, installLocation: location };
}
/**
* Prompt user for subagent installation preferences
*/
async promptSubagentInstallation(subagentConfig) {
// First ask if they want to install subagents
const install = await prompts.select({
message: 'Would you like to install Antigravity subagents for enhanced functionality?',
choices: [
{ name: 'Yes, install all subagents', value: 'all' },
{ name: 'Yes, let me choose specific subagents', value: 'selective' },
{ name: 'No, skip subagent installation', value: 'none' },
],
default: 'all',
});
if (install === 'selective') {
// Show list of available subagents with descriptions
const subagentInfo = {
'market-researcher.md': 'Market research and competitive analysis',
'requirements-analyst.md': 'Requirements extraction and validation',
'technical-evaluator.md': 'Technology stack evaluation',
'epic-optimizer.md': 'Epic and story breakdown optimization',
'document-reviewer.md': 'Document quality review',
};
const selected = await prompts.multiselect({
message: `Select subagents to install ${chalk.dim('(↑/↓ navigates multiselect, SPACE toggles, A to toggles All, ENTER confirm)')}:`,
choices: subagentConfig.files.map((file) => ({
name: `${file.replace('.md', '')} - ${subagentInfo[file] || 'Specialized assistant'}`,
value: file,
checked: true,
})),
});
return { install: 'selective', selected };
}
return { install };
}
/**
* Inject content at specified point in file
*/
async injectContent(projectDir, injection, subagentChoices = null) {
const targetPath = path.join(projectDir, injection.file);
if (await this.exists(targetPath)) {
let content = await fs.readFile(targetPath, 'utf8');
const marker = `<!-- IDE-INJECT-POINT: ${injection.point} -->`;
if (content.includes(marker)) {
let injectionContent = injection.content;
// Filter content if selective subagents chosen
if (subagentChoices && subagentChoices.install === 'selective' && injection.point === 'pm-agent-instructions') {
injectionContent = filterAgentInstructions(injection.content, subagentChoices.selected);
}
content = content.replace(marker, injectionContent);
await fs.writeFile(targetPath, content);
console.log(chalk.dim(` Injected: ${injection.point}${injection.file}`));
}
}
}
/**
* Copy selected subagents to appropriate Antigravity agents directory
*/
async copySelectedSubagents(projectDir, handlerBaseDir, subagentConfig, choices, location) {
const os = require('node:os');
// Determine target directory based on user choice
let targetDir;
if (location === 'user') {
targetDir = path.join(os.homedir(), '.agent', 'agents');
console.log(chalk.dim(` Installing subagents globally to: ~/.agent/agents/`));
} else {
targetDir = path.join(projectDir, '.agent', 'agents');
console.log(chalk.dim(` Installing subagents to project: .agent/agents/`));
}
// Ensure target directory exists
await this.ensureDir(targetDir);
const resolvedFiles = await resolveSubagentFiles(handlerBaseDir, subagentConfig, choices);
let copiedCount = 0;
for (const resolved of resolvedFiles) {
try {
const sourcePath = resolved.absolutePath;
const subFolder = path.dirname(resolved.relativePath);
let targetPath;
if (subFolder && subFolder !== '.') {
const targetSubDir = path.join(targetDir, subFolder);
await this.ensureDir(targetSubDir);
targetPath = path.join(targetSubDir, path.basename(resolved.file));
} else {
targetPath = path.join(targetDir, path.basename(resolved.file));
}
await fs.copyFile(sourcePath, targetPath);
console.log(chalk.green(` ✓ Installed: ${subFolder === '.' ? '' : `${subFolder}/`}${path.basename(resolved.file, '.md')}`));
copiedCount++;
} catch (error) {
console.log(chalk.yellow(` ⚠ Error copying ${resolved.file}: ${error.message}`));
}
}
if (copiedCount > 0) {
console.log(chalk.dim(` Total subagents installed: ${copiedCount}`));
}
}
/**
* Install a custom agent launcher for Antigravity
* @param {string} projectDir - Project directory
* @param {string} agentName - Agent name (e.g., "fred-commit-poet")
* @param {string} agentPath - Path to compiled agent (relative to project root)
* @param {Object} metadata - Agent metadata
* @returns {Object} Installation result
*/
async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) {
// Create .agent/workflows/bmad directory structure (same as regular agents)
const agentDir = path.join(projectDir, this.configDir);
const workflowsDir = path.join(agentDir, this.workflowsDir);
const bmadWorkflowsDir = path.join(workflowsDir, 'bmad');
await fs.ensureDir(bmadWorkflowsDir);
// Create custom agent launcher with same pattern as regular agents
const launcherContent = `name: '${agentName}'
description: '${agentName} agent'
usage: |
Custom BMAD agent: ${agentName}
Launch with: /${agentName}
You must fully embody this agent's persona and follow all activation instructions exactly as specified. NEVER break character until given an exit command.
<agent-activation CRITICAL="TRUE">
1. LOAD the FULL agent file from @${agentPath}
2. READ its entire contents - this contains the complete agent persona, menu, and instructions
3. EXECUTE as ${agentName} with full persona adoption
</agent-activation>
---
**IMPORTANT**: Run @${agentPath} to load the complete agent before using this launcher!`;
// Use underscore format: bmad_custom_fred-commit-poet.md
const fileName = customAgentDashName(agentName);
const launcherPath = path.join(bmadWorkflowsDir, fileName);
// Write the launcher file
await fs.writeFile(launcherPath, launcherContent, 'utf8');
return {
ide: 'antigravity',
path: path.relative(projectDir, launcherPath),
command: `/${fileName.replace('.md', '')}`,
type: 'custom-agent-launcher',
};
}
}
module.exports = { AntigravitySetup };

View File

@ -1,244 +0,0 @@
const path = require('node:path');
const fs = require('fs-extra');
const { BaseIdeSetup } = require('./_base-ide');
const chalk = require('chalk');
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');
/**
* Auggie CLI setup handler
* Installs to project directory (.augment/commands)
*/
class AuggieSetup extends BaseIdeSetup {
constructor() {
super('auggie', 'Auggie CLI');
this.detectionPaths = ['.augment'];
}
/**
* Setup Auggie CLI configuration
* @param {string} projectDir - Project directory
* @param {string} bmadDir - BMAD installation directory
* @param {Object} options - Setup options
*/
async setup(projectDir, bmadDir, options = {}) {
console.log(chalk.cyan(`Setting up ${this.name}...`));
// Always use project directory
const location = path.join(projectDir, '.augment', 'commands');
// Clean up old BMAD installation first
await this.cleanup(projectDir);
// Generate agent launchers
const agentGen = new AgentCommandGenerator(this.bmadFolderName);
const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []);
// Get tasks, tools, and workflows (ALL workflows now generate commands)
const tasks = await this.getTasks(bmadDir, true);
const tools = await this.getTools(bmadDir, true);
// Get ALL workflows using the new workflow command generator
const workflowGenerator = new WorkflowCommandGenerator(this.bmadFolderName);
const { artifacts: workflowArtifacts, counts: workflowCounts } = await workflowGenerator.collectWorkflowArtifacts(bmadDir);
// Convert workflow artifacts to expected format
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,
}));
const bmadCommandsDir = path.join(location, 'bmad');
const agentsDir = path.join(bmadCommandsDir, 'agents');
const tasksDir = path.join(bmadCommandsDir, 'tasks');
const toolsDir = path.join(bmadCommandsDir, 'tools');
const workflowsDir = path.join(bmadCommandsDir, 'workflows');
await this.ensureDir(agentsDir);
await this.ensureDir(tasksDir);
await this.ensureDir(toolsDir);
await this.ensureDir(workflowsDir);
// Install agent launchers
for (const artifact of agentArtifacts) {
const targetPath = path.join(agentsDir, `${artifact.module}-${artifact.name}.md`);
await this.writeFile(targetPath, artifact.content);
}
// Install tasks
for (const task of tasks) {
const content = await this.readFile(task.path);
const commandContent = this.createTaskCommand(task, content);
const targetPath = path.join(tasksDir, `${task.module}-${task.name}.md`);
await this.writeFile(targetPath, commandContent);
}
// Install tools
for (const tool of tools) {
const content = await this.readFile(tool.path);
const commandContent = this.createToolCommand(tool, content);
const targetPath = path.join(toolsDir, `${tool.module}-${tool.name}.md`);
await this.writeFile(targetPath, commandContent);
}
// Install workflows (already generated commands)
for (const workflow of workflows) {
// Use the pre-generated workflow command content
const targetPath = path.join(workflowsDir, `${workflow.module}-${workflow.name}.md`);
await this.writeFile(targetPath, workflow.content);
}
const totalInstalled = agentArtifacts.length + tasks.length + tools.length + workflows.length;
console.log(chalk.green(`${this.name} configured:`));
console.log(chalk.dim(` - ${agentArtifacts.length} agents installed`));
console.log(chalk.dim(` - ${tasks.length} tasks installed`));
console.log(chalk.dim(` - ${tools.length} tools installed`));
console.log(chalk.dim(` - ${workflows.length} workflows installed`));
console.log(chalk.dim(` - Location: ${path.relative(projectDir, location)}`));
console.log(chalk.yellow(`\n 💡 Tip: Add 'model: gpt-4o' to command frontmatter to specify AI model`));
return {
success: true,
agents: agentArtifacts.length,
tasks: tasks.length,
tools: tools.length,
workflows: workflows.length,
};
}
/**
* Create task command content
*/
createTaskCommand(task, content) {
const nameMatch = content.match(/name="([^"]+)"/);
const taskName = nameMatch ? nameMatch[1] : this.formatTitle(task.name);
return `---
description: "Execute the ${taskName} task"
---
# ${taskName} Task
${content}
## Module
BMAD ${task.module.toUpperCase()} module
`;
}
/**
* Create tool command content
*/
createToolCommand(tool, content) {
const nameMatch = content.match(/name="([^"]+)"/);
const toolName = nameMatch ? nameMatch[1] : this.formatTitle(tool.name);
return `---
description: "Use the ${toolName} tool"
---
# ${toolName} Tool
${content}
## Module
BMAD ${tool.module.toUpperCase()} module
`;
}
/**
* Create workflow command content
*/
createWorkflowCommand(workflow, content) {
const description = workflow.description || `Execute the ${workflow.name} workflow`;
return `---
description: "${description}"
---
# ${workflow.name} Workflow
${content}
## Module
BMAD ${workflow.module.toUpperCase()} module
`;
}
/**
* Cleanup Auggie configuration
*/
async cleanup(projectDir) {
const fs = require('fs-extra');
// Only clean up project directory
const location = path.join(projectDir, '.augment', 'commands');
const bmadDir = path.join(location, 'bmad');
if (await fs.pathExists(bmadDir)) {
await fs.remove(bmadDir);
console.log(chalk.dim(` Removed old BMAD commands`));
}
}
/**
* Install a custom agent launcher for Auggie
* @param {string} projectDir - Project directory
* @param {string} agentName - Agent name (e.g., "fred-commit-poet")
* @param {string} agentPath - Path to compiled agent (relative to project root)
* @param {Object} metadata - Agent metadata
* @returns {Object} Installation result
*/
async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) {
// Auggie uses .augment/commands directory
const location = path.join(projectDir, '.augment', 'commands');
const bmadCommandsDir = path.join(location, 'bmad');
const agentsDir = path.join(bmadCommandsDir, 'agents');
// Create .augment/commands/bmad/agents directory if it doesn't exist
await fs.ensureDir(agentsDir);
// Create custom agent launcher
const launcherContent = `---
description: "Use the ${agentName} custom agent"
---
# ${agentName} Custom Agent
** IMPORTANT**: Run @${agentPath} first to load the complete agent!
This is a launcher for the custom BMAD agent "${agentName}".
## Usage
1. First run: \`${agentPath}\` to load the complete agent
2. Then use this command to activate ${agentName}
The agent will follow the persona and instructions from the main agent file.
## Module
BMAD Custom agent
`;
const fileName = `custom-${agentName.toLowerCase()}.md`;
const launcherPath = path.join(agentsDir, fileName);
// Write the launcher file
await fs.writeFile(launcherPath, launcherContent, 'utf8');
return {
ide: 'auggie',
path: path.relative(projectDir, launcherPath),
command: agentName,
type: 'custom-agent-launcher',
};
}
}
module.exports = { AuggieSetup };

View File

@ -1,506 +0,0 @@
const path = require('node:path');
const fs = require('fs-extra');
const { BaseIdeSetup } = require('./_base-ide');
const chalk = require('chalk');
const { getProjectRoot, getSourcePath, getModulePath } = require('../../../lib/project-root');
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');
const { TaskToolCommandGenerator } = require('./shared/task-tool-command-generator');
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
const {
loadModuleInjectionConfig,
shouldApplyInjection,
filterAgentInstructions,
resolveSubagentFiles,
} = require('./shared/module-injections');
const { getAgentsFromBmad, getAgentsFromDir } = require('./shared/bmad-artifacts');
const { customAgentColonName } = require('./shared/path-utils');
const prompts = require('../../../lib/prompts');
/**
* Claude Code IDE setup handler
*/
class ClaudeCodeSetup extends BaseIdeSetup {
constructor() {
super('claude-code', 'Claude Code', true); // preferred IDE
this.configDir = '.claude';
this.commandsDir = 'commands';
this.agentsDir = 'agents';
}
/**
* Prompt for subagent installation location
* @returns {Promise<string>} Selected location ('project' or 'user')
*/
async promptInstallLocation() {
return prompts.select({
message: 'Where would you like to install Claude Code subagents?',
choices: [
{ name: 'Project level (.claude/agents/)', value: 'project' },
{ name: 'User level (~/.claude/agents/)', value: 'user' },
],
default: 'project',
});
}
// /**
// * Collect configuration choices before installation
// * @param {Object} options - Configuration options
// * @returns {Object} Collected configuration
// */
// async collectConfiguration(options = {}) {
// const config = {
// subagentChoices: null,
// installLocation: null,
// };
// const sourceModulesPath = getSourcePath('modules');
// const modules = options.selectedModules || [];
// for (const moduleName of modules) {
// // Check for Claude Code sub-module injection config in SOURCE directory
// const injectionConfigPath = path.join(sourceModulesPath, moduleName, 'sub-modules', 'claude-code', 'injections.yaml');
// if (await this.exists(injectionConfigPath)) {
// const yaml = require('yaml');
// try {
// // Load injection configuration
// const configContent = await fs.readFile(injectionConfigPath, 'utf8');
// const injectionConfig = yaml.parse(configContent);
// // Ask about subagents if they exist and we haven't asked yet
// if (injectionConfig.subagents && !config.subagentChoices) {
// config.subagentChoices = await this.promptSubagentInstallation(injectionConfig.subagents);
// if (config.subagentChoices.install !== 'none') {
// config.installLocation = await this.promptInstallLocation();
// }
// }
// } catch (error) {
// console.log(chalk.yellow(` Warning: Failed to process ${moduleName} features: ${error.message}`));
// }
// }
// }
// return config;
// }
/**
* Cleanup old BMAD installation before reinstalling
* @param {string} projectDir - Project directory
*/
async cleanup(projectDir) {
const commandsDir = path.join(projectDir, this.configDir, this.commandsDir);
// Remove any bmad* files from the commands directory (cleans up old bmad: and bmad- formats)
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}`));
}
}
/**
* Setup Claude Code IDE configuration
* @param {string} projectDir - Project directory
* @param {string} bmadDir - BMAD installation directory
* @param {Object} options - Setup options
*/
async setup(projectDir, bmadDir, options = {}) {
// Store project directory for use in processContent
this.projectDir = projectDir;
console.log(chalk.cyan(`Setting up ${this.name}...`));
// Clean up old BMAD installation first
await this.cleanup(projectDir);
// Create .claude/commands directory structure
const claudeDir = path.join(projectDir, this.configDir);
const commandsDir = path.join(claudeDir, this.commandsDir);
await this.ensureDir(commandsDir);
// Use underscore 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 || []);
// Write agent launcher files using flat underscore 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
if (options.preCollectedConfig && options.preCollectedConfig._alreadyConfigured) {
// IDE is already configured from previous installation, skip prompting
// Just process with default/existing configuration
await this.processModuleInjectionsWithConfig(projectDir, bmadDir, options, {});
} else if (options.preCollectedConfig) {
await this.processModuleInjectionsWithConfig(projectDir, bmadDir, options, options.preCollectedConfig);
} else {
await this.processModuleInjections(projectDir, bmadDir, options);
}
// Skip CLAUDE.md creation - let user manage their own CLAUDE.md file
// await this.createClaudeConfig(projectDir, modules);
// Generate workflow commands from manifest (if it exists)
const workflowGen = new WorkflowCommandGenerator(this.bmadFolderName);
const { artifacts: workflowArtifacts } = await workflowGen.collectWorkflowArtifacts(bmadDir);
// Write workflow-command artifacts using flat underscore 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.generateColonTaskToolCommands(projectDir, bmadDir, commandsDir);
console.log(chalk.green(`${this.name} configured:`));
console.log(chalk.dim(` - ${agentCount} agents installed`));
if (workflowCommandCount > 0) {
console.log(chalk.dim(` - ${workflowCommandCount} workflow commands generated`));
}
if (taskToolResult.generated > 0) {
console.log(
chalk.dim(
` - ${taskToolResult.generated} task/tool commands generated (${taskToolResult.tasks} tasks, ${taskToolResult.tools} tools)`,
),
);
}
console.log(chalk.dim(` - Commands directory: ${path.relative(projectDir, commandsDir)}`));
return {
success: true,
agents: agentCount,
};
}
// Method removed - CLAUDE.md file management left to user
/**
* Read and process file content
*/
async readAndProcess(filePath, metadata) {
const content = await fs.readFile(filePath, 'utf8');
return this.processContent(content, metadata);
}
/**
* Override processContent to keep {project-root} placeholder
*/
processContent(content, metadata = {}) {
// Use the base class method WITHOUT projectDir to preserve {project-root} placeholder
return super.processContent(content, metadata);
}
/**
* Get agents from source modules (not installed location)
*/
async getAgentsFromSource(sourceDir, selectedModules) {
const agents = [];
// Add core agents
const corePath = getModulePath('core');
if (await fs.pathExists(path.join(corePath, 'agents'))) {
const coreAgents = await getAgentsFromDir(path.join(corePath, 'agents'), 'core');
agents.push(...coreAgents);
}
// Add module agents
for (const moduleName of selectedModules) {
const modulePath = path.join(sourceDir, moduleName);
const agentsPath = path.join(modulePath, 'agents');
if (await fs.pathExists(agentsPath)) {
const moduleAgents = await getAgentsFromDir(agentsPath, moduleName);
agents.push(...moduleAgents);
}
}
return agents;
}
/**
* Process module injections with pre-collected configuration
*/
async processModuleInjectionsWithConfig(projectDir, bmadDir, options, preCollectedConfig) {
// Get list of installed modules
const modules = options.selectedModules || [];
const { subagentChoices, installLocation } = preCollectedConfig;
// Get the actual source directory (not the installation directory)
await this.processModuleInjectionsInternal({
projectDir,
modules,
handler: 'claude-code',
subagentChoices,
installLocation,
interactive: false,
});
}
/**
* Process Claude Code specific injections for installed modules
* Looks for injections.yaml in each module's claude-code sub-module
*/
async processModuleInjections(projectDir, bmadDir, options) {
// Get list of installed modules
const modules = options.selectedModules || [];
let subagentChoices = null;
let installLocation = null;
// Get the actual source directory (not the installation directory)
const { subagentChoices: updatedChoices, installLocation: updatedLocation } = await this.processModuleInjectionsInternal({
projectDir,
modules,
handler: 'claude-code',
subagentChoices,
installLocation,
interactive: true,
});
if (updatedChoices) {
subagentChoices = updatedChoices;
}
if (updatedLocation) {
installLocation = updatedLocation;
}
}
async processModuleInjectionsInternal({ projectDir, modules, handler, subagentChoices, installLocation, interactive = false }) {
let choices = subagentChoices;
let location = installLocation;
for (const moduleName of modules) {
const configData = await loadModuleInjectionConfig(handler, moduleName);
if (!configData) {
continue;
}
const { config, handlerBaseDir } = configData;
if (interactive) {
console.log(chalk.cyan(`\nConfiguring ${moduleName} ${handler.replace('-', ' ')} features...`));
}
if (interactive && config.subagents && !choices) {
// choices = await this.promptSubagentInstallation(config.subagents);
// if (choices.install !== 'none') {
// location = await this.promptInstallLocation();
// }
}
if (config.injections && choices && choices.install !== 'none') {
for (const injection of config.injections) {
if (shouldApplyInjection(injection, choices)) {
await this.injectContent(projectDir, injection, choices);
}
}
}
if (config.subagents && choices && choices.install !== 'none') {
await this.copySelectedSubagents(projectDir, handlerBaseDir, config.subagents, choices, location || 'project');
}
}
return { subagentChoices: choices, installLocation: location };
}
/**
* Prompt user for subagent installation preferences
*/
async promptSubagentInstallation(subagentConfig) {
// First ask if they want to install subagents
const install = await prompts.select({
message: 'Would you like to install Claude Code subagents for enhanced functionality?',
choices: [
{ name: 'Yes, install all subagents', value: 'all' },
{ name: 'Yes, let me choose specific subagents', value: 'selective' },
{ name: 'No, skip subagent installation', value: 'none' },
],
default: 'all',
});
if (install === 'selective') {
// Show list of available subagents with descriptions
const subagentInfo = {
'market-researcher.md': 'Market research and competitive analysis',
'requirements-analyst.md': 'Requirements extraction and validation',
'technical-evaluator.md': 'Technology stack evaluation',
'epic-optimizer.md': 'Epic and story breakdown optimization',
'document-reviewer.md': 'Document quality review',
};
const selected = await prompts.multiselect({
message: `Select subagents to install ${chalk.dim('(↑/↓ navigates multiselect, SPACE toggles, A to toggles All, ENTER confirm)')}:`,
options: subagentConfig.files.map((file) => ({
label: `${file.replace('.md', '')} - ${subagentInfo[file] || 'Specialized assistant'}`,
value: file,
})),
initialValues: subagentConfig.files,
});
return { install: 'selective', selected };
}
return { install };
}
/**
* Inject content at specified point in file
*/
async injectContent(projectDir, injection, subagentChoices = null) {
const targetPath = path.join(projectDir, injection.file);
if (await this.exists(targetPath)) {
let content = await fs.readFile(targetPath, 'utf8');
const marker = `<!-- IDE-INJECT-POINT: ${injection.point} -->`;
if (content.includes(marker)) {
let injectionContent = injection.content;
// Filter content if selective subagents chosen
if (subagentChoices && subagentChoices.install === 'selective' && injection.point === 'pm-agent-instructions') {
injectionContent = filterAgentInstructions(injection.content, subagentChoices.selected);
}
content = content.replace(marker, injectionContent);
await fs.writeFile(targetPath, content);
console.log(chalk.dim(` Injected: ${injection.point}${injection.file}`));
}
}
}
/**
* Copy selected subagents to appropriate Claude agents directory
*/
async copySelectedSubagents(projectDir, handlerBaseDir, subagentConfig, choices, location) {
const os = require('node:os');
// Determine target directory based on user choice
let targetDir;
if (location === 'user') {
targetDir = path.join(os.homedir(), '.claude', 'agents');
console.log(chalk.dim(` Installing subagents globally to: ~/.claude/agents/`));
} else {
targetDir = path.join(projectDir, '.claude', 'agents');
console.log(chalk.dim(` Installing subagents to project: .claude/agents/`));
}
// Ensure target directory exists
await this.ensureDir(targetDir);
const resolvedFiles = await resolveSubagentFiles(handlerBaseDir, subagentConfig, choices);
let copiedCount = 0;
for (const resolved of resolvedFiles) {
try {
const sourcePath = resolved.absolutePath;
const subFolder = path.dirname(resolved.relativePath);
let targetPath;
if (subFolder && subFolder !== '.') {
const targetSubDir = path.join(targetDir, subFolder);
await this.ensureDir(targetSubDir);
targetPath = path.join(targetSubDir, path.basename(resolved.file));
} else {
targetPath = path.join(targetDir, path.basename(resolved.file));
}
await fs.copyFile(sourcePath, targetPath);
console.log(chalk.green(` ✓ Installed: ${subFolder === '.' ? '' : `${subFolder}/`}${path.basename(resolved.file, '.md')}`));
copiedCount++;
} catch (error) {
console.log(chalk.yellow(` ⚠ Error copying ${resolved.file}: ${error.message}`));
}
}
if (copiedCount > 0) {
console.log(chalk.dim(` Total subagents installed: ${copiedCount}`));
}
}
/**
* Install a custom agent launcher for Claude Code
* @param {string} projectDir - Project directory
* @param {string} agentName - Agent name (e.g., "fred-commit-poet")
* @param {string} agentPath - Path to compiled agent (relative to project root)
* @param {Object} metadata - Agent metadata
* @returns {Object|null} Info about created command
*/
async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) {
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(commandsDir);
const launcherContent = `---
name: '${agentName}'
description: '${agentName} agent'
---
You must fully embody this agent's persona and follow all activation instructions exactly as specified. NEVER break character until given an exit command.
<agent-activation CRITICAL="TRUE">
1. LOAD the FULL agent file from @${agentPath}
2. READ its entire contents - this contains the complete agent persona, menu, and instructions
3. FOLLOW every step in the <activation> section precisely
4. DISPLAY the welcome/greeting as instructed
5. PRESENT the numbered menu
6. WAIT for user input before proceeding
</agent-activation>
`;
// Use underscore format: bmad_custom_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: `/${launcherName.replace('.md', '')}`,
};
}
}
module.exports = { ClaudeCodeSetup };

View File

@ -1,272 +0,0 @@
const path = require('node:path');
const fs = require('fs-extra');
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
* Installs BMAD artifacts to .clinerules/workflows with flattened naming
*/
class ClineSetup extends BaseIdeSetup {
constructor() {
super('cline', 'Cline', false);
this.configDir = '.clinerules';
this.workflowsDir = 'workflows';
}
/**
* Setup Cline IDE configuration
* @param {string} projectDir - Project directory
* @param {string} bmadDir - BMAD installation directory
* @param {Object} options - Setup options
*/
async setup(projectDir, bmadDir, options = {}) {
console.log(chalk.cyan(`Setting up ${this.name}...`));
// Create .clinerules/workflows directory
const clineDir = path.join(projectDir, this.configDir);
const workflowsDir = path.join(clineDir, this.workflowsDir);
await this.ensureDir(workflowsDir);
// Clear old BMAD files
await this.clearOldBmadFiles(workflowsDir);
// Collect all artifacts
const { artifacts, counts } = await this.collectClineArtifacts(projectDir, bmadDir, options);
// Write flattened files
const written = await this.flattenAndWriteArtifacts(artifacts, workflowsDir);
console.log(chalk.green(`${this.name} configured:`));
console.log(chalk.dim(` - ${counts.agents} agents installed`));
console.log(chalk.dim(` - ${counts.tasks} tasks installed`));
console.log(chalk.dim(` - ${counts.workflows} workflow commands installed`));
if (counts.workflowLaunchers > 0) {
console.log(chalk.dim(` - ${counts.workflowLaunchers} workflow launchers installed`));
}
console.log(chalk.dim(` - ${written} files written to ${path.relative(projectDir, workflowsDir)}`));
// Usage instructions
console.log(chalk.yellow('\n ⚠️ How to Use Cline Workflows'));
console.log(chalk.cyan(' BMAD workflows are available as slash commands in Cline'));
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_pm'));
return {
success: true,
agents: counts.agents,
tasks: counts.tasks,
workflows: counts.workflows,
workflowLaunchers: counts.workflowLaunchers,
written,
};
}
/**
* Detect Cline installation by checking for .clinerules/workflows directory
*/
async detect(projectDir) {
const workflowsDir = path.join(projectDir, this.configDir, this.workflowsDir);
if (!(await fs.pathExists(workflowsDir))) {
return false;
}
const entries = await fs.readdir(workflowsDir);
return entries.some((entry) => entry.startsWith('bmad'));
}
/**
* Collect all artifacts for Cline export
*/
async collectClineArtifacts(projectDir, bmadDir, options = {}) {
const selectedModules = options.selectedModules || [];
const artifacts = [];
// Generate agent launchers
const agentGen = new AgentCommandGenerator(this.bmadFolderName);
const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, selectedModules);
// Process agent launchers with project-specific paths
for (const agentArtifact of agentArtifacts) {
const content = agentArtifact.content;
artifacts.push({
type: 'agent',
module: agentArtifact.module,
sourcePath: agentArtifact.sourcePath,
relativePath: agentArtifact.relativePath,
content,
});
}
// Get tasks
const tasks = await getTasksFromBmad(bmadDir, selectedModules);
for (const task of tasks) {
const content = await this.readAndProcessWithProject(
task.path,
{
module: task.module,
name: task.name,
},
projectDir,
);
artifacts.push({
type: 'task',
module: task.module,
sourcePath: task.path,
relativePath: path.join(task.module, 'tasks', `${task.name}.md`),
content,
});
}
// Get workflows
const workflowGenerator = new WorkflowCommandGenerator(this.bmadFolderName);
const { artifacts: workflowArtifacts, counts: workflowCounts } = await workflowGenerator.collectWorkflowArtifacts(bmadDir);
artifacts.push(...workflowArtifacts);
return {
artifacts,
counts: {
agents: agentArtifacts.length,
tasks: tasks.length,
workflows: workflowCounts.commands,
workflowLaunchers: workflowCounts.launchers,
},
};
}
/**
* Flatten file path to bmad_module_type_name.md format
* Uses shared toDashPath utility
*/
flattenFilename(relativePath) {
return toDashPath(relativePath);
}
/**
* Write all artifacts with flattened names
*/
async flattenAndWriteArtifacts(artifacts, destDir) {
let written = 0;
for (const artifact of artifacts) {
const flattenedName = this.flattenFilename(artifact.relativePath);
const targetPath = path.join(destDir, flattenedName);
await fs.writeFile(targetPath, artifact.content);
written++;
}
return written;
}
/**
* Clear old BMAD files from the workflows directory
*/
async clearOldBmadFiles(destDir) {
if (!(await fs.pathExists(destDir))) {
return;
}
const entries = await fs.readdir(destDir);
for (const entry of entries) {
if (!entry.startsWith('bmad')) {
continue;
}
const entryPath = path.join(destDir, entry);
const stat = await fs.stat(entryPath);
if (stat.isFile()) {
await fs.remove(entryPath);
} else if (stat.isDirectory()) {
await fs.remove(entryPath);
}
}
}
/**
* Read and process file with project-specific paths
*/
async readAndProcessWithProject(filePath, metadata, projectDir) {
const content = await fs.readFile(filePath, 'utf8');
return super.processContent(content, metadata, projectDir);
}
/**
* Cleanup Cline configuration
*/
async cleanup(projectDir) {
const workflowsDir = path.join(projectDir, this.configDir, this.workflowsDir);
await this.clearOldBmadFiles(workflowsDir);
console.log(chalk.dim(`Removed ${this.name} BMAD configuration`));
}
/**
* Install a custom agent launcher for Cline
* @param {string} projectDir - Project directory
* @param {string} agentName - Agent name (e.g., "fred-commit-poet")
* @param {string} agentPath - Path to compiled agent (relative to project root)
* @param {Object} metadata - Agent metadata
* @returns {Object} Installation result
*/
async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) {
const clineDir = path.join(projectDir, this.configDir);
const workflowsDir = path.join(clineDir, this.workflowsDir);
// Create .clinerules/workflows directory if it doesn't exist
await fs.ensureDir(workflowsDir);
// Create custom agent launcher workflow
const launcherContent = `name: ${agentName}
description: Custom BMAD agent: ${agentName}
# ${agentName} Custom Agent
** IMPORTANT**: Run @${agentPath} first to load the complete agent!
This is a launcher for the custom BMAD agent "${agentName}".
## Usage
1. First run: \`${agentPath}\` to load the complete agent
2. Then use this workflow as ${agentName}
The agent will follow the persona and instructions from the main agent file.
---
*Generated by BMAD Method*`;
// Use underscore format: bmad_custom_fred-commit-poet.md
const fileName = customAgentDashName(agentName);
const launcherPath = path.join(workflowsDir, fileName);
// Write the launcher file
await fs.writeFile(launcherPath, launcherContent, 'utf8');
return {
ide: 'cline',
path: path.relative(projectDir, launcherPath),
command: fileName.replace('.md', ''),
type: 'custom-agent-launcher',
};
}
/**
* Utility: Ensure directory exists
*/
async ensureDir(dirPath) {
await fs.ensureDir(dirPath);
}
}
module.exports = { ClineSetup };

View File

@ -154,17 +154,25 @@ class CodexSetup extends BaseIdeSetup {
// Check global location
if (await fs.pathExists(globalDir)) {
const entries = await fs.readdir(globalDir);
if (entries.some((entry) => entry.startsWith('bmad'))) {
return true;
try {
const entries = await fs.readdir(globalDir);
if (entries && entries.some((entry) => entry && typeof entry === 'string' && entry.startsWith('bmad'))) {
return true;
}
} catch {
// Ignore errors
}
}
// Check project-specific location
if (await fs.pathExists(projectSpecificDir)) {
const entries = await fs.readdir(projectSpecificDir);
if (entries.some((entry) => entry.startsWith('bmad'))) {
return true;
try {
const entries = await fs.readdir(projectSpecificDir);
if (entries && entries.some((entry) => entry && typeof entry === 'string' && entry.startsWith('bmad'))) {
return true;
}
} catch {
// Ignore errors
}
}
@ -253,19 +261,39 @@ class CodexSetup extends BaseIdeSetup {
return;
}
const entries = await fs.readdir(destDir);
let entries;
try {
entries = await fs.readdir(destDir);
} catch (error) {
// Directory exists but can't be read - skip cleanup
console.warn(chalk.yellow(`Warning: Could not read directory ${destDir}: ${error.message}`));
return;
}
if (!entries || !Array.isArray(entries)) {
return;
}
for (const entry of entries) {
// Skip non-strings or undefined entries
if (!entry || typeof entry !== 'string') {
continue;
}
if (!entry.startsWith('bmad')) {
continue;
}
const entryPath = path.join(destDir, entry);
const stat = await fs.stat(entryPath);
if (stat.isFile()) {
await fs.remove(entryPath);
} else if (stat.isDirectory()) {
await fs.remove(entryPath);
try {
const stat = await fs.stat(entryPath);
if (stat.isFile()) {
await fs.remove(entryPath);
} else if (stat.isDirectory()) {
await fs.remove(entryPath);
}
} catch (error) {
// Skip files that can't be processed
console.warn(chalk.dim(` Skipping ${entry}: ${error.message}`));
}
}
}

View File

@ -1,149 +0,0 @@
const path = require('node:path');
const fs = require('fs-extra');
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 using flat colon naming
*/
class CrushSetup extends BaseIdeSetup {
constructor() {
super('crush', 'Crush');
this.configDir = '.crush';
this.commandsDir = 'commands';
}
/**
* Setup Crush IDE configuration
* @param {string} projectDir - Project directory
* @param {string} bmadDir - BMAD installation directory
* @param {Object} options - Setup options
*/
async setup(projectDir, bmadDir, options = {}) {
console.log(chalk.cyan(`Setting up ${this.name}...`));
// Clean up old BMAD installation first
await this.cleanup(projectDir);
// Create .crush/commands directory
const crushDir = path.join(projectDir, this.configDir);
const commandsDir = path.join(crushDir, this.commandsDir);
await this.ensureDir(commandsDir);
// Use underscore 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 || []);
// Write agent launcher files using flat underscore 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 } = await workflowGenerator.collectWorkflowArtifacts(bmadDir);
// Write workflow-command artifacts using flat underscore naming
// Creates files like: bmad_bmm_correct-course.md
const workflowCount = await workflowGenerator.writeColonArtifacts(commandsDir, workflowArtifacts);
// Generate task and tool commands using flat underscore 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} 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,
agents: agentCount,
tasks: taskToolResult.tasks || 0,
tools: taskToolResult.tools || 0,
workflows: workflowCount,
};
}
/**
* Cleanup Crush configuration
*/
async cleanup(projectDir) {
const commandsDir = path.join(projectDir, this.configDir, this.commandsDir);
// Remove any bmad* files from the commands directory (cleans up old bmad: and bmad- formats)
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`));
}
}
/**
* Install a custom agent launcher for Crush
* @param {string} projectDir - Project directory
* @param {string} agentName - Agent name (e.g., "fred-commit-poet")
* @param {string} agentPath - Path to compiled agent (relative to project root)
* @param {Object} metadata - Agent metadata
* @returns {Object} Installation result
*/
async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) {
const commandsDir = path.join(projectDir, this.configDir, this.commandsDir);
// Create .crush/commands directory if it doesn't exist
await fs.ensureDir(commandsDir);
// Create custom agent launcher
const launcherContent = `# ${agentName} Custom Agent
** IMPORTANT**: Run @${agentPath} first to load the complete agent!
This is a launcher for the custom BMAD agent "${agentName}".
## Usage
1. First run: \`${agentPath}\` to load the complete agent
2. Then use this command to activate ${agentName}
The agent will follow the persona and instructions from the main agent file.
---
*Generated by BMAD Method*`;
// Use underscore format: bmad_custom_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');
return {
ide: 'crush',
path: path.relative(projectDir, launcherPath),
command: launcherName.replace('.md', ''),
type: 'custom-agent-launcher',
};
}
}
module.exports = { CrushSetup };

View File

@ -1,160 +0,0 @@
const path = require('node:path');
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');
/**
* Cursor IDE setup handler
*/
class CursorSetup extends BaseIdeSetup {
constructor() {
super('cursor', 'Cursor', true); // preferred IDE
this.configDir = '.cursor';
this.rulesDir = 'rules';
this.commandsDir = 'commands';
}
/**
* Cleanup old BMAD installation before reinstalling
* @param {string} projectDir - Project directory
*/
async cleanup(projectDir) {
const fs = require('fs-extra');
const commandsDir = path.join(projectDir, this.configDir, this.commandsDir);
// Remove any bmad* files from the commands directory (cleans up old bmad: and bmad- formats)
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 old BMAD commands from ${this.name}`));
}
}
/**
* Setup Cursor IDE configuration
* @param {string} projectDir - Project directory
* @param {string} bmadDir - BMAD installation directory
* @param {Object} options - Setup options
*/
async setup(projectDir, bmadDir, options = {}) {
console.log(chalk.cyan(`Setting up ${this.name}...`));
// Clean up old BMAD installation first
await this.cleanup(projectDir);
// Create .cursor/commands directory structure
const cursorDir = path.join(projectDir, this.configDir);
const commandsDir = path.join(cursorDir, this.commandsDir);
await this.ensureDir(commandsDir);
// Use underscore 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 || []);
// Write agent launcher files using flat underscore 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 workflow-command artifacts using flat underscore 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.generateColonTaskToolCommands(projectDir, bmadDir, commandsDir);
console.log(chalk.green(`${this.name} configured:`));
console.log(chalk.dim(` - ${agentCount} agents installed`));
if (workflowCommandCount > 0) {
console.log(chalk.dim(` - ${workflowCommandCount} workflow commands generated`));
}
if (taskToolResult.generated > 0) {
console.log(
chalk.dim(
` - ${taskToolResult.generated} task/tool commands generated (${taskToolResult.tasks} tasks, ${taskToolResult.tools} tools)`,
),
);
}
console.log(chalk.dim(` - Commands directory: ${path.relative(projectDir, commandsDir)}`));
return {
success: true,
agents: agentCount,
tasks: taskToolResult.tasks || 0,
tools: taskToolResult.tools || 0,
workflows: workflowCommandCount,
};
}
/**
* Install a custom agent launcher for Cursor
* @param {string} projectDir - Project directory
* @param {string} agentName - Agent name (e.g., "fred-commit-poet")
* @param {string} agentPath - Path to compiled agent (relative to project root)
* @param {Object} metadata - Agent metadata
* @returns {Object|null} Info about created command
*/
async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) {
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(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.
<agent-activation CRITICAL="TRUE">
1. LOAD the FULL agent file from @${agentPath}
2. READ its entire contents - this contains the complete agent persona, menu, and instructions
3. FOLLOW every step in the <activation> section precisely
4. DISPLAY the welcome/greeting as instructed
5. PRESENT the numbered menu
6. WAIT for user input before proceeding
</agent-activation>
`;
// Cursor uses YAML frontmatter matching Claude Code format
const commandContent = `---
name: '${agentName}'
description: '${agentName} agent'
---
${launcherContent}
`;
// Use underscore format: bmad_custom_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: `/${launcherName.replace('.md', '')}`,
};
}
}
module.exports = { CursorSetup };

View File

@ -1,301 +0,0 @@
const path = require('node:path');
const fs = require('fs-extra');
const yaml = require('yaml');
const { BaseIdeSetup } = require('./_base-ide');
const chalk = require('chalk');
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');
/**
* Gemini CLI setup handler
* Creates TOML files in .gemini/commands/ structure
*/
class GeminiSetup extends BaseIdeSetup {
constructor() {
super('gemini', 'Gemini CLI', false);
this.configDir = '.gemini';
this.commandsDir = 'commands';
this.agentTemplatePath = path.join(__dirname, 'templates', 'gemini-agent-command.toml');
this.taskTemplatePath = path.join(__dirname, 'templates', 'gemini-task-command.toml');
}
/**
* Load config values from bmad installation
* @param {string} bmadDir - BMAD installation directory
* @returns {Object} Config values
*/
async loadConfigValues(bmadDir) {
const configValues = {
user_name: 'User', // Default fallback
};
// Try to load core config.yaml
const coreConfigPath = path.join(bmadDir, 'core', 'config.yaml');
if (await fs.pathExists(coreConfigPath)) {
try {
const configContent = await fs.readFile(coreConfigPath, 'utf8');
const config = yaml.parse(configContent);
if (config.user_name) {
configValues.user_name = config.user_name;
}
} catch (error) {
console.warn(chalk.yellow(` Warning: Could not load config values: ${error.message}`));
}
}
return configValues;
}
/**
* Setup Gemini CLI configuration
* @param {string} projectDir - Project directory
* @param {string} bmadDir - BMAD installation directory
* @param {Object} options - Setup options
*/
async setup(projectDir, bmadDir, options = {}) {
console.log(chalk.cyan(`Setting up ${this.name}...`));
// Create .gemini/commands directory (flat structure with bmad- prefix)
const geminiDir = path.join(projectDir, this.configDir);
const commandsDir = path.join(geminiDir, this.commandsDir);
await this.ensureDir(commandsDir);
// Clean up any existing BMAD files before reinstalling
await this.cleanup(projectDir);
// Generate agent launchers
const agentGen = new AgentCommandGenerator(this.bmadFolderName);
const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []);
// Get tasks and workflows (ALL workflows now generate commands)
const tasks = await this.getTasks(bmadDir);
// Get ALL workflows using the new workflow command generator
const workflowGenerator = new WorkflowCommandGenerator(this.bmadFolderName);
const { artifacts: workflowArtifacts, counts: workflowCounts } = await workflowGenerator.collectWorkflowArtifacts(bmadDir);
// Install agents as TOML files with bmad- prefix (flat structure)
let agentCount = 0;
for (const artifact of agentArtifacts) {
const tomlContent = await this.createAgentLauncherToml(artifact);
// Flat structure: bmad-agent-{module}-{name}.toml
const tomlPath = path.join(commandsDir, `bmad-agent-${artifact.module}-${artifact.name}.toml`);
await this.writeFile(tomlPath, tomlContent);
agentCount++;
console.log(chalk.green(` ✓ Added agent: /bmad_agents_${artifact.module}_${artifact.name}`));
}
// Install tasks as TOML files with bmad- prefix (flat structure)
let taskCount = 0;
for (const task of tasks) {
const content = await this.readFile(task.path);
const tomlContent = await this.createTaskToml(task, content);
// Flat structure: bmad-task-{module}-{name}.toml
const tomlPath = path.join(commandsDir, `bmad-task-${task.module}-${task.name}.toml`);
await this.writeFile(tomlPath, tomlContent);
taskCount++;
console.log(chalk.green(` ✓ Added task: /bmad_tasks_${task.module}_${task.name}`));
}
// Install workflows as TOML files with bmad- prefix (flat structure)
let workflowCount = 0;
for (const artifact of workflowArtifacts) {
if (artifact.type === 'workflow-command') {
// Create TOML wrapper around workflow command content
const tomlContent = await this.createWorkflowToml(artifact);
// Flat structure: bmad-workflow-{module}-{name}.toml
const workflowName = path.basename(artifact.relativePath, '.md');
const tomlPath = path.join(commandsDir, `bmad-workflow-${artifact.module}-${workflowName}.toml`);
await this.writeFile(tomlPath, tomlContent);
workflowCount++;
console.log(chalk.green(` ✓ Added workflow: /bmad_workflows_${artifact.module}_${workflowName}`));
}
}
console.log(chalk.green(`${this.name} configured:`));
console.log(chalk.dim(` - ${agentCount} agents configured`));
console.log(chalk.dim(` - ${taskCount} tasks configured`));
console.log(chalk.dim(` - ${workflowCount} workflows configured`));
console.log(chalk.dim(` - Commands directory: ${path.relative(projectDir, commandsDir)}`));
console.log(chalk.dim(` - Agent activation: /bmad_agents_{agent-name}`));
console.log(chalk.dim(` - Task activation: /bmad_tasks_{task-name}`));
console.log(chalk.dim(` - Workflow activation: /bmad_workflows_{workflow-name}`));
return {
success: true,
agents: agentCount,
tasks: taskCount,
workflows: workflowCount,
};
}
/**
* Create agent launcher TOML content from artifact
*/
async createAgentLauncherToml(artifact) {
// Strip frontmatter from launcher content
const frontmatterRegex = /^---\s*\n[\s\S]*?\n---\s*\n/;
const contentWithoutFrontmatter = artifact.content.replace(frontmatterRegex, '').trim();
// Extract title from launcher frontmatter
const titleMatch = artifact.content.match(/description:\s*"([^"]+)"/);
const title = titleMatch ? titleMatch[1] : this.formatTitle(artifact.name);
// Create TOML wrapper around launcher content (without frontmatter)
const description = `BMAD ${artifact.module.toUpperCase()} Agent: ${title}`;
return `description = "${description}"
prompt = """
${contentWithoutFrontmatter}
"""
`;
}
/**
* Create agent TOML content using template
*/
async createAgentToml(agent, content) {
// Extract metadata
const titleMatch = content.match(/title="([^"]+)"/);
const title = titleMatch ? titleMatch[1] : this.formatTitle(agent.name);
// Load template
const template = await fs.readFile(this.agentTemplatePath, 'utf8');
// Replace template variables
// Note: {user_name} and other {config_values} are left as-is for runtime substitution by Gemini
const tomlContent = template
.replaceAll('{{title}}', title)
.replaceAll('{_bmad}', '_bmad')
.replaceAll('{_bmad}', this.bmadFolderName)
.replaceAll('{{module}}', agent.module)
.replaceAll('{{name}}', agent.name);
return tomlContent;
}
/**
* Create task TOML content using template
*/
async createTaskToml(task, content) {
// Extract task name from XML if available
const nameMatch = content.match(/<name>([^<]+)<\/name>/);
const taskName = nameMatch ? nameMatch[1] : this.formatTitle(task.name);
// Load template
const template = await fs.readFile(this.taskTemplatePath, 'utf8');
// Replace template variables
const tomlContent = template
.replaceAll('{{taskName}}', taskName)
.replaceAll('{_bmad}', '_bmad')
.replaceAll('{_bmad}', this.bmadFolderName)
.replaceAll('{{module}}', task.module)
.replaceAll('{{filename}}', task.filename);
return tomlContent;
}
/**
* Create workflow TOML content from artifact
*/
async createWorkflowToml(artifact) {
// Extract description from artifact content
const descriptionMatch = artifact.content.match(/description:\s*"([^"]+)"/);
const description = descriptionMatch
? descriptionMatch[1]
: `BMAD ${artifact.module.toUpperCase()} Workflow: ${path.basename(artifact.relativePath, '.md')}`;
// Strip frontmatter from command content
const frontmatterRegex = /^---\s*\n[\s\S]*?\n---\s*\n/;
const contentWithoutFrontmatter = artifact.content.replace(frontmatterRegex, '').trim();
return `description = "${description}"
prompt = """
${contentWithoutFrontmatter}
"""
`;
}
/**
* Cleanup Gemini configuration - surgically remove only BMAD files
*/
async cleanup(projectDir) {
const fs = require('fs-extra');
const commandsDir = path.join(projectDir, this.configDir, this.commandsDir);
if (await fs.pathExists(commandsDir)) {
// Remove any bmad* files (cleans up old bmad- and bmad: formats)
const files = await fs.readdir(commandsDir);
let removed = 0;
for (const file of files) {
if (file.startsWith('bmad') && file.endsWith('.toml')) {
await fs.remove(path.join(commandsDir, file));
removed++;
}
}
if (removed > 0) {
console.log(chalk.dim(` Cleaned up ${removed} existing BMAD files`));
}
}
}
/**
* Install a custom agent launcher for Gemini
* @param {string} projectDir - Project directory
* @param {string} agentName - Agent name (e.g., "fred-commit-poet")
* @param {string} agentPath - Path to compiled agent (relative to project root)
* @param {Object} metadata - Agent metadata
* @returns {Object} Installation result
*/
async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) {
const geminiDir = path.join(projectDir, this.configDir);
const commandsDir = path.join(geminiDir, this.commandsDir);
// Create .gemini/commands directory if it doesn't exist
await fs.ensureDir(commandsDir);
// Create custom agent launcher in TOML format
const launcherContent = `description = "Custom BMAD Agent: ${agentName}"
prompt = """
** IMPORTANT**: Run @${agentPath} first to load the complete agent!
This is a launcher for the custom BMAD agent "${agentName}".
## Usage
1. First run: \`${agentPath}\` to load the complete agent
2. Then use this command to activate ${agentName}
The agent will follow the persona and instructions from the main agent file.
---
*Generated by BMAD Method*
"""`;
const fileName = `bmad-custom-${agentName.toLowerCase()}.toml`;
const launcherPath = path.join(commandsDir, fileName);
// Write the launcher file
await fs.writeFile(launcherPath, launcherContent, 'utf8');
return {
ide: 'gemini',
path: path.relative(projectDir, launcherPath),
command: agentName,
type: 'custom-agent-launcher',
};
}
}
module.exports = { GeminiSetup };

View File

@ -1,383 +0,0 @@
const path = require('node:path');
const { BaseIdeSetup } = require('./_base-ide');
const chalk = require('chalk');
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
const prompts = require('../../../lib/prompts');
/**
* GitHub Copilot setup handler
* Creates agents in .github/agents/ and configures VS Code settings
*/
class GitHubCopilotSetup extends BaseIdeSetup {
constructor() {
super('github-copilot', 'GitHub Copilot', true); // preferred IDE
this.configDir = '.github';
this.agentsDir = 'agents';
this.vscodeDir = '.vscode';
}
/**
* Collect configuration choices before installation
* @param {Object} options - Configuration options
* @returns {Object} Collected configuration
*/
async collectConfiguration(options = {}) {
const config = {};
console.log('\n' + chalk.blue(' 🔧 VS Code Settings Configuration'));
console.log(chalk.dim(' GitHub Copilot works best with specific settings\n'));
config.vsCodeConfig = await prompts.select({
message: 'How would you like to configure VS Code settings?',
choices: [
{ name: 'Use recommended defaults (fastest)', value: 'defaults' },
{ name: 'Configure each setting manually', value: 'manual' },
{ name: 'Skip settings configuration', value: 'skip' },
],
default: 'defaults',
});
if (config.vsCodeConfig === 'manual') {
config.manualSettings = await prompts.prompt([
{
type: 'input',
name: 'maxRequests',
message: 'Maximum requests per session (1-50)?',
default: '15',
validate: (input) => {
const num = parseInt(input, 10);
if (isNaN(num)) return 'Enter a valid number 1-50';
if (num < 1 || num > 50) return 'Enter a number between 1-50';
return true;
},
},
{
type: 'confirm',
name: 'runTasks',
message: 'Allow running workspace tasks?',
default: true,
},
{
type: 'confirm',
name: 'mcpDiscovery',
message: 'Enable MCP server discovery?',
default: true,
},
{
type: 'confirm',
name: 'autoFix',
message: 'Enable automatic error fixing?',
default: true,
},
{
type: 'confirm',
name: 'autoApprove',
message: 'Auto-approve tools (less secure)?',
default: false,
},
]);
}
return config;
}
/**
* Setup GitHub Copilot configuration
* @param {string} projectDir - Project directory
* @param {string} bmadDir - BMAD installation directory
* @param {Object} options - Setup options
*/
async setup(projectDir, bmadDir, options = {}) {
console.log(chalk.cyan(`Setting up ${this.name}...`));
// Configure VS Code settings using pre-collected config if available
const config = options.preCollectedConfig || {};
await this.configureVsCodeSettings(projectDir, { ...options, ...config });
// Create .github/agents directory
const githubDir = path.join(projectDir, this.configDir);
const agentsDir = path.join(githubDir, this.agentsDir);
await this.ensureDir(agentsDir);
// Clean up any existing BMAD files before reinstalling
await this.cleanup(projectDir);
// Generate agent launchers
const agentGen = new AgentCommandGenerator(this.bmadFolderName);
const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []);
// Create agent files with bmd- prefix
let agentCount = 0;
for (const artifact of agentArtifacts) {
const content = artifact.content;
const agentContent = await this.createAgentContent({ module: artifact.module, name: artifact.name }, content);
// Use bmd- prefix: bmd-custom-{module}-{name}.agent.md
const targetPath = path.join(agentsDir, `bmd-custom-${artifact.module}-${artifact.name}.agent.md`);
await this.writeFile(targetPath, agentContent);
agentCount++;
console.log(chalk.green(` ✓ Created agent: bmd-custom-${artifact.module}-${artifact.name}`));
}
console.log(chalk.green(`${this.name} configured:`));
console.log(chalk.dim(` - ${agentCount} agents created`));
console.log(chalk.dim(` - Agents directory: ${path.relative(projectDir, agentsDir)}`));
console.log(chalk.dim(` - VS Code settings configured`));
console.log(chalk.dim('\n Agents available in VS Code Chat view'));
return {
success: true,
agents: agentCount,
settings: true,
};
}
/**
* Configure VS Code settings for GitHub Copilot
*/
async configureVsCodeSettings(projectDir, options) {
const fs = require('fs-extra');
const vscodeDir = path.join(projectDir, this.vscodeDir);
const settingsPath = path.join(vscodeDir, 'settings.json');
await this.ensureDir(vscodeDir);
// Read existing settings
let existingSettings = {};
if (await fs.pathExists(settingsPath)) {
try {
const content = await fs.readFile(settingsPath, 'utf8');
existingSettings = JSON.parse(content);
console.log(chalk.yellow(' Found existing .vscode/settings.json'));
} catch {
console.warn(chalk.yellow(' Could not parse settings.json, creating new'));
}
}
// Use pre-collected configuration or skip if not available
let configChoice = options.vsCodeConfig;
if (!configChoice) {
// If no pre-collected config, skip configuration
console.log(chalk.yellow(' ⚠ No configuration collected, skipping VS Code settings'));
return;
}
if (configChoice === 'skip') {
console.log(chalk.yellow(' ⚠ Skipping VS Code settings'));
return;
}
let bmadSettings = {};
if (configChoice === 'defaults') {
bmadSettings = {
'chat.agent.enabled': true,
'chat.agent.maxRequests': 15,
'github.copilot.chat.agent.runTasks': true,
'chat.mcp.discovery.enabled': true,
'github.copilot.chat.agent.autoFix': true,
'chat.tools.autoApprove': false,
};
console.log(chalk.green(' ✓ Using recommended defaults'));
} else {
// Manual configuration - use pre-collected settings
const manual = options.manualSettings || {};
const maxRequests = parseInt(manual.maxRequests || '15', 10);
bmadSettings = {
'chat.agent.enabled': true,
'chat.agent.maxRequests': isNaN(maxRequests) ? 15 : maxRequests,
'github.copilot.chat.agent.runTasks': manual.runTasks === undefined ? true : manual.runTasks,
'chat.mcp.discovery.enabled': manual.mcpDiscovery === undefined ? true : manual.mcpDiscovery,
'github.copilot.chat.agent.autoFix': manual.autoFix === undefined ? true : manual.autoFix,
'chat.tools.autoApprove': manual.autoApprove || false,
};
}
// Merge settings (existing take precedence)
const mergedSettings = { ...bmadSettings, ...existingSettings };
// Write settings
await fs.writeFile(settingsPath, JSON.stringify(mergedSettings, null, 2));
console.log(chalk.green(' ✓ VS Code settings configured'));
}
/**
* Create agent content
*/
async createAgentContent(agent, content) {
// Extract metadata from launcher frontmatter if present
const descMatch = content.match(/description:\s*"([^"]+)"/);
const title = descMatch ? descMatch[1] : this.formatTitle(agent.name);
const description = `Activates the ${title} agent persona.`;
// Strip any existing frontmatter from the content
const frontmatterRegex = /^---\s*\n[\s\S]*?\n---\s*\n/;
let cleanContent = content;
if (frontmatterRegex.test(content)) {
cleanContent = content.replace(frontmatterRegex, '').trim();
}
// Available GitHub Copilot tools (November 2025 - Official VS Code Documentation)
// Reference: https://code.visualstudio.com/docs/copilot/reference/copilot-vscode-features#_chat-tools
const tools = [
'changes', // List of source control changes
'edit', // Edit files in your workspace including: createFile, createDirectory, editNotebook, newJupyterNotebook and editFiles
'fetch', // Fetch content from web page
'githubRepo', // Perform code search in GitHub repo
'problems', // Add workspace issues from Problems panel
'runCommands', // Runs commands in the terminal including: getTerminalOutput, terminalSelection, terminalLastCommand and runInTerminal
'runTasks', // Runs tasks and gets their output for your workspace
'runTests', // Run unit tests in workspace
'search', // Search and read files in your workspace, including:fileSearch, textSearch, listDirectory, readFile, codebase and searchResults
'runSubagent', // Runs a task within an isolated subagent context. Enables efficient organization of tasks and context window management.
'testFailure', // Get unit test failure information
'todos', // Tool for managing and tracking todo items for task planning
'usages', // Find references and navigate definitions
];
let agentContent = `---
description: "${description.replaceAll('"', String.raw`\"`)}"
tools: ${JSON.stringify(tools)}
---
# ${title} Agent
${cleanContent}
`;
return agentContent;
}
/**
* Format name as title
*/
formatTitle(name) {
return name
.split('-')
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ');
}
/**
* Cleanup GitHub Copilot configuration - surgically remove only BMAD files
*/
async cleanup(projectDir) {
const fs = require('fs-extra');
// Clean up old chatmodes directory
const chatmodesDir = path.join(projectDir, this.configDir, 'chatmodes');
if (await fs.pathExists(chatmodesDir)) {
const files = await fs.readdir(chatmodesDir);
let removed = 0;
for (const file of files) {
if (file.startsWith('bmad') && file.endsWith('.chatmode.md')) {
await fs.remove(path.join(chatmodesDir, file));
removed++;
}
}
if (removed > 0) {
console.log(chalk.dim(` Cleaned up ${removed} old BMAD chat modes`));
}
}
// Clean up new agents directory
const agentsDir = path.join(projectDir, this.configDir, this.agentsDir);
if (await fs.pathExists(agentsDir)) {
const files = await fs.readdir(agentsDir);
let removed = 0;
for (const file of files) {
if (file.startsWith('bmd-') && file.endsWith('.agent.md')) {
await fs.remove(path.join(agentsDir, file));
removed++;
}
}
if (removed > 0) {
console.log(chalk.dim(` Cleaned up ${removed} existing BMAD agents`));
}
}
}
/**
* Install a custom agent launcher for GitHub Copilot
* @param {string} projectDir - Project directory
* @param {string} agentName - Agent name (e.g., "fred-commit-poet")
* @param {string} agentPath - Path to compiled agent (relative to project root)
* @param {Object} metadata - Agent metadata
* @returns {Object|null} Info about created command
*/
async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) {
const agentsDir = path.join(projectDir, this.configDir, this.agentsDir);
if (!(await this.exists(path.join(projectDir, this.configDir)))) {
return null; // IDE not configured for this project
}
await this.ensureDir(agentsDir);
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.
<agent-activation CRITICAL="TRUE">
1. LOAD the FULL agent file from @${agentPath}
2. READ its entire contents - this contains the complete agent persona, menu, and instructions
3. FOLLOW every step in the <activation> section precisely
4. DISPLAY the welcome/greeting as instructed
5. PRESENT the numbered menu
6. WAIT for user input before proceeding
</agent-activation>
`;
// GitHub Copilot needs specific tools in frontmatter
const copilotTools = [
'changes',
'codebase',
'createDirectory',
'createFile',
'editFiles',
'fetch',
'fileSearch',
'githubRepo',
'listDirectory',
'problems',
'readFile',
'runInTerminal',
'runTask',
'runTests',
'runVscodeCommand',
'search',
'searchResults',
'terminalLastCommand',
'terminalSelection',
'testFailure',
'textSearch',
'usages',
];
const agentContent = `---
description: "Activates the ${metadata.title || agentName} agent persona."
tools: ${JSON.stringify(copilotTools)}
---
# ${metadata.title || agentName} Agent
${launcherContent}
`;
const agentFilePath = path.join(agentsDir, `bmd-custom-${agentName}.agent.md`);
await this.writeFile(agentFilePath, agentContent);
return {
path: agentFilePath,
command: `bmd-custom-${agentName}`,
};
}
}
module.exports = { GitHubCopilotSetup };

View File

@ -1,191 +0,0 @@
const path = require('node:path');
const fs = require('fs-extra');
const { BaseIdeSetup } = require('./_base-ide');
const chalk = require('chalk');
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');
/**
* iFlow CLI setup handler
* Creates commands in .iflow/commands/ directory structure
*/
class IFlowSetup extends BaseIdeSetup {
constructor() {
super('iflow', 'iFlow CLI');
this.configDir = '.iflow';
this.commandsDir = 'commands';
}
/**
* Setup iFlow CLI configuration
* @param {string} projectDir - Project directory
* @param {string} bmadDir - BMAD installation directory
* @param {Object} options - Setup options
*/
async setup(projectDir, bmadDir, options = {}) {
console.log(chalk.cyan(`Setting up ${this.name}...`));
// Create .iflow/commands/bmad directory structure
const iflowDir = path.join(projectDir, this.configDir);
const commandsDir = path.join(iflowDir, this.commandsDir, 'bmad');
const agentsDir = path.join(commandsDir, 'agents');
const tasksDir = path.join(commandsDir, 'tasks');
const workflowsDir = path.join(commandsDir, 'workflows');
await this.ensureDir(agentsDir);
await this.ensureDir(tasksDir);
await this.ensureDir(workflowsDir);
// Generate agent launchers
const agentGen = new AgentCommandGenerator(this.bmadFolderName);
const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []);
// Setup agents as commands
let agentCount = 0;
for (const artifact of agentArtifacts) {
const commandContent = await this.createAgentCommand(artifact);
const targetPath = path.join(agentsDir, `${artifact.module}-${artifact.name}.md`);
await this.writeFile(targetPath, commandContent);
agentCount++;
}
// Get tasks and workflows (ALL workflows now generate commands)
const tasks = await this.getTasks(bmadDir);
// Get ALL workflows using the new workflow command generator
const workflowGenerator = new WorkflowCommandGenerator(this.bmadFolderName);
const { artifacts: workflowArtifacts, counts: workflowCounts } = await workflowGenerator.collectWorkflowArtifacts(bmadDir);
// Setup tasks as commands
let taskCount = 0;
for (const task of tasks) {
const content = await this.readFile(task.path);
const commandContent = this.createTaskCommand(task, content);
const targetPath = path.join(tasksDir, `${task.module}-${task.name}.md`);
await this.writeFile(targetPath, commandContent);
taskCount++;
}
// Setup workflows as commands (already generated)
let workflowCount = 0;
for (const artifact of workflowArtifacts) {
if (artifact.type === 'workflow-command') {
const targetPath = path.join(workflowsDir, `${artifact.module}-${path.basename(artifact.relativePath, '.md')}.md`);
await this.writeFile(targetPath, artifact.content);
workflowCount++;
}
}
console.log(chalk.green(`${this.name} configured:`));
console.log(chalk.dim(` - ${agentCount} agent commands created`));
console.log(chalk.dim(` - ${taskCount} task commands created`));
console.log(chalk.dim(` - ${workflowCount} workflow commands created`));
console.log(chalk.dim(` - Commands directory: ${path.relative(projectDir, commandsDir)}`));
return {
success: true,
agents: agentCount,
tasks: taskCount,
workflows: workflowCount,
};
}
/**
* Create agent command content
*/
async createAgentCommand(artifact) {
// The launcher content is already complete - just return it as-is
return artifact.content;
}
/**
* Create task command content
*/
createTaskCommand(task, content) {
// Extract task name
const nameMatch = content.match(/<name>([^<]+)<\/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}
## 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;
}
/**
* Cleanup iFlow configuration
*/
async cleanup(projectDir) {
const fs = require('fs-extra');
const bmadCommandsDir = path.join(projectDir, this.configDir, this.commandsDir, 'bmad');
if (await fs.pathExists(bmadCommandsDir)) {
await fs.remove(bmadCommandsDir);
console.log(chalk.dim(`Removed BMAD commands from iFlow CLI`));
}
}
/**
* Install a custom agent launcher for iFlow
* @param {string} projectDir - Project directory
* @param {string} agentName - Agent name (e.g., "fred-commit-poet")
* @param {string} agentPath - Path to compiled agent (relative to project root)
* @param {Object} metadata - Agent metadata
* @returns {Object} Installation result
*/
async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) {
const iflowDir = path.join(projectDir, this.configDir);
const bmadCommandsDir = path.join(iflowDir, this.commandsDir, 'bmad');
// Create .iflow/commands/bmad directory if it doesn't exist
await fs.ensureDir(bmadCommandsDir);
// Create custom agent launcher
const launcherContent = `# ${agentName} Custom Agent
** IMPORTANT**: Run @${agentPath} first to load the complete agent!
This is a launcher for the custom BMAD agent "${agentName}".
## Usage
1. First run: \`${agentPath}\` to load the complete agent
2. Then use this command to activate ${agentName}
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);
// Write the launcher file
await fs.writeFile(launcherPath, launcherContent, 'utf8');
return {
ide: 'iflow',
path: path.relative(projectDir, launcherPath),
command: agentName,
type: 'custom-agent-launcher',
};
}
}
module.exports = { IFlowSetup };

View File

@ -5,11 +5,15 @@ const chalk = require('chalk');
/**
* IDE Manager - handles IDE-specific setup
* Dynamically discovers and loads IDE handlers
*
* Loading strategy:
* 1. Custom installer files (codex.js, kilo.js, kiro-cli.js) - for platforms with unique installation logic
* 2. Config-driven handlers (from platform-codes.yaml) - for standard IDE installation patterns
*/
class IdeManager {
constructor() {
this.handlers = new Map();
this.loadHandlers();
this._initialized = false;
this.bmadFolderName = 'bmad'; // Default, can be overridden
}
@ -28,53 +32,76 @@ class IdeManager {
}
/**
* Dynamically load all IDE handlers from directory
* Ensure handlers are loaded (lazy loading)
*/
loadHandlers() {
async ensureInitialized() {
if (!this._initialized) {
await this.loadHandlers();
this._initialized = true;
}
}
/**
* Dynamically load all IDE handlers
* 1. Load custom installer files first (codex.js, kilo.js, kiro-cli.js)
* 2. Load config-driven handlers from platform-codes.yaml
*/
async loadHandlers() {
// Load custom installer files
this.loadCustomInstallerFiles();
// Load config-driven handlers from platform-codes.yaml
await this.loadConfigDrivenHandlers();
}
/**
* Load custom installer files (unique installation logic)
* These files have special installation patterns that don't fit the config-driven model
*/
loadCustomInstallerFiles() {
const ideDir = __dirname;
const customFiles = ['codex.js', 'kilo.js', 'kiro-cli.js'];
try {
// Get all JS files in the IDE directory
const files = fs.readdirSync(ideDir).filter((file) => {
// Skip base class, manager, utility files (starting with _), and helper modules
return (
file.endsWith('.js') &&
!file.startsWith('_') &&
file !== 'manager.js' &&
file !== 'workflow-command-generator.js' &&
file !== 'task-tool-command-generator.js'
);
});
for (const file of customFiles) {
const filePath = path.join(ideDir, file);
if (!fs.existsSync(filePath)) continue;
// Sort alphabetically for consistent ordering
files.sort();
try {
const HandlerModule = require(filePath);
const HandlerClass = HandlerModule.default || Object.values(HandlerModule)[0];
for (const file of files) {
const moduleName = path.basename(file, '.js');
try {
const modulePath = path.join(ideDir, file);
const HandlerModule = require(modulePath);
// Get the first exported class (handles various export styles)
const HandlerClass = HandlerModule.default || HandlerModule[Object.keys(HandlerModule)[0]];
if (HandlerClass) {
const instance = new HandlerClass();
// Use the name property from the instance (set in constructor)
// Only add if the instance has a valid name
if (instance.name && typeof instance.name === 'string') {
this.handlers.set(instance.name, instance);
} else {
console.log(chalk.yellow(` Warning: ${moduleName} handler missing valid 'name' property`));
}
if (HandlerClass) {
const instance = new HandlerClass();
if (instance.name && typeof instance.name === 'string') {
this.handlers.set(instance.name, instance);
}
} catch (error) {
console.log(chalk.yellow(` Warning: Could not load ${moduleName}: ${error.message}`));
}
} catch (error) {
console.log(chalk.yellow(` Warning: Could not load ${file}: ${error.message}`));
}
} catch (error) {
console.error(chalk.red('Failed to load IDE handlers:'), error.message);
}
}
/**
* Load config-driven handlers from platform-codes.yaml
* This creates ConfigDrivenIdeSetup instances for platforms with installer config
*/
async loadConfigDrivenHandlers() {
const { loadPlatformCodes } = require('./platform-codes');
const platformConfig = await loadPlatformCodes();
const { ConfigDrivenIdeSetup } = require('./_config-driven');
for (const [platformCode, platformInfo] of Object.entries(platformConfig.platforms)) {
// Skip if already loaded by custom installer
if (this.handlers.has(platformCode)) continue;
// Skip if no installer config (platform may not need installation)
if (!platformInfo.installer) continue;
const handler = new ConfigDrivenIdeSetup(platformCode, platformInfo);
handler.setBmadFolderName(this.bmadFolderName);
this.handlers.set(platformCode, handler);
}
}

View File

@ -1,257 +0,0 @@
const path = require('node:path');
const fs = require('fs-extra');
const os = require('node:os');
const chalk = require('chalk');
const yaml = require('yaml');
const { BaseIdeSetup } = require('./_base-ide');
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');
const { TaskToolCommandGenerator } = require('./shared/task-tool-command-generator');
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
/**
* OpenCode IDE setup handler
*/
class OpenCodeSetup extends BaseIdeSetup {
constructor() {
super('opencode', 'OpenCode', true); // Mark as preferred/recommended
this.configDir = '.opencode';
this.commandsDir = 'command';
this.agentsDir = 'agent';
}
async setup(projectDir, bmadDir, options = {}) {
console.log(chalk.cyan(`Setting up ${this.name}...`));
const baseDir = path.join(projectDir, this.configDir);
const commandsBaseDir = path.join(baseDir, this.commandsDir);
const agentsBaseDir = path.join(baseDir, this.agentsDir);
await this.ensureDir(commandsBaseDir);
await this.ensureDir(agentsBaseDir);
// Clean up any existing BMAD files before reinstalling
await this.cleanup(projectDir);
// Generate agent launchers
const agentGen = new AgentCommandGenerator(this.bmadFolderName);
const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []);
// Install primary agents with flat naming: bmad-agent-{module}-{name}.md
// OpenCode agents go in the agent folder (not command folder)
let agentCount = 0;
for (const artifact of agentArtifacts) {
const agentContent = artifact.content;
// Flat structure in agent folder: bmad-agent-{module}-{name}.md
const targetPath = path.join(agentsBaseDir, `bmad-agent-${artifact.module}-${artifact.name}.md`);
await this.writeFile(targetPath, agentContent);
agentCount++;
}
// Install workflow commands with flat naming: bmad-{module}-{workflow-name}
const workflowGenerator = new WorkflowCommandGenerator(this.bmadFolderName);
const { artifacts: workflowArtifacts, counts: workflowCounts } = await workflowGenerator.collectWorkflowArtifacts(bmadDir);
let workflowCommandCount = 0;
for (const artifact of workflowArtifacts) {
if (artifact.type === 'workflow-command') {
const commandContent = artifact.content;
// Flat structure: bmad-{module}-{workflow-name}.md
// artifact.relativePath is like: bmm/workflows/plan-project.md
const workflowName = path.basename(artifact.relativePath, '.md');
const targetPath = path.join(commandsBaseDir, `bmad-${artifact.module}-${workflowName}.md`);
await this.writeFile(targetPath, commandContent);
workflowCommandCount++;
}
// Skip workflow launcher READMEs as they're not needed in flat structure
}
// Install task and tool commands with flat naming
const { tasks, tools } = await this.generateFlatTaskToolCommands(bmadDir, commandsBaseDir);
console.log(chalk.green(`${this.name} configured:`));
console.log(chalk.dim(` - ${agentCount} agents installed to .opencode/agent/`));
if (workflowCommandCount > 0) {
console.log(chalk.dim(` - ${workflowCommandCount} workflows installed to .opencode/command/`));
}
if (tasks + tools > 0) {
console.log(chalk.dim(` - ${tasks + tools} tasks/tools installed to .opencode/command/ (${tasks} tasks, ${tools} tools)`));
}
return {
success: true,
agents: agentCount,
workflows: workflowCommandCount,
workflowCounts,
};
}
/**
* Generate flat task and tool commands for OpenCode
* OpenCode doesn't support nested command directories
*/
async generateFlatTaskToolCommands(bmadDir, commandsBaseDir) {
const taskToolGen = new TaskToolCommandGenerator();
const tasks = await taskToolGen.loadTaskManifest(bmadDir);
const tools = await taskToolGen.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) : [];
// Generate command files for tasks with flat naming: bmad-task-{module}-{name}.md
for (const task of standaloneTasks) {
const commandContent = taskToolGen.generateCommandContent(task, 'task');
const targetPath = path.join(commandsBaseDir, `bmad-task-${task.module}-${task.name}.md`);
await this.writeFile(targetPath, commandContent);
}
// Generate command files for tools with flat naming: bmad-tool-{module}-{name}.md
for (const tool of standaloneTools) {
const commandContent = taskToolGen.generateCommandContent(tool, 'tool');
const targetPath = path.join(commandsBaseDir, `bmad-tool-${tool.module}-${tool.name}.md`);
await this.writeFile(targetPath, commandContent);
}
return {
tasks: standaloneTasks.length,
tools: standaloneTools.length,
};
}
async readAndProcess(filePath, metadata) {
const content = await fs.readFile(filePath, 'utf8');
return this.processContent(content, metadata);
}
async createAgentContent(content, metadata) {
const { frontmatter = {}, body } = this.parseFrontmatter(content);
frontmatter.description =
frontmatter.description && String(frontmatter.description).trim().length > 0
? frontmatter.description
: `BMAD ${metadata.module} agent: ${metadata.name}`;
// OpenCode agents use: 'primary' mode for main agents
frontmatter.mode = 'primary';
const frontmatterString = this.stringifyFrontmatter(frontmatter);
// Get the activation header from central template
const activationHeader = await this.getAgentCommandHeader();
return `${frontmatterString}\n\n${activationHeader}\n\n${body}`;
}
parseFrontmatter(content) {
const match = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n?/);
if (!match) {
return { data: {}, body: content };
}
const body = content.slice(match[0].length);
let frontmatter = {};
try {
frontmatter = yaml.parse(match[1]) || {};
} catch {
frontmatter = {};
}
return { frontmatter, body };
}
stringifyFrontmatter(frontmatter) {
const yamlText = yaml
.dump(frontmatter, {
indent: 2,
lineWidth: -1,
noRefs: true,
sortKeys: false,
})
.trimEnd();
return `---\n${yamlText}\n---`;
}
/**
* Cleanup OpenCode configuration - surgically remove only BMAD files
*/
async cleanup(projectDir) {
const agentsDir = path.join(projectDir, this.configDir, this.agentsDir);
const commandsDir = path.join(projectDir, this.configDir, this.commandsDir);
let removed = 0;
// Clean up agent folder
if (await fs.pathExists(agentsDir)) {
const files = await fs.readdir(agentsDir);
for (const file of files) {
if (file.startsWith('bmad') && file.endsWith('.md')) {
await fs.remove(path.join(agentsDir, file));
removed++;
}
}
}
// Clean up command folder
if (await fs.pathExists(commandsDir)) {
const files = await fs.readdir(commandsDir);
for (const file of files) {
if (file.startsWith('bmad') && file.endsWith('.md')) {
await fs.remove(path.join(commandsDir, file));
removed++;
}
}
}
if (removed > 0) {
console.log(chalk.dim(` Cleaned up ${removed} existing BMAD files`));
}
}
/**
* Install a custom agent launcher for OpenCode
* @param {string} projectDir - Project directory
* @param {string} agentName - Agent name (e.g., "fred-commit-poet")
* @param {string} agentPath - Path to compiled agent (relative to project root)
* @param {Object} metadata - Agent metadata
* @returns {Object|null} Info about created command
*/
async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) {
const agentsDir = path.join(projectDir, this.configDir, this.agentsDir);
if (!(await this.exists(path.join(projectDir, this.configDir)))) {
return null; // IDE not configured for this project
}
await this.ensureDir(agentsDir);
const launcherContent = `---
name: '${agentName}'
description: '${metadata.title || agentName} agent'
mode: 'primary'
---
You must fully embody this agent's persona and follow all activation instructions exactly as specified. NEVER break character until given an exit command.
<agent-activation CRITICAL="TRUE">
1. LOAD the FULL agent file from @${agentPath}
2. READ its entire contents - this contains the complete agent persona, menu, and instructions
3. FOLLOW every step in the <activation> section precisely
4. DISPLAY the welcome/greeting as instructed
5. PRESENT the numbered menu
6. WAIT for user input before proceeding
</agent-activation>
`;
// OpenCode uses flat naming: bmad-agent-custom-{name}.md
const launcherPath = path.join(agentsDir, `bmad-agent-custom-${agentName}.md`);
await this.writeFile(launcherPath, launcherContent);
return {
path: launcherPath,
command: `bmad-agent-custom-${agentName}`,
};
}
}
module.exports = { OpenCodeSetup };

View File

@ -0,0 +1,100 @@
const fs = require('fs-extra');
const path = require('node:path');
const yaml = require('yaml');
const PLATFORM_CODES_PATH = path.join(__dirname, 'platform-codes.yaml');
let _cachedPlatformCodes = null;
/**
* Load the platform codes configuration from YAML
* @returns {Object} Platform codes configuration
*/
async function loadPlatformCodes() {
if (_cachedPlatformCodes) {
return _cachedPlatformCodes;
}
if (!(await fs.pathExists(PLATFORM_CODES_PATH))) {
throw new Error(`Platform codes configuration not found at: ${PLATFORM_CODES_PATH}`);
}
const content = await fs.readFile(PLATFORM_CODES_PATH, 'utf8');
_cachedPlatformCodes = yaml.parse(content);
return _cachedPlatformCodes;
}
/**
* Get platform information by code
* @param {string} platformCode - Platform code (e.g., 'claude-code', 'cursor')
* @returns {Object|null} Platform info or null if not found
*/
function getPlatformInfo(platformCode) {
if (!_cachedPlatformCodes) {
throw new Error('Platform codes not loaded. Call loadPlatformCodes() first.');
}
return _cachedPlatformCodes.platforms[platformCode] || null;
}
/**
* Get all preferred platforms
* @returns {Promise<Array>} Array of preferred platform codes
*/
async function getPreferredPlatforms() {
const config = await loadPlatformCodes();
return Object.entries(config.platforms)
.filter(([_, info]) => info.preferred)
.map(([code, _]) => code);
}
/**
* Get all platform codes by category
* @param {string} category - Category to filter by (ide, cli, tool, etc.)
* @returns {Promise<Array>} Array of platform codes in the category
*/
async function getPlatformsByCategory(category) {
const config = await loadPlatformCodes();
return Object.entries(config.platforms)
.filter(([_, info]) => info.category === category)
.map(([code, _]) => code);
}
/**
* Get all platforms with installer config
* @returns {Promise<Array>} Array of platform codes that have installer config
*/
async function getConfigDrivenPlatforms() {
const config = await loadPlatformCodes();
return Object.entries(config.platforms)
.filter(([_, info]) => info.installer)
.map(([code, _]) => code);
}
/**
* Get platforms that use custom installers (no installer config)
* @returns {Promise<Array>} Array of platform codes with custom installers
*/
async function getCustomInstallerPlatforms() {
const config = await loadPlatformCodes();
return Object.entries(config.platforms)
.filter(([_, info]) => !info.installer)
.map(([code, _]) => code);
}
/**
* Clear the cached platform codes (useful for testing)
*/
function clearCache() {
_cachedPlatformCodes = null;
}
module.exports = {
loadPlatformCodes,
getPlatformInfo,
getPreferredPlatforms,
getPlatformsByCategory,
getConfigDrivenPlatforms,
getCustomInstallerPlatforms,
clearCache,
};

View File

@ -0,0 +1,241 @@
# BMAD Platform Codes Configuration
# Central configuration for all platform/IDE codes used in the BMAD system
#
# This file defines:
# 1. Platform metadata (name, preferred status, category, description)
# 2. Installer configuration (target directories, templates, artifact types)
#
# Format:
# code: Platform identifier used internally
# name: Display name shown to users
# preferred: Whether this platform is shown as a recommended option on install
# category: Type of platform (ide, cli, tool, service)
# description: Brief description of the platform
# installer: Installation configuration (optional - omit for custom installers)
platforms:
# ============================================================================
# CLI Tools
# ============================================================================
claude-code:
name: "Claude Code"
preferred: true
category: cli
description: "Anthropic's official CLI for Claude"
installer:
target_dir: .claude/commands
template_type: default
auggie:
name: "Auggie"
preferred: false
category: cli
description: "AI development tool"
installer:
target_dir: .augment/commands
template_type: default
gemini:
name: "Gemini CLI"
preferred: false
category: cli
description: "Google's CLI for Gemini"
installer:
target_dir: .gemini/commands
template_type: gemini
# ============================================================================
# IDEs
# ============================================================================
cursor:
name: "Cursor"
preferred: true
category: ide
description: "AI-first code editor"
installer:
target_dir: .cursor/commands
template_type: default
windsurf:
name: "Windsurf"
preferred: true
category: ide
description: "AI-powered IDE with cascade flows"
installer:
target_dir: .windsurf/workflows
template_type: windsurf
cline:
name: "Cline"
preferred: false
category: ide
description: "AI coding assistant"
installer:
target_dir: .clinerules/workflows
template_type: windsurf
roo:
name: "Roo Cline"
preferred: false
category: ide
description: "Enhanced Cline fork"
installer:
target_dir: .roo/commands
template_type: default
github-copilot:
name: "GitHub Copilot"
preferred: false
category: ide
description: "GitHub's AI pair programmer"
installer:
targets:
- target_dir: .github/agents
template_type: copilot_agents
artifact_types: [agents]
- target_dir: .vscode
template_type: vscode_settings
artifact_types: []
opencode:
name: "OpenCode"
preferred: false
category: ide
description: "OpenCode terminal coding assistant"
installer:
target_dir: .opencode/command
template_type: opencode
crush:
name: "Crush"
preferred: false
category: ide
description: "AI development assistant"
installer:
target_dir: .crush/commands
template_type: default
iflow:
name: "iFlow"
preferred: false
category: ide
description: "AI workflow automation"
installer:
target_dir: .iflow/commands
template_type: default
qwen:
name: "QwenCoder"
preferred: false
category: ide
description: "Qwen AI coding assistant"
installer:
target_dir: .qwen/commands
template_type: default
rovo-dev:
name: "Rovo Dev"
preferred: false
category: ide
description: "Atlassian's Rovo development environment"
installer:
target_dir: .rovodev/workflows
template_type: rovodev
trae:
name: "Trae"
preferred: false
category: ide
description: "AI coding tool"
installer:
target_dir: .trae/rules
template_type: trae
antigravity:
name: "Google Antigravity"
preferred: false
category: ide
description: "Google's AI development environment"
installer:
target_dir: .agent/workflows
template_type: antigravity
# Note: Antigravity uses .agent/workflows/ directory (not .antigravity/)
# ============================================================================
# Custom Installers (no installer config - use custom file)
# These have unique installation logic that doesn't fit the config-driven model
# ============================================================================
codex:
name: "Codex"
preferred: false
category: cli
description: "OpenAI Codex integration"
# No installer config - uses custom codex.js
kilo:
name: "KiloCoder"
preferred: false
category: ide
description: "AI coding platform"
# No installer config - uses custom kilo.js (creates .kilocodemodes file)
kiro-cli:
name: "Kiro CLI"
preferred: false
category: cli
description: "Kiro command-line interface"
# No installer config - uses custom kiro-cli.js (YAML→JSON conversion)
# ============================================================================
# Installer Config Schema
# ============================================================================
#
# installer:
# target_dir: string # Directory where artifacts are installed
# template_type: string # Default template type to use
# header_template: string (optional) # Override for header/frontmatter template
# body_template: string (optional) # Override for body/content template
# targets: array (optional) # For multi-target installations
# - target_dir: string
# template_type: string
# artifact_types: [agents, workflows, tasks, tools]
# artifact_types: array (optional) # Filter which artifacts to install (default: all)
# skip_existing: boolean (optional) # Skip files that already exist (default: false)
# ============================================================================
# Platform Categories
# ============================================================================
categories:
ide:
name: "Integrated Development Environment"
description: "Full-featured code editors with AI assistance"
cli:
name: "Command Line Interface"
description: "Terminal-based tools"
tool:
name: "Development Tool"
description: "Standalone development utilities"
service:
name: "Cloud Service"
description: "Cloud-based development platforms"
extension:
name: "Editor Extension"
description: "Plugins for existing editors"
# ============================================================================
# Naming Conventions and Rules
# ============================================================================
conventions:
code_format: "lowercase-kebab-case"
name_format: "Title Case"
max_code_length: 20
allowed_characters: "a-z0-9-"

View File

@ -1,372 +0,0 @@
const path = require('node:path');
const fs = require('fs-extra');
const { BaseIdeSetup } = require('./_base-ide');
const chalk = require('chalk');
const { getAgentsFromBmad, getTasksFromBmad } = require('./shared/bmad-artifacts');
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
/**
* Qwen Code setup handler
* Creates TOML command files in .qwen/commands/BMad/
*/
class QwenSetup extends BaseIdeSetup {
constructor() {
super('qwen', 'Qwen Code');
this.configDir = '.qwen';
this.commandsDir = 'commands';
this.bmadDir = 'bmad';
}
/**
* Setup Qwen Code configuration
* @param {string} projectDir - Project directory
* @param {string} bmadDir - BMAD installation directory
* @param {Object} options - Setup options
*/
async setup(projectDir, bmadDir, options = {}) {
console.log(chalk.cyan(`Setting up ${this.name}...`));
// Create .qwen/commands/BMad directory structure
const qwenDir = path.join(projectDir, this.configDir);
const commandsDir = path.join(qwenDir, this.commandsDir);
const bmadCommandsDir = path.join(commandsDir, this.bmadDir);
await this.ensureDir(bmadCommandsDir);
// Update existing settings.json if present
await this.updateSettings(qwenDir);
// Clean up old configuration if exists
await this.cleanupOldConfig(qwenDir);
// Generate agent launchers
const agentGen = new AgentCommandGenerator(this.bmadFolderName);
const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []);
// Get tasks, tools, and workflows (standalone only for tools/workflows)
const tasks = await getTasksFromBmad(bmadDir, options.selectedModules || []);
const tools = await this.getTools(bmadDir, true);
const workflows = await this.getWorkflows(bmadDir, true);
// Create directories for each module (including standalone)
const modules = new Set();
for (const item of [...agentArtifacts, ...tasks, ...tools, ...workflows]) modules.add(item.module);
for (const module of modules) {
await this.ensureDir(path.join(bmadCommandsDir, module));
await this.ensureDir(path.join(bmadCommandsDir, module, 'agents'));
await this.ensureDir(path.join(bmadCommandsDir, module, 'tasks'));
await this.ensureDir(path.join(bmadCommandsDir, module, 'tools'));
await this.ensureDir(path.join(bmadCommandsDir, module, 'workflows'));
}
// Create TOML files for each agent launcher
let agentCount = 0;
for (const artifact of agentArtifacts) {
// Convert markdown launcher content to TOML format
const tomlContent = this.processAgentLauncherContent(artifact.content, {
module: artifact.module,
name: artifact.name,
});
const targetPath = path.join(bmadCommandsDir, artifact.module, 'agents', `${artifact.name}.toml`);
await this.writeFile(targetPath, tomlContent);
agentCount++;
console.log(chalk.green(` ✓ Added agent: /bmad_${artifact.module}_agents_${artifact.name}`));
}
// Create TOML files for each task
let taskCount = 0;
for (const task of tasks) {
const content = await this.readAndProcess(task.path, {
module: task.module,
name: task.name,
});
const targetPath = path.join(bmadCommandsDir, task.module, 'tasks', `${task.name}.toml`);
await this.writeFile(targetPath, content);
taskCount++;
console.log(chalk.green(` ✓ Added task: /bmad_${task.module}_tasks_${task.name}`));
}
// Create TOML files for each tool
let toolCount = 0;
for (const tool of tools) {
const content = await this.readAndProcess(tool.path, {
module: tool.module,
name: tool.name,
});
const targetPath = path.join(bmadCommandsDir, tool.module, 'tools', `${tool.name}.toml`);
await this.writeFile(targetPath, content);
toolCount++;
console.log(chalk.green(` ✓ Added tool: /bmad_${tool.module}_tools_${tool.name}`));
}
// Create TOML files for each workflow
let workflowCount = 0;
for (const workflow of workflows) {
const content = await this.readAndProcess(workflow.path, {
module: workflow.module,
name: workflow.name,
});
const targetPath = path.join(bmadCommandsDir, workflow.module, 'workflows', `${workflow.name}.toml`);
await this.writeFile(targetPath, content);
workflowCount++;
console.log(chalk.green(` ✓ Added workflow: /bmad_${workflow.module}_workflows_${workflow.name}`));
}
console.log(chalk.green(`${this.name} configured:`));
console.log(chalk.dim(` - ${agentCount} agents configured`));
console.log(chalk.dim(` - ${taskCount} tasks configured`));
console.log(chalk.dim(` - ${toolCount} tools configured`));
console.log(chalk.dim(` - ${workflowCount} workflows configured`));
console.log(chalk.dim(` - Commands directory: ${path.relative(projectDir, bmadCommandsDir)}`));
return {
success: true,
agents: agentCount,
tasks: taskCount,
tools: toolCount,
workflows: workflowCount,
};
}
/**
* Update settings.json to remove old agent references
*/
async updateSettings(qwenDir) {
const fs = require('fs-extra');
const settingsPath = path.join(qwenDir, 'settings.json');
if (await fs.pathExists(settingsPath)) {
try {
const settingsContent = await fs.readFile(settingsPath, 'utf8');
const settings = JSON.parse(settingsContent);
let updated = false;
// Remove agent file references from contextFileName
if (settings.contextFileName && Array.isArray(settings.contextFileName)) {
const originalLength = settings.contextFileName.length;
settings.contextFileName = settings.contextFileName.filter(
(fileName) => !fileName.startsWith('agents/') && !fileName.startsWith('bmad-method/'),
);
if (settings.contextFileName.length !== originalLength) {
updated = true;
}
}
if (updated) {
await fs.writeFile(settingsPath, JSON.stringify(settings, null, 2));
console.log(chalk.green(' ✓ Updated .qwen/settings.json'));
}
} catch (error) {
console.warn(chalk.yellow(' ⚠ Could not update settings.json:'), error.message);
}
}
}
/**
* Clean up old configuration directories
*/
async cleanupOldConfig(qwenDir) {
const fs = require('fs-extra');
const agentsDir = path.join(qwenDir, 'agents');
const bmadMethodDir = path.join(qwenDir, 'bmad-method');
const bmadDir = path.join(qwenDir, 'bmadDir');
if (await fs.pathExists(agentsDir)) {
await fs.remove(agentsDir);
console.log(chalk.green(' ✓ Removed old agents directory'));
}
if (await fs.pathExists(bmadMethodDir)) {
await fs.remove(bmadMethodDir);
console.log(chalk.green(' ✓ Removed old bmad-method directory'));
}
if (await fs.pathExists(bmadDir)) {
await fs.remove(bmadDir);
console.log(chalk.green(' ✓ Removed old BMad directory'));
}
}
/**
* Read and process file content
*/
async readAndProcess(filePath, metadata) {
const fs = require('fs-extra');
const content = await fs.readFile(filePath, 'utf8');
return this.processContent(content, metadata);
}
/**
* Process agent launcher content and convert to TOML format
* @param {string} launcherContent - Launcher markdown content
* @param {Object} metadata - File metadata
* @returns {string} TOML formatted content
*/
processAgentLauncherContent(launcherContent, metadata = {}) {
// Strip frontmatter from launcher content
const frontmatterRegex = /^---\s*\n[\s\S]*?\n---\s*\n/;
const contentWithoutFrontmatter = launcherContent.replace(frontmatterRegex, '');
// Extract title for TOML description
const titleMatch = launcherContent.match(/description:\s*"([^"]+)"/);
const title = titleMatch ? titleMatch[1] : metadata.name;
// Create TOML with launcher content (without frontmatter)
return `description = "BMAD ${metadata.module.toUpperCase()} Agent: ${title}"
prompt = """
${contentWithoutFrontmatter.trim()}
"""
`;
}
/**
* Override processContent to add TOML metadata header for Qwen
* @param {string} content - File content
* @param {Object} metadata - File metadata
* @returns {string} Processed content with Qwen template
*/
processContent(content, metadata = {}) {
// First apply base processing (includes activation injection for agents)
let prompt = super.processContent(content, metadata);
// Determine the type and description based on content
const isAgent = content.includes('<agent');
const isTask = content.includes('<task');
const isTool = content.includes('<tool');
const isWorkflow = content.includes('workflow:') || content.includes('name:');
let description = '';
if (isAgent) {
// Extract agent title if available
const titleMatch = content.match(/title="([^"]+)"/);
const title = titleMatch ? titleMatch[1] : metadata.name;
description = `BMAD ${metadata.module.toUpperCase()} Agent: ${title}`;
} else if (isTask) {
// Extract task name if available
const nameMatch = content.match(/name="([^"]+)"/);
const taskName = nameMatch ? nameMatch[1] : metadata.name;
description = `BMAD ${metadata.module.toUpperCase()} Task: ${taskName}`;
} else if (isTool) {
// Extract tool name if available
const nameMatch = content.match(/name="([^"]+)"/);
const toolName = nameMatch ? nameMatch[1] : metadata.name;
description = `BMAD ${metadata.module.toUpperCase()} Tool: ${toolName}`;
} else if (isWorkflow) {
// Workflow
description = `BMAD ${metadata.module.toUpperCase()} Workflow: ${metadata.name}`;
} else {
description = `BMAD ${metadata.module.toUpperCase()}: ${metadata.name}`;
}
return `description = "${description}"
prompt = """
${prompt}
"""
`;
}
/**
* Format name as title
*/
formatTitle(name) {
return name
.split('-')
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ');
}
/**
* Cleanup Qwen configuration
*/
async cleanup(projectDir) {
const fs = require('fs-extra');
const bmadCommandsDir = path.join(projectDir, this.configDir, this.commandsDir, this.bmadDir);
const oldBmadMethodDir = path.join(projectDir, this.configDir, 'bmad-method');
const oldBMadDir = path.join(projectDir, this.configDir, 'BMad');
if (await fs.pathExists(bmadCommandsDir)) {
await fs.remove(bmadCommandsDir);
console.log(chalk.dim(`Removed BMAD configuration from Qwen Code`));
}
if (await fs.pathExists(oldBmadMethodDir)) {
await fs.remove(oldBmadMethodDir);
console.log(chalk.dim(`Removed old BMAD configuration from Qwen Code`));
}
if (await fs.pathExists(oldBMadDir)) {
await fs.remove(oldBMadDir);
console.log(chalk.dim(`Removed old BMAD configuration from Qwen Code`));
}
}
/**
* Install a custom agent launcher for Qwen
* @param {string} projectDir - Project directory
* @param {string} agentName - Agent name (e.g., "fred-commit-poet")
* @param {string} agentPath - Path to compiled agent (relative to project root)
* @param {Object} metadata - Agent metadata
* @returns {Object} Installation result
*/
async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) {
const qwenDir = path.join(projectDir, this.configDir);
const commandsDir = path.join(qwenDir, this.commandsDir);
const bmadCommandsDir = path.join(commandsDir, this.bmadDir);
// Create .qwen/commands/BMad directory if it doesn't exist
await fs.ensureDir(bmadCommandsDir);
// Create custom agent launcher in TOML format (same pattern as regular agents)
const launcherContent = `# ${agentName} Custom Agent
** IMPORTANT**: Run @${agentPath} first to load the complete agent!
This is a launcher for the custom BMAD agent "${agentName}".
## Usage
1. First run: \`${agentPath}\` to load the complete agent
2. Then use this command to activate ${agentName}
The agent will follow the persona and instructions from the main agent file.
---
*Generated by BMAD Method*`;
// Use Qwen's TOML conversion method
const tomlContent = this.processAgentLauncherContent(launcherContent, {
name: agentName,
module: 'custom',
});
const fileName = `custom-${agentName.toLowerCase()}.toml`;
const launcherPath = path.join(bmadCommandsDir, fileName);
// Write the launcher file
await fs.writeFile(launcherPath, tomlContent, 'utf8');
return {
ide: 'qwen',
path: path.relative(projectDir, launcherPath),
command: agentName,
type: 'custom-agent-launcher',
};
}
}
module.exports = { QwenSetup };

View File

@ -1,273 +0,0 @@
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
* Creates custom commands in .roo/commands directory
*/
class RooSetup extends BaseIdeSetup {
constructor() {
super('roo', 'Roo Code');
this.configDir = '.roo';
this.commandsDir = 'commands';
}
/**
* Setup Roo IDE configuration
* @param {string} projectDir - Project directory
* @param {string} bmadDir - BMAD installation directory
* @param {Object} options - Setup options
*/
async setup(projectDir, bmadDir, options = {}) {
console.log(chalk.cyan(`Setting up ${this.name}...`));
// Create .roo/commands directory
const rooCommandsDir = path.join(projectDir, this.configDir, this.commandsDir);
await this.ensureDir(rooCommandsDir);
// Generate agent launchers
const agentGen = new AgentCommandGenerator(this.bmadFolderName);
const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []);
let addedCount = 0;
let skippedCount = 0;
for (const artifact of agentArtifacts) {
// 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
if (await this.pathExists(commandPath)) {
console.log(chalk.dim(` Skipping ${commandName} - already exists`));
skippedCount++;
continue;
}
// artifact.sourcePath contains the full path to the agent file
if (!artifact.sourcePath) {
console.error(`Error: Missing sourcePath for artifact ${artifact.name} from module ${artifact.module}`);
console.error(`Artifact object:`, artifact);
throw new Error(`Missing sourcePath for agent: ${artifact.name}`);
}
const content = await this.readFile(artifact.sourcePath);
// Create command file that references the actual _bmad agent
await this.createCommandFile(
{ module: artifact.module, name: artifact.name, path: artifact.sourcePath },
content,
commandPath,
projectDir,
);
addedCount++;
console.log(chalk.green(` ✓ Added command: ${commandName}`));
}
console.log(chalk.green(`${this.name} configured:`));
console.log(chalk.dim(` - ${addedCount} commands added`));
if (skippedCount > 0) {
console.log(chalk.dim(` - ${skippedCount} commands skipped (already exist)`));
}
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 {
success: true,
commands: addedCount,
skipped: skippedCount,
};
}
/**
* Create a unified command file for agents
* @param {string} commandPath - Path where to write the command file
* @param {Object} options - Command options
* @param {string} options.name - Display name for the command
* @param {string} options.description - Description for the command
* @param {string} options.agentPath - Path to the agent file (relative to project root)
* @param {string} [options.icon] - Icon emoji (defaults to 🤖)
* @param {string} [options.extraContent] - Additional content to include before activation
*/
async createAgentCommandFile(commandPath, options) {
const { name, description, agentPath, icon = '🤖', extraContent = '' } = options;
// Build command content with YAML frontmatter
let commandContent = `---\n`;
commandContent += `name: '${icon} ${name}'\n`;
commandContent += `description: '${description}'\n`;
commandContent += `---\n\n`;
commandContent += `You must fully embody this agent's persona and follow all activation instructions exactly as specified. NEVER break character until given an exit command.\n\n`;
// Add any extra content (e.g., warnings for custom agents)
if (extraContent) {
commandContent += `${extraContent}\n\n`;
}
commandContent += `<agent-activation CRITICAL="TRUE">\n`;
commandContent += `1. LOAD the FULL agent file from @${agentPath}\n`;
commandContent += `2. READ its entire contents - this contains the complete agent persona, menu, and instructions\n`;
commandContent += `3. Execute ALL activation steps exactly as written in the agent file\n`;
commandContent += `4. Follow the agent's persona and menu system precisely\n`;
commandContent += `5. Stay in character throughout the session\n`;
commandContent += `</agent-activation>\n`;
// Write command file
await this.writeFile(commandPath, commandContent);
}
/**
* Create a command file for an agent
*/
async createCommandFile(agent, content, commandPath, projectDir) {
// Extract metadata from agent content
const titleMatch = content.match(/title="([^"]+)"/);
const title = titleMatch ? titleMatch[1] : this.formatTitle(agent.name);
const iconMatch = content.match(/icon="([^"]+)"/);
const icon = iconMatch ? iconMatch[1] : '🤖';
const whenToUseMatch = content.match(/whenToUse="([^"]+)"/);
const whenToUse = whenToUseMatch ? whenToUseMatch[1] : `Use for ${title} tasks`;
// Get relative path
const relativePath = path.relative(projectDir, agent.path).replaceAll('\\', '/');
// Use unified method
await this.createAgentCommandFile(commandPath, {
name: title,
description: whenToUse,
agentPath: relativePath,
icon: icon,
});
}
/**
* Format name as title
*/
formatTitle(name) {
return name
.split('-')
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ');
}
/**
* Cleanup Roo configuration
*/
async cleanup(projectDir) {
const fs = require('fs-extra');
const rooCommandsDir = path.join(projectDir, this.configDir, this.commandsDir);
if (await fs.pathExists(rooCommandsDir)) {
const files = await fs.readdir(rooCommandsDir);
let removedCount = 0;
for (const file of files) {
if (file.startsWith('bmad') && file.endsWith('.md')) {
await fs.remove(path.join(rooCommandsDir, file));
removedCount++;
}
}
if (removedCount > 0) {
console.log(chalk.dim(`Removed ${removedCount} BMAD commands from .roo/commands/`));
}
}
// Also clean up old .roomodes file if it exists
const roomodesPath = path.join(projectDir, '.roomodes');
if (await fs.pathExists(roomodesPath)) {
const content = await fs.readFile(roomodesPath, 'utf8');
// Remove BMAD modes only
const lines = content.split('\n');
const filteredLines = [];
let skipMode = false;
let removedCount = 0;
for (const line of lines) {
if (/^\s*- slug: bmad/.test(line)) {
skipMode = true;
removedCount++;
} else if (skipMode && /^\s*- slug: /.test(line)) {
skipMode = false;
}
if (!skipMode) {
filteredLines.push(line);
}
}
// Write back filtered content
await fs.writeFile(roomodesPath, filteredLines.join('\n'));
if (removedCount > 0) {
console.log(chalk.dim(`Removed ${removedCount} BMAD modes from legacy .roomodes file`));
}
}
}
/**
* Install a custom agent launcher for Roo
* @param {string} projectDir - Project directory
* @param {string} agentName - Agent name (e.g., "fred-commit-poet")
* @param {string} agentPath - Path to compiled agent (relative to project root)
* @param {Object} metadata - Agent metadata (unused, kept for compatibility)
* @returns {Object} Installation result
*/
async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) {
const rooCommandsDir = path.join(projectDir, this.configDir, this.commandsDir);
await this.ensureDir(rooCommandsDir);
// Use underscore format: bmad_custom_fred-commit-poet.md
const commandName = customAgentDashName(agentName).replace('.md', '');
const commandPath = path.join(rooCommandsDir, `${commandName}.md`);
// Check if command already exists
if (await this.pathExists(commandPath)) {
return {
ide: 'roo',
path: path.join(this.configDir, this.commandsDir, `${commandName}.md`),
command: commandName,
type: 'custom-agent-launcher',
alreadyExists: true,
};
}
// Read the custom agent file to extract metadata (same as regular agents)
const fullAgentPath = path.join(projectDir, agentPath);
const content = await this.readFile(fullAgentPath);
// Extract metadata from agent content
const titleMatch = content.match(/title="([^"]+)"/);
const title = titleMatch ? titleMatch[1] : this.formatTitle(agentName);
const iconMatch = content.match(/icon="([^"]+)"/);
const icon = iconMatch ? iconMatch[1] : '🤖';
const whenToUseMatch = content.match(/whenToUse="([^"]+)"/);
const whenToUse = whenToUseMatch ? whenToUseMatch[1] : `Use for ${title} tasks`;
// Use unified method without extra content (clean)
await this.createAgentCommandFile(commandPath, {
name: title,
description: whenToUse,
agentPath: agentPath,
icon: icon,
});
return {
ide: 'roo',
path: path.join(this.configDir, this.commandsDir, `${commandName}.md`),
command: commandName,
type: 'custom-agent-launcher',
};
}
}
module.exports = { RooSetup };

View File

@ -1,290 +0,0 @@
const path = require('node:path');
const fs = require('fs-extra');
const chalk = require('chalk');
const { BaseIdeSetup } = require('./_base-ide');
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');
const { TaskToolCommandGenerator } = require('./shared/task-tool-command-generator');
/**
* Rovo Dev IDE setup handler
*
* Installs BMAD agents as Rovo Dev subagents in .rovodev/subagents/
* Installs workflows and tasks/tools as reference guides in .rovodev/
* Rovo Dev automatically discovers agents and integrates with BMAD like other IDEs
*/
class RovoDevSetup extends BaseIdeSetup {
constructor() {
super('rovo-dev', 'Atlassian Rovo Dev', false);
this.configDir = '.rovodev';
this.subagentsDir = 'subagents';
this.workflowsDir = 'workflows';
this.referencesDir = 'references';
}
/**
* Cleanup old BMAD installation before reinstalling
* @param {string} projectDir - Project directory
*/
async cleanup(projectDir) {
const rovoDevDir = path.join(projectDir, this.configDir);
if (!(await fs.pathExists(rovoDevDir))) {
return;
}
// Clean BMAD agents from subagents directory
const subagentsDir = path.join(rovoDevDir, this.subagentsDir);
if (await fs.pathExists(subagentsDir)) {
const entries = await fs.readdir(subagentsDir);
const bmadFiles = entries.filter((file) => file.startsWith('bmad') && file.endsWith('.md'));
for (const file of bmadFiles) {
await fs.remove(path.join(subagentsDir, file));
}
}
// Clean BMAD workflows from workflows directory
const workflowsDir = path.join(rovoDevDir, this.workflowsDir);
if (await fs.pathExists(workflowsDir)) {
const entries = await fs.readdir(workflowsDir);
const bmadFiles = entries.filter((file) => file.startsWith('bmad') && file.endsWith('.md'));
for (const file of bmadFiles) {
await fs.remove(path.join(workflowsDir, file));
}
}
// Clean BMAD tasks/tools from references directory
const referencesDir = path.join(rovoDevDir, this.referencesDir);
if (await fs.pathExists(referencesDir)) {
const entries = await fs.readdir(referencesDir);
const bmadFiles = entries.filter((file) => file.startsWith('bmad') && file.endsWith('.md'));
for (const file of bmadFiles) {
await fs.remove(path.join(referencesDir, file));
}
}
}
/**
* Setup Rovo Dev configuration
* @param {string} projectDir - Project directory
* @param {string} bmadDir - BMAD installation directory
* @param {Object} options - Setup options
*/
async setup(projectDir, bmadDir, options = {}) {
console.log(chalk.cyan(`Setting up ${this.name}...`));
// Clean up old BMAD installation first
await this.cleanup(projectDir);
// Create .rovodev directory structure
const rovoDevDir = path.join(projectDir, this.configDir);
const subagentsDir = path.join(rovoDevDir, this.subagentsDir);
const workflowsDir = path.join(rovoDevDir, this.workflowsDir);
const referencesDir = path.join(rovoDevDir, this.referencesDir);
await this.ensureDir(subagentsDir);
await this.ensureDir(workflowsDir);
await this.ensureDir(referencesDir);
// Generate and install agents
const agentGen = new AgentCommandGenerator(this.bmadFolderName);
const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []);
let agentCount = 0;
for (const artifact of agentArtifacts) {
const subagentFilename = `bmad-${artifact.module}-${artifact.name}.md`;
const targetPath = path.join(subagentsDir, subagentFilename);
const subagentContent = this.convertToRovoDevSubagent(artifact.content, artifact.name, artifact.module);
await this.writeFile(targetPath, subagentContent);
agentCount++;
}
// Generate and install workflows
const workflowGen = new WorkflowCommandGenerator(this.bmadFolderName);
const { artifacts: workflowArtifacts, counts: workflowCounts } = await workflowGen.collectWorkflowArtifacts(bmadDir);
let workflowCount = 0;
for (const artifact of workflowArtifacts) {
if (artifact.type === 'workflow-command') {
const workflowFilename = path.basename(artifact.relativePath);
const targetPath = path.join(workflowsDir, workflowFilename);
await this.writeFile(targetPath, artifact.content);
workflowCount++;
}
}
// Generate and install tasks and tools
const taskToolGen = new TaskToolCommandGenerator();
const { tasks: taskCount, tools: toolCount } = await this.generateTaskToolReferences(bmadDir, referencesDir, taskToolGen);
// Summary output
console.log(chalk.green(`${this.name} configured:`));
console.log(chalk.dim(` - ${agentCount} agents installed to .rovodev/subagents/`));
if (workflowCount > 0) {
console.log(chalk.dim(` - ${workflowCount} workflows installed to .rovodev/workflows/`));
}
if (taskCount + toolCount > 0) {
console.log(
chalk.dim(` - ${taskCount + toolCount} tasks/tools installed to .rovodev/references/ (${taskCount} tasks, ${toolCount} tools)`),
);
}
console.log(chalk.yellow(`\n Note: Agents are automatically discovered by Rovo Dev`));
console.log(chalk.dim(` - Access agents by typing @ in Rovo Dev to see available options`));
console.log(chalk.dim(` - Workflows and references are available in .rovodev/ directory`));
return {
success: true,
agents: agentCount,
workflows: workflowCount,
tasks: taskCount,
tools: toolCount,
};
}
/**
* Generate task and tool reference guides
* @param {string} bmadDir - BMAD directory
* @param {string} referencesDir - References directory
* @param {TaskToolCommandGenerator} taskToolGen - Generator instance
*/
async generateTaskToolReferences(bmadDir, referencesDir, taskToolGen) {
const tasks = await taskToolGen.loadTaskManifest(bmadDir);
const tools = await taskToolGen.loadToolManifest(bmadDir);
const standaloneTasks = tasks ? tasks.filter((t) => t.standalone === 'true' || t.standalone === true) : [];
const standaloneTools = tools ? tools.filter((t) => t.standalone === 'true' || t.standalone === true) : [];
let taskCount = 0;
for (const task of standaloneTasks) {
const commandContent = taskToolGen.generateCommandContent(task, 'task');
const targetPath = path.join(referencesDir, `bmad-task-${task.module}-${task.name}.md`);
await this.writeFile(targetPath, commandContent);
taskCount++;
}
let toolCount = 0;
for (const tool of standaloneTools) {
const commandContent = taskToolGen.generateCommandContent(tool, 'tool');
const targetPath = path.join(referencesDir, `bmad-tool-${tool.module}-${tool.name}.md`);
await this.writeFile(targetPath, commandContent);
toolCount++;
}
return { tasks: taskCount, tools: toolCount };
}
/**
* Convert BMAD agent launcher to Rovo Dev subagent format
*
* Rovo Dev subagents use Markdown files with YAML frontmatter containing:
* - name: Unique identifier for the subagent
* - description: One-line description of the subagent's purpose
* - tools: Array of tools the subagent can use (optional)
* - model: Specific model for this subagent (optional)
* - load_memory: Whether to load memory files (optional, defaults to true)
*
* @param {string} launcherContent - Original agent launcher content
* @param {string} agentName - Name of the agent
* @param {string} moduleName - Name of the module
* @returns {string} Rovo Dev subagent-formatted content
*/
convertToRovoDevSubagent(launcherContent, agentName, moduleName) {
// Extract metadata from the launcher XML
const titleMatch = launcherContent.match(/title="([^"]+)"/);
const title = titleMatch ? titleMatch[1] : this.formatTitle(agentName);
const descriptionMatch = launcherContent.match(/description="([^"]+)"/);
const description = descriptionMatch ? descriptionMatch[1] : `BMAD agent: ${title}`;
const roleDefinitionMatch = launcherContent.match(/roleDefinition="([^"]+)"/);
const roleDefinition = roleDefinitionMatch ? roleDefinitionMatch[1] : `You are a specialized agent for ${title.toLowerCase()} tasks.`;
// Extract the main system prompt from the launcher (content after closing tags)
let systemPrompt = roleDefinition;
// Try to extract additional instructions from the launcher content
const instructionsMatch = launcherContent.match(/<instructions>([\s\S]*?)<\/instructions>/);
if (instructionsMatch) {
systemPrompt += '\n\n' + instructionsMatch[1].trim();
}
// Build YAML frontmatter for Rovo Dev subagent
const frontmatter = {
name: `bmad-${moduleName}-${agentName}`,
description: description,
// Note: tools and model can be added by users in their .rovodev/subagents/*.md files
// We don't enforce specific tools since BMAD agents are flexible
};
// Create YAML frontmatter string with proper quoting for special characters
let yamlContent = '---\n';
yamlContent += `name: ${frontmatter.name}\n`;
// Quote description to handle colons and other special characters in YAML
yamlContent += `description: "${frontmatter.description.replaceAll('"', String.raw`\"`)}"\n`;
yamlContent += '---\n';
// Combine frontmatter with system prompt
const subagentContent = yamlContent + systemPrompt;
return subagentContent;
}
/**
* Detect whether Rovo Dev is already configured in the project
* @param {string} projectDir - Project directory
* @returns {boolean}
*/
async detect(projectDir) {
const rovoDevDir = path.join(projectDir, this.configDir);
if (!(await fs.pathExists(rovoDevDir))) {
return false;
}
// Check for BMAD agents in subagents directory
const subagentsDir = path.join(rovoDevDir, this.subagentsDir);
if (await fs.pathExists(subagentsDir)) {
try {
const entries = await fs.readdir(subagentsDir);
if (entries.some((entry) => entry.startsWith('bmad') && entry.endsWith('.md'))) {
return true;
}
} catch {
// Continue checking other directories
}
}
// Check for BMAD workflows in workflows directory
const workflowsDir = path.join(rovoDevDir, this.workflowsDir);
if (await fs.pathExists(workflowsDir)) {
try {
const entries = await fs.readdir(workflowsDir);
if (entries.some((entry) => entry.startsWith('bmad') && entry.endsWith('.md'))) {
return true;
}
} catch {
// Continue checking other directories
}
}
// Check for BMAD tasks/tools in references directory
const referencesDir = path.join(rovoDevDir, this.referencesDir);
if (await fs.pathExists(referencesDir)) {
try {
const entries = await fs.readdir(referencesDir);
if (entries.some((entry) => entry.startsWith('bmad') && entry.endsWith('.md'))) {
return true;
}
} catch {
// Continue
}
}
return false;
}
}
module.exports = { RovoDevSetup };

View File

@ -31,11 +31,23 @@ class AgentCommandGenerator {
const launcherContent = await this.generateLauncherContent(agent);
// Use relativePath if available (for nested agents), otherwise just name with .md
const agentPathInModule = agent.relativePath || `${agent.name}.md`;
// Calculate the relative agent path (e.g., bmm/agents/pm.md)
let agentRelPath = agent.path;
// Remove _bmad/ prefix if present to get relative path from project root
// Handle both absolute paths (/path/to/_bmad/...) and relative paths (_bmad/...)
if (agentRelPath.includes('_bmad/')) {
const parts = agentRelPath.split(/_bmad\//);
if (parts.length > 1) {
agentRelPath = parts.slice(1).join('/');
}
}
artifacts.push({
type: 'agent-launcher',
module: agent.module,
name: agent.name,
relativePath: path.join(agent.module, 'agents', agentPathInModule),
description: agent.description || `${agent.name} agent`,
module: agent.module,
relativePath: path.join(agent.module, 'agents', agentPathInModule), // For command filename
agentPath: agentRelPath, // Relative path to actual agent file
content: launcherContent,
sourcePath: agent.path,
});
@ -119,8 +131,10 @@ class AgentCommandGenerator {
}
/**
* Write agent launcher artifacts using underscore format (Windows-compatible)
* Creates flat files like: bmad_bmm_pm.md
* Write agent launcher artifacts using dash format (NEW STANDARD)
* Creates flat files like: bmad-bmm-pm.agent.md
*
* The .agent.md suffix distinguishes agents from workflows/tasks/tools.
*
* @param {string} baseCommandsDir - Base commands directory for the IDE
* @param {Array} artifacts - Agent launcher artifacts
@ -131,7 +145,7 @@ class AgentCommandGenerator {
for (const artifact of artifacts) {
if (artifact.type === 'agent-launcher') {
// Convert relativePath to underscore format: bmm/agents/pm.md → bmad_bmm_pm.md
// Convert relativePath to dash format: bmm/agents/pm.md → bmad-bmm-pm.agent.md
const flatName = toDashPath(artifact.relativePath);
const launcherPath = path.join(baseCommandsDir, flatName);
await fs.ensureDir(path.dirname(launcherPath));

View File

@ -86,6 +86,11 @@ async function getAgentsFromDir(dirPath, moduleName, relativePath = '') {
const entries = await fs.readdir(dirPath, { withFileTypes: true });
for (const entry of entries) {
// Skip if entry.name is undefined or not a string
if (!entry.name || typeof entry.name !== 'string') {
continue;
}
const fullPath = path.join(dirPath, entry.name);
const newRelativePath = relativePath ? `${relativePath}/${entry.name}` : entry.name;

View File

@ -2,7 +2,16 @@
* Path transformation utilities for IDE installer standardization
*
* Provides utilities to convert hierarchical paths to flat naming conventions.
* - Underscore format (bmad_module_name.md) - Windows-compatible universal format
*
* DASH-BASED NAMING (new standard):
* - Agents: bmad-module-name.agent.md (with .agent.md suffix)
* - Workflows/Tasks/Tools: bmad-module-name.md
*
* Example outputs:
* - cis/agents/storymaster.md bmad-cis-storymaster.agent.md
* - bmm/workflows/plan-project.md bmad-bmm-plan-project.md
* - bmm/tasks/create-story.md bmad-bmm-create-story.md
* - core/agents/brainstorming.md bmad-brainstorming.agent.md
*/
// Type segments - agents are included in naming, others are filtered out
@ -10,111 +19,120 @@ const TYPE_SEGMENTS = ['workflows', 'tasks', 'tools'];
const AGENT_SEGMENT = 'agents';
/**
* Convert hierarchical path to flat underscore-separated name
* Converts: 'bmm', 'agents', 'pm' 'bmad_bmm_agent_pm.md'
* Converts: 'bmm', 'workflows', 'correct-course' 'bmad_bmm_correct-course.md'
* Converts: 'core', 'agents', 'brainstorming' 'bmad_agent_brainstorming.md' (core items skip module prefix)
* Convert hierarchical path to flat dash-separated name (NEW STANDARD)
* Converts: 'bmm', 'agents', 'pm' 'bmad-bmm-pm.agent.md'
* Converts: 'bmm', 'workflows', 'correct-course' 'bmad-bmm-correct-course.md'
* Converts: 'core', 'agents', 'brainstorming' 'bmad-brainstorming.agent.md' (core items skip module prefix)
*
* @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', 'brainstorming')
* @returns {string} Flat filename like 'bmad_bmm_agent_pm.md' or 'bmad_bmm_correct-course.md'
* @returns {string} Flat filename like 'bmad-bmm-pm.agent.md' or 'bmad-bmm-correct-course.md'
*/
function toUnderscoreName(module, type, name) {
function toDashName(module, type, name) {
const isAgent = type === AGENT_SEGMENT;
// For core module, skip the module prefix: use 'bmad_name.md' instead of 'bmad_core_name.md'
// For core module, skip the module prefix: use 'bmad-name.md' instead of 'bmad-core-name.md'
if (module === 'core') {
return isAgent ? `bmad_agent_${name}.md` : `bmad_${name}.md`;
return isAgent ? `bmad-${name}.agent.md` : `bmad-${name}.md`;
}
return isAgent ? `bmad_${module}_agent_${name}.md` : `bmad_${module}_${name}.md`;
// Module artifacts: bmad-module-name.md or bmad-module-name.agent.md
// eslint-disable-next-line unicorn/prefer-string-replace-all -- regex replace is intentional here
const dashName = name.replace(/\//g, '-'); // Flatten nested paths
return isAgent ? `bmad-${module}-${dashName}.agent.md` : `bmad-${module}-${dashName}.md`;
}
/**
* Convert relative path to flat underscore-separated name
* Converts: 'bmm/agents/pm.md' 'bmad_bmm_agent_pm.md'
* Converts: 'bmm/workflows/correct-course.md' 'bmad_bmm_correct-course.md'
* Converts: 'core/agents/brainstorming.md' 'bmad_agent_brainstorming.md' (core items skip module prefix)
* Convert relative path to flat dash-separated name
* Converts: 'bmm/agents/pm.md' 'bmad-bmm-pm.agent.md'
* Converts: 'bmm/workflows/correct-course.md' 'bmad-bmm-correct-course.md'
* Converts: 'core/agents/brainstorming.md' 'bmad-brainstorming.agent.md' (core items skip module prefix)
*
* @param {string} relativePath - Path like 'bmm/agents/pm.md'
* @returns {string} Flat filename like 'bmad_bmm_agent_pm.md' or 'bmad_brainstorming.md'
* @returns {string} Flat filename like 'bmad-bmm-pm.agent.md' or 'bmad-brainstorming.md'
*/
function toUnderscorePath(relativePath) {
function toDashPath(relativePath) {
if (!relativePath || typeof relativePath !== 'string') {
// Return a safe default for invalid input
return 'bmad-unknown.md';
}
const withoutExt = relativePath.replace('.md', '');
const parts = withoutExt.split(/[/\\]/);
const module = parts[0];
const type = parts[1];
const name = parts.slice(2).join('_');
const name = parts.slice(2).join('-');
// Use toUnderscoreName for consistency
return toUnderscoreName(module, type, name);
return toDashName(module, type, name);
}
/**
* Create custom agent underscore name
* Creates: 'bmad_custom_fred-commit-poet.md'
* Create custom agent dash name
* Creates: 'bmad-custom-fred-commit-poet.agent.md'
*
* @param {string} agentName - Custom agent name
* @returns {string} Flat filename like 'bmad_custom_fred-commit-poet.md'
* @returns {string} Flat filename like 'bmad-custom-fred-commit-poet.agent.md'
*/
function customAgentUnderscoreName(agentName) {
return `bmad_custom_${agentName}.md`;
function customAgentDashName(agentName) {
return `bmad-custom-${agentName}.agent.md`;
}
/**
* Check if a filename uses underscore format
* Check if a filename uses dash format
* @param {string} filename - Filename to check
* @returns {boolean} True if filename uses underscore format
* @returns {boolean} True if filename uses dash format
*/
function isUnderscoreFormat(filename) {
return filename.startsWith('bmad_') && filename.includes('_');
function isDashFormat(filename) {
return filename.startsWith('bmad-') && filename.includes('-');
}
/**
* Extract parts from an underscore-formatted filename
* Parses: 'bmad_bmm_agent_pm.md' { prefix: 'bmad', module: 'bmm', type: 'agents', name: 'pm' }
* Parses: 'bmad_bmm_correct-course.md' { prefix: 'bmad', module: 'bmm', type: 'workflows', name: 'correct-course' }
* Parses: 'bmad_agent_brainstorming.md' { prefix: 'bmad', module: 'core', type: 'agents', name: 'brainstorming' } (core agents)
* Parses: 'bmad_brainstorming.md' { prefix: 'bmad', module: 'core', type: 'workflows', name: 'brainstorming' } (core workflows)
* Extract parts from a dash-formatted filename
* Parses: 'bmad-bmm-pm.agent.md' { prefix: 'bmad', module: 'bmm', type: 'agents', name: 'pm' }
* Parses: 'bmad-bmm-correct-course.md' { prefix: 'bmad', module: 'bmm', type: 'workflows', name: 'correct-course' }
* Parses: 'bmad-brainstorming.agent.md' { prefix: 'bmad', module: 'core', type: 'agents', name: 'brainstorming' } (core agents)
* Parses: 'bmad-brainstorming.md' { prefix: 'bmad', module: 'core', type: 'workflows', name: 'brainstorming' } (core workflows)
*
* @param {string} filename - Underscore-formatted filename
* @param {string} filename - Dash-formatted filename
* @returns {Object|null} Parsed parts or null if invalid format
*/
function parseUnderscoreName(filename) {
function parseDashName(filename) {
const withoutExt = filename.replace('.md', '');
const parts = withoutExt.split('_');
const parts = withoutExt.split('-');
if (parts.length < 2 || parts[0] !== 'bmad') {
return null;
}
// Check if this is an agent file (has 'agent' as one of the parts)
const agentIndex = parts.indexOf('agent');
// Check if this is an agent file (has .agent suffix)
const isAgent = withoutExt.endsWith('.agent');
if (agentIndex !== -1) {
if (isAgent) {
// This is an agent file
// Format: bmad_agent_name (core) or bmad_module_agent_name
if (agentIndex === 1) {
// Core agent: bmad_agent_name
// Format: bmad-name.agent (core) or bmad-module-name.agent
if (parts.length === 3) {
// Core agent: bmad-name.agent
return {
prefix: parts[0],
module: 'core',
type: 'agents',
name: parts.slice(agentIndex + 1).join('_'),
name: parts[1],
};
} else {
// Module agent: bmad_module_agent_name
// Module agent: bmad-module-name.agent
return {
prefix: parts[0],
module: parts[1],
type: 'agents',
name: parts.slice(agentIndex + 1).join('_'),
name: parts.slice(2).join('-'),
};
}
}
// Not an agent file - must be a workflow/tool/task
// If only 2 parts (bmad_name), it's a core workflow/tool/task
// If only 2 parts (bmad-name), it's a core workflow/tool/task
if (parts.length === 2) {
return {
prefix: parts[0],
@ -124,42 +142,140 @@ function parseUnderscoreName(filename) {
};
}
// Otherwise, it's a module workflow/tool/task (bmad_module_name)
// Otherwise, it's a module workflow/tool/task (bmad-module-name)
return {
prefix: parts[0],
module: parts[1],
type: 'workflows', // Default to workflows for non-agent module items
name: parts.slice(2).join('-'),
};
}
// ============================================================================
// LEGACY FUNCTIONS (underscore format) - kept for backward compatibility
// ============================================================================
/**
* Convert hierarchical path to flat underscore-separated name (LEGACY)
* @deprecated Use toDashName instead
*/
function toUnderscoreName(module, type, name) {
const isAgent = type === AGENT_SEGMENT;
if (module === 'core') {
return isAgent ? `bmad_agent_${name}.md` : `bmad_${name}.md`;
}
return isAgent ? `bmad_${module}_agent_${name}.md` : `bmad_${module}_${name}.md`;
}
/**
* Convert relative path to flat underscore-separated name (LEGACY)
* @deprecated Use toDashPath instead
*/
function toUnderscorePath(relativePath) {
const withoutExt = relativePath.replace('.md', '');
const parts = withoutExt.split(/[/\\]/);
const module = parts[0];
const type = parts[1];
const name = parts.slice(2).join('_');
return toUnderscoreName(module, type, name);
}
/**
* Create custom agent underscore name (LEGACY)
* @deprecated Use customAgentDashName instead
*/
function customAgentUnderscoreName(agentName) {
return `bmad_custom_${agentName}.md`;
}
/**
* Check if a filename uses underscore format (LEGACY)
* @deprecated Use isDashFormat instead
*/
function isUnderscoreFormat(filename) {
return filename.startsWith('bmad_') && filename.includes('_');
}
/**
* Extract parts from an underscore-formatted filename (LEGACY)
* @deprecated Use parseDashName instead
*/
function parseUnderscoreName(filename) {
const withoutExt = filename.replace('.md', '');
const parts = withoutExt.split('_');
if (parts.length < 2 || parts[0] !== 'bmad') {
return null;
}
const agentIndex = parts.indexOf('agent');
if (agentIndex !== -1) {
if (agentIndex === 1) {
return {
prefix: parts[0],
module: 'core',
type: 'agents',
name: parts.slice(agentIndex + 1).join('_'),
};
} else {
return {
prefix: parts[0],
module: parts[1],
type: 'agents',
name: parts.slice(agentIndex + 1).join('_'),
};
}
}
if (parts.length === 2) {
return {
prefix: parts[0],
module: 'core',
type: 'workflows',
name: parts[1],
};
}
return {
prefix: parts[0],
module: parts[1],
type: 'workflows',
name: parts.slice(2).join('_'),
};
}
// Backward compatibility aliases (deprecated)
// Backward compatibility aliases (colon format was same as underscore)
const toColonName = toUnderscoreName;
const toColonPath = toUnderscorePath;
const toDashPath = toUnderscorePath;
const customAgentColonName = customAgentUnderscoreName;
const customAgentDashName = customAgentUnderscoreName;
const isColonFormat = isUnderscoreFormat;
const isDashFormat = isUnderscoreFormat;
const parseColonName = parseUnderscoreName;
const parseDashName = parseUnderscoreName;
module.exports = {
// New standard (dash-based)
toDashName,
toDashPath,
customAgentDashName,
isDashFormat,
parseDashName,
// Legacy (underscore-based) - kept for backward compatibility
toUnderscoreName,
toUnderscorePath,
customAgentUnderscoreName,
isUnderscoreFormat,
parseUnderscoreName,
// Backward compatibility aliases
toColonName,
toColonPath,
toDashPath,
customAgentColonName,
customAgentDashName,
isColonFormat,
isDashFormat,
parseColonName,
parseDashName,
TYPE_SEGMENTS,
AGENT_SEGMENT,
};

View File

@ -66,7 +66,7 @@ class TaskToolCommandGenerator {
// Convert path to use {project-root} placeholder
let itemPath = item.path;
if (itemPath.startsWith('bmad/')) {
if (itemPath && typeof itemPath === 'string' && itemPath.startsWith('bmad/')) {
itemPath = `{project-root}/${itemPath}`;
}
@ -239,8 +239,10 @@ Follow all instructions in the ${type} file exactly as written.
}
/**
* Write task/tool artifacts using underscore format (Windows-compatible)
* Creates flat files like: bmad_bmm_bmad-help.md
* Write task/tool artifacts using dash format (NEW STANDARD)
* Creates flat files like: bmad-bmm-bmad-help.md
*
* Note: Tasks/tools do NOT have .agent.md suffix - only agents do.
*
* @param {string} baseCommandsDir - Base commands directory for the IDE
* @param {Array} artifacts - Task/tool artifacts with relativePath
@ -252,7 +254,7 @@ Follow all instructions in the ${type} file exactly as written.
for (const artifact of artifacts) {
if (artifact.type === 'task' || artifact.type === 'tool') {
const commandContent = this.generateCommandContent(artifact, artifact.type);
// Use underscore format: bmad_module_name.md
// Use dash format: bmad-module-name.md
const flatName = toDashPath(artifact.relativePath);
const commandPath = path.join(baseCommandsDir, flatName);
await fs.ensureDir(path.dirname(commandPath));

View File

@ -67,10 +67,26 @@ class WorkflowCommandGenerator {
for (const workflow of allWorkflows) {
const commandContent = await this.generateCommandContent(workflow, bmadDir);
// Calculate the relative workflow path (e.g., bmm/workflows/4-implementation/sprint-planning/workflow.yaml)
let workflowRelPath = workflow.path;
// Remove _bmad/ prefix if present to get relative path from project root
// Handle both absolute paths (/path/to/_bmad/...) and relative paths (_bmad/...)
if (workflowRelPath.includes('_bmad/')) {
const parts = workflowRelPath.split(/_bmad\//);
if (parts.length > 1) {
workflowRelPath = parts.slice(1).join('/');
}
}
// Determine if this is a YAML workflow
const isYamlWorkflow = workflow.path.endsWith('.yaml') || workflow.path.endsWith('.yml');
artifacts.push({
type: 'workflow-command',
isYamlWorkflow: isYamlWorkflow, // For template selection
name: workflow.name,
description: workflow.description || `${workflow.name} workflow`,
module: workflow.module,
relativePath: path.join(workflow.module, 'workflows', `${workflow.name}.md`),
workflowPath: workflowRelPath, // Relative path to actual workflow file
content: commandContent,
sourcePath: workflow.path,
});
@ -265,8 +281,10 @@ When running any workflow:
}
/**
* Write workflow command artifacts using underscore format (Windows-compatible)
* Creates flat files like: bmad_bmm_correct-course.md
* Write workflow command artifacts using dash format (NEW STANDARD)
* Creates flat files like: bmad-bmm-correct-course.md
*
* Note: Workflows do NOT have .agent.md suffix - only agents do.
*
* @param {string} baseCommandsDir - Base commands directory for the IDE
* @param {Array} artifacts - Workflow artifacts
@ -277,7 +295,7 @@ When running any workflow:
for (const artifact of artifacts) {
if (artifact.type === 'workflow-command') {
// Convert relativePath to underscore format: bmm/workflows/correct-course.md → bmad_bmm_correct-course.md
// 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));

View File

@ -0,0 +1,6 @@
name: '{{name}}'
description: '{{description}}'
LOAD and execute the workflow at: {project-root}/_bmad/{{workflow_path}}
Follow all instructions in the workflow file exactly as written.

View File

@ -0,0 +1 @@
default-agent.md

View File

@ -0,0 +1 @@
default-workflow-yaml.md

View File

@ -0,0 +1 @@
default-workflow.md

View File

@ -0,0 +1,15 @@
---
name: '{{name}}'
description: '{{description}}'
---
You must fully embody this agent's persona and follow all activation instructions exactly as specified. NEVER break character until given an exit command.
<agent-activation CRITICAL="TRUE">
1. LOAD the FULL agent file from {project-root}/_bmad/{{path}}
2. READ its entire contents - this contains the complete agent persona, menu, and instructions
3. FOLLOW every step in the <activation> section precisely
4. DISPLAY the welcome/greeting as instructed
5. PRESENT the numbered menu
6. WAIT for user input before proceeding
</agent-activation>

View File

@ -0,0 +1,14 @@
---
name: '{{name}}'
description: '{{description}}'
---
IT IS CRITICAL THAT YOU FOLLOW THESE STEPS - while staying in character as the current agent persona you may have loaded:
<steps CRITICAL="TRUE">
1. Always LOAD the FULL @{project-root}/{{bmadFolderName}}/core/tasks/workflow.xml
2. READ its entire contents - this is the CORE OS for EXECUTING the specific workflow-config @{project-root}/{{bmadFolderName}}/{{path}}
3. Pass the yaml path @{project-root}/{{bmadFolderName}}/{{path}} as 'workflow-config' parameter to the workflow.xml instructions
4. Follow workflow.xml instructions EXACTLY as written to process and follow the specific workflow config and its instructions
5. Save outputs after EACH section when generating any documents from templates
</steps>

View File

@ -0,0 +1,6 @@
---
name: '{{name}}'
description: '{{description}}'
---
IT IS CRITICAL THAT YOU FOLLOW THIS COMMAND: LOAD the FULL @{project-root}/{{bmadFolderName}}/{{path}}, READ its entire contents and follow its directions exactly!

View File

@ -0,0 +1,9 @@
# {{name}}
{{description}}
---
LOAD and execute the workflow at: {project-root}/_bmad/{{workflow_path}}
Follow all instructions in the workflow file exactly as written.

View File

@ -0,0 +1,9 @@
# {{name}}
{{description}}
## Instructions
LOAD and execute the workflow at: {project-root}/_bmad/{{workflow_path}}
Follow all instructions in the workflow file exactly as written.

View File

@ -0,0 +1,10 @@
---
description: '{{description}}'
auto_execution_mode: "iterate"
---
# {{name}}
LOAD and execute the workflow at: {project-root}/_bmad/{{workflow_path}}
Follow all instructions in the workflow file exactly as written.

View File

@ -0,0 +1,10 @@
You must fully embody this agent's persona and follow all activation instructions exactly as specified. NEVER break character until given an exit command.
<agent-activation CRITICAL="TRUE">
1. LOAD the FULL agent file from {project-root}/_bmad/{{path}}
2. READ its entire contents - this contains the complete agent persona, menu, and instructions
3. FOLLOW every step in the <activation> section precisely
4. DISPLAY the welcome/greeting as instructed
5. PRESENT the numbered menu
6. WAIT for user input before proceeding
</agent-activation>

View File

@ -0,0 +1,2 @@
name = "{{name}}"
description = "{{description}}"

View File

@ -0,0 +1,10 @@
You must fully embody this agent's persona and follow all activation instructions exactly as specified. NEVER break character until given an exit command.
<agent-activation CRITICAL="TRUE">
1. LOAD the FULL agent file from {project-root}/_bmad/{{path}}
2. READ its entire contents - this contains the complete agent persona, menu, and instructions
3. FOLLOW every step in the <activation> section precisely
4. DISPLAY the welcome/greeting as instructed
5. PRESENT the numbered menu
6. WAIT for user input before proceeding
</agent-activation>

View File

@ -0,0 +1,4 @@
---
name: '{{name}}'
description: '{{description}}'
---

View File

@ -1,313 +0,0 @@
const path = require('node:path');
const fs = require('fs-extra');
const { BaseIdeSetup } = require('./_base-ide');
const chalk = require('chalk');
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
/**
* Trae IDE setup handler
*/
class TraeSetup extends BaseIdeSetup {
constructor() {
super('trae', 'Trae');
this.configDir = '.trae';
this.rulesDir = 'rules';
}
/**
* Setup Trae IDE configuration
* @param {string} projectDir - Project directory
* @param {string} bmadDir - BMAD installation directory
* @param {Object} options - Setup options
*/
async setup(projectDir, bmadDir, options = {}) {
console.log(chalk.cyan(`Setting up ${this.name}...`));
// Create .trae/rules directory
const traeDir = path.join(projectDir, this.configDir);
const rulesDir = path.join(traeDir, this.rulesDir);
await this.ensureDir(rulesDir);
// Clean up any existing BMAD files before reinstalling
await this.cleanup(projectDir);
// Generate agent launchers
const agentGen = new AgentCommandGenerator(this.bmadFolderName);
const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []);
// Get tasks, tools, and workflows (standalone only)
const tasks = await this.getTasks(bmadDir, true);
const tools = await this.getTools(bmadDir, true);
const workflows = await this.getWorkflows(bmadDir, true);
// Process agents as rules with bmad- prefix
let agentCount = 0;
for (const artifact of agentArtifacts) {
const processedContent = await this.createAgentRule(artifact, bmadDir, projectDir);
// Use bmad- prefix: bmad-agent-{module}-{name}.md
const targetPath = path.join(rulesDir, `bmad-agent-${artifact.module}-${artifact.name}.md`);
await this.writeFile(targetPath, processedContent);
agentCount++;
}
// Process tasks as rules with bmad- prefix
let taskCount = 0;
for (const task of tasks) {
const content = await this.readFile(task.path);
const processedContent = this.createTaskRule(task, content);
// Use bmad- prefix: bmad-task-{module}-{name}.md
const targetPath = path.join(rulesDir, `bmad-task-${task.module}-${task.name}.md`);
await this.writeFile(targetPath, processedContent);
taskCount++;
}
// Process tools as rules with bmad- prefix
let toolCount = 0;
for (const tool of tools) {
const content = await this.readFile(tool.path);
const processedContent = this.createToolRule(tool, content);
// Use bmad- prefix: bmad-tool-{module}-{name}.md
const targetPath = path.join(rulesDir, `bmad-tool-${tool.module}-${tool.name}.md`);
await this.writeFile(targetPath, processedContent);
toolCount++;
}
// Process workflows as rules with bmad- prefix
let workflowCount = 0;
for (const workflow of workflows) {
const content = await this.readFile(workflow.path);
const processedContent = this.createWorkflowRule(workflow, content);
// Use bmad- prefix: bmad-workflow-{module}-{name}.md
const targetPath = path.join(rulesDir, `bmad-workflow-${workflow.module}-${workflow.name}.md`);
await this.writeFile(targetPath, processedContent);
workflowCount++;
}
const totalRules = agentCount + taskCount + toolCount + workflowCount;
console.log(chalk.green(`${this.name} configured:`));
console.log(chalk.dim(` - ${agentCount} agent rules created`));
console.log(chalk.dim(` - ${taskCount} task rules created`));
console.log(chalk.dim(` - ${toolCount} tool rules created`));
console.log(chalk.dim(` - ${workflowCount} workflow rules created`));
console.log(chalk.dim(` - Total: ${totalRules} rules`));
console.log(chalk.dim(` - Rules directory: ${path.relative(projectDir, rulesDir)}`));
console.log(chalk.dim(` - Agents can be activated with @{agent-name}`));
return {
success: true,
rules: totalRules,
agents: agentCount,
tasks: taskCount,
tools: toolCount,
workflows: workflowCount,
};
}
/**
* Create rule content for an agent
*/
async createAgentRule(artifact, bmadDir, projectDir) {
// Strip frontmatter from launcher
const frontmatterRegex = /^---\s*\n[\s\S]*?\n---\s*\n/;
const contentWithoutFrontmatter = artifact.content.replace(frontmatterRegex, '').trim();
// Extract metadata from launcher content
const titleMatch = artifact.content.match(/description:\s*"([^"]+)"/);
const title = titleMatch ? titleMatch[1] : this.formatTitle(artifact.name);
// Calculate relative path for reference
const relativePath = path.relative(projectDir, artifact.sourcePath).replaceAll('\\', '/');
let ruleContent = `# ${title} Agent Rule
This rule is triggered when the user types \`@${artifact.name}\` and activates the ${title} agent persona.
## Agent Activation
${contentWithoutFrontmatter}
## File Reference
The full agent definition is located at: \`${relativePath}\`
`;
return ruleContent;
}
/**
* Create rule content for a task
*/
createTaskRule(task, content) {
// Extract task name from content
const nameMatch = content.match(/name="([^"]+)"/);
const taskName = nameMatch ? nameMatch[1] : this.formatTitle(task.name);
let ruleContent = `# ${taskName} Task Rule
This rule defines the ${taskName} task workflow.
## Task Definition
When this task is triggered, execute the following workflow:
${content}
## Usage
Reference this task with \`@task-${task.name}\` to execute the defined workflow.
## Module
Part of the BMAD ${task.module.toUpperCase()} module.
`;
return ruleContent;
}
/**
* Create rule content for a tool
*/
createToolRule(tool, content) {
// Extract tool name from content
const nameMatch = content.match(/name="([^"]+)"/);
const toolName = nameMatch ? nameMatch[1] : this.formatTitle(tool.name);
let ruleContent = `# ${toolName} Tool Rule
This rule defines the ${toolName} tool.
## Tool Definition
When this tool is triggered, execute the following:
${content}
## Usage
Reference this tool with \`@tool-${tool.name}\` to execute it.
## Module
Part of the BMAD ${tool.module.toUpperCase()} module.
`;
return ruleContent;
}
/**
* Create rule content for a workflow
*/
createWorkflowRule(workflow, content) {
let ruleContent = `# ${workflow.name} Workflow Rule
This rule defines the ${workflow.name} workflow.
## Workflow Description
${workflow.description || 'No description provided'}
## Workflow Definition
${content}
## Usage
Reference this workflow with \`@workflow-${workflow.name}\` to execute the guided workflow.
## Module
Part of the BMAD ${workflow.module.toUpperCase()} module.
`;
return ruleContent;
}
/**
* Format agent/task name as title
*/
formatTitle(name) {
return name
.split('-')
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ');
}
/**
* Cleanup Trae configuration - surgically remove only BMAD files
*/
async cleanup(projectDir) {
const fs = require('fs-extra');
const rulesPath = path.join(projectDir, this.configDir, this.rulesDir);
if (await fs.pathExists(rulesPath)) {
// Remove any bmad* files (cleans up old bmad- and bmad: formats)
const files = await fs.readdir(rulesPath);
let removed = 0;
for (const file of files) {
if (file.startsWith('bmad') && file.endsWith('.md')) {
await fs.remove(path.join(rulesPath, file));
removed++;
}
}
if (removed > 0) {
console.log(chalk.dim(` Cleaned up ${removed} existing BMAD rules`));
}
}
}
/**
* Install a custom agent launcher for Trae
* @param {string} projectDir - Project directory
* @param {string} agentName - Agent name (e.g., "fred-commit-poet")
* @param {string} agentPath - Path to compiled agent (relative to project root)
* @param {Object} metadata - Agent metadata
* @returns {Object} Installation result
*/
async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) {
const traeDir = path.join(projectDir, this.configDir);
const rulesDir = path.join(traeDir, this.rulesDir);
// Create .trae/rules directory if it doesn't exist
await fs.ensureDir(rulesDir);
// Create custom agent launcher
const launcherContent = `# ${agentName} Custom Agent
** IMPORTANT**: Run @${agentPath} first to load the complete agent!
This is a launcher for the custom BMAD agent "${agentName}".
## Usage
1. First run: \`${agentPath}\` to load the complete agent
2. Then use this rule to activate ${agentName}
The agent will follow the persona and instructions from the main agent file.
---
*Generated by BMAD Method*`;
const fileName = `bmad-agent-custom-${agentName.toLowerCase()}.md`;
const launcherPath = path.join(rulesDir, fileName);
// Write the launcher file
await fs.writeFile(launcherPath, launcherContent, 'utf8');
return {
ide: 'trae',
path: path.relative(projectDir, launcherPath),
command: agentName,
type: 'custom-agent-launcher',
};
}
}
module.exports = { TraeSetup };

View File

@ -1,258 +0,0 @@
const path = require('node:path');
const { BaseIdeSetup } = require('./_base-ide');
const chalk = require('chalk');
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
/**
* Windsurf IDE setup handler
*/
class WindsurfSetup extends BaseIdeSetup {
constructor() {
super('windsurf', 'Windsurf', true); // preferred IDE
this.configDir = '.windsurf';
this.workflowsDir = 'workflows';
}
/**
* Setup Windsurf IDE configuration
* @param {string} projectDir - Project directory
* @param {string} bmadDir - BMAD installation directory
* @param {Object} options - Setup options
*/
async setup(projectDir, bmadDir, options = {}) {
console.log(chalk.cyan(`Setting up ${this.name}...`));
// Create .windsurf/workflows/bmad directory structure
const windsurfDir = path.join(projectDir, this.configDir);
const workflowsDir = path.join(windsurfDir, this.workflowsDir);
const bmadWorkflowsDir = path.join(workflowsDir, 'bmad');
await this.ensureDir(bmadWorkflowsDir);
// Clean up any existing BMAD workflows before reinstalling
await this.cleanup(projectDir);
// Generate agent launchers
const agentGen = new AgentCommandGenerator(this.bmadFolderName);
const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []);
// Convert artifacts to agent format for module organization
const agents = agentArtifacts.map((a) => ({ module: a.module, name: a.name }));
// Get tasks, tools, and workflows (standalone only)
const tasks = await this.getTasks(bmadDir, true);
const tools = await this.getTools(bmadDir, true);
const workflows = await this.getWorkflows(bmadDir, true);
// Create directories for each module under bmad/
const modules = new Set();
for (const item of [...agents, ...tasks, ...tools, ...workflows]) modules.add(item.module);
for (const module of modules) {
await this.ensureDir(path.join(bmadWorkflowsDir, module));
await this.ensureDir(path.join(bmadWorkflowsDir, module, 'agents'));
await this.ensureDir(path.join(bmadWorkflowsDir, module, 'tasks'));
await this.ensureDir(path.join(bmadWorkflowsDir, module, 'tools'));
await this.ensureDir(path.join(bmadWorkflowsDir, module, 'workflows'));
}
// Process agent launchers as workflows with organized structure
let agentCount = 0;
for (const artifact of agentArtifacts) {
const processedContent = this.createWorkflowContent({ module: artifact.module, name: artifact.name }, artifact.content);
// Organized path: bmad/module/agents/agent-name.md
const targetPath = path.join(bmadWorkflowsDir, artifact.module, 'agents', `${artifact.name}.md`);
await this.writeFile(targetPath, processedContent);
agentCount++;
}
// Process tasks as workflows with organized structure
let taskCount = 0;
for (const task of tasks) {
const content = await this.readFile(task.path);
const processedContent = this.createTaskWorkflowContent(task, content);
// Organized path: bmad/module/tasks/task-name.md
const targetPath = path.join(bmadWorkflowsDir, task.module, 'tasks', `${task.name}.md`);
await this.writeFile(targetPath, processedContent);
taskCount++;
}
// Process tools as workflows with organized structure
let toolCount = 0;
for (const tool of tools) {
const content = await this.readFile(tool.path);
const processedContent = this.createToolWorkflowContent(tool, content);
// Organized path: bmad/module/tools/tool-name.md
const targetPath = path.join(bmadWorkflowsDir, tool.module, 'tools', `${tool.name}.md`);
await this.writeFile(targetPath, processedContent);
toolCount++;
}
// Process workflows with organized structure
let workflowCount = 0;
for (const workflow of workflows) {
const content = await this.readFile(workflow.path);
const processedContent = this.createWorkflowWorkflowContent(workflow, content);
// Organized path: bmad/module/workflows/workflow-name.md
const targetPath = path.join(bmadWorkflowsDir, workflow.module, 'workflows', `${workflow.name}.md`);
await this.writeFile(targetPath, processedContent);
workflowCount++;
}
console.log(chalk.green(`${this.name} configured:`));
console.log(chalk.dim(` - ${agentCount} agents installed`));
console.log(chalk.dim(` - ${taskCount} tasks installed`));
console.log(chalk.dim(` - ${toolCount} tools installed`));
console.log(chalk.dim(` - ${workflowCount} workflows installed`));
console.log(chalk.dim(` - Organized in modules: ${[...modules].join(', ')}`));
console.log(chalk.dim(` - Workflows directory: ${path.relative(projectDir, workflowsDir)}`));
// Provide additional configuration hints
if (options.showHints !== false) {
console.log(chalk.dim('\n Windsurf workflow settings:'));
console.log(chalk.dim(' - auto_execution_mode: 3 (recommended for agents)'));
console.log(chalk.dim(' - auto_execution_mode: 2 (recommended for tasks/tools)'));
console.log(chalk.dim(' - auto_execution_mode: 1 (recommended for workflows)'));
console.log(chalk.dim(' - Workflows can be triggered via the Windsurf menu'));
}
return {
success: true,
agents: agentCount,
tasks: taskCount,
tools: toolCount,
workflows: workflowCount,
};
}
/**
* Create workflow content for an agent
*/
createWorkflowContent(agent, content) {
// Strip existing frontmatter from launcher
const frontmatterRegex = /^---\s*\n[\s\S]*?\n---\s*\n/;
const contentWithoutFrontmatter = content.replace(frontmatterRegex, '');
// Create simple Windsurf frontmatter matching original format
let workflowContent = `---
description: ${agent.name}
auto_execution_mode: 3
---
${contentWithoutFrontmatter}`;
return workflowContent;
}
/**
* Create workflow content for a task
*/
createTaskWorkflowContent(task, content) {
// Create simple Windsurf frontmatter matching original format
let workflowContent = `---
description: task-${task.name}
auto_execution_mode: 2
---
${content}`;
return workflowContent;
}
/**
* Create workflow content for a tool
*/
createToolWorkflowContent(tool, content) {
// Create simple Windsurf frontmatter matching original format
let workflowContent = `---
description: tool-${tool.name}
auto_execution_mode: 2
---
${content}`;
return workflowContent;
}
/**
* Create workflow content for a workflow
*/
createWorkflowWorkflowContent(workflow, content) {
// Create simple Windsurf frontmatter matching original format
let workflowContent = `---
description: ${workflow.name}
auto_execution_mode: 1
---
${content}`;
return workflowContent;
}
/**
* Cleanup Windsurf configuration - surgically remove only BMAD files
*/
async cleanup(projectDir) {
const fs = require('fs-extra');
const bmadPath = path.join(projectDir, this.configDir, this.workflowsDir, 'bmad');
if (await fs.pathExists(bmadPath)) {
// Remove the entire bmad folder - this is our territory
await fs.remove(bmadPath);
console.log(chalk.dim(` Cleaned up existing BMAD workflows`));
}
}
/**
* Install a custom agent launcher for Windsurf
* @param {string} projectDir - Project directory
* @param {string} agentName - Agent name (e.g., "fred-commit-poet")
* @param {string} agentPath - Path to compiled agent (relative to project root)
* @param {Object} metadata - Agent metadata
* @returns {Object|null} Info about created command
*/
async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) {
const fs = require('fs-extra');
const customAgentsDir = path.join(projectDir, this.configDir, this.workflowsDir, 'bmad', 'custom', 'agents');
if (!(await this.exists(path.join(projectDir, this.configDir)))) {
return null; // IDE not configured for this project
}
await this.ensureDir(customAgentsDir);
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.
<agent-activation CRITICAL="TRUE">
1. LOAD the FULL agent file from @${agentPath}
2. READ its entire contents - this contains the complete agent persona, menu, and instructions
3. FOLLOW every step in the <activation> section precisely
4. DISPLAY the welcome/greeting as instructed
5. PRESENT the numbered menu
6. WAIT for user input before proceeding
</agent-activation>
`;
// Windsurf uses workflow format with frontmatter
const workflowContent = `---
description: ${metadata.title || agentName}
auto_execution_mode: 3
---
${launcherContent}`;
const launcherPath = path.join(customAgentsDir, `${agentName}.md`);
await fs.writeFile(launcherPath, workflowContent);
return {
path: launcherPath,
command: `bmad/custom/agents/${agentName}`,
};
}
}
module.exports = { WindsurfSetup };

View File

@ -362,6 +362,7 @@ class UI {
// Get IDE manager to fetch available IDEs dynamically
const { IdeManager } = require('../installers/lib/ide/manager');
const ideManager = new IdeManager();
await ideManager.ensureInitialized(); // IMPORTANT: Must initialize before getting IDEs
const preferredIdes = ideManager.getPreferredIdes();
const otherIdes = ideManager.getOtherIdes();