# 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