mirror of
https://github.com/bmadcode/BMAD-METHOD.git
synced 2025-12-29 16:14:59 +00:00
1191 lines
39 KiB
Markdown
1191 lines
39 KiB
Markdown
|
|
# BMAD Custom Module Installer - Implementation Plan
|
|||
|
|
|
|||
|
|
**Document Version**: 1.0
|
|||
|
|
**Date**: 2025-10-19
|
|||
|
|
**Status**: Planning Phase
|
|||
|
|
**Owner**: CLI Chief (Scott) + BMad
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Executive Summary
|
|||
|
|
|
|||
|
|
This document outlines the architecture and implementation plan for a new BMAD CLI tool that enables installation of **custom modules from any location**. This tool is critical for the future of BMAD as an extensible framework where module authors can create and distribute modules independently of the core BMAD repository.
|
|||
|
|
|
|||
|
|
### The Vision
|
|||
|
|
|
|||
|
|
- **Core as npm package**: Future state where `@bmad/core` is an npm package with CLI tools
|
|||
|
|
- **Custom modules**: Module authors use BMad Builder (BMB) to create standalone modules
|
|||
|
|
- **Universal installer**: A CLI tool that can install any valid BMAD module from any path
|
|||
|
|
- **IDE integration**: Compiled agents work with 14+ IDE environments (Codex, Cursor, Windsurf, etc.)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Problem Statement
|
|||
|
|
|
|||
|
|
### Current Limitations
|
|||
|
|
|
|||
|
|
The existing `bmad install` command (tools/cli/commands/install.js) is hardcoded to:
|
|||
|
|
|
|||
|
|
- Discover modules ONLY from `src/modules/` directory
|
|||
|
|
- Install bundled modules (BMM, BMB, CIS) that ship with the framework
|
|||
|
|
- Cannot handle external/custom modules from arbitrary filesystem locations
|
|||
|
|
|
|||
|
|
**Code Reference**: `tools/cli/installers/lib/modules/manager.js:27`
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
this.modulesSourcePath = getSourcePath('modules'); // Hardcoded to src/modules/
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Real-World Use Case
|
|||
|
|
|
|||
|
|
- User has BMD module at `/Users/brianmadison/dev/BMAD-METHOD/bmd` (standalone folder)
|
|||
|
|
- Module has agents that need compilation (YAML → Markdown with XML)
|
|||
|
|
- Module needs IDE integration (generate commands for Claude Code, etc.)
|
|||
|
|
- Current installer cannot handle this - module must be in `src/modules/` to be discovered
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Critical Architectural Understanding
|
|||
|
|
|
|||
|
|
### Module Structure (SOURCE - What Authors Create)
|
|||
|
|
|
|||
|
|
**CORRECT STRUCTURE:**
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
my-custom-module/
|
|||
|
|
├── agents/
|
|||
|
|
│ └── my-agent.agent.yaml ← Required: At least one agent
|
|||
|
|
├── workflows/ ← Optional: Workflow definitions
|
|||
|
|
│ └── my-workflow/
|
|||
|
|
│ ├── README.md
|
|||
|
|
│ └── workflow.yaml
|
|||
|
|
└── _module-installer/ ← Required: Installation configuration
|
|||
|
|
├── install-config.yaml ← REQUIRED: Defines config questions
|
|||
|
|
└── installer.js ← OPTIONAL: Custom install hooks
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**CRITICAL: NO config.yaml in source!**
|
|||
|
|
|
|||
|
|
- The `config.yaml` is GENERATED at install time from user answers
|
|||
|
|
- Source modules use `_module-installer/install-config.yaml` to define questions
|
|||
|
|
- The legacy pattern of having `config.yaml` in source is being deprecated
|
|||
|
|
|
|||
|
|
### Module Structure (INSTALLED - What Gets Generated)
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
{target-project}/bmad/my-custom-module/
|
|||
|
|
├── agents/
|
|||
|
|
│ └── my-agent.md ← Compiled from .agent.yaml
|
|||
|
|
├── workflows/
|
|||
|
|
│ └── my-workflow/
|
|||
|
|
└── config.yaml ← GENERATED from user answers during install
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Key Points:**
|
|||
|
|
|
|||
|
|
- `_module-installer/` directory is NOT copied to target (only used during install)
|
|||
|
|
- Agents are compiled from YAML to Markdown with XML
|
|||
|
|
- `config.yaml` is generated fresh for each installation
|
|||
|
|
|
|||
|
|
### Example: install-config.yaml
|
|||
|
|
|
|||
|
|
**Reference**: `/Users/brianmadison/dev/BMAD-METHOD/src/modules/bmm/_module-installer/install-config.yaml`
|
|||
|
|
|
|||
|
|
```yaml
|
|||
|
|
# Module metadata
|
|||
|
|
code: bmm
|
|||
|
|
name: 'BMM: BMad Method Agile-AI Driven-Development'
|
|||
|
|
default_selected: true
|
|||
|
|
|
|||
|
|
# Optional welcome message
|
|||
|
|
prompt:
|
|||
|
|
- 'Thank you for choosing the BMAD™ Method...'
|
|||
|
|
- 'All paths are relative to project root, with no leading slash.'
|
|||
|
|
|
|||
|
|
# Configuration questions
|
|||
|
|
project_name:
|
|||
|
|
prompt: 'What is the title of your project?'
|
|||
|
|
default: '{directory_name}'
|
|||
|
|
result: '{value}'
|
|||
|
|
|
|||
|
|
user_skill_level:
|
|||
|
|
prompt:
|
|||
|
|
- 'What is your technical experience level?'
|
|||
|
|
default: 'intermediate'
|
|||
|
|
result: '{value}'
|
|||
|
|
single-select:
|
|||
|
|
- value: 'beginner'
|
|||
|
|
label: 'Beginner - New to development'
|
|||
|
|
- value: 'intermediate'
|
|||
|
|
label: 'Intermediate - Familiar with development'
|
|||
|
|
- value: 'expert'
|
|||
|
|
label: 'Expert - Deep technical knowledge'
|
|||
|
|
|
|||
|
|
tech_docs:
|
|||
|
|
prompt: 'Where is Technical Documentation located?'
|
|||
|
|
default: 'docs'
|
|||
|
|
result: '{project-root}/{value}'
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**How ConfigCollector Uses This:**
|
|||
|
|
|
|||
|
|
1. Reads `install-config.yaml` from source module
|
|||
|
|
2. Builds interactive prompts for each config item
|
|||
|
|
3. Collects user answers
|
|||
|
|
4. Processes answers with variable substitution (`{value}`, `{project-root}`, etc.)
|
|||
|
|
5. Generates `config.yaml` in installed module location
|
|||
|
|
|
|||
|
|
**Code Reference**: `tools/cli/installers/lib/core/config-collector.js:108-122`
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Current CLI Architecture
|
|||
|
|
|
|||
|
|
### Installation Flow (Existing System)
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
User runs: npm run install:bmad
|
|||
|
|
|
|||
|
|
1. Command Handler (commands/install.js)
|
|||
|
|
├── Prompts for target directory, modules, IDEs
|
|||
|
|
└── Calls Installer.install(config)
|
|||
|
|
|
|||
|
|
2. Installer (installers/lib/core/installer.js)
|
|||
|
|
├── Validates target directory
|
|||
|
|
├── Resolves module dependencies
|
|||
|
|
├── Calls ModuleManager.install() for each module
|
|||
|
|
├── Calls IdeManager.setup() for each IDE
|
|||
|
|
└── Generates manifests
|
|||
|
|
|
|||
|
|
3. ModuleManager (installers/lib/modules/manager.js)
|
|||
|
|
├── Discovers modules from src/modules/ ONLY
|
|||
|
|
├── Copies module files to {target}/bmad/{module}/
|
|||
|
|
├── Compiles agents using YamlXmlBuilder
|
|||
|
|
└── Runs module-specific installer if exists
|
|||
|
|
|
|||
|
|
4. ConfigCollector (installers/lib/core/config-collector.js)
|
|||
|
|
├── Reads _module-installer/install-config.yaml
|
|||
|
|
├── Prompts user for configuration
|
|||
|
|
├── Generates config.yaml in target
|
|||
|
|
|
|||
|
|
5. IdeManager (installers/lib/ide/manager.js)
|
|||
|
|
├── For each selected IDE (codex, windsurf, cursor, etc.)
|
|||
|
|
├── Creates IDE-specific artifacts
|
|||
|
|
│ - Claude Code: .claude/commands/*.md
|
|||
|
|
│ - Windsurf: .windsurf/workflows/*.yaml
|
|||
|
|
│ - Cursor: .cursor/rules/*.txt
|
|||
|
|
└── Runs platform-specific hooks
|
|||
|
|
|
|||
|
|
6. ManifestGenerator (installers/lib/core/manifest-generator.js)
|
|||
|
|
├── manifest.yaml (installation metadata)
|
|||
|
|
├── workflow-manifest.csv (workflow catalog)
|
|||
|
|
├── agent-manifest.csv (agent metadata)
|
|||
|
|
└── files-manifest.csv (file integrity hashes)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Key Components (Reusable for Custom Installer)
|
|||
|
|
|
|||
|
|
**Agent Compilation Engine:**
|
|||
|
|
|
|||
|
|
- `tools/cli/lib/yaml-xml-builder.js` - YamlXmlBuilder class
|
|||
|
|
- `tools/cli/lib/activation-builder.js` - Generates activation blocks
|
|||
|
|
- `tools/cli/lib/agent-analyzer.js` - Detects required handlers
|
|||
|
|
- `src/utility/models/fragments/*.xml` - Reusable XML fragments
|
|||
|
|
|
|||
|
|
**Installation Infrastructure:**
|
|||
|
|
|
|||
|
|
- `tools/cli/installers/lib/core/config-collector.js` - ConfigCollector class
|
|||
|
|
- `tools/cli/installers/lib/ide/manager.js` - IdeManager class
|
|||
|
|
- `tools/cli/installers/lib/core/manifest-generator.js` - ManifestGenerator class
|
|||
|
|
- `tools/cli/installers/lib/modules/manager.js` - ModuleManager class (needs adaptation)
|
|||
|
|
|
|||
|
|
**Key Insight**: 80% of the code we need already exists! We just need to:
|
|||
|
|
|
|||
|
|
1. Create a new command handler
|
|||
|
|
2. Adapt ModuleManager to accept external paths
|
|||
|
|
3. Wire everything together
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Proposed Solution Architecture
|
|||
|
|
|
|||
|
|
### New Command: `install-module`
|
|||
|
|
|
|||
|
|
**Purpose**: Install a custom module from any filesystem location
|
|||
|
|
|
|||
|
|
**Usage:**
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# Interactive mode
|
|||
|
|
bmad install-module
|
|||
|
|
|
|||
|
|
# Non-interactive mode
|
|||
|
|
bmad install-module \
|
|||
|
|
--source /path/to/custom-module \
|
|||
|
|
--target /path/to/project \
|
|||
|
|
--ides codex,windsurf
|
|||
|
|
|
|||
|
|
# CI/CD mode
|
|||
|
|
bmad install-module \
|
|||
|
|
--source ./my-module \
|
|||
|
|
--target . \
|
|||
|
|
--ides codex \
|
|||
|
|
--non-interactive \
|
|||
|
|
--config-file ./module-config.json
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### System Architecture
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
┌──────────────────────────────────────────────────────────────┐
|
|||
|
|
│ NEW: install-module Command │
|
|||
|
|
│ File: tools/cli/commands/install-module.js │
|
|||
|
|
│ │
|
|||
|
|
│ Responsibilities: │
|
|||
|
|
│ - Parse command-line flags │
|
|||
|
|
│ - Prompt for missing information (interactive mode) │
|
|||
|
|
│ - Validate inputs │
|
|||
|
|
│ - Call CustomModuleInstaller │
|
|||
|
|
└──────────────────────────────────────────────────────────────┘
|
|||
|
|
↓
|
|||
|
|
┌──────────────────────────────────────────────────────────────┐
|
|||
|
|
│ NEW: CustomModuleInstaller Class │
|
|||
|
|
│ File: tools/cli/installers/lib/core/custom-module-installer.js│
|
|||
|
|
│ │
|
|||
|
|
│ Responsibilities: │
|
|||
|
|
│ 1. Validate source module structure (ModuleValidator) │
|
|||
|
|
│ 2. Ensure core is installed in target │
|
|||
|
|
│ 3. Collect module configuration (ConfigCollector) │
|
|||
|
|
│ 4. Install module files (ModuleManager) │
|
|||
|
|
│ 5. Compile agents (YamlXmlBuilder) │
|
|||
|
|
│ 6. Generate IDE artifacts (IdeManager) │
|
|||
|
|
│ 7. Update manifests (ManifestGenerator) │
|
|||
|
|
│ 8. Run custom installer hooks (if exists) │
|
|||
|
|
└──────────────────────────────────────────────────────────────┘
|
|||
|
|
↓
|
|||
|
|
┌──────────────────────────────────────────────────────────────┐
|
|||
|
|
│ NEW: ModuleValidator Class │
|
|||
|
|
│ File: tools/cli/installers/lib/core/module-validator.js │
|
|||
|
|
│ │
|
|||
|
|
│ Validates: │
|
|||
|
|
│ ✓ _module-installer/install-config.yaml exists │
|
|||
|
|
│ ✓ At least one agents/*.agent.yaml exists │
|
|||
|
|
│ ✓ Module metadata is valid │
|
|||
|
|
│ ⚠ Warns if legacy config.yaml found in source │
|
|||
|
|
│ ✗ Fails if required structure missing │
|
|||
|
|
└──────────────────────────────────────────────────────────────┘
|
|||
|
|
↓
|
|||
|
|
┌──────────────────────────────────────────────────────────────┐
|
|||
|
|
│ REUSED: Existing Infrastructure │
|
|||
|
|
│ │
|
|||
|
|
│ - ConfigCollector (configuration prompts) │
|
|||
|
|
│ - YamlXmlBuilder (agent compilation) │
|
|||
|
|
│ - IdeManager (IDE integration) │
|
|||
|
|
│ - ManifestGenerator (tracking) │
|
|||
|
|
│ - ModuleManager (file operations) │
|
|||
|
|
└──────────────────────────────────────────────────────────────┘
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Detailed Installation Flow
|
|||
|
|
|
|||
|
|
### Phase 1: Validation
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Input: --source /path/to/custom-module
|
|||
|
|
|
|||
|
|
1. ModuleValidator.validate(sourcePath)
|
|||
|
|
├── Check: _module-installer/install-config.yaml exists
|
|||
|
|
├── Check: agents/ directory exists
|
|||
|
|
├── Check: At least one *.agent.yaml in agents/
|
|||
|
|
├── Parse: install-config.yaml for metadata
|
|||
|
|
│ - Extract: code, name, version
|
|||
|
|
│ - Extract: dependencies (if any)
|
|||
|
|
│ - Extract: core_version requirement
|
|||
|
|
├── Warn: If legacy config.yaml found in source
|
|||
|
|
└── Return: { valid: true/false, errors: [], warnings: [], metadata: {} }
|
|||
|
|
|
|||
|
|
2. If invalid:
|
|||
|
|
├── Display all errors clearly
|
|||
|
|
└── Exit with helpful message + link to module authoring guide
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Phase 2: Core Dependency Check
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Input: --target /path/to/project
|
|||
|
|
|
|||
|
|
1. Check if core installed:
|
|||
|
|
├── Look for: {target}/bmad/core/
|
|||
|
|
├── Validate: core/config.yaml exists
|
|||
|
|
└── Check version compatibility
|
|||
|
|
|
|||
|
|
2. If core NOT installed:
|
|||
|
|
├── Display message: "Core framework required but not found"
|
|||
|
|
├── Prompt: "Install core framework now? (Y/n)"
|
|||
|
|
├── If yes: Run core installer
|
|||
|
|
│ └── Use existing Installer.installCore() or similar
|
|||
|
|
├── If no: Exit with error
|
|||
|
|
└── After core install: Continue to Phase 3
|
|||
|
|
|
|||
|
|
3. If core installed but incompatible version:
|
|||
|
|
├── Display warning with version mismatch details
|
|||
|
|
├── Prompt: "Continue anyway? (may cause issues)"
|
|||
|
|
└── Respect user choice
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Phase 3: Configuration Collection
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Input: Module's install-config.yaml
|
|||
|
|
|
|||
|
|
1. ConfigCollector.collectModuleConfig(moduleName, projectDir)
|
|||
|
|
├── Read: {source}/_module-installer/install-config.yaml
|
|||
|
|
├── Display: Module welcome prompt (if defined)
|
|||
|
|
├── Build questions:
|
|||
|
|
│ - Text inputs
|
|||
|
|
│ - Single-select (radio)
|
|||
|
|
│ - Multi-select (checkboxes)
|
|||
|
|
│ - Confirmations
|
|||
|
|
├── Check for existing values:
|
|||
|
|
│ - If module already installed, load existing config
|
|||
|
|
│ - Prompt: "Use existing value or change?"
|
|||
|
|
├── Prompt user interactively (or use --config-file in non-interactive mode)
|
|||
|
|
└── Return: { key: value } answers object
|
|||
|
|
|
|||
|
|
2. Process answers with variable substitution:
|
|||
|
|
├── {value} → actual answer
|
|||
|
|
├── {project-root} → absolute target path
|
|||
|
|
├── {directory_name} → basename of target directory
|
|||
|
|
├── {value:other_key} → reference another config value
|
|||
|
|
└── Return: Final configuration object
|
|||
|
|
|
|||
|
|
3. Store configuration (will be written in Phase 5)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Phase 4: File Installation
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Input: Source module path, Target bmad directory
|
|||
|
|
|
|||
|
|
1. ModuleManager.installFromPath(sourcePath, bmadDir, fileTrackingCallback)
|
|||
|
|
├── Determine module name from metadata
|
|||
|
|
├── Create target directory: {bmadDir}/{module-name}/
|
|||
|
|
├── Copy files with filtering:
|
|||
|
|
│ ├── COPY: agents/ (all files)
|
|||
|
|
│ ├── COPY: workflows/ (strip web_bundle sections from workflow.yaml)
|
|||
|
|
│ ├── SKIP: _module-installer/ (not needed in target)
|
|||
|
|
│ ├── SKIP: config.yaml from source (if exists - legacy)
|
|||
|
|
│ ├── SKIP: *.bak files
|
|||
|
|
│ └── SKIP: Agents with localskip="true" (web-only agents)
|
|||
|
|
└── Track all copied files for manifest generation
|
|||
|
|
|
|||
|
|
2. File tracking callback:
|
|||
|
|
└── Store: { path, hash } for each file (for files-manifest.csv)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Phase 5: Agent Compilation
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Input: Installed module path
|
|||
|
|
|
|||
|
|
1. For each agents/*.agent.yaml:
|
|||
|
|
├── Read YAML file
|
|||
|
|
├── Check for customize.yaml (sidecar file)
|
|||
|
|
├── Merge if exists: agent.yaml + customize.yaml
|
|||
|
|
├── YamlXmlBuilder.build(agentData, options)
|
|||
|
|
│ - forWebBundle: false (IDE mode)
|
|||
|
|
│ - includeMetadata: true
|
|||
|
|
│ - skipActivation: false
|
|||
|
|
├── AgentAnalyzer.analyze(agentData)
|
|||
|
|
│ - Detect: Which handlers are used (workflow, exec, tmpl, data, action)
|
|||
|
|
├── ActivationBuilder.build(handlers)
|
|||
|
|
│ - Load: activation-steps.xml (base)
|
|||
|
|
│ - Inject: Only needed handler fragments
|
|||
|
|
├── Generate: Markdown file with XML
|
|||
|
|
└── Write: {bmadDir}/{module}/agents/{name}.md
|
|||
|
|
|
|||
|
|
2. Result:
|
|||
|
|
└── Compiled agents ready for IDE consumption
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Phase 6: Configuration File Generation
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Input: Collected configuration from Phase 3
|
|||
|
|
|
|||
|
|
1. Build config.yaml content:
|
|||
|
|
├── Add: Module metadata (code, name, version)
|
|||
|
|
├── Add: All configuration values from user answers
|
|||
|
|
├── Add: Installation metadata
|
|||
|
|
│ - installed_date
|
|||
|
|
│ - installed_version
|
|||
|
|
└── Add: User info from core config
|
|||
|
|
- user_name
|
|||
|
|
- communication_language
|
|||
|
|
- output_folder
|
|||
|
|
|
|||
|
|
2. Write config.yaml:
|
|||
|
|
└── {bmadDir}/{module}/config.yaml
|
|||
|
|
|
|||
|
|
3. This is the ONLY config.yaml that exists after installation
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Phase 7: IDE Integration
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Input: Selected IDEs (codex, windsurf, cursor, etc.)
|
|||
|
|
|
|||
|
|
1. IdeManager.setup(selectedIdes, bmadDir, projectRoot)
|
|||
|
|
├── For each IDE:
|
|||
|
|
│ ├── Load IDE handler: ide/{ide-code}.js
|
|||
|
|
│ ├── Call: handler.setup()
|
|||
|
|
│ ├── Call: handler.createArtifacts()
|
|||
|
|
│ │ └── Generate IDE-specific files
|
|||
|
|
│ └── Run: Platform-specific hooks if defined
|
|||
|
|
│ - Check: {source}/_module-installer/platform-specifics/{ide}.js
|
|||
|
|
│ - Execute if exists
|
|||
|
|
└── Examples:
|
|||
|
|
- Claude Code: .claude/commands/bmad/{module}/agents/*.md
|
|||
|
|
- Windsurf: .windsurf/workflows/bmad-{module}-*.yaml
|
|||
|
|
- Cursor: .cursor/rules/bmad-{module}.txt
|
|||
|
|
|
|||
|
|
2. Workflow Command Generation:
|
|||
|
|
├── Read: workflow-manifest.csv (from Phase 8)
|
|||
|
|
├── For each workflow in module:
|
|||
|
|
│ └── Generate: IDE command to launch workflow
|
|||
|
|
└── Format varies by IDE
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Phase 8: Manifest Updates
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Input: Installation details, installed files, module metadata
|
|||
|
|
|
|||
|
|
1. ManifestGenerator.update(bmadDir, installData)
|
|||
|
|
├── Update: {bmadDir}/_cfg/manifest.yaml
|
|||
|
|
│ - Add module to installed_modules[]
|
|||
|
|
│ - Add custom_modules[] section (track source path)
|
|||
|
|
│ - Update: last_modified timestamp
|
|||
|
|
│
|
|||
|
|
├── Update: {bmadDir}/_cfg/agent-manifest.csv
|
|||
|
|
│ - Add row for each agent
|
|||
|
|
│ - Columns: module, agent_path, agent_name, role, identity_summary,
|
|||
|
|
│ communication_style, expertise, approach, responsibilities, workflows
|
|||
|
|
│
|
|||
|
|
├── Update: {bmadDir}/_cfg/workflow-manifest.csv
|
|||
|
|
│ - Add row for each workflow
|
|||
|
|
│ - Columns: module, workflow_path, workflow_name, description, scale_level
|
|||
|
|
│
|
|||
|
|
├── Update: {bmadDir}/_cfg/files-manifest.csv
|
|||
|
|
│ - Add row for each installed file
|
|||
|
|
│ - Columns: file_path, file_type, module, hash (SHA256)
|
|||
|
|
│
|
|||
|
|
└── Update: {bmadDir}/_cfg/task-manifest.csv (if tasks exist - legacy)
|
|||
|
|
|
|||
|
|
2. Manifest purposes:
|
|||
|
|
- Update detection (compare file hashes)
|
|||
|
|
- Installation integrity validation
|
|||
|
|
- Rollback capability
|
|||
|
|
- IDE artifact generation
|
|||
|
|
- Documentation generation
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Phase 9: Custom Installer Hooks
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Input: Module's _module-installer/installer.js (if exists)
|
|||
|
|
|
|||
|
|
1. Check for custom installer:
|
|||
|
|
└── {source}/_module-installer/installer.js
|
|||
|
|
|
|||
|
|
2. If exists:
|
|||
|
|
├── Load module: require(installerPath)
|
|||
|
|
├── Validate: exports.install is a function
|
|||
|
|
├── Prepare context:
|
|||
|
|
│ {
|
|||
|
|
│ projectRoot: '/path/to/project',
|
|||
|
|
│ config: { collected user configuration },
|
|||
|
|
│ installedIDEs: ['codex', 'windsurf'],
|
|||
|
|
│ logger: { log, error, warn }
|
|||
|
|
│ }
|
|||
|
|
├── Execute: await installer.install(context)
|
|||
|
|
└── Handle errors gracefully
|
|||
|
|
|
|||
|
|
3. Custom installer use cases:
|
|||
|
|
- Create subagent variations
|
|||
|
|
- Set up additional project files
|
|||
|
|
- Run initialization scripts
|
|||
|
|
- Configure external dependencies
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Phase 10: Validation & Completion
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
1. Validate installation:
|
|||
|
|
├── Check: All manifest files exist
|
|||
|
|
├── Verify: Agent files compiled successfully
|
|||
|
|
├── Verify: IDE artifacts created
|
|||
|
|
├── Validate: File hashes match manifest
|
|||
|
|
└── Check: No errors during installation
|
|||
|
|
|
|||
|
|
2. Display success message:
|
|||
|
|
├── Show: Module name and version
|
|||
|
|
├── Show: Installation location
|
|||
|
|
├── Show: Installed agents count
|
|||
|
|
├── Show: IDE integrations configured
|
|||
|
|
└── Show: Next steps
|
|||
|
|
|
|||
|
|
3. Next steps message:
|
|||
|
|
- How to use the module
|
|||
|
|
- How to verify IDE integration
|
|||
|
|
- Link to module documentation
|
|||
|
|
- How to update or uninstall
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Implementation Checklist
|
|||
|
|
|
|||
|
|
### New Files to Create
|
|||
|
|
|
|||
|
|
1. **`tools/cli/commands/install-module.js`**
|
|||
|
|
- Command handler for `bmad install-module`
|
|||
|
|
- CLI argument parsing
|
|||
|
|
- Interactive prompts for missing info
|
|||
|
|
- Call CustomModuleInstaller
|
|||
|
|
|
|||
|
|
2. **`tools/cli/installers/lib/core/custom-module-installer.js`**
|
|||
|
|
- CustomModuleInstaller class
|
|||
|
|
- Main orchestration logic
|
|||
|
|
- Coordinate all phases (1-10)
|
|||
|
|
- Error handling and rollback
|
|||
|
|
|
|||
|
|
3. **`tools/cli/installers/lib/core/module-validator.js`**
|
|||
|
|
- ModuleValidator class
|
|||
|
|
- Validate module structure
|
|||
|
|
- Check required files
|
|||
|
|
- Parse and validate metadata
|
|||
|
|
- Return detailed validation results
|
|||
|
|
|
|||
|
|
4. **`tools/cli/installers/lib/core/core-installer.js`** (optional)
|
|||
|
|
- CoreInstaller class
|
|||
|
|
- Install just the core framework
|
|||
|
|
- Can be extracted from existing Installer class
|
|||
|
|
|
|||
|
|
### Files to Modify
|
|||
|
|
|
|||
|
|
5. **`tools/cli/installers/lib/modules/manager.js`**
|
|||
|
|
- Add: `installFromPath(sourcePath, bmadDir, ...)` method
|
|||
|
|
- Adapt existing `install()` logic to work with external paths
|
|||
|
|
- Keep existing functionality intact (backward compatibility)
|
|||
|
|
|
|||
|
|
6. **`tools/cli/installers/lib/core/manifest-generator.js`**
|
|||
|
|
- Add: Support for tracking custom module source paths
|
|||
|
|
- Add: `custom_modules` section in manifest.yaml
|
|||
|
|
- Format:
|
|||
|
|
```yaml
|
|||
|
|
custom_modules:
|
|||
|
|
- name: my-module
|
|||
|
|
source_path: /path/to/source/my-module
|
|||
|
|
installed_date: 2025-10-19
|
|||
|
|
version: 1.0.0
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
7. **`tools/cli/bmad-cli.js`**
|
|||
|
|
- Already dynamically loads commands, no changes needed
|
|||
|
|
- New command will be auto-discovered
|
|||
|
|
|
|||
|
|
### Files to Document
|
|||
|
|
|
|||
|
|
8. **`docs/custom-module-authoring-guide.md`** (new)
|
|||
|
|
- How to create a custom module
|
|||
|
|
- Required structure and files
|
|||
|
|
- install-config.yaml format
|
|||
|
|
- Best practices
|
|||
|
|
- Testing your module
|
|||
|
|
- Distribution strategies
|
|||
|
|
|
|||
|
|
9. **`tools/cli/README.md`** (update)
|
|||
|
|
- Add documentation for `install-module` command
|
|||
|
|
- Update architecture diagrams
|
|||
|
|
- Add examples
|
|||
|
|
|
|||
|
|
### Testing Strategy
|
|||
|
|
|
|||
|
|
10. **Test with existing BMD module**
|
|||
|
|
- Source: `/Users/brianmadison/dev/BMAD-METHOD/bmd`
|
|||
|
|
- Target: Test project
|
|||
|
|
- Validate: All phases work correctly
|
|||
|
|
|
|||
|
|
11. **Create test fixtures**
|
|||
|
|
- Minimal valid module
|
|||
|
|
- Module with all optional features
|
|||
|
|
- Invalid modules (for error testing)
|
|||
|
|
|
|||
|
|
12. **IDE integration tests**
|
|||
|
|
- Test with Claude Code
|
|||
|
|
- Test with Windsurf
|
|||
|
|
- Verify artifact generation
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Code Examples
|
|||
|
|
|
|||
|
|
### Example: ModuleValidator.validate()
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
// tools/cli/installers/lib/core/module-validator.js
|
|||
|
|
|
|||
|
|
const path = require('node:path');
|
|||
|
|
const fs = require('fs-extra');
|
|||
|
|
const yaml = require('js-yaml');
|
|||
|
|
|
|||
|
|
class ModuleValidator {
|
|||
|
|
async validate(sourcePath) {
|
|||
|
|
const result = {
|
|||
|
|
valid: false,
|
|||
|
|
errors: [],
|
|||
|
|
warnings: [],
|
|||
|
|
metadata: null,
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 1. Check _module-installer/install-config.yaml
|
|||
|
|
const installConfigPath = path.join(sourcePath, '_module-installer', 'install-config.yaml');
|
|||
|
|
|
|||
|
|
if (!(await fs.pathExists(installConfigPath))) {
|
|||
|
|
result.errors.push('Missing required file: _module-installer/install-config.yaml');
|
|||
|
|
} else {
|
|||
|
|
// Parse and validate
|
|||
|
|
try {
|
|||
|
|
const content = await fs.readFile(installConfigPath, 'utf8');
|
|||
|
|
const config = yaml.load(content);
|
|||
|
|
|
|||
|
|
// Extract metadata
|
|||
|
|
result.metadata = {
|
|||
|
|
code: config.code,
|
|||
|
|
name: config.name,
|
|||
|
|
version: config.version || '1.0.0',
|
|||
|
|
dependencies: config.dependencies || [],
|
|||
|
|
core_version: config.core_version,
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// Validate required metadata
|
|||
|
|
if (!config.code) {
|
|||
|
|
result.errors.push('install-config.yaml missing required field: code');
|
|||
|
|
}
|
|||
|
|
if (!config.name) {
|
|||
|
|
result.errors.push('install-config.yaml missing required field: name');
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
result.errors.push(`Invalid install-config.yaml: ${error.message}`);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 2. Check agents/ directory
|
|||
|
|
const agentsPath = path.join(sourcePath, 'agents');
|
|||
|
|
if (!(await fs.pathExists(agentsPath))) {
|
|||
|
|
result.errors.push('Missing required directory: agents/');
|
|||
|
|
} else {
|
|||
|
|
const agentFiles = await fs.readdir(agentsPath);
|
|||
|
|
const yamlAgents = agentFiles.filter((f) => f.endsWith('.agent.yaml'));
|
|||
|
|
|
|||
|
|
if (yamlAgents.length === 0) {
|
|||
|
|
result.errors.push('No agent YAML files found in agents/ directory');
|
|||
|
|
} else {
|
|||
|
|
result.metadata = result.metadata || {};
|
|||
|
|
result.metadata.agent_count = yamlAgents.length;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 3. Warn about legacy config.yaml
|
|||
|
|
const legacyConfigPath = path.join(sourcePath, 'config.yaml');
|
|||
|
|
if (await fs.pathExists(legacyConfigPath)) {
|
|||
|
|
result.warnings.push(
|
|||
|
|
'Found config.yaml in module source. This is legacy and will be ignored. ' +
|
|||
|
|
'The installer will generate config.yaml from user input. ' +
|
|||
|
|
'Use _module-installer/install-config.yaml instead.',
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 4. Check for workflows (optional but log if missing)
|
|||
|
|
const workflowsPath = path.join(sourcePath, 'workflows');
|
|||
|
|
if (!(await fs.pathExists(workflowsPath))) {
|
|||
|
|
result.warnings.push('No workflows/ directory found (optional but recommended)');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Set valid flag
|
|||
|
|
result.valid = result.errors.length === 0;
|
|||
|
|
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
module.exports = { ModuleValidator };
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Example: CustomModuleInstaller.install()
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
// tools/cli/installers/lib/core/custom-module-installer.js
|
|||
|
|
|
|||
|
|
const chalk = require('chalk');
|
|||
|
|
const ora = require('ora');
|
|||
|
|
const { ModuleValidator } = require('./module-validator');
|
|||
|
|
const { ConfigCollector } = require('./config-collector');
|
|||
|
|
const { ModuleManager } = require('../modules/manager');
|
|||
|
|
const { IdeManager } = require('../ide/manager');
|
|||
|
|
const { ManifestGenerator } = require('./manifest-generator');
|
|||
|
|
|
|||
|
|
class CustomModuleInstaller {
|
|||
|
|
constructor() {
|
|||
|
|
this.validator = new ModuleValidator();
|
|||
|
|
this.configCollector = new ConfigCollector();
|
|||
|
|
this.moduleManager = new ModuleManager();
|
|||
|
|
this.ideManager = new IdeManager();
|
|||
|
|
this.manifestGenerator = new ManifestGenerator();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async install(options) {
|
|||
|
|
const { sourcePath, targetPath, selectedIdes } = options;
|
|||
|
|
|
|||
|
|
console.log(chalk.cyan('\n🔧 BMAD Custom Module Installer\n'));
|
|||
|
|
|
|||
|
|
// PHASE 1: Validate source module
|
|||
|
|
console.log(chalk.bold('Phase 1: Validating module structure...'));
|
|||
|
|
const validation = await this.validator.validate(sourcePath);
|
|||
|
|
|
|||
|
|
if (!validation.valid) {
|
|||
|
|
console.error(chalk.red('\n❌ Module validation failed:\n'));
|
|||
|
|
validation.errors.forEach((err) => console.error(chalk.red(` - ${err}`)));
|
|||
|
|
throw new Error('Invalid module structure');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (validation.warnings.length > 0) {
|
|||
|
|
console.log(chalk.yellow('\n⚠️ Warnings:'));
|
|||
|
|
validation.warnings.forEach((warn) => console.log(chalk.yellow(` - ${warn}`)));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.log(chalk.green('✓ Module structure valid'));
|
|||
|
|
console.log(chalk.dim(` Module: ${validation.metadata.name}`));
|
|||
|
|
console.log(chalk.dim(` Code: ${validation.metadata.code}`));
|
|||
|
|
console.log(chalk.dim(` Agents: ${validation.metadata.agent_count}`));
|
|||
|
|
|
|||
|
|
// PHASE 2: Check core dependency
|
|||
|
|
console.log(chalk.bold('\nPhase 2: Checking core framework...'));
|
|||
|
|
const bmadDir = path.join(targetPath, 'bmad');
|
|||
|
|
const coreInstalled = await this.checkCoreInstalled(bmadDir);
|
|||
|
|
|
|||
|
|
if (!coreInstalled) {
|
|||
|
|
// Prompt to install core
|
|||
|
|
const shouldInstall = await this.promptInstallCore();
|
|||
|
|
if (shouldInstall) {
|
|||
|
|
await this.installCore(targetPath);
|
|||
|
|
} else {
|
|||
|
|
throw new Error('Core framework required for module installation');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.log(chalk.green('✓ Core framework available'));
|
|||
|
|
|
|||
|
|
// PHASE 3: Collect configuration
|
|||
|
|
console.log(chalk.bold('\nPhase 3: Collecting module configuration...'));
|
|||
|
|
const config = await this.configCollector.collectModuleConfigFromPath(sourcePath, validation.metadata.code, targetPath);
|
|||
|
|
console.log(chalk.green('✓ Configuration collected'));
|
|||
|
|
|
|||
|
|
// PHASE 4-6: Install module files and compile agents
|
|||
|
|
console.log(chalk.bold('\nPhase 4-6: Installing module and compiling agents...'));
|
|||
|
|
const spinner = ora('Installing module files...').start();
|
|||
|
|
|
|||
|
|
const installResult = await this.moduleManager.installFromPath(sourcePath, bmadDir, (file) => this.trackFile(file), {
|
|||
|
|
moduleConfig: config,
|
|||
|
|
installedIDEs: selectedIdes,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
spinner.succeed('Module files installed and agents compiled');
|
|||
|
|
|
|||
|
|
// PHASE 7: IDE integration
|
|||
|
|
if (selectedIdes && selectedIdes.length > 0) {
|
|||
|
|
console.log(chalk.bold('\nPhase 7: Configuring IDE integrations...'));
|
|||
|
|
await this.ideManager.setup(selectedIdes, bmadDir, targetPath);
|
|||
|
|
console.log(chalk.green(`✓ Configured ${selectedIdes.length} IDE(s)`));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// PHASE 8: Update manifests
|
|||
|
|
console.log(chalk.bold('\nPhase 8: Updating manifests...'));
|
|||
|
|
await this.manifestGenerator.updateForCustomModule({
|
|||
|
|
bmadDir,
|
|||
|
|
moduleName: validation.metadata.code,
|
|||
|
|
sourcePath,
|
|||
|
|
metadata: validation.metadata,
|
|||
|
|
installedFiles: this.trackedFiles,
|
|||
|
|
});
|
|||
|
|
console.log(chalk.green('✓ Manifests updated'));
|
|||
|
|
|
|||
|
|
// PHASE 9: Run custom installer
|
|||
|
|
const customInstallerPath = path.join(sourcePath, '_module-installer', 'installer.js');
|
|||
|
|
if (await fs.pathExists(customInstallerPath)) {
|
|||
|
|
console.log(chalk.bold('\nPhase 9: Running custom installer hooks...'));
|
|||
|
|
await this.runCustomInstaller(customInstallerPath, {
|
|||
|
|
projectRoot: targetPath,
|
|||
|
|
config,
|
|||
|
|
installedIDEs: selectedIdes,
|
|||
|
|
});
|
|||
|
|
console.log(chalk.green('✓ Custom installer completed'));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// PHASE 10: Success
|
|||
|
|
console.log(chalk.green('\n✨ Module installation complete!\n'));
|
|||
|
|
console.log(chalk.cyan('Module:'), chalk.bold(validation.metadata.name));
|
|||
|
|
console.log(chalk.cyan('Location:'), path.join(bmadDir, validation.metadata.code));
|
|||
|
|
console.log(chalk.cyan('Agents:'), validation.metadata.agent_count);
|
|||
|
|
|
|||
|
|
if (selectedIdes && selectedIdes.length > 0) {
|
|||
|
|
console.log(chalk.cyan('IDE Integration:'), selectedIdes.join(', '));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return { success: true };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
trackFile(filePath) {
|
|||
|
|
if (!this.trackedFiles) this.trackedFiles = [];
|
|||
|
|
this.trackedFiles.push(filePath);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ... other helper methods
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
module.exports = { CustomModuleInstaller };
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Example: ModuleManager.installFromPath()
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
// Addition to tools/cli/installers/lib/modules/manager.js
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Install a module from an external path (not from src/modules/)
|
|||
|
|
* @param {string} sourcePath - Absolute path to module source
|
|||
|
|
* @param {string} bmadDir - Target bmad directory
|
|||
|
|
* @param {Function} fileTrackingCallback - Optional callback to track files
|
|||
|
|
* @param {Object} options - Installation options
|
|||
|
|
*/
|
|||
|
|
async installFromPath(sourcePath, bmadDir, fileTrackingCallback = null, options = {}) {
|
|||
|
|
// Read module metadata from install-config.yaml
|
|||
|
|
const installConfigPath = path.join(
|
|||
|
|
sourcePath,
|
|||
|
|
'_module-installer',
|
|||
|
|
'install-config.yaml'
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
const configContent = await fs.readFile(installConfigPath, 'utf8');
|
|||
|
|
const config = yaml.load(configContent);
|
|||
|
|
const moduleName = config.code;
|
|||
|
|
|
|||
|
|
const targetPath = path.join(bmadDir, moduleName);
|
|||
|
|
|
|||
|
|
// Check if already installed
|
|||
|
|
if (await fs.pathExists(targetPath)) {
|
|||
|
|
console.log(chalk.yellow(`Module '${moduleName}' already installed, updating...`));
|
|||
|
|
await fs.remove(targetPath);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Copy module files with filtering (reuse existing method)
|
|||
|
|
await this.copyModuleWithFiltering(sourcePath, targetPath, fileTrackingCallback);
|
|||
|
|
|
|||
|
|
// Process agent files to inject activation block (reuse existing method)
|
|||
|
|
await this.processAgentFiles(targetPath, moduleName);
|
|||
|
|
|
|||
|
|
// Write generated config.yaml
|
|||
|
|
if (options.moduleConfig) {
|
|||
|
|
const configYamlPath = path.join(targetPath, 'config.yaml');
|
|||
|
|
const configYaml = yaml.dump(options.moduleConfig);
|
|||
|
|
await fs.writeFile(configYamlPath, configYaml, 'utf8');
|
|||
|
|
|
|||
|
|
if (fileTrackingCallback) {
|
|||
|
|
fileTrackingCallback(configYamlPath);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Call module-specific installer if it exists
|
|||
|
|
if (!options.skipModuleInstaller) {
|
|||
|
|
await this.runModuleInstallerFromPath(sourcePath, bmadDir, options);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
success: true,
|
|||
|
|
module: moduleName,
|
|||
|
|
path: targetPath,
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Run module-specific installer from external path
|
|||
|
|
*/
|
|||
|
|
async runModuleInstallerFromPath(sourcePath, bmadDir, options = {}) {
|
|||
|
|
const installerPath = path.join(sourcePath, '_module-installer', 'installer.js');
|
|||
|
|
|
|||
|
|
if (!(await fs.pathExists(installerPath))) {
|
|||
|
|
return; // No custom installer
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const moduleInstaller = require(installerPath);
|
|||
|
|
|
|||
|
|
if (typeof moduleInstaller.install === 'function') {
|
|||
|
|
const projectRoot = path.dirname(bmadDir);
|
|||
|
|
const logger = options.logger || {
|
|||
|
|
log: console.log,
|
|||
|
|
error: console.error,
|
|||
|
|
warn: console.warn,
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const result = await moduleInstaller.install({
|
|||
|
|
projectRoot,
|
|||
|
|
config: options.moduleConfig || {},
|
|||
|
|
installedIDEs: options.installedIDEs || [],
|
|||
|
|
logger,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (!result) {
|
|||
|
|
console.warn(chalk.yellow(`Module installer returned false`));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error(chalk.red(`Error running module installer: ${error.message}`));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Command-Line Interface Design
|
|||
|
|
|
|||
|
|
### Interactive Mode
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
$ bmad install-module
|
|||
|
|
|
|||
|
|
🔧 BMAD Custom Module Installer
|
|||
|
|
|
|||
|
|
? Module source path: /Users/brianmadison/dev/my-custom-module
|
|||
|
|
? Target project path: /Users/brianmadison/dev/my-app
|
|||
|
|
? Select IDEs to integrate with: (Use arrows, space to select)
|
|||
|
|
◉ codex (Claude Code)
|
|||
|
|
◯ windsurf (Windsurf)
|
|||
|
|
◯ cursor (Cursor)
|
|||
|
|
◯ cline (Cline)
|
|||
|
|
|
|||
|
|
Validating module structure...
|
|||
|
|
✓ Module structure valid
|
|||
|
|
Module: My Custom Module
|
|||
|
|
Code: my-module
|
|||
|
|
Agents: 3
|
|||
|
|
|
|||
|
|
... (rest of installation)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Non-Interactive Mode
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
bmad install-module \
|
|||
|
|
--source /path/to/module \
|
|||
|
|
--target /path/to/project \
|
|||
|
|
--ides codex,windsurf \
|
|||
|
|
--non-interactive
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### With Config File (CI/CD)
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# Create config file: module-config.json
|
|||
|
|
{
|
|||
|
|
"project_name": "My Project",
|
|||
|
|
"user_skill_level": "intermediate",
|
|||
|
|
"tech_docs": "docs"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Install with config
|
|||
|
|
bmad install-module \
|
|||
|
|
--source ./my-module \
|
|||
|
|
--target . \
|
|||
|
|
--ides codex \
|
|||
|
|
--config-file ./module-config.json \
|
|||
|
|
--non-interactive
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Future Enhancements
|
|||
|
|
|
|||
|
|
### npm Package Integration
|
|||
|
|
|
|||
|
|
When core becomes `@bmad/core`:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# Install globally
|
|||
|
|
npm install -g @bmad/core
|
|||
|
|
|
|||
|
|
# Use anywhere
|
|||
|
|
bmad install-module --source ~/modules/my-module --target ./project
|
|||
|
|
|
|||
|
|
# Or as project dependency
|
|||
|
|
npm install --save-dev @bmad/core
|
|||
|
|
npx bmad install-module --source ./custom-module --target .
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Module Registry
|
|||
|
|
|
|||
|
|
Future consideration: BMAD module registry
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# Publish to registry
|
|||
|
|
bmad publish-module --source ./my-module
|
|||
|
|
|
|||
|
|
# Install from registry
|
|||
|
|
bmad install-module my-module # Looks up in registry
|
|||
|
|
|
|||
|
|
# Search registry
|
|||
|
|
bmad search-module testing
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Update Detection
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# Check for updates to custom modules
|
|||
|
|
bmad check-updates
|
|||
|
|
|
|||
|
|
# Update specific module
|
|||
|
|
bmad update-module my-module --from-source /path/to/latest
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Testing Plan
|
|||
|
|
|
|||
|
|
### Unit Tests
|
|||
|
|
|
|||
|
|
1. **ModuleValidator tests**
|
|||
|
|
- Valid module structure
|
|||
|
|
- Missing required files
|
|||
|
|
- Invalid metadata
|
|||
|
|
- Legacy warnings
|
|||
|
|
|
|||
|
|
2. **ConfigCollector tests**
|
|||
|
|
- Read install-config.yaml
|
|||
|
|
- Variable substitution
|
|||
|
|
- Multi-select handling
|
|||
|
|
|
|||
|
|
3. **ModuleManager.installFromPath tests**
|
|||
|
|
- File copying
|
|||
|
|
- Filtering logic
|
|||
|
|
- Agent compilation
|
|||
|
|
|
|||
|
|
### Integration Tests
|
|||
|
|
|
|||
|
|
1. **End-to-end installation**
|
|||
|
|
- Install BMD module
|
|||
|
|
- Verify all files copied
|
|||
|
|
- Verify agents compiled
|
|||
|
|
- Verify IDE artifacts created
|
|||
|
|
- Verify manifests updated
|
|||
|
|
|
|||
|
|
2. **Error scenarios**
|
|||
|
|
- Invalid module structure
|
|||
|
|
- Missing core
|
|||
|
|
- Installation failures
|
|||
|
|
- Rollback behavior
|
|||
|
|
|
|||
|
|
### Manual Testing
|
|||
|
|
|
|||
|
|
1. **Test with BMD module**
|
|||
|
|
- Source: `/Users/brianmadison/dev/BMAD-METHOD/bmd`
|
|||
|
|
- Various IDEs
|
|||
|
|
- Verify functionality
|
|||
|
|
|
|||
|
|
2. **Test with minimal module**
|
|||
|
|
- Create simple test module
|
|||
|
|
- Verify basic flow works
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Key Insights & Decisions
|
|||
|
|
|
|||
|
|
### Why This Approach?
|
|||
|
|
|
|||
|
|
1. **Reuses 80% of existing code**: YamlXmlBuilder, IdeManager, ConfigCollector, ManifestGenerator all work as-is
|
|||
|
|
|
|||
|
|
2. **Clean separation**: New CustomModuleInstaller doesn't interfere with existing Installer
|
|||
|
|
|
|||
|
|
3. **Backward compatible**: Existing `bmad install` continues to work unchanged
|
|||
|
|
|
|||
|
|
4. **Future-proof**: Architecture supports npm packaging and module registry
|
|||
|
|
|
|||
|
|
5. **Extensible**: Easy to add new features like update detection, module search, etc.
|
|||
|
|
|
|||
|
|
### Critical Design Principles
|
|||
|
|
|
|||
|
|
1. **Source modules NEVER have config.yaml** - it's generated at install time
|
|||
|
|
2. **install-config.yaml is the source of truth** for module configuration
|
|||
|
|
3. **\_module-installer/ is transient** - used during install, not copied to target
|
|||
|
|
4. **Core is always required** - custom modules extend core functionality
|
|||
|
|
5. **IDE integration is modular** - easy to add new IDE support
|
|||
|
|
|
|||
|
|
### Common Pitfalls to Avoid
|
|||
|
|
|
|||
|
|
1. ❌ Don't copy config.yaml from source
|
|||
|
|
2. ❌ Don't skip validation - always validate module structure first
|
|||
|
|
3. ❌ Don't ignore legacy warnings - help users modernize
|
|||
|
|
4. ❌ Don't forget to update manifests - critical for integrity
|
|||
|
|
5. ❌ Don't hardcode paths - use {project-root} placeholders
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## References
|
|||
|
|
|
|||
|
|
### Key Files to Study
|
|||
|
|
|
|||
|
|
1. **tools/cli/commands/install.js** - Current installer command
|
|||
|
|
2. **tools/cli/installers/lib/core/installer.js** - Main installer orchestration
|
|||
|
|
3. **tools/cli/installers/lib/modules/manager.js** - Module management logic
|
|||
|
|
4. **tools/cli/installers/lib/core/config-collector.js** - Configuration collection
|
|||
|
|
5. **tools/cli/lib/yaml-xml-builder.js** - Agent compilation engine
|
|||
|
|
6. **tools/cli/installers/lib/ide/manager.js** - IDE integration
|
|||
|
|
7. **src/modules/bmm/\_module-installer/install-config.yaml** - Example config
|
|||
|
|
|
|||
|
|
### Documentation
|
|||
|
|
|
|||
|
|
1. **tools/cli/README.md** - CLI documentation
|
|||
|
|
2. **CLAUDE.md** - Project conventions and architecture
|
|||
|
|
3. **src/modules/bmm/workflows/README.md** - BMM workflow guide
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Next Steps (When Building)
|
|||
|
|
|
|||
|
|
1. **Read this document completely**
|
|||
|
|
2. **Study the referenced key files** to understand existing patterns
|
|||
|
|
3. **Start with ModuleValidator** - it's the simplest and most isolated
|
|||
|
|
4. **Then CustomModuleInstaller** - wire everything together
|
|||
|
|
5. **Then command handler** - user interface
|
|||
|
|
6. **Test incrementally** - validate each phase works before moving on
|
|||
|
|
7. **Test with BMD module** - real-world validation
|
|||
|
|
8. **Update documentation** - CLI README and create authoring guide
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Contact & Support
|
|||
|
|
|
|||
|
|
- **Owner**: BMad (user_name from config)
|
|||
|
|
- **Agent**: Scott - Chief CLI Tooling Officer
|
|||
|
|
- **Primary Domain**: tools/cli/
|
|||
|
|
- **Discord**: https://discord.gg/gk8jAdXWmj (#general-dev)
|
|||
|
|
- **GitHub Issues**: https://github.com/bmad-code-org/BMAD-METHOD/issues
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
**Document Status**: Ready for implementation
|
|||
|
|
**Last Updated**: 2025-10-19
|
|||
|
|
**Version**: 1.0
|