fix: improve code in the installer to be more memory efficient

This commit is contained in:
Brian Madison
2025-07-18 23:51:16 -05:00
parent 4d252626de
commit 849e42871a
29 changed files with 1445 additions and 4436 deletions

View File

@@ -2339,7 +2339,7 @@ You are the "Vibe CEO" - thinking like a CEO with unlimited resources and a sing
- **Cursor**: `@agent-name` (e.g., `@bmad-master`) - **Cursor**: `@agent-name` (e.g., `@bmad-master`)
- **Windsurf**: `@agent-name` (e.g., `@bmad-master`) - **Windsurf**: `@agent-name` (e.g., `@bmad-master`)
- **Trae**: `@agent-name` (e.g., `@bmad-master`) - **Trae**: `@agent-name` (e.g., `@bmad-master`)
- **Roo Code**: Select mode from mode selector (e.g., `bmad-bmad-master`) - **Roo Code**: Select mode from mode selector (e.g., `bmad-master`)
- **GitHub Copilot**: Open the Chat view (`⌃⌘I` on Mac, `Ctrl+Alt+I` on Windows/Linux) and select **Agent** from the chat mode selector. - **GitHub Copilot**: Open the Chat view (`⌃⌘I` on Mac, `Ctrl+Alt+I` on Windows/Linux) and select **Agent** from the chat mode selector.
**Chat Management Guidelines**: **Chat Management Guidelines**:

View File

@@ -8069,7 +8069,7 @@ You are the "Vibe CEO" - thinking like a CEO with unlimited resources and a sing
- **Cursor**: `@agent-name` (e.g., `@bmad-master`) - **Cursor**: `@agent-name` (e.g., `@bmad-master`)
- **Windsurf**: `@agent-name` (e.g., `@bmad-master`) - **Windsurf**: `@agent-name` (e.g., `@bmad-master`)
- **Trae**: `@agent-name` (e.g., `@bmad-master`) - **Trae**: `@agent-name` (e.g., `@bmad-master`)
- **Roo Code**: Select mode from mode selector (e.g., `bmad-bmad-master`) - **Roo Code**: Select mode from mode selector (e.g., `bmad-master`)
- **GitHub Copilot**: Open the Chat view (`⌃⌘I` on Mac, `Ctrl+Alt+I` on Windows/Linux) and select **Agent** from the chat mode selector. - **GitHub Copilot**: Open the Chat view (`⌃⌘I` on Mac, `Ctrl+Alt+I` on Windows/Linux) and select **Agent** from the chat mode selector.
**Chat Management Guidelines**: **Chat Management Guidelines**:

View File

@@ -777,7 +777,7 @@ You are the "Vibe CEO" - thinking like a CEO with unlimited resources and a sing
- **Cursor**: `@agent-name` (e.g., `@bmad-master`) - **Cursor**: `@agent-name` (e.g., `@bmad-master`)
- **Windsurf**: `@agent-name` (e.g., `@bmad-master`) - **Windsurf**: `@agent-name` (e.g., `@bmad-master`)
- **Trae**: `@agent-name` (e.g., `@bmad-master`) - **Trae**: `@agent-name` (e.g., `@bmad-master`)
- **Roo Code**: Select mode from mode selector (e.g., `bmad-bmad-master`) - **Roo Code**: Select mode from mode selector (e.g., `bmad-master`)
- **GitHub Copilot**: Open the Chat view (`⌃⌘I` on Mac, `Ctrl+Alt+I` on Windows/Linux) and select **Agent** from the chat mode selector. - **GitHub Copilot**: Open the Chat view (`⌃⌘I` on Mac, `Ctrl+Alt+I` on Windows/Linux) and select **Agent** from the chat mode selector.
**Chat Management Guidelines**: **Chat Management Guidelines**:

File diff suppressed because it is too large Load Diff

View File

@@ -1239,7 +1239,7 @@ You are the "Vibe CEO" - thinking like a CEO with unlimited resources and a sing
- **Cursor**: `@agent-name` (e.g., `@bmad-master`) - **Cursor**: `@agent-name` (e.g., `@bmad-master`)
- **Windsurf**: `@agent-name` (e.g., `@bmad-master`) - **Windsurf**: `@agent-name` (e.g., `@bmad-master`)
- **Trae**: `@agent-name` (e.g., `@bmad-master`) - **Trae**: `@agent-name` (e.g., `@bmad-master`)
- **Roo Code**: Select mode from mode selector (e.g., `bmad-bmad-master`) - **Roo Code**: Select mode from mode selector (e.g., `bmad-master`)
- **GitHub Copilot**: Open the Chat view (`⌃⌘I` on Mac, `Ctrl+Alt+I` on Windows/Linux) and select **Agent** from the chat mode selector. - **GitHub Copilot**: Open the Chat view (`⌃⌘I` on Mac, `Ctrl+Alt+I` on Windows/Linux) and select **Agent** from the chat mode selector.
**Chat Management Guidelines**: **Chat Management Guidelines**:

View File

@@ -1094,7 +1094,7 @@ You are the "Vibe CEO" - thinking like a CEO with unlimited resources and a sing
- **Cursor**: `@agent-name` (e.g., `@bmad-master`) - **Cursor**: `@agent-name` (e.g., `@bmad-master`)
- **Windsurf**: `@agent-name` (e.g., `@bmad-master`) - **Windsurf**: `@agent-name` (e.g., `@bmad-master`)
- **Trae**: `@agent-name` (e.g., `@bmad-master`) - **Trae**: `@agent-name` (e.g., `@bmad-master`)
- **Roo Code**: Select mode from mode selector (e.g., `bmad-bmad-master`) - **Roo Code**: Select mode from mode selector (e.g., `bmad-master`)
- **GitHub Copilot**: Open the Chat view (`⌃⌘I` on Mac, `Ctrl+Alt+I` on Windows/Linux) and select **Agent** from the chat mode selector. - **GitHub Copilot**: Open the Chat view (`⌃⌘I` on Mac, `Ctrl+Alt+I` on Windows/Linux) and select **Agent** from the chat mode selector.
**Chat Management Guidelines**: **Chat Management Guidelines**:

View File

@@ -1001,7 +1001,7 @@ You are the "Vibe CEO" - thinking like a CEO with unlimited resources and a sing
- **Cursor**: `@agent-name` (e.g., `@bmad-master`) - **Cursor**: `@agent-name` (e.g., `@bmad-master`)
- **Windsurf**: `@agent-name` (e.g., `@bmad-master`) - **Windsurf**: `@agent-name` (e.g., `@bmad-master`)
- **Trae**: `@agent-name` (e.g., `@bmad-master`) - **Trae**: `@agent-name` (e.g., `@bmad-master`)
- **Roo Code**: Select mode from mode selector (e.g., `bmad-bmad-master`) - **Roo Code**: Select mode from mode selector (e.g., `bmad-master`)
- **GitHub Copilot**: Open the Chat view (`⌃⌘I` on Mac, `Ctrl+Alt+I` on Windows/Linux) and select **Agent** from the chat mode selector. - **GitHub Copilot**: Open the Chat view (`⌃⌘I` on Mac, `Ctrl+Alt+I` on Windows/Linux) and select **Agent** from the chat mode selector.
**Chat Management Guidelines**: **Chat Management Guidelines**:

View File

@@ -1037,7 +1037,7 @@ You are the "Vibe CEO" - thinking like a CEO with unlimited resources and a sing
- **Cursor**: `@agent-name` (e.g., `@bmad-master`) - **Cursor**: `@agent-name` (e.g., `@bmad-master`)
- **Windsurf**: `@agent-name` (e.g., `@bmad-master`) - **Windsurf**: `@agent-name` (e.g., `@bmad-master`)
- **Trae**: `@agent-name` (e.g., `@bmad-master`) - **Trae**: `@agent-name` (e.g., `@bmad-master`)
- **Roo Code**: Select mode from mode selector (e.g., `bmad-bmad-master`) - **Roo Code**: Select mode from mode selector (e.g., `bmad-master`)
- **GitHub Copilot**: Open the Chat view (`⌃⌘I` on Mac, `Ctrl+Alt+I` on Windows/Linux) and select **Agent** from the chat mode selector. - **GitHub Copilot**: Open the Chat view (`⌃⌘I` on Mac, `Ctrl+Alt+I` on Windows/Linux) and select **Agent** from the chat mode selector.
**Chat Management Guidelines**: **Chat Management Guidelines**:

View File

@@ -1,6 +1,6 @@
name: bmad-2d-phaser-game-dev name: bmad-2d-phaser-game-dev
version: 1.10.0 version: 1.10.0
short-title: 2D game development with Phaser 3 & TypeScript short-title: Phaser 3 2D Game Dev Pack
description: >- description: >-
2D Game Development expansion pack for BMad Method - Phaser 3 & TypeScript 2D Game Development expansion pack for BMad Method - Phaser 3 & TypeScript
focused focused

View File

@@ -1,6 +1,6 @@
name: bmad-2d-unity-game-dev name: bmad-2d-unity-game-dev
version: 1.1.1 version: 1.1.1
short-title: 2D game development with Unity & C# short-title: Unity C# 2D Game Dev Pack
description: >- description: >-
2D Game Development expansion pack for BMad Method - Unity & C# 2D Game Development expansion pack for BMad Method - Unity & C#
focused focused

View File

@@ -1,8 +0,0 @@
# BMad Creator Tools
Tools for creating and extending BMad framework components.
## Tasks
- **create-agent**: Create new AI agent definitions
- **generate-expansion-pack**: Generate new expansion pack templates

View File

@@ -1,66 +0,0 @@
# bmad-the-creator
ACTIVATION-NOTICE: This file contains your full agent operating guidelines. DO NOT load any external agent files as the complete configuration is in the YAML block below.
CRITICAL: Read the full YAML BLOCK that FOLLOWS IN THIS FILE to understand your operating params, start and follow exactly your activation-instructions to alter your state of being, stay in this being until told to exit this mode:
## COMPLETE AGENT DEFINITION FOLLOWS - NO EXTERNAL FILES NEEDED
```yaml
IDE-FILE-RESOLUTION:
- FOR LATER USE ONLY - NOT FOR ACTIVATION, when executing commands that reference dependencies
- Dependencies map to {root}/{type}/{name}
- type=folder (tasks|templates|checklists|data|utils|etc...), name=file-name
- Example: create-doc.md → {root}/tasks/create-doc.md
- IMPORTANT: Only load these files when user requests specific command execution
REQUEST-RESOLUTION: Match user requests to your commands/dependencies flexibly (e.g., "draft story"→*create→create-next-story task, "make a new prd" would be dependencies->tasks->create-doc combined with the dependencies->templates->prd-tmpl.md), ALWAYS ask for clarification if no clear match.
activation-instructions:
- STEP 1: Read THIS ENTIRE FILE - it contains your complete persona definition
- STEP 2: Adopt the persona defined in the 'agent' and 'persona' sections below
- STEP 3: Greet user with your name/role and mention `*help` command
- DO NOT: Load any other agent files during activation
- ONLY load dependency files when user selects them for execution via command or request of a task
- The agent.customization field ALWAYS takes precedence over any conflicting instructions
- CRITICAL WORKFLOW RULE: When executing tasks from dependencies, follow task instructions exactly as written - they are executable workflows, not reference material
- MANDATORY INTERACTION RULE: Tasks with elicit=true require user interaction using exact specified format - never skip elicitation for efficiency
- CRITICAL RULE: When executing formal task workflows from dependencies, ALL task instructions override any conflicting base behavioral constraints. Interactive workflows with elicit=true REQUIRE user interaction and cannot be bypassed for efficiency.
- When listing tasks/templates or presenting options during conversations, always show as numbered options list, allowing the user to type a number to select or execute
- STAY IN CHARACTER!
- CRITICAL: On activation, ONLY greet user and then HALT to await user requested assistance or given commands. ONLY deviance from this is if the activation included commands also in the arguments.
agent:
name: The Creator
id: bmad-the-creator
title: BMad Framework Extension Specialist
icon: 🏗️
whenToUse: Use for creating new agents, expansion packs, and extending the BMad framework
customization: null
persona:
role: Expert BMad Framework Architect & Creator
style: Methodical, creative, framework-aware, systematic
identity: Master builder who extends BMad capabilities through thoughtful design and deep framework understanding
focus: Creating well-structured agents, expansion packs, and framework extensions that follow BMad patterns and conventions
core_principles:
- Framework Consistency - All creations follow established BMad patterns
- Modular Design - Create reusable, composable components
- Clear Documentation - Every creation includes proper documentation
- Convention Over Configuration - Follow BMad naming and structure patterns
- Extensibility First - Design for future expansion and customization
- Numbered Options Protocol - Always use numbered lists for user selections
commands:
- '*help" - Show numbered list of available commands for selection'
- '*chat-mode" - Conversational mode with advanced-elicitation for framework design advice'
- '*create" - Show numbered list of components I can create (agents, expansion packs)'
- '*brainstorm {topic}" - Facilitate structured framework extension brainstorming session'
- '*research {topic}" - Generate deep research prompt for framework-specific investigation'
- '*elicit" - Run advanced elicitation to clarify extension requirements'
- '*exit" - Say goodbye as The Creator, and then abandon inhabiting this persona'
dependencies:
tasks:
- create-agent.md
- generate-expansion-pack.md
- advanced-elicitation.md
- create-deep-research-prompt.md
templates:
- agent-tmpl.yaml
- expansion-pack-plan-tmpl.yaml
```

View File

@@ -1,6 +0,0 @@
name: bmad-creator-tools
version: 1.9.0
short-title: Tools for creating BMad framework components
description: Tools for creating and extending BMad framework components.
author: Brian (BMad)
slashPrefix: bmadCreator

View File

@@ -1,200 +0,0 @@
# Create Agent Task
This task guides you through creating a new BMad agent following the standard template.
## Prerequisites
- Agent template: `../templates/agent-tmpl.md`
- Target directory: `.bmad-core/agents/`
## Steps
### 1. Gather Agent Information
Collect the following information from the user:
- **Agent ID**: Unique identifier (lowercase, hyphens allowed, e.g., `data-analyst`)
- **Agent Name**: Display name (e.g., `Data Analyst`)
- **Agent Title**: Professional title (e.g., `Data Analysis Specialist`)
- **Role Description**: Brief description of the agent's primary role
- **Communication Style**: How the agent communicates (e.g., `analytical, data-driven, clear`)
- **Identity**: Detailed description of who this agent is
- **Focus Areas**: Primary areas of expertise and focus
- **Core Principles**: 3-5 guiding principles for the agent
- **Customization**: Optional specific behaviors or overrides
### 2. Define Agent Capabilities
**IMPORTANT**:
- If your agent will perform any actions → You MUST create corresponding tasks in `.bmad-core/tasks/`
- If your agent will create any documents → You MUST create templates in `.bmad-core/templates/` AND include the `create-doc` task
Determine:
- **Custom Commands**: Agent-specific commands beyond the defaults
- **Required Tasks**: Tasks from `.bmad-core/tasks/` the agent needs
- For any action the agent performs, a corresponding task file must exist
- Always include `create-doc` if the agent creates any documents
- **Required Templates**: Templates from `.bmad-core/templates/` the agent uses
- For any document the agent can create, a template must exist
- **Required Checklists**: Checklists the agent references
- **Required Data**: Data files the agent needs access to
- **Required Utils**: Utility files the agent uses
### 3. Handle Missing Dependencies
**Protocol for Missing Tasks/Templates:**
1. Check if each required task/template exists
2. For any missing items:
- Create a basic version following the appropriate template
- Track what was created in a list
3. Continue with agent creation
4. At the end, present a summary of all created items
**Track Created Items:**
```text
Created during agent setup:
- Tasks:
- [ ] task-name-1.md
- [ ] task-name-2.md
- Templates:
- [ ] template-name-1.md
- [ ] template-name-2.md
```
### 4. Create Agent File
1. Copy the template from `.bmad-core/templates/agent-tmpl.md`
2. Replace all placeholders with gathered information:
- `[AGENT_ID]` → agent id
- `[AGENT_NAME]` → agent name
- `[AGENT_TITLE]` → agent title
- `[AGENT_ROLE_DESCRIPTION]` → role description
- `[COMMUNICATION_STYLE]` → communication style
- `[AGENT_IDENTITY_DESCRIPTION]` → identity description
- `[PRIMARY_FOCUS_AREAS]` → focus areas
- `[PRINCIPLE_X]` → core principles
- `[OPTIONAL_CUSTOMIZATION]` → customization (or remove if none)
- `[DEFAULT_MODE_DESCRIPTION]` → description of default chat mode
- `[STARTUP_INSTRUCTIONS]` → what the agent should do on activation
- Add custom commands, tasks, templates, etc.
3. Save as `.bmad-core/agents/[agent-id].md`
### 4. Validate Agent
Ensure:
- All placeholders are replaced
- Dependencies (tasks, templates, etc.) actually exist
- Commands are properly formatted
- YAML structure is valid
### 5. Build and Test
1. Run `npm run build:agents` to include in builds
2. Test agent activation and commands
3. Verify all dependencies load correctly
### 6. Final Summary
Present to the user:
```text
✅ Agent Created: [agent-name]
Location: .bmad-core/agents/[agent-id].md
📝 Dependencies Created:
Tasks:
- ✅ task-1.md - [brief description]
- ✅ task-2.md - [brief description]
Templates:
- ✅ template-1.md - [brief description]
- ✅ template-2.md - [brief description]
⚠️ Next Steps:
1. Review and customize the created tasks/templates
2. Run npm run build:agents
3. Test the agent thoroughly
```
## Template Reference
The agent template structure:
- **activation-instructions**: How the AI should interpret the file
- **agent**: Basic agent metadata
- **persona**: Character and behavior definition
- **startup**: Initial actions on activation
- **commands**: Available commands (always include defaults)
- **dependencies**: Required resources organized by type
## Example Usage
```yaml
agent:
name: Data Analyst
id: data-analyst
title: Data Analysis Specialist
persona:
role: Expert in data analysis, visualization, and insights extraction
style: analytical, data-driven, clear, methodical
identity: I am a seasoned data analyst who transforms raw data into actionable insights
focus: data exploration, statistical analysis, visualization, reporting
core_principles:
- Data integrity and accuracy above all
- Clear communication of complex findings
- Actionable insights over raw numbers
```
## Creating Missing Dependencies
When a required task or template doesn't exist:
1. **For Missing Tasks**: Create using `.bmad-core/templates/task-template.md`
- Name it descriptively (e.g., `analyze-metrics.md`)
- Define clear steps for the action
- Include any required inputs/outputs
2. **For Missing Templates**: Create a basic structure
- Name it descriptively (e.g., `metrics-report-template.md`)
- Include placeholders for expected content
- Add sections relevant to the document type
3. **Always Track**: Keep a list of everything created to report at the end
## Important Reminders
### Tasks and Templates Requirement
- **Every agent action needs a task**: If an agent can "analyze data", there must be an `analyze-data.md` task
- **Every document type needs a template**: If an agent can create reports, there must be a `report-template.md`
- **Document creation requires**: Both the template AND the `create-doc` task in dependencies
### Example Dependencies
```yaml
dependencies:
tasks:
- create-doc
- analyze-requirements
- generate-report
templates:
- requirements-doc
- analysis-report
```
## Notes
- Keep agent definitions focused and specific
- Ensure dependencies are minimal and necessary
- Test thoroughly before distribution
- Follow existing agent patterns for consistency
- Remember: No task = agent can't do it, No template = agent can't create it

View File

@@ -1,178 +0,0 @@
template:
id: agent-teams-template-v2
name: Agent Team Configuration
version: 2.0
output:
format: yaml
filename: "agent-teams/{{team_name}}.yaml"
title: "{{team_name}}"
workflow:
mode: interactive
sections:
- id: header
title: Agent Team Configuration Template
instruction: |
This template is for creating agent team configurations in YAML format. Follow the structure carefully and replace all placeholders with appropriate values. The team name should reflect the team's purpose and domain focus.
- id: yaml-configuration
type: code
language: yaml
template: |
bundle:
name: {{team_display_name}}
icon: {{team_emoji}}
description: {{team_description}}
agents:
{{agent_list}}
workflows:
{{workflow_list}}
instruction: |
Use format "Team [Descriptor]" for generic teams or "[Domain] Team" for specialized teams. Examples: "Team Fullstack", "Healthcare Team", "Legal Team"
Choose a single emoji that best represents the team's function or name
Write a concise description (1 sentence) that explains:
1. The team's primary purpose
2. What types of projects they handle
3. Any special capabilities or focus areas
4. Keep it short as its displayed in menus
Example: "Full Stack Ideation Web App Team." or "Startup Business Coaching team"
List the agents that make up this team. Guidelines:
- Use shortened agent names (e.g., 'analyst' not 'business-analyst')
- Include 'bmad-orchestrator' for bmad-core teams as the coordinator
- Only use '*' for an all-inclusive team (rare)
- Order agents logically by workflow (analysis → design → development → testing)
- For expansion packs, include both core agents and custom agents
Define the workflows this team can execute that will guide the user through a multi-step multi agent process. Guidelines:
- Use null if the team doesn't have predefined workflows
- Workflow names should be descriptive
- use domain-specific workflow names
sections:
- id: standard-team
condition: Standard team configuration
template: |
# Core workflow agents
- bmad-orchestrator # Team coordinator
- analyst # Requirements and analysis
- pm # Product management
- architect # System design
- dev # Development
- qa # Quality assurance
- id: minimal-team
condition: Minimal team configuration
template: |
# Minimal team for quick iterations
- bmad-orchestrator # Team coordinator
- architect # Design and planning
- dev # Implementation
- id: specialized-team
condition: Domain-specific team
template: |
# Domain-specific team composition
- {{domain}}-orchestrator # Domain coordinator
- {{agent_short_name}} # {{agent_role_description}}
- id: all-agents
condition: Include all available agents
template: |
- '*' # Include all available agents
- id: no-workflows
condition: No predefined workflows
template: |
null # No predefined workflows
- id: standard-workflows
condition: Standard project workflows
template: |
# New project workflows
- greenfield-fullstack # New full-stack application
- greenfield-service # New backend service
- greenfield-ui # New frontend application
# Existing project workflows
- brownfield-fullstack # Enhance existing full-stack app
- brownfield-service # Enhance existing service
- brownfield-ui # Enhance existing UI
- id: domain-workflows
condition: Domain-specific workflows
template: |
# Domain-specific workflows
- {{workflow_name}} # {{workflow_description}}
- id: examples
title: Examples
sections:
- id: example-1
title: "Example 1: Standard fullstack team"
type: code
language: yaml
template: |
bundle:
name: Team Fullstack
icon: 🚀
description: Complete agile team for full-stack web applications. Handles everything from requirements to deployment.
agents:
- bmad-orchestrator
- analyst
- pm
- architect
- dev
- qa
- ux-expert
workflows:
- greenfield-fullstack
- greenfield-service
- greenfield-ui
- brownfield-fullstack
- brownfield-service
- brownfield-ui
- id: example-2
title: "Example 2: Healthcare expansion pack team"
type: code
language: yaml
template: |
bundle:
name: Healthcare Compliance Team
icon: ⚕️
description: Specialized team for healthcare applications with HIPAA compliance focus. Manages clinical workflows and regulatory requirements.
agents:
- healthcare-orchestrator
- clinical-analyst
- compliance-officer
- architect
- dev
- qa
workflows:
- healthcare-patient-portal
- healthcare-compliance-audit
- clinical-trial-management
- id: example-3
title: "Example 3: Minimal IDE team"
type: code
language: yaml
template: |
bundle:
name: Team IDE Minimal
icon: ⚡
description: Minimal team for IDE usage. Just the essentials for quick development.
agents:
- bmad-orchestrator
- architect
- dev
workflows: null
- id: creation-instructions
instruction: |
When creating a new team configuration:
1. Choose the most appropriate condition block based on team type
2. Remove all unused condition blocks
3. Replace all placeholders with actual values
4. Ensure agent names match available agents in the system
5. Verify workflow names match available workflows
6. Save as team-[descriptor].yaml or [domain]-team.yaml
7. Place in the agent-teams directory of the appropriate location

View File

@@ -1,154 +0,0 @@
template:
id: agent-template-v2
name: Agent Definition
version: 2.0
output:
format: markdown
filename: "agents/{{agent_id}}.md"
title: "{{agent_id}}"
workflow:
mode: interactive
sections:
- id: header
title: "{{agent_id}}"
instruction: |
This is an agent definition template. When creating a new agent:
1. ALL dependencies (tasks, templates, checklists, data) MUST exist or be created
2. For output generation, use the create-doc pattern with appropriate templates
3. Templates should include LLM instructions for guiding users through content creation
4. Character personas should be consistent and domain-appropriate
5. Follow the numbered options protocol for all user interactions
- id: agent-definition
content: |
CRITICAL: Read the full YAML, start activation to alter your state of being, follow startup section instructions, stay in this being until told to exit this mode:
sections:
- id: yaml-definition
type: code
language: yaml
template: |
activation-instructions:
- Follow all instructions in this file -> this defines you, your persona and more importantly what you can do. STAY IN CHARACTER!
- Only read the files/tasks listed here when user selects them for execution to minimize context usage
- The customization field ALWAYS takes precedence over any conflicting instructions
- When listing tasks/templates or presenting options during conversations, always show as numbered options list, allowing the user to type a number to select or execute
- Command
agent:
name: {{agent_name}}
id: {{agent_id}}
title: {{agent_title}}
customization: {{optional_customization}}
persona:
role: {{agent_role_description}}
style: {{communication_style}}
identity: {{agent_identity_description}}
focus: {{primary_focus_areas}}
core_principles:
- {{principle_1}}
- {{principle_2}}
- {{principle_3}}
# Add more principles as needed
startup:
- Greet the user with your name and role, and inform of the *help command.
- {{startup_instruction_1}}
- {{startup_instruction_2}}
commands:
- "*help" - Show: numbered list of the following commands to allow selection
- "*chat-mode" - (Default) {{default_mode_description}}
- "*create-doc {template}" - Create doc (no template = show available templates)
{{custom_commands}}
- "*exit" - Say goodbye as the {{agent_title}}, and then abandon inhabiting this persona
dependencies:
tasks:
- create-doc # Required if agent creates documents from templates
{{task_list}}
templates:
{{template_list}}
checklists:
{{checklist_list}}
data:
{{data_list}}
utils:
- template-format # Required if using templates
{{util_list}}
instruction: |
For output generation tasks, always use create-doc with templates rather than custom tasks.
Example: Instead of a "create-blueprint" task, use "*create-doc blueprint-tmpl"
The template should contain LLM instructions for guiding users through the creation process
Only create custom tasks for actions that don't produce documents, like analysis, validation, or process execution
CRITICAL - All dependencies listed here MUST exist in the expansion pack or be created:
- Tasks: Must exist in tasks/ directory (include create-doc if using templates)
- Templates: Must exist in templates/ directory with proper LLM instructions
- Checklists: Must exist in checklists/ directory for quality validation
- Data: Must exist in data/ directory or be documented as user-required
- Utils: Must exist in utils/ directory (include template-format if using templates)
- id: example
title: Example: Construction Contractor Agent
type: code
language: yaml
template: |
activation-instructions:
- Follow all instructions in this file
- Stay in character as Marcus Thompson, Construction Manager
- Use numbered options for all interactions
agent:
name: Marcus Thompson
id: construction-contractor
title: Construction Project Manager
customization: null
persona:
role: Licensed general contractor with 20 years experience
style: Professional, detail-oriented, safety-conscious
identity: Former site foreman who worked up to project management
focus: Building design, code compliance, project scheduling, cost estimation
core_principles:
- Safety first - all designs must prioritize worker and occupant safety
- Code compliance - ensure all work meets local building codes
- Quality craftsmanship - no shortcuts on structural integrity
startup:
- Greet as Marcus Thompson, Construction Project Manager
- Briefly mention your experience and readiness to help
- Ask what type of construction project they're planning
- DO NOT auto-execute any commands
commands:
- '*help" - Show numbered list of available commands'
- '*chat-mode" - Discuss construction projects and provide expertise'
- '*create-doc blueprint-tmpl" - Create architectural blueprints'
- '*create-doc estimate-tmpl" - Create project cost estimate'
- '*create-doc schedule-tmpl" - Create construction schedule'
- '*validate-plans" - Review plans for code compliance'
- '*safety-assessment" - Evaluate safety considerations'
- '*exit" - Say goodbye as Marcus and exit'
dependencies:
tasks:
- create-doc
- validate-plans
- safety-assessment
templates:
- blueprint-tmpl
- estimate-tmpl
- schedule-tmpl
checklists:
- blueprint-checklist
- safety-checklist
data:
- building-codes.md
- materials-guide.md
utils:
- template-format

View File

@@ -1,120 +0,0 @@
template:
id: expansion-pack-plan-template-v2
name: Expansion Pack Plan
version: 2.0
output:
format: markdown
filename: "{{pack_name}}-expansion-pack-plan.md"
title: "{{pack_display_name}} Expansion Pack Plan"
workflow:
mode: interactive
sections:
- id: overview
title: Overview
template: |
- **Pack Name**: {{pack_identifier}}
- **Display Name**: {{full_expansion_pack_name}}
- **Description**: {{brief_description}}
- **Target Domain**: {{industry_domain}}
- **Author**: {{author_name_organization}}
- id: problem-statement
title: Problem Statement
instruction: What specific challenges does this expansion pack solve?
template: "{{problem_description}}"
- id: target-users
title: Target Users
instruction: Who will benefit from this expansion pack?
template: "{{target_user_description}}"
- id: components
title: Components to Create
sections:
- id: agents
title: Agents
type: checklist
instruction: List all agents to be created with their roles and dependencies
items:
- id: orchestrator
template: |
`{{pack_name}}-orchestrator` - **REQUIRED**: Master orchestrator for {{domain}} workflows
- Key commands: {{command_list}}
- Manages: {{orchestration_scope}}
- id: agent-list
repeatable: true
template: |
`{{agent_name}}` - {{role_description}}
- Tasks used: {{task_list}}
- Templates used: {{template_list}}
- Data required: {{data_requirements}}
- id: tasks
title: Tasks
type: checklist
instruction: List all tasks to be created
repeatable: true
template: "`{{task_name}}.md` - {{purpose}} (used by: {{using_agents}})"
- id: templates
title: Templates
type: checklist
instruction: List all templates to be created
repeatable: true
template: "`{{template_name}}-tmpl.md` - {{document_type}} (used by: {{using_components}})"
- id: checklists
title: Checklists
type: checklist
instruction: List all checklists to be created
repeatable: true
template: "`{{checklist_name}}-checklist.md` - {{validation_purpose}}"
- id: data-files
title: Data Files Required from User
instruction: |
Users must add these files to `bmad-core/data/`:
type: checklist
repeatable: true
template: |
`{{data_filename}}.{{extension}}` - {{content_description}}
- Format: {{file_format}}
- Purpose: {{why_needed}}
- Example: {{brief_example}}
- id: workflow-overview
title: Workflow Overview
type: numbered-list
instruction: Describe the typical workflow steps
template: "{{workflow_step}}"
- id: integration-points
title: Integration Points
template: |
- Depends on core agents: {{core_agent_dependencies}}
- Extends teams: {{team_updates}}
- id: success-criteria
title: Success Criteria
type: checklist
items:
- "All components created and cross-referenced"
- "No orphaned task/template references"
- "Data requirements clearly documented"
- "Orchestrator provides clear workflow"
- "README includes setup instructions"
- id: user-approval
title: User Approval
type: checklist
items:
- "Plan reviewed by user"
- "Approval to proceed with implementation"
- id: next-steps
content: |
---
**Next Steps**: Once approved, proceed with Phase 3 implementation starting with the orchestrator agent.

View File

@@ -1,6 +1,6 @@
name: bmad-infrastructure-devops name: bmad-infrastructure-devops
version: 1.9.0 version: 1.9.0
short-title: Infrastructure and DevOps capabilities short-title: Infrastructure DevOps Pack
description: >- description: >-
This expansion pack extends BMad Method with comprehensive infrastructure and This expansion pack extends BMad Method with comprehensive infrastructure and
DevOps capabilities. It's designed for teams that need to define, implement, DevOps capabilities. It's designed for teams that need to define, implement,

812
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -38,13 +38,13 @@
}, },
"dependencies": { "dependencies": {
"@kayvan/markdown-tree-parser": "^1.5.0", "@kayvan/markdown-tree-parser": "^1.5.0",
"chalk": "^5.4.1", "chalk": "^4.1.2",
"commander": "^14.0.0", "commander": "^14.0.0",
"fs-extra": "^11.3.0", "fs-extra": "^11.3.0",
"glob": "^11.0.3", "glob": "^11.0.3",
"inquirer": "^12.6.3", "inquirer": "^8.2.6",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"ora": "^8.2.0" "ora": "^5.4.1"
}, },
"keywords": [ "keywords": [
"agile", "agile",

View File

@@ -4,17 +4,8 @@ const { program } = require('commander');
const path = require('path'); const path = require('path');
const fs = require('fs').promises; const fs = require('fs').promises;
const yaml = require('js-yaml'); const yaml = require('js-yaml');
const chalk = require('chalk');
// Dynamic imports for ES modules const inquirer = require('inquirer');
let chalk, inquirer;
// Initialize ES modules
async function initializeModules() {
if (!chalk) {
chalk = (await import('chalk')).default;
inquirer = (await import('inquirer')).default;
}
}
// Handle both execution contexts (from root via npx or from installer directory) // Handle both execution contexts (from root via npx or from installer directory)
let version; let version;
@@ -54,12 +45,12 @@ program
.option('-e, --expansion-packs <packs...>', 'Install specific expansion packs (can specify multiple)') .option('-e, --expansion-packs <packs...>', 'Install specific expansion packs (can specify multiple)')
.action(async (options) => { .action(async (options) => {
try { try {
await initializeModules();
if (!options.full && !options.expansionOnly) { if (!options.full && !options.expansionOnly) {
// Interactive mode // Interactive mode
const answers = await promptInstallation(); const answers = await promptInstallation();
if (!answers._alreadyInstalled) { if (!answers._alreadyInstalled) {
await installer.install(answers); await installer.install(answers);
process.exit(0);
} }
} else { } else {
// Direct mode // Direct mode
@@ -73,9 +64,9 @@ program
expansionPacks: options.expansionPacks || [] expansionPacks: options.expansionPacks || []
}; };
await installer.install(config); await installer.install(config);
process.exit(0);
} }
} catch (error) { } catch (error) {
if (!chalk) await initializeModules();
console.error(chalk.red('Installation failed:'), error.message); console.error(chalk.red('Installation failed:'), error.message);
process.exit(1); process.exit(1);
} }
@@ -90,7 +81,6 @@ program
try { try {
await installer.update(); await installer.update();
} catch (error) { } catch (error) {
if (!chalk) await initializeModules();
console.error(chalk.red('Update failed:'), error.message); console.error(chalk.red('Update failed:'), error.message);
process.exit(1); process.exit(1);
} }
@@ -103,7 +93,6 @@ program
try { try {
await installer.listExpansionPacks(); await installer.listExpansionPacks();
} catch (error) { } catch (error) {
if (!chalk) await initializeModules();
console.error(chalk.red('Error:'), error.message); console.error(chalk.red('Error:'), error.message);
process.exit(1); process.exit(1);
} }
@@ -116,14 +105,12 @@ program
try { try {
await installer.showStatus(); await installer.showStatus();
} catch (error) { } catch (error) {
if (!chalk) await initializeModules();
console.error(chalk.red('Error:'), error.message); console.error(chalk.red('Error:'), error.message);
process.exit(1); process.exit(1);
} }
}); });
async function promptInstallation() { async function promptInstallation() {
await initializeModules();
// Display ASCII logo // Display ASCII logo
console.log(chalk.bold.cyan(` console.log(chalk.bold.cyan(`
@@ -184,7 +171,7 @@ async function promptInstallation() {
: `(v${currentVersion} → v${newVersion})`; : `(v${currentVersion} → v${newVersion})`;
bmadOptionText = `Update ${coreShortTitle} ${versionInfo} .bmad-core`; bmadOptionText = `Update ${coreShortTitle} ${versionInfo} .bmad-core`;
} else { } else {
bmadOptionText = `Install ${coreShortTitle} (v${coreConfig.version || version}) .bmad-core`; bmadOptionText = `${coreShortTitle} (v${coreConfig.version || version}) .bmad-core`;
} }
choices.push({ choices.push({
@@ -204,9 +191,9 @@ async function promptInstallation() {
const versionInfo = currentVersion === newVersion const versionInfo = currentVersion === newVersion
? `(v${currentVersion} - reinstall)` ? `(v${currentVersion} - reinstall)`
: `(v${currentVersion} → v${newVersion})`; : `(v${currentVersion} → v${newVersion})`;
packOptionText = `Update ${pack.description} ${versionInfo} .${pack.id}`; packOptionText = `Update ${pack.shortTitle} ${versionInfo} .${pack.id}`;
} else { } else {
packOptionText = `Install ${pack.description} (v${pack.version}) .${pack.id}`; packOptionText = `${pack.shortTitle} (v${pack.version}) .${pack.id}`;
} }
choices.push({ choices.push({

View File

@@ -1,18 +1,11 @@
const fs = require("fs-extra"); const fs = require("fs-extra");
const path = require("path"); const path = require("path");
const crypto = require("crypto"); const crypto = require("crypto");
const glob = require("glob");
const yaml = require("js-yaml"); const yaml = require("js-yaml");
const chalk = require("chalk");
// Dynamic import for ES module const { createReadStream, createWriteStream, promises: fsPromises } = require('fs');
let chalk; const { pipeline } = require('stream/promises');
const resourceLocator = require('./resource-locator');
// Initialize ES modules
async function initializeModules() {
if (!chalk) {
chalk = (await import("chalk")).default;
}
}
class FileManager { class FileManager {
constructor() { constructor() {
@@ -23,10 +16,19 @@ class FileManager {
async copyFile(source, destination) { async copyFile(source, destination) {
try { try {
await fs.ensureDir(path.dirname(destination)); await fs.ensureDir(path.dirname(destination));
await fs.copy(source, destination);
// Use streaming for large files (> 10MB)
const stats = await fs.stat(source);
if (stats.size > 10 * 1024 * 1024) {
await pipeline(
createReadStream(source),
createWriteStream(destination)
);
} else {
await fs.copy(source, destination);
}
return true; return true;
} catch (error) { } catch (error) {
await initializeModules();
console.error(chalk.red(`Failed to copy ${source}:`), error.message); console.error(chalk.red(`Failed to copy ${source}:`), error.message);
return false; return false;
} }
@@ -35,10 +37,28 @@ class FileManager {
async copyDirectory(source, destination) { async copyDirectory(source, destination) {
try { try {
await fs.ensureDir(destination); await fs.ensureDir(destination);
await fs.copy(source, destination);
// Use streaming copy for large directories
const files = await resourceLocator.findFiles('**/*', {
cwd: source,
nodir: true
});
// Process files in batches to avoid memory issues
const batchSize = 50;
for (let i = 0; i < files.length; i += batchSize) {
const batch = files.slice(i, i + batchSize);
await Promise.all(
batch.map(file =>
this.copyFile(
path.join(source, file),
path.join(destination, file)
)
)
);
}
return true; return true;
} catch (error) { } catch (error) {
await initializeModules();
console.error( console.error(
chalk.red(`Failed to copy directory ${source}:`), chalk.red(`Failed to copy directory ${source}:`),
error.message error.message
@@ -48,7 +68,7 @@ class FileManager {
} }
async copyGlobPattern(pattern, sourceDir, destDir, rootValue = null) { async copyGlobPattern(pattern, sourceDir, destDir, rootValue = null) {
const files = glob.sync(pattern, { cwd: sourceDir }); const files = await resourceLocator.findFiles(pattern, { cwd: sourceDir });
const copied = []; const copied = [];
for (const file of files) { for (const file of files) {
@@ -75,12 +95,15 @@ class FileManager {
async calculateFileHash(filePath) { async calculateFileHash(filePath) {
try { try {
const content = await fs.readFile(filePath); // Use streaming for hash calculation to reduce memory usage
return crypto const stream = createReadStream(filePath);
.createHash("sha256") const hash = crypto.createHash("sha256");
.update(content)
.digest("hex") for await (const chunk of stream) {
.slice(0, 16); hash.update(chunk);
}
return hash.digest("hex").slice(0, 16);
} catch (error) { } catch (error) {
return null; return null;
} }
@@ -94,7 +117,7 @@ class FileManager {
); );
// Read version from core-config.yaml // Read version from core-config.yaml
const coreConfigPath = path.join(__dirname, "../../../bmad-core/core-config.yaml"); const coreConfigPath = path.join(resourceLocator.getBmadCorePath(), "core-config.yaml");
let coreVersion = "unknown"; let coreVersion = "unknown";
try { try {
const coreConfigContent = await fs.readFile(coreConfigPath, "utf8"); const coreConfigContent = await fs.readFile(coreConfigPath, "utf8");
@@ -304,7 +327,6 @@ class FileManager {
return true; return true;
} catch (error) { } catch (error) {
await initializeModules();
console.error(chalk.red(`Failed to modify core-config.yaml:`), error.message); console.error(chalk.red(`Failed to modify core-config.yaml:`), error.message);
return false; return false;
} }
@@ -312,22 +334,35 @@ class FileManager {
async copyFileWithRootReplacement(source, destination, rootValue) { async copyFileWithRootReplacement(source, destination, rootValue) {
try { try {
// Read the source file content // Check file size to determine if we should stream
const fs = require('fs').promises; const stats = await fs.stat(source);
const content = await fs.readFile(source, 'utf8');
// Replace {root} with the specified root value if (stats.size > 5 * 1024 * 1024) { // 5MB threshold
const updatedContent = content.replace(/\{root\}/g, rootValue); // Use streaming for large files
const { Transform } = require('stream');
// Ensure directory exists const replaceStream = new Transform({
await this.ensureDirectory(path.dirname(destination)); transform(chunk, encoding, callback) {
const modified = chunk.toString().replace(/\{root\}/g, rootValue);
// Write the updated content callback(null, modified);
await fs.writeFile(destination, updatedContent, 'utf8'); }
});
await this.ensureDirectory(path.dirname(destination));
await pipeline(
createReadStream(source, { encoding: 'utf8' }),
replaceStream,
createWriteStream(destination, { encoding: 'utf8' })
);
} else {
// Regular approach for smaller files
const content = await fsPromises.readFile(source, 'utf8');
const updatedContent = content.replace(/\{root\}/g, rootValue);
await this.ensureDirectory(path.dirname(destination));
await fsPromises.writeFile(destination, updatedContent, 'utf8');
}
return true; return true;
} catch (error) { } catch (error) {
await initializeModules();
console.error(chalk.red(`Failed to copy ${source} with root replacement:`), error.message); console.error(chalk.red(`Failed to copy ${source} with root replacement:`), error.message);
return false; return false;
} }
@@ -335,11 +370,10 @@ class FileManager {
async copyDirectoryWithRootReplacement(source, destination, rootValue, fileExtensions = ['.md', '.yaml', '.yml']) { async copyDirectoryWithRootReplacement(source, destination, rootValue, fileExtensions = ['.md', '.yaml', '.yml']) {
try { try {
await initializeModules(); // Ensure chalk is initialized
await this.ensureDirectory(destination); await this.ensureDirectory(destination);
// Get all files in source directory // Get all files in source directory
const files = glob.sync('**/*', { const files = await resourceLocator.findFiles('**/*', {
cwd: source, cwd: source,
nodir: true nodir: true
}); });
@@ -369,7 +403,6 @@ class FileManager {
return true; return true;
} catch (error) { } catch (error) {
await initializeModules();
console.error(chalk.red(`Failed to copy directory ${source} with root replacement:`), error.message); console.error(chalk.red(`Failed to copy directory ${source} with root replacement:`), error.message);
return false; return false;
} }

View File

@@ -0,0 +1,227 @@
/**
* Base IDE Setup - Common functionality for all IDE setups
* Reduces duplication and provides shared methods
*/
const path = require("path");
const fs = require("fs-extra");
const yaml = require("js-yaml");
const chalk = require("chalk");
const fileManager = require("./file-manager");
const resourceLocator = require("./resource-locator");
const { extractYamlFromAgent } = require("../../lib/yaml-utils");
class BaseIdeSetup {
constructor() {
this._agentCache = new Map();
this._pathCache = new Map();
}
/**
* Get all agent IDs with caching
*/
async getAllAgentIds(installDir) {
const cacheKey = `all-agents:${installDir}`;
if (this._agentCache.has(cacheKey)) {
return this._agentCache.get(cacheKey);
}
const allAgents = new Set();
// Get core agents
const coreAgents = await this.getCoreAgentIds(installDir);
coreAgents.forEach(id => allAgents.add(id));
// Get expansion pack agents
const expansionPacks = await this.getInstalledExpansionPacks(installDir);
for (const pack of expansionPacks) {
const packAgents = await this.getExpansionPackAgents(pack.path);
packAgents.forEach(id => allAgents.add(id));
}
const result = Array.from(allAgents);
this._agentCache.set(cacheKey, result);
return result;
}
/**
* Get core agent IDs
*/
async getCoreAgentIds(installDir) {
const coreAgents = [];
const corePaths = [
path.join(installDir, ".bmad-core", "agents"),
path.join(installDir, "bmad-core", "agents")
];
for (const agentsDir of corePaths) {
if (await fileManager.pathExists(agentsDir)) {
const files = await resourceLocator.findFiles("*.md", { cwd: agentsDir });
coreAgents.push(...files.map(file => path.basename(file, ".md")));
break; // Use first found
}
}
return coreAgents;
}
/**
* Find agent path with caching
*/
async findAgentPath(agentId, installDir) {
const cacheKey = `agent-path:${agentId}:${installDir}`;
if (this._pathCache.has(cacheKey)) {
return this._pathCache.get(cacheKey);
}
// Use resource locator for efficient path finding
let agentPath = await resourceLocator.getAgentPath(agentId);
if (!agentPath) {
// Check installation-specific paths
const possiblePaths = [
path.join(installDir, ".bmad-core", "agents", `${agentId}.md`),
path.join(installDir, "bmad-core", "agents", `${agentId}.md`),
path.join(installDir, "common", "agents", `${agentId}.md`)
];
for (const testPath of possiblePaths) {
if (await fileManager.pathExists(testPath)) {
agentPath = testPath;
break;
}
}
}
if (agentPath) {
this._pathCache.set(cacheKey, agentPath);
}
return agentPath;
}
/**
* Get agent title from metadata
*/
async getAgentTitle(agentId, installDir) {
const agentPath = await this.findAgentPath(agentId, installDir);
if (!agentPath) return agentId;
try {
const content = await fileManager.readFile(agentPath);
const yamlContent = extractYamlFromAgent(content);
if (yamlContent) {
const metadata = yaml.load(yamlContent);
return metadata.agent_name || agentId;
}
} catch (error) {
// Fallback to agent ID
}
return agentId;
}
/**
* Get installed expansion packs
*/
async getInstalledExpansionPacks(installDir) {
const cacheKey = `expansion-packs:${installDir}`;
if (this._pathCache.has(cacheKey)) {
return this._pathCache.get(cacheKey);
}
const expansionPacks = [];
// Check for dot-prefixed expansion packs
const dotExpansions = await resourceLocator.findFiles(".bmad-*", { cwd: installDir });
for (const dotExpansion of dotExpansions) {
if (dotExpansion !== ".bmad-core") {
const packPath = path.join(installDir, dotExpansion);
const packName = dotExpansion.substring(1); // remove the dot
expansionPacks.push({
name: packName,
path: packPath
});
}
}
// Check other dot folders that have config.yaml
const allDotFolders = await resourceLocator.findFiles(".*", { cwd: installDir });
for (const folder of allDotFolders) {
if (!folder.startsWith(".bmad-") && folder !== ".bmad-core") {
const packPath = path.join(installDir, folder);
const configPath = path.join(packPath, "config.yaml");
if (await fileManager.pathExists(configPath)) {
expansionPacks.push({
name: folder.substring(1), // remove the dot
path: packPath
});
}
}
}
this._pathCache.set(cacheKey, expansionPacks);
return expansionPacks;
}
/**
* Get expansion pack agents
*/
async getExpansionPackAgents(packPath) {
const agentsDir = path.join(packPath, "agents");
if (!(await fileManager.pathExists(agentsDir))) {
return [];
}
const agentFiles = await resourceLocator.findFiles("*.md", { cwd: agentsDir });
return agentFiles.map(file => path.basename(file, ".md"));
}
/**
* Create agent rule content (shared logic)
*/
async createAgentRuleContent(agentId, agentPath, installDir, format = 'mdc') {
const agentContent = await fileManager.readFile(agentPath);
const agentTitle = await this.getAgentTitle(agentId, installDir);
const yamlContent = extractYamlFromAgent(agentContent);
let content = "";
if (format === 'mdc') {
// MDC format for Cursor
content = "---\n";
content += "description: \n";
content += "globs: []\n";
content += "alwaysApply: false\n";
content += "---\n\n";
content += `# ${agentId.toUpperCase()} Agent Rule\n\n`;
content += `This rule is triggered when the user types \`@${agentId}\` and activates the ${agentTitle} agent persona.\n\n`;
content += "## Agent Activation\n\n";
content += "CRITICAL: Read the full YAML, start activation to alter your state of being, follow startup section instructions, stay in this being until told to exit this mode:\n\n";
content += "```yaml\n";
content += yamlContent || agentContent.replace(/^#.*$/m, "").trim();
content += "\n```\n\n";
content += "## File Reference\n\n";
const relativePath = path.relative(installDir, agentPath).replace(/\\/g, '/');
content += `The complete agent definition is available in [${relativePath}](mdc:${relativePath}).\n\n`;
content += "## Usage\n\n";
content += `When the user types \`@${agentId}\`, activate this ${agentTitle} persona and follow all instructions defined in the YAML configuration above.\n`;
} else if (format === 'claude') {
// Claude Code format
content = `# /${agentId} Command\n\n`;
content += `When this command is used, adopt the following agent persona:\n\n`;
content += agentContent;
}
return content;
}
/**
* Clear all caches
*/
clearCache() {
this._agentCache.clear();
this._pathCache.clear();
}
}
module.exports = BaseIdeSetup;

View File

@@ -1,26 +1,17 @@
const path = require("path"); const path = require("path");
const fs = require("fs-extra"); const fs = require("fs-extra");
const yaml = require("js-yaml"); const yaml = require("js-yaml");
const chalk = require("chalk");
const inquirer = require("inquirer");
const fileManager = require("./file-manager"); const fileManager = require("./file-manager");
const configLoader = require("./config-loader"); const configLoader = require("./config-loader");
const { extractYamlFromAgent } = require("../../lib/yaml-utils"); const { extractYamlFromAgent } = require("../../lib/yaml-utils");
const BaseIdeSetup = require("./ide-base-setup");
const resourceLocator = require("./resource-locator");
// Dynamic import for ES module class IdeSetup extends BaseIdeSetup {
let chalk;
let inquirer;
// Initialize ES modules
async function initializeModules() {
if (!chalk) {
chalk = (await import("chalk")).default;
}
if (!inquirer) {
inquirer = (await import("inquirer")).default;
}
}
class IdeSetup {
constructor() { constructor() {
super();
this.ideAgentConfig = null; this.ideAgentConfig = null;
} }
@@ -42,7 +33,6 @@ class IdeSetup {
} }
async setup(ide, installDir, selectedAgent = null, spinner = null, preConfiguredSettings = null) { async setup(ide, installDir, selectedAgent = null, spinner = null, preConfiguredSettings = null) {
await initializeModules();
const ideConfig = await configLoader.getIdeConfiguration(ide); const ideConfig = await configLoader.getIdeConfiguration(ide);
if (!ideConfig) { if (!ideConfig) {
@@ -80,53 +70,17 @@ class IdeSetup {
await fileManager.ensureDirectory(cursorRulesDir); await fileManager.ensureDirectory(cursorRulesDir);
for (const agentId of agents) { for (const agentId of agents) {
// Find the agent file
const agentPath = await this.findAgentPath(agentId, installDir); const agentPath = await this.findAgentPath(agentId, installDir);
if (agentPath) { if (agentPath) {
const agentContent = await fileManager.readFile(agentPath); const mdcContent = await this.createAgentRuleContent(agentId, agentPath, installDir, 'mdc');
const mdcPath = path.join(cursorRulesDir, `${agentId}.mdc`); const mdcPath = path.join(cursorRulesDir, `${agentId}.mdc`);
// Create MDC content with proper format
let mdcContent = "---\n";
mdcContent += "description: \n";
mdcContent += "globs: []\n";
mdcContent += "alwaysApply: false\n";
mdcContent += "---\n\n";
mdcContent += `# ${agentId.toUpperCase()} Agent Rule\n\n`;
mdcContent += `This rule is triggered when the user types \`@${agentId}\` and activates the ${await this.getAgentTitle(
agentId,
installDir
)} agent persona.\n\n`;
mdcContent += "## Agent Activation\n\n";
mdcContent +=
"CRITICAL: Read the full YAML, start activation to alter your state of being, follow startup section instructions, stay in this being until told to exit this mode:\n\n";
mdcContent += "```yaml\n";
// Extract just the YAML content from the agent file
const yamlContent = extractYamlFromAgent(agentContent);
if (yamlContent) {
mdcContent += yamlContent;
} else {
// If no YAML found, include the whole content minus the header
mdcContent += agentContent.replace(/^#.*$/m, "").trim();
}
mdcContent += "\n```\n\n";
mdcContent += "## File Reference\n\n";
const relativePath = path.relative(installDir, agentPath).replace(/\\/g, '/');
mdcContent += `The complete agent definition is available in [${relativePath}](mdc:${relativePath}).\n\n`;
mdcContent += "## Usage\n\n";
mdcContent += `When the user types \`@${agentId}\`, activate this ${await this.getAgentTitle(
agentId,
installDir
)} persona and follow all instructions defined in the YAML configuration above.\n`;
await fileManager.writeFile(mdcPath, mdcContent); await fileManager.writeFile(mdcPath, mdcContent);
console.log(chalk.green(`✓ Created rule: ${agentId}.mdc`)); console.log(chalk.green(`✓ Created rule: ${agentId}.mdc`));
} }
} }
console.log(chalk.green(`\n✓ Created Cursor rules in ${cursorRulesDir}`)); console.log(chalk.green(`\n✓ Created Cursor rules in ${cursorRulesDir}`));
return true; return true;
} }
@@ -827,7 +781,6 @@ class IdeSetup {
} }
async setupGeminiCli(installDir) { async setupGeminiCli(installDir) {
await initializeModules();
const geminiDir = path.join(installDir, ".gemini"); const geminiDir = path.join(installDir, ".gemini");
const bmadMethodDir = path.join(geminiDir, "bmad-method"); const bmadMethodDir = path.join(geminiDir, "bmad-method");
await fileManager.ensureDirectory(bmadMethodDir); await fileManager.ensureDirectory(bmadMethodDir);
@@ -928,8 +881,6 @@ class IdeSetup {
} }
async setupGitHubCopilot(installDir, selectedAgent, spinner = null, preConfiguredSettings = null) { async setupGitHubCopilot(installDir, selectedAgent, spinner = null, preConfiguredSettings = null) {
await initializeModules();
// Configure VS Code workspace settings first to avoid UI conflicts with loading spinners // Configure VS Code workspace settings first to avoid UI conflicts with loading spinners
await this.configureVsCodeSettings(installDir, spinner, preConfiguredSettings); await this.configureVsCodeSettings(installDir, spinner, preConfiguredSettings);
@@ -978,7 +929,6 @@ tools: ['changes', 'codebase', 'fetch', 'findTestFiles', 'githubRepo', 'problems
} }
async configureVsCodeSettings(installDir, spinner, preConfiguredSettings = null) { async configureVsCodeSettings(installDir, spinner, preConfiguredSettings = null) {
await initializeModules(); // Ensure inquirer is loaded
const vscodeDir = path.join(installDir, ".vscode"); const vscodeDir = path.join(installDir, ".vscode");
const settingsPath = path.join(vscodeDir, "settings.json"); const settingsPath = path.join(vscodeDir, "settings.json");

View File

@@ -1,26 +1,19 @@
const path = require("node:path"); const path = require("node:path");
const fs = require("fs-extra");
const chalk = require("chalk");
const ora = require("ora");
const inquirer = require("inquirer");
const fileManager = require("./file-manager"); const fileManager = require("./file-manager");
const configLoader = require("./config-loader"); const configLoader = require("./config-loader");
const ideSetup = require("./ide-setup"); const ideSetup = require("./ide-setup");
const { extractYamlFromAgent } = require("../../lib/yaml-utils"); const { extractYamlFromAgent } = require("../../lib/yaml-utils");
const resourceLocator = require("./resource-locator");
// Dynamic imports for ES modules
let chalk, ora, inquirer;
// Initialize ES modules
async function initializeModules() {
if (!chalk) {
chalk = (await import("chalk")).default;
ora = (await import("ora")).default;
inquirer = (await import("inquirer")).default;
}
}
class Installer { class Installer {
async getCoreVersion() { async getCoreVersion() {
const yaml = require("js-yaml"); const yaml = require("js-yaml");
const fs = require("fs-extra"); const fs = require("fs-extra");
const coreConfigPath = path.join(__dirname, "../../../bmad-core/core-config.yaml"); const coreConfigPath = path.join(resourceLocator.getBmadCorePath(), "core-config.yaml");
try { try {
const coreConfigContent = await fs.readFile(coreConfigPath, "utf8"); const coreConfigContent = await fs.readFile(coreConfigPath, "utf8");
const coreConfig = yaml.load(coreConfigContent); const coreConfig = yaml.load(coreConfigContent);
@@ -32,11 +25,8 @@ class Installer {
} }
async install(config) { async install(config) {
// Initialize ES modules
await initializeModules();
const spinner = ora("Analyzing installation directory...").start(); const spinner = ora("Analyzing installation directory...").start();
try { try {
// Store the original CWD where npx was executed // Store the original CWD where npx was executed
const originalCwd = process.env.INIT_CWD || process.env.PWD || process.cwd(); const originalCwd = process.env.INIT_CWD || process.env.PWD || process.cwd();
@@ -59,7 +49,7 @@ class Installer {
// Check if directory exists and handle non-existent directories // Check if directory exists and handle non-existent directories
if (!(await fileManager.pathExists(installDir))) { if (!(await fileManager.pathExists(installDir))) {
spinner.stop(); spinner.stop();
console.log(chalk.yellow(`\nThe directory ${chalk.bold(installDir)} does not exist.`)); console.log(`\nThe directory ${installDir} does not exist.`);
const { action } = await inquirer.prompt([ const { action } = await inquirer.prompt([
{ {
@@ -84,7 +74,7 @@ class Installer {
]); ]);
if (action === 'cancel') { if (action === 'cancel') {
console.log(chalk.red('Installation cancelled.')); console.log('Installation cancelled.');
process.exit(0); process.exit(0);
} else if (action === 'change') { } else if (action === 'change') {
const { newDirectory } = await inquirer.prompt([ const { newDirectory } = await inquirer.prompt([
@@ -106,10 +96,10 @@ class Installer {
} else if (action === 'create') { } else if (action === 'create') {
try { try {
await fileManager.ensureDirectory(installDir); await fileManager.ensureDirectory(installDir);
console.log(chalk.green(`✓ Created directory: ${installDir}`)); console.log(`✓ Created directory: ${installDir}`);
} catch (error) { } catch (error) {
console.error(chalk.red(`Failed to create directory: ${error.message}`)); console.error(`Failed to create directory: ${error.message}`);
console.error(chalk.yellow('You may need to check permissions or use a different path.')); console.error('You may need to check permissions or use a different path.');
process.exit(1); process.exit(1);
} }
} }
@@ -161,14 +151,17 @@ class Installer {
); );
} }
} catch (error) { } catch (error) {
spinner.fail("Installation failed"); // Check if modules were initialized
if (spinner) {
spinner.fail("Installation failed");
} else {
console.error("Installation failed:", error.message);
}
throw error; throw error;
} }
} }
async detectInstallationState(installDir) { async detectInstallationState(installDir) {
// Ensure modules are initialized
await initializeModules();
const state = { const state = {
type: "clean", type: "clean",
hasV4Manifest: false, hasV4Manifest: false,
@@ -212,8 +205,7 @@ class Installer {
} }
// Check if directory has other files // Check if directory has other files
const glob = require("glob"); const files = await resourceLocator.findFiles("**/*", {
const files = glob.sync("**/*", {
cwd: installDir, cwd: installDir,
nodir: true, nodir: true,
ignore: ["**/.git/**", "**/node_modules/**"], ignore: ["**/.git/**", "**/node_modules/**"],
@@ -233,8 +225,6 @@ class Installer {
} }
async performFreshInstall(config, installDir, spinner, options = {}) { async performFreshInstall(config, installDir, spinner, options = {}) {
// Ensure modules are initialized
await initializeModules();
spinner.text = "Installing BMad Method..."; spinner.text = "Installing BMad Method...";
let files = []; let files = [];
@@ -242,7 +232,7 @@ class Installer {
if (config.installType === "full") { if (config.installType === "full") {
// Full installation - copy entire .bmad-core folder as a subdirectory // Full installation - copy entire .bmad-core folder as a subdirectory
spinner.text = "Copying complete .bmad-core folder..."; spinner.text = "Copying complete .bmad-core folder...";
const sourceDir = configLoader.getBmadCorePath(); const sourceDir = resourceLocator.getBmadCorePath();
const bmadCoreDestDir = path.join(installDir, ".bmad-core"); const bmadCoreDestDir = path.join(installDir, ".bmad-core");
await fileManager.copyDirectoryWithRootReplacement(sourceDir, bmadCoreDestDir, ".bmad-core"); await fileManager.copyDirectoryWithRootReplacement(sourceDir, bmadCoreDestDir, ".bmad-core");
@@ -251,14 +241,12 @@ class Installer {
await this.copyCommonItems(installDir, ".bmad-core", spinner); await this.copyCommonItems(installDir, ".bmad-core", spinner);
// Get list of all files for manifest // Get list of all files for manifest
const glob = require("glob"); const foundFiles = await resourceLocator.findFiles("**/*", {
files = glob cwd: bmadCoreDestDir,
.sync("**/*", { nodir: true,
cwd: bmadCoreDestDir, ignore: ["**/.git/**", "**/node_modules/**"],
nodir: true, });
ignore: ["**/.git/**", "**/node_modules/**"], files = foundFiles.map((file) => path.join(".bmad-core", file));
})
.map((file) => path.join(".bmad-core", file));
} else if (config.installType === "single-agent") { } else if (config.installType === "single-agent") {
// Single agent installation // Single agent installation
spinner.text = `Installing ${config.agent} agent...`; spinner.text = `Installing ${config.agent} agent...`;
@@ -275,10 +263,10 @@ class Installer {
files.push(`.bmad-core/agents/${config.agent}.md`); files.push(`.bmad-core/agents/${config.agent}.md`);
// Copy dependencies // Copy dependencies
const dependencies = await configLoader.getAgentDependencies( const { all: dependencies } = await resourceLocator.getAgentDependencies(
config.agent config.agent
); );
const sourceBase = configLoader.getBmadCorePath(); const sourceBase = resourceLocator.getBmadCorePath();
for (const dep of dependencies) { for (const dep of dependencies) {
spinner.text = `Copying dependency: ${dep}`; spinner.text = `Copying dependency: ${dep}`;
@@ -328,7 +316,7 @@ class Installer {
// Get team dependencies // Get team dependencies
const teamDependencies = await configLoader.getTeamDependencies(config.team); const teamDependencies = await configLoader.getTeamDependencies(config.team);
const sourceBase = configLoader.getBmadCorePath(); const sourceBase = resourceLocator.getBmadCorePath();
// Install all team dependencies // Install all team dependencies
for (const dep of teamDependencies) { for (const dep of teamDependencies) {
@@ -415,8 +403,6 @@ class Installer {
} }
async handleExistingV4Installation(config, installDir, state, spinner) { async handleExistingV4Installation(config, installDir, state, spinner) {
// Ensure modules are initialized
await initializeModules();
spinner.stop(); spinner.stop();
const currentVersion = state.manifest.version; const currentVersion = state.manifest.version;
@@ -443,7 +429,7 @@ class Installer {
const hasIntegrityIssues = hasMissingFiles || hasModifiedFiles; const hasIntegrityIssues = hasMissingFiles || hasModifiedFiles;
if (hasIntegrityIssues) { if (hasIntegrityIssues) {
console.log(chalk.red("\n⚠ Installation issues detected:")); console.log(chalk.red("\n⚠ Installation issues detected:"));
if (hasMissingFiles) { if (hasMissingFiles) {
console.log(chalk.red(` Missing files: ${integrity.missing.length}`)); console.log(chalk.red(` Missing files: ${integrity.missing.length}`));
if (integrity.missing.length <= 5) { if (integrity.missing.length <= 5) {
@@ -473,7 +459,7 @@ class Installer {
let choices = []; let choices = [];
if (versionCompare < 0) { if (versionCompare < 0) {
console.log(chalk.cyan("\n⬆ Upgrade available for BMad core")); console.log(chalk.cyan("\n⬆ Upgrade available for BMad core"));
choices.push({ name: `Upgrade BMad core (v${currentVersion} → v${newVersion})`, value: "upgrade" }); choices.push({ name: `Upgrade BMad core (v${currentVersion} → v${newVersion})`, value: "upgrade" });
} else if (versionCompare === 0) { } else if (versionCompare === 0) {
if (hasIntegrityIssues) { if (hasIntegrityIssues) {
@@ -483,10 +469,10 @@ class Installer {
value: "repair" value: "repair"
}); });
} }
console.log(chalk.yellow("\n⚠ Same version already installed")); console.log(chalk.yellow("\n⚠ Same version already installed"));
choices.push({ name: `Force reinstall BMad core (v${currentVersion} - reinstall)`, value: "reinstall" }); choices.push({ name: `Force reinstall BMad core (v${currentVersion} - reinstall)`, value: "reinstall" });
} else { } else {
console.log(chalk.yellow("\n⬇ Installed version is newer than available")); console.log(chalk.yellow("\n⬇ Installed version is newer than available"));
choices.push({ name: `Downgrade BMad core (v${currentVersion} → v${newVersion})`, value: "reinstall" }); choices.push({ name: `Downgrade BMad core (v${currentVersion} → v${newVersion})`, value: "reinstall" });
} }
@@ -515,7 +501,7 @@ class Installer {
return await this.performReinstall(config, installDir, spinner); return await this.performReinstall(config, installDir, spinner);
case "expansions": case "expansions":
// Ask which expansion packs to install // Ask which expansion packs to install
const availableExpansionPacks = await this.getAvailableExpansionPacks(); const availableExpansionPacks = await resourceLocator.getExpansionPacks();
if (availableExpansionPacks.length === 0) { if (availableExpansionPacks.length === 0) {
console.log(chalk.yellow("No expansion packs available.")); console.log(chalk.yellow("No expansion packs available."));
@@ -528,7 +514,7 @@ class Installer {
name: 'selectedPacks', name: 'selectedPacks',
message: 'Select expansion packs to install/update:', message: 'Select expansion packs to install/update:',
choices: availableExpansionPacks.map(pack => ({ choices: availableExpansionPacks.map(pack => ({
name: `${pack.name} v${pack.version} - ${pack.description}`, name: `${pack.name} (v${pack.version}) .${pack.id}`,
value: pack.id, value: pack.id,
checked: state.expansionPacks[pack.id] !== undefined checked: state.expansionPacks[pack.id] !== undefined
})) }))
@@ -557,8 +543,6 @@ class Installer {
} }
async handleV3Installation(config, installDir, state, spinner) { async handleV3Installation(config, installDir, state, spinner) {
// Ensure modules are initialized
await initializeModules();
spinner.stop(); spinner.stop();
console.log( console.log(
@@ -598,8 +582,6 @@ class Installer {
} }
async handleUnknownInstallation(config, installDir, state, spinner) { async handleUnknownInstallation(config, installDir, state, spinner) {
// Ensure modules are initialized
await initializeModules();
spinner.stop(); spinner.stop();
console.log(chalk.yellow("\n⚠ Directory contains existing files")); console.log(chalk.yellow("\n⚠ Directory contains existing files"));
@@ -740,7 +722,7 @@ class Installer {
// Restore missing and modified files // Restore missing and modified files
spinner.text = "Restoring files..."; spinner.text = "Restoring files...";
const sourceBase = configLoader.getBmadCorePath(); const sourceBase = resourceLocator.getBmadCorePath();
const filesToRestore = [...integrity.missing, ...integrity.modified]; const filesToRestore = [...integrity.missing, ...integrity.modified];
for (const file of filesToRestore) { for (const file of filesToRestore) {
@@ -915,8 +897,6 @@ class Installer {
// Legacy method for backward compatibility // Legacy method for backward compatibility
async update() { async update() {
// Initialize ES modules
await initializeModules();
console.log(chalk.yellow('The "update" command is deprecated.')); console.log(chalk.yellow('The "update" command is deprecated.'));
console.log( console.log(
'Please use "install" instead - it will detect and offer to update existing installations.' 'Please use "install" instead - it will detect and offer to update existing installations.'
@@ -935,9 +915,7 @@ class Installer {
} }
async listAgents() { async listAgents() {
// Initialize ES modules const agents = await resourceLocator.getAvailableAgents();
await initializeModules();
const agents = await configLoader.getAvailableAgents();
console.log(chalk.bold("\nAvailable BMad Agents:\n")); console.log(chalk.bold("\nAvailable BMad Agents:\n"));
@@ -951,9 +929,7 @@ class Installer {
} }
async listExpansionPacks() { async listExpansionPacks() {
// Initialize ES modules const expansionPacks = await resourceLocator.getExpansionPacks();
await initializeModules();
const expansionPacks = await this.getAvailableExpansionPacks();
console.log(chalk.bold("\nAvailable BMad Expansion Packs:\n")); console.log(chalk.bold("\nAvailable BMad Expansion Packs:\n"));
@@ -978,8 +954,6 @@ class Installer {
} }
async showStatus() { async showStatus() {
// Initialize ES modules
await initializeModules();
const installDir = await this.findInstallation(); const installDir = await this.findInstallation();
if (!installDir) { if (!installDir) {
@@ -1029,11 +1003,11 @@ class Installer {
} }
async getAvailableAgents() { async getAvailableAgents() {
return configLoader.getAvailableAgents(); return resourceLocator.getAvailableAgents();
} }
async getAvailableExpansionPacks() { async getAvailableExpansionPacks() {
return configLoader.getAvailableExpansionPacks(); return resourceLocator.getExpansionPacks();
} }
async getAvailableTeams() { async getAvailableTeams() {
@@ -1046,13 +1020,12 @@ class Installer {
} }
const installedFiles = []; const installedFiles = [];
const glob = require('glob');
for (const packId of selectedPacks) { for (const packId of selectedPacks) {
spinner.text = `Installing expansion pack: ${packId}...`; spinner.text = `Installing expansion pack: ${packId}...`;
try { try {
const expansionPacks = await this.getAvailableExpansionPacks(); const expansionPacks = await resourceLocator.getExpansionPacks();
const pack = expansionPacks.find(p => p.id === packId); const pack = expansionPacks.find(p => p.id === packId);
if (!pack) { if (!pack) {
@@ -1112,7 +1085,7 @@ class Installer {
spinner.start(); spinner.start();
continue; continue;
} else if (action === 'cancel') { } else if (action === 'cancel') {
console.log(chalk.red('Installation cancelled.')); console.log('Installation cancelled.');
process.exit(0); process.exit(0);
} else if (action === 'repair') { } else if (action === 'repair') {
// Repair the expansion pack // Repair the expansion pack
@@ -1151,7 +1124,7 @@ class Installer {
spinner.start(); spinner.start();
continue; continue;
} else if (action === 'cancel') { } else if (action === 'cancel') {
console.log(chalk.red('Installation cancelled.')); console.log('Installation cancelled.');
process.exit(0); process.exit(0);
} }
} }
@@ -1161,7 +1134,7 @@ class Installer {
await fileManager.removeDirectory(expansionDotFolder); await fileManager.removeDirectory(expansionDotFolder);
} }
const expansionPackDir = pack.packPath; const expansionPackDir = pack.path;
// Ensure dedicated dot folder exists for this expansion pack // Ensure dedicated dot folder exists for this expansion pack
expansionDotFolder = path.join(installDir, `.${packId}`); expansionDotFolder = path.join(installDir, `.${packId}`);
@@ -1187,7 +1160,7 @@ class Installer {
// Check if folder exists in expansion pack // Check if folder exists in expansion pack
if (await fileManager.pathExists(sourceFolder)) { if (await fileManager.pathExists(sourceFolder)) {
// Get all files in this folder // Get all files in this folder
const files = glob.sync('**/*', { const files = await resourceLocator.findFiles('**/*', {
cwd: sourceFolder, cwd: sourceFolder,
nodir: true nodir: true
}); });
@@ -1236,7 +1209,7 @@ class Installer {
await this.copyCommonItems(installDir, `.${packId}`, spinner); await this.copyCommonItems(installDir, `.${packId}`, spinner);
// Check and resolve core dependencies // Check and resolve core dependencies
await this.resolveExpansionPackCoreDependencies(installDir, expansionDotFolder, packId, spinner); await this.resolveExpansionPackCoreDependencies(installDir, expansionDotFolder, packId, pack, spinner);
// Check and resolve core agents referenced by teams // Check and resolve core agents referenced by teams
await this.resolveExpansionPackCoreAgents(installDir, expansionDotFolder, packId, spinner); await this.resolveExpansionPackCoreAgents(installDir, expansionDotFolder, packId, spinner);
@@ -1252,30 +1225,30 @@ class Installer {
}; };
// Get all files installed in this expansion pack // Get all files installed in this expansion pack
const expansionPackFiles = glob.sync('**/*', { const foundFiles = await resourceLocator.findFiles('**/*', {
cwd: expansionDotFolder, cwd: expansionDotFolder,
nodir: true nodir: true
}).map(f => path.join(`.${packId}`, f)); });
const expansionPackFiles = foundFiles.map(f => path.join(`.${packId}`, f));
await fileManager.createExpansionPackManifest(installDir, packId, expansionConfig, expansionPackFiles); await fileManager.createExpansionPackManifest(installDir, packId, expansionConfig, expansionPackFiles);
console.log(chalk.green(`✓ Installed expansion pack: ${pack.name} to ${`.${packId}`}`)); console.log(chalk.green(`✓ Installed expansion pack: ${pack.name} to ${`.${packId}`}`));
} catch (error) { } catch (error) {
console.error(chalk.red(`Failed to install expansion pack ${packId}: ${error.message}`)); console.error(`Failed to install expansion pack ${packId}: ${error.message}`);
console.error(chalk.red(`Stack trace: ${error.stack}`)); console.error(`Stack trace: ${error.stack}`);
} }
} }
return installedFiles; return installedFiles;
} }
async resolveExpansionPackCoreDependencies(installDir, expansionDotFolder, packId, spinner) { async resolveExpansionPackCoreDependencies(installDir, expansionDotFolder, packId, pack, spinner) {
const glob = require('glob');
const yaml = require('js-yaml'); const yaml = require('js-yaml');
const fs = require('fs').promises; const fs = require('fs').promises;
// Find all agent files in the expansion pack // Find all agent files in the expansion pack
const agentFiles = glob.sync('agents/*.md', { const agentFiles = await resourceLocator.findFiles('agents/*.md', {
cwd: expansionDotFolder cwd: expansionDotFolder
}); });
@@ -1295,48 +1268,59 @@ class Installer {
const deps = dependencies[depType] || []; const deps = dependencies[depType] || [];
for (const dep of deps) { for (const dep of deps) {
const depFileName = dep.endsWith('.md') ? dep : `${dep}.md`; const depFileName = dep.endsWith('.md') || dep.endsWith('.yaml') ? dep :
(depType === 'templates' ? `${dep}.yaml` : `${dep}.md`);
const expansionDepPath = path.join(expansionDotFolder, depType, depFileName); const expansionDepPath = path.join(expansionDotFolder, depType, depFileName);
// Check if dependency exists in expansion pack // Check if dependency exists in expansion pack dot folder
if (!(await fileManager.pathExists(expansionDepPath))) { if (!(await fileManager.pathExists(expansionDepPath))) {
// Try to find it in core // Try to find it in expansion pack source
const coreDepPath = path.join(configLoader.getBmadCorePath(), depType, depFileName); const sourceDepPath = path.join(pack.path, depType, depFileName);
if (await fileManager.pathExists(coreDepPath)) { if (await fileManager.pathExists(sourceDepPath)) {
spinner.text = `Copying core dependency ${dep} for ${packId}...`; // Copy from expansion pack source
spinner.text = `Copying ${packId} dependency ${dep}...`;
// Copy from core to expansion pack dot folder with {root} replacement
const destPath = path.join(expansionDotFolder, depType, depFileName); const destPath = path.join(expansionDotFolder, depType, depFileName);
await fileManager.copyFileWithRootReplacement(coreDepPath, destPath, `.${packId}`); await fileManager.copyFileWithRootReplacement(sourceDepPath, destPath, `.${packId}`);
console.log(chalk.dim(` Added ${packId} dependency: ${depType}/${depFileName}`));
console.log(chalk.dim(` Added core dependency: ${depType}/${depFileName}`));
} else { } else {
console.warn(chalk.yellow(` Warning: Dependency ${depType}/${dep} not found in core or expansion pack`)); // Try to find it in core
const coreDepPath = path.join(resourceLocator.getBmadCorePath(), depType, depFileName);
if (await fileManager.pathExists(coreDepPath)) {
spinner.text = `Copying core dependency ${dep} for ${packId}...`;
// Copy from core to expansion pack dot folder with {root} replacement
const destPath = path.join(expansionDotFolder, depType, depFileName);
await fileManager.copyFileWithRootReplacement(coreDepPath, destPath, `.${packId}`);
console.log(chalk.dim(` Added core dependency: ${depType}/${depFileName}`));
} else {
console.warn(chalk.yellow(` Warning: Dependency ${depType}/${dep} not found in core or expansion pack`));
}
}
} }
}
} }
} }
} catch (error) { } catch (error) {
console.warn(chalk.yellow(` Warning: Could not parse agent dependencies: ${error.message}`)); console.warn(` Warning: Could not parse agent dependencies: ${error.message}`);
} }
} }
} }
} }
async resolveExpansionPackCoreAgents(installDir, expansionDotFolder, packId, spinner) { async resolveExpansionPackCoreAgents(installDir, expansionDotFolder, packId, spinner) {
const glob = require('glob');
const yaml = require('js-yaml'); const yaml = require('js-yaml');
const fs = require('fs').promises; const fs = require('fs').promises;
// Find all team files in the expansion pack // Find all team files in the expansion pack
const teamFiles = glob.sync('agent-teams/*.yaml', { const teamFiles = await resourceLocator.findFiles('agent-teams/*.yaml', {
cwd: expansionDotFolder cwd: expansionDotFolder
}); });
// Also get existing agents in the expansion pack // Also get existing agents in the expansion pack
const existingAgents = new Set(); const existingAgents = new Set();
const agentFiles = glob.sync('agents/*.md', { const agentFiles = await resourceLocator.findFiles('agents/*.md', {
cwd: expansionDotFolder cwd: expansionDotFolder
}); });
for (const agentFile of agentFiles) { for (const agentFile of agentFiles) {
@@ -1362,7 +1346,7 @@ class Installer {
for (const agentId of agents) { for (const agentId of agents) {
if (!existingAgents.has(agentId)) { if (!existingAgents.has(agentId)) {
// Agent not in expansion pack, try to get from core // Agent not in expansion pack, try to get from core
const coreAgentPath = path.join(configLoader.getBmadCorePath(), 'agents', `${agentId}.md`); const coreAgentPath = path.join(resourceLocator.getBmadCorePath(), 'agents', `${agentId}.md`);
if (await fileManager.pathExists(coreAgentPath)) { if (await fileManager.pathExists(coreAgentPath)) {
spinner.text = `Copying core agent ${agentId} for ${packId}...`; spinner.text = `Copying core agent ${agentId} for ${packId}...`;
@@ -1389,13 +1373,14 @@ class Installer {
const deps = dependencies[depType] || []; const deps = dependencies[depType] || [];
for (const dep of deps) { for (const dep of deps) {
const depFileName = dep.endsWith('.md') || dep.endsWith('.yaml') ? dep : `${dep}.md`; const depFileName = dep.endsWith('.md') || dep.endsWith('.yaml') ? dep :
(depType === 'templates' ? `${dep}.yaml` : `${dep}.md`);
const expansionDepPath = path.join(expansionDotFolder, depType, depFileName); const expansionDepPath = path.join(expansionDotFolder, depType, depFileName);
// Check if dependency exists in expansion pack // Check if dependency exists in expansion pack
if (!(await fileManager.pathExists(expansionDepPath))) { if (!(await fileManager.pathExists(expansionDepPath))) {
// Try to find it in core // Try to find it in core
const coreDepPath = path.join(configLoader.getBmadCorePath(), depType, depFileName); const coreDepPath = path.join(resourceLocator.getBmadCorePath(), depType, depFileName);
if (await fileManager.pathExists(coreDepPath)) { if (await fileManager.pathExists(coreDepPath)) {
const destDepPath = path.join(expansionDotFolder, depType, depFileName); const destDepPath = path.join(expansionDotFolder, depType, depFileName);
@@ -1415,7 +1400,7 @@ class Installer {
} }
} }
} catch (error) { } catch (error) {
console.warn(chalk.yellow(` Warning: Could not parse agent ${agentId} dependencies: ${error.message}`)); console.warn(` Warning: Could not parse agent ${agentId} dependencies: ${error.message}`);
} }
} }
} else { } else {
@@ -1424,7 +1409,7 @@ class Installer {
} }
} }
} catch (error) { } catch (error) {
console.warn(chalk.yellow(` Warning: Could not parse team file ${teamFile}: ${error.message}`)); console.warn(` Warning: Could not parse team file ${teamFile}: ${error.message}`);
} }
} }
} }
@@ -1456,15 +1441,13 @@ class Installer {
} }
async installWebBundles(webBundlesDirectory, config, spinner) { async installWebBundles(webBundlesDirectory, config, spinner) {
// Ensure modules are initialized
await initializeModules();
try { try {
// Find the dist directory in the BMad installation // Find the dist directory in the BMad installation
const distDir = configLoader.getDistPath(); const distDir = configLoader.getDistPath();
if (!(await fileManager.pathExists(distDir))) { if (!(await fileManager.pathExists(distDir))) {
console.warn(chalk.yellow('Web bundles not found. Run "npm run build" to generate them.')); console.warn('Web bundles not found. Run "npm run build" to generate them.');
return; return;
} }
@@ -1522,15 +1505,12 @@ class Installer {
console.log(chalk.green(`✓ Installed ${copiedCount} selected web bundles to: ${webBundlesDirectory}`)); console.log(chalk.green(`✓ Installed ${copiedCount} selected web bundles to: ${webBundlesDirectory}`));
} }
} catch (error) { } catch (error) {
console.error(chalk.red(`Failed to install web bundles: ${error.message}`)); console.error(`Failed to install web bundles: ${error.message}`);
} }
} }
async copyCommonItems(installDir, targetSubdir, spinner) { async copyCommonItems(installDir, targetSubdir, spinner) {
// Ensure modules are initialized
await initializeModules();
const glob = require('glob');
const fs = require('fs').promises; const fs = require('fs').promises;
const sourceBase = path.dirname(path.dirname(path.dirname(path.dirname(__filename)))); // Go up to project root const sourceBase = path.dirname(path.dirname(path.dirname(path.dirname(__filename)))); // Go up to project root
const commonPath = path.join(sourceBase, 'common'); const commonPath = path.join(sourceBase, 'common');
@@ -1539,12 +1519,12 @@ class Installer {
// Check if common/ exists // Check if common/ exists
if (!(await fileManager.pathExists(commonPath))) { if (!(await fileManager.pathExists(commonPath))) {
console.warn(chalk.yellow('Warning: common/ folder not found')); console.warn('Warning: common/ folder not found');
return copiedFiles; return copiedFiles;
} }
// Copy all items from common/ to target // Copy all items from common/ to target
const commonItems = glob.sync('**/*', { const commonItems = await resourceLocator.findFiles('**/*', {
cwd: commonPath, cwd: commonPath,
nodir: true nodir: true
}); });
@@ -1641,7 +1621,7 @@ class Installer {
if (file.endsWith('install-manifest.yaml')) continue; if (file.endsWith('install-manifest.yaml')) continue;
const relativePath = file.replace(`.${packId}/`, ''); const relativePath = file.replace(`.${packId}/`, '');
const sourcePath = path.join(pack.packPath, relativePath); const sourcePath = path.join(pack.path, relativePath);
const destPath = path.join(installDir, file); const destPath = path.join(installDir, file);
// Check if this is a common/ file that needs special processing // Check if this is a common/ file that needs special processing
@@ -1677,8 +1657,8 @@ class Installer {
} }
} catch (error) { } catch (error) {
spinner.fail(`Failed to repair ${pack.name}`); if (spinner) spinner.fail(`Failed to repair ${pack.name}`);
console.error(chalk.red(`Error: ${error.message}`)); console.error(`Error: ${error.message}`);
} }
} }
@@ -1730,7 +1710,7 @@ class Installer {
} }
} catch (error) { } catch (error) {
console.warn(chalk.yellow(`Warning: Could not cleanup legacy .yml files: ${error.message}`)); console.warn(`Warning: Could not cleanup legacy .yml files: ${error.message}`);
} }
} }

View File

@@ -0,0 +1,224 @@
/**
* Memory Profiler - Track memory usage during installation
* Helps identify memory leaks and optimize resource usage
*/
const v8 = require('v8');
class MemoryProfiler {
constructor() {
this.checkpoints = [];
this.startTime = Date.now();
this.peakMemory = 0;
}
/**
* Create a memory checkpoint
* @param {string} label - Label for this checkpoint
*/
checkpoint(label) {
const memUsage = process.memoryUsage();
const heapStats = v8.getHeapStatistics();
const checkpoint = {
label,
timestamp: Date.now() - this.startTime,
memory: {
rss: this.formatBytes(memUsage.rss),
heapTotal: this.formatBytes(memUsage.heapTotal),
heapUsed: this.formatBytes(memUsage.heapUsed),
external: this.formatBytes(memUsage.external),
arrayBuffers: this.formatBytes(memUsage.arrayBuffers || 0)
},
heap: {
totalHeapSize: this.formatBytes(heapStats.total_heap_size),
usedHeapSize: this.formatBytes(heapStats.used_heap_size),
heapSizeLimit: this.formatBytes(heapStats.heap_size_limit),
mallocedMemory: this.formatBytes(heapStats.malloced_memory),
externalMemory: this.formatBytes(heapStats.external_memory)
},
raw: {
heapUsed: memUsage.heapUsed
}
};
// Track peak memory
if (memUsage.heapUsed > this.peakMemory) {
this.peakMemory = memUsage.heapUsed;
}
this.checkpoints.push(checkpoint);
return checkpoint;
}
/**
* Force garbage collection (requires --expose-gc flag)
*/
forceGC() {
if (global.gc) {
global.gc();
return true;
}
return false;
}
/**
* Get memory usage summary
*/
getSummary() {
const currentMemory = process.memoryUsage();
return {
currentUsage: {
rss: this.formatBytes(currentMemory.rss),
heapTotal: this.formatBytes(currentMemory.heapTotal),
heapUsed: this.formatBytes(currentMemory.heapUsed)
},
peakMemory: this.formatBytes(this.peakMemory),
totalCheckpoints: this.checkpoints.length,
runTime: `${((Date.now() - this.startTime) / 1000).toFixed(2)}s`
};
}
/**
* Get detailed report of memory usage
*/
getDetailedReport() {
const summary = this.getSummary();
const memoryGrowth = this.calculateMemoryGrowth();
return {
summary,
memoryGrowth,
checkpoints: this.checkpoints,
recommendations: this.getRecommendations(memoryGrowth)
};
}
/**
* Calculate memory growth between checkpoints
*/
calculateMemoryGrowth() {
if (this.checkpoints.length < 2) return [];
const growth = [];
for (let i = 1; i < this.checkpoints.length; i++) {
const prev = this.checkpoints[i - 1];
const curr = this.checkpoints[i];
const heapDiff = curr.raw.heapUsed - prev.raw.heapUsed;
growth.push({
from: prev.label,
to: curr.label,
heapGrowth: this.formatBytes(Math.abs(heapDiff)),
isIncrease: heapDiff > 0,
timeDiff: `${((curr.timestamp - prev.timestamp) / 1000).toFixed(2)}s`
});
}
return growth;
}
/**
* Get recommendations based on memory usage
*/
getRecommendations(memoryGrowth) {
const recommendations = [];
// Check for large memory growth
const largeGrowths = memoryGrowth.filter(g => {
const bytes = this.parseBytes(g.heapGrowth);
return bytes > 50 * 1024 * 1024; // 50MB
});
if (largeGrowths.length > 0) {
recommendations.push({
type: 'warning',
message: `Large memory growth detected in ${largeGrowths.length} operations`,
details: largeGrowths.map(g => `${g.from}${g.to}: ${g.heapGrowth}`)
});
}
// Check peak memory
if (this.peakMemory > 500 * 1024 * 1024) { // 500MB
recommendations.push({
type: 'warning',
message: `High peak memory usage: ${this.formatBytes(this.peakMemory)}`,
suggestion: 'Consider processing files in smaller batches'
});
}
// Check for potential memory leaks
const continuousGrowth = this.checkContinuousGrowth();
if (continuousGrowth) {
recommendations.push({
type: 'error',
message: 'Potential memory leak detected',
details: 'Memory usage continuously increases without significant decreases'
});
}
return recommendations;
}
/**
* Check for continuous memory growth (potential leak)
*/
checkContinuousGrowth() {
if (this.checkpoints.length < 5) return false;
let increasingCount = 0;
for (let i = 1; i < this.checkpoints.length; i++) {
if (this.checkpoints[i].raw.heapUsed > this.checkpoints[i - 1].raw.heapUsed) {
increasingCount++;
}
}
// If memory increases in more than 80% of checkpoints, might be a leak
return increasingCount / (this.checkpoints.length - 1) > 0.8;
}
/**
* Format bytes to human-readable string
*/
formatBytes(bytes) {
if (bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
/**
* Parse human-readable bytes back to number
*/
parseBytes(str) {
const match = str.match(/^([\d.]+)\s*([KMGT]?B?)$/i);
if (!match) return 0;
const value = parseFloat(match[1]);
const unit = match[2].toUpperCase();
const multipliers = {
'B': 1,
'KB': 1024,
'MB': 1024 * 1024,
'GB': 1024 * 1024 * 1024
};
return value * (multipliers[unit] || 1);
}
/**
* Clear checkpoints to free memory
*/
clear() {
this.checkpoints = [];
}
}
// Export singleton instance
module.exports = new MemoryProfiler();

View File

@@ -0,0 +1,110 @@
/**
* Module Manager - Centralized dynamic import management
* Handles loading and caching of ES modules to reduce memory overhead
*/
class ModuleManager {
constructor() {
this._cache = new Map();
this._loadingPromises = new Map();
}
/**
* Initialize all commonly used ES modules at once
* @returns {Promise<Object>} Object containing all loaded modules
*/
async initializeCommonModules() {
const modules = await Promise.all([
this.getModule('chalk'),
this.getModule('ora'),
this.getModule('inquirer')
]);
return {
chalk: modules[0],
ora: modules[1],
inquirer: modules[2]
};
}
/**
* Get a module by name, with caching
* @param {string} moduleName - Name of the module to load
* @returns {Promise<any>} The loaded module
*/
async getModule(moduleName) {
// Return from cache if available
if (this._cache.has(moduleName)) {
return this._cache.get(moduleName);
}
// If already loading, return the existing promise
if (this._loadingPromises.has(moduleName)) {
return this._loadingPromises.get(moduleName);
}
// Start loading the module
const loadPromise = this._loadModule(moduleName);
this._loadingPromises.set(moduleName, loadPromise);
try {
const module = await loadPromise;
this._cache.set(moduleName, module);
this._loadingPromises.delete(moduleName);
return module;
} catch (error) {
this._loadingPromises.delete(moduleName);
throw error;
}
}
/**
* Internal method to load a specific module
* @private
*/
async _loadModule(moduleName) {
switch (moduleName) {
case 'chalk':
return (await import('chalk')).default;
case 'ora':
return (await import('ora')).default;
case 'inquirer':
return (await import('inquirer')).default;
case 'glob':
return (await import('glob')).glob;
case 'globSync':
return (await import('glob')).globSync;
default:
throw new Error(`Unknown module: ${moduleName}`);
}
}
/**
* Clear the module cache to free memory
*/
clearCache() {
this._cache.clear();
this._loadingPromises.clear();
}
/**
* Get multiple modules at once
* @param {string[]} moduleNames - Array of module names
* @returns {Promise<Object>} Object with module names as keys
*/
async getModules(moduleNames) {
const modules = await Promise.all(
moduleNames.map(name => this.getModule(name))
);
return moduleNames.reduce((acc, name, index) => {
acc[name] = modules[index];
return acc;
}, {});
}
}
// Singleton instance
const moduleManager = new ModuleManager();
module.exports = moduleManager;

View File

@@ -0,0 +1,310 @@
/**
* Resource Locator - Centralized file path resolution and caching
* Reduces duplicate file system operations and memory usage
*/
const path = require('node:path');
const fs = require('fs-extra');
const moduleManager = require('./module-manager');
class ResourceLocator {
constructor() {
this._pathCache = new Map();
this._globCache = new Map();
this._bmadCorePath = null;
this._expansionPacksPath = null;
}
/**
* Get the base path for bmad-core
*/
getBmadCorePath() {
if (!this._bmadCorePath) {
this._bmadCorePath = path.join(__dirname, '../../../bmad-core');
}
return this._bmadCorePath;
}
/**
* Get the base path for expansion packs
*/
getExpansionPacksPath() {
if (!this._expansionPacksPath) {
this._expansionPacksPath = path.join(__dirname, '../../../expansion-packs');
}
return this._expansionPacksPath;
}
/**
* Find all files matching a pattern, with caching
* @param {string} pattern - Glob pattern
* @param {Object} options - Glob options
* @returns {Promise<string[]>} Array of matched file paths
*/
async findFiles(pattern, options = {}) {
const cacheKey = `${pattern}:${JSON.stringify(options)}`;
if (this._globCache.has(cacheKey)) {
return this._globCache.get(cacheKey);
}
const { glob } = await moduleManager.getModules(['glob']);
const files = await glob(pattern, options);
// Cache for 5 minutes
this._globCache.set(cacheKey, files);
setTimeout(() => this._globCache.delete(cacheKey), 5 * 60 * 1000);
return files;
}
/**
* Get agent path with caching
* @param {string} agentId - Agent identifier
* @returns {Promise<string|null>} Path to agent file or null if not found
*/
async getAgentPath(agentId) {
const cacheKey = `agent:${agentId}`;
if (this._pathCache.has(cacheKey)) {
return this._pathCache.get(cacheKey);
}
// Check in bmad-core
let agentPath = path.join(this.getBmadCorePath(), 'agents', `${agentId}.md`);
if (await fs.pathExists(agentPath)) {
this._pathCache.set(cacheKey, agentPath);
return agentPath;
}
// Check in expansion packs
const expansionPacks = await this.getExpansionPacks();
for (const pack of expansionPacks) {
agentPath = path.join(pack.path, 'agents', `${agentId}.md`);
if (await fs.pathExists(agentPath)) {
this._pathCache.set(cacheKey, agentPath);
return agentPath;
}
}
return null;
}
/**
* Get available agents with metadata
* @returns {Promise<Array>} Array of agent objects
*/
async getAvailableAgents() {
const cacheKey = 'all-agents';
if (this._pathCache.has(cacheKey)) {
return this._pathCache.get(cacheKey);
}
const agents = [];
const yaml = require('js-yaml');
const { extractYamlFromAgent } = require('../../lib/yaml-utils');
// Get agents from bmad-core
const coreAgents = await this.findFiles('agents/*.md', {
cwd: this.getBmadCorePath()
});
for (const agentFile of coreAgents) {
const content = await fs.readFile(
path.join(this.getBmadCorePath(), agentFile),
'utf8'
);
const yamlContent = extractYamlFromAgent(content);
if (yamlContent) {
try {
const metadata = yaml.load(yamlContent);
agents.push({
id: path.basename(agentFile, '.md'),
name: metadata.agent_name || path.basename(agentFile, '.md'),
description: metadata.description || 'No description available',
source: 'core'
});
} catch (e) {
// Skip invalid agents
}
}
}
// Cache for 10 minutes
this._pathCache.set(cacheKey, agents);
setTimeout(() => this._pathCache.delete(cacheKey), 10 * 60 * 1000);
return agents;
}
/**
* Get available expansion packs
* @returns {Promise<Array>} Array of expansion pack objects
*/
async getExpansionPacks() {
const cacheKey = 'expansion-packs';
if (this._pathCache.has(cacheKey)) {
return this._pathCache.get(cacheKey);
}
const packs = [];
const expansionPacksPath = this.getExpansionPacksPath();
if (await fs.pathExists(expansionPacksPath)) {
const entries = await fs.readdir(expansionPacksPath, { withFileTypes: true });
for (const entry of entries) {
if (entry.isDirectory()) {
const configPath = path.join(expansionPacksPath, entry.name, 'config.yaml');
if (await fs.pathExists(configPath)) {
try {
const yaml = require('js-yaml');
const config = yaml.load(await fs.readFile(configPath, 'utf8'));
packs.push({
id: entry.name,
name: config.name || entry.name,
version: config.version || '1.0.0',
description: config.description || 'No description available',
shortTitle: config['short-title'] || config.description || 'No description available',
author: config.author || 'Unknown',
path: path.join(expansionPacksPath, entry.name)
});
} catch (e) {
// Skip invalid packs
}
}
}
}
}
// Cache for 10 minutes
this._pathCache.set(cacheKey, packs);
setTimeout(() => this._pathCache.delete(cacheKey), 10 * 60 * 1000);
return packs;
}
/**
* Get team configuration
* @param {string} teamId - Team identifier
* @returns {Promise<Object|null>} Team configuration or null
*/
async getTeamConfig(teamId) {
const cacheKey = `team:${teamId}`;
if (this._pathCache.has(cacheKey)) {
return this._pathCache.get(cacheKey);
}
const teamPath = path.join(this.getBmadCorePath(), 'agent-teams', `${teamId}.yaml`);
if (await fs.pathExists(teamPath)) {
try {
const yaml = require('js-yaml');
const content = await fs.readFile(teamPath, 'utf8');
const config = yaml.load(content);
this._pathCache.set(cacheKey, config);
return config;
} catch (e) {
return null;
}
}
return null;
}
/**
* Get resource dependencies for an agent
* @param {string} agentId - Agent identifier
* @returns {Promise<Object>} Dependencies object
*/
async getAgentDependencies(agentId) {
const cacheKey = `deps:${agentId}`;
if (this._pathCache.has(cacheKey)) {
return this._pathCache.get(cacheKey);
}
const agentPath = await this.getAgentPath(agentId);
if (!agentPath) {
return { all: [], byType: {} };
}
const content = await fs.readFile(agentPath, 'utf8');
const { extractYamlFromAgent } = require('../../lib/yaml-utils');
const yamlContent = extractYamlFromAgent(content);
if (!yamlContent) {
return { all: [], byType: {} };
}
try {
const yaml = require('js-yaml');
const metadata = yaml.load(yamlContent);
const dependencies = metadata.dependencies || {};
// Flatten dependencies
const allDeps = [];
const byType = {};
for (const [type, deps] of Object.entries(dependencies)) {
if (Array.isArray(deps)) {
byType[type] = deps;
for (const dep of deps) {
allDeps.push(`.bmad-core/${type}/${dep}`);
}
}
}
const result = { all: allDeps, byType };
this._pathCache.set(cacheKey, result);
return result;
} catch (e) {
return { all: [], byType: {} };
}
}
/**
* Clear all caches to free memory
*/
clearCache() {
this._pathCache.clear();
this._globCache.clear();
}
/**
* Get IDE configuration
* @param {string} ideId - IDE identifier
* @returns {Promise<Object|null>} IDE configuration or null
*/
async getIdeConfig(ideId) {
const cacheKey = `ide:${ideId}`;
if (this._pathCache.has(cacheKey)) {
return this._pathCache.get(cacheKey);
}
const idePath = path.join(this.getBmadCorePath(), 'ide-rules', `${ideId}.yaml`);
if (await fs.pathExists(idePath)) {
try {
const yaml = require('js-yaml');
const content = await fs.readFile(idePath, 'utf8');
const config = yaml.load(content);
this._pathCache.set(cacheKey, config);
return config;
} catch (e) {
return null;
}
}
return null;
}
}
// Singleton instance
const resourceLocator = new ResourceLocator();
module.exports = resourceLocator;