Skip to content

Conversation

@8nevil8
Copy link

@8nevil8 8nevil8 commented Dec 17, 2025

Overview

This PR introduces comprehensive non-interactive installation support for BMAD, enabling automated, scriptable installations through CLI flags and environment variables. This addresses the need for CI/CD pipelines, containerized deployments, and hands-off consistent installations across teams.

Problem Statement

Previously, BMAD installation required interactive prompts for every configuration option, making it unsuitable for:

  • Automated deployment scripts
  • CI/CD pipelines
  • Docker/container builds
  • Team standardization (everyone needed to manually configure the same settings)
  • Batch installations across multiple projects

Solution

Added a complete non-interactive installation system with:

Core Features

  • -y, --non-interactive flag - Skip all prompts, use defaults or CLI-provided values
  • CLI options for all configuration - --user-name, --skill-level, --output-folder, etc.
  • Environment variable fallbacks - Automatically detect system settings (USER, LANG, HOME)
  • Team-based installations - Install predefined agent/workflow bundles (--team=fullstack, --team=gamedev)
  • Profile-based installations - Quick presets (--profile=minimal, --profile=solo-dev, --profile=full)
  • Selective module/agent/workflow installation - Fine-grained control over what gets installed

Architecture

New Modules:

  • env-resolver.js - Resolves configuration from CLI → ENV → defaults with priority chain
  • options-parser.js - Parses and validates CLI options, handles profile/team expansion
  • team-loader.js - Discovers and loads team definitions from YAML files
  • profiles/definitions.js - Pre-defined installation profiles (minimal, solo-dev, team, full)

Modified Modules:

  • config-collector.js - Added collectModuleConfigNonInteractive() for automated config resolution
  • manifest-generator.js - Enhanced with filtering for agents/workflows based on selections
  • ui.js - Added buildNonInteractiveConfig() to construct full installation config without prompts
  • installer.js - Updated manifest to include installation mode and selected items

Usage Examples

# Minimal installation with defaults
npx bmad-method@alpha install -y

# Custom configuration
npx bmad-method@alpha install -y \
  --user-name=Alice \
  --skill-level=advanced \
  --output-folder=.bmad-output

# Team-based installation
npx bmad-method@alpha install -y --team=fullstack

# Profile-based installation
npx bmad-method@alpha install -y --profile=minimal

# Selective agents and workflows
npx bmad-method@alpha install -y \
  --agents=dev,architect,pm \
  --workflows=create-prd,create-tech-spec,dev-story

Documentation

  • README.md - Added quick-start non-interactive examples
  • docs/non-interactive-installation-guide.md - Comprehensive 440-line guide covering:
    • Quick-start commands
    • CLI options reference
    • Team/profile scenarios
    • Environment variable mapping
    • Troubleshooting
    • Advanced examples

Testing

  • Integration tests - test-non-interactive.sh with 8 test cases validating various flag combinations
  • All existing tests pass - Schema validation, installation components, linting, formatting

Changes Summary

  • 12 files changed with 1,549 additions
  • 7 new modules created
  • 5 existing modules enhanced
  • 100% backward compatible - Interactive mode unchanged and remains the default

Benefits

  1. CI/CD Ready - Can now be installed in automated pipelines
  2. Team Consistency - Teams can share a single installation command ensuring everyone has the same setup
  3. Faster Onboarding - New team members can install with one command
  4. Container-Friendly - Works in Docker builds and other containerized environments
  5. Flexible - Supports everything from minimal (one agent) to full (all agents/workflows) installations

Related Issues

This addresses longstanding requests for scriptable BMAD installations, particularly for:

  • Organizations adopting AI agents across multiple projects
  • DevOps teams integrating BMAD into deployment workflows
  • Teams wanting consistent "gentlemen's set" of agents per project type

Testing Checklist

  • Schema validation tests pass (49/49)
  • Installation component tests pass (13/13)
  • ESLint passes with no errors
  • Markdown lint passes
  • Prettier formatting passes
  • Shell integration tests created and passing
  • Manual testing of non-interactive installations
  • Backward compatibility verified (interactive mode unchanged)

Breaking Changes

None - this is purely additive. Default behavior (interactive installation) remains unchanged.

@bmadcode
Copy link
Collaborator

Thank you - love this idea, its been on the backburner for some time. I will need some time to go through it. I really like the profiles idea also. The general idea is longer term I do want even fuller customization, so the idea that this could be also a user provided config to allow for hands off consistent installations is great.

@8nevil8
Copy link
Author

8nevil8 commented Dec 17, 2025

@bmadcode that's what really needed for cases where adoption of agents just started and no need to bring full suite to any agents. many organizations rely on single coding agent and want simple 'gentlemen' set of agents/workflows customized per project
ping me once you validate idea, I'll update PR

@alexeyv
Copy link
Contributor

alexeyv commented Dec 18, 2025

@CodeRabbit review

@coderabbitai
Copy link

coderabbitai bot commented Dec 18, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link

coderabbitai bot commented Dec 18, 2025

Walkthrough

This PR introduces comprehensive non-interactive installation support for BMAD, including new CLI options parsing, environment variable resolution, team/profile management, and installation filtering capabilities. Documentation and test coverage are added to validate the new non-interactive workflow.

Changes

Cohort / File(s) Summary
Documentation
README.md, docs/non-interactive-installation-guide.md
Added interactive and non-interactive installation sections to README; created new comprehensive guide covering quick-start commands, CLI options reference, team/profile scenarios, troubleshooting, and advanced examples.
CLI Command
tools/cli/commands/install.js
Enhanced command description with rich documentation (examples, special values, modifiers, teams/profiles) and expanded options array. Updated action to pass CLI options to promptInstall().
Environment & Options
tools/cli/installers/lib/core/env-resolver.js, options-parser.js
Created new env-resolver module with functions for resolving user name, language, home directory, and generic value resolution (CLI → ENV → default). Created new options-parser module for parsing and validating CLI options, handling profile expansion, modifiers, and special values.
Configuration Collection
tools/cli/installers/lib/core/config-collector.js
Added collectModuleConfigNonInteractive() method for non-interactive module config resolution using priority order (CLI → ENV → existing → default). Imported environment resolver utilities.
Installation Workflow
tools/cli/installers/lib/core/installer.js, manifest-generator.js
Updated manifest generation to include install mode (interactive/non-interactive) and selected agents/workflows. Enhanced manifest-generator with filtering methods (filterWorkflows(), filterAgents()) and updated collection signatures to accept selection parameters.
Profiles & Teams
tools/cli/installers/lib/profiles/definitions.js, tools/cli/installers/lib/teams/team-loader.js
Created profiles module with pre-defined bundles (minimal, full, solo-dev, team) and getter functions. Created team-loader module providing discovery, loading, expansion, and modifier application for team definitions with error handling and metadata extraction.
UI Integration
tools/cli/lib/ui.js
Updated promptInstall() to accept cliOptions and route to new non-interactive flow. Added buildNonInteractiveConfig() method to construct full installation configuration without prompts, including team expansion, module selection, and action detection.
Testing
tools/cli/test/test-non-interactive.sh
Added comprehensive Bash test suite with 8 test cases validating non-interactive CLI invocations with various flag combinations and installation artifact verification.

Sequence Diagram(s)

sequenceDiagram
    participant CLI as CLI Entry
    participant Parser as Options Parser
    participant EnvRes as Environment Resolver
    participant Profiles as Profiles
    participant Teams as Teams
    participant Collector as Config Collector
    participant MGen as Manifest Generator
    participant Installer as Installer
    
    CLI->>Parser: parseOptions(cliOptions)
    Parser->>Profiles: getProfile(profileName)
    Profiles-->>Parser: profile defaults
    Parser->>EnvRes: getEnvironmentDefaults()
    EnvRes-->>Parser: resolved env values
    Parser-->>CLI: normalized options
    
    CLI->>CLI: detect action (install/update)
    
    alt Team-based Expansion
        CLI->>Teams: loadTeam(teamName)
        Teams-->>CLI: team {agents, workflows}
        CLI->>Teams: applyTeamModifiers(team, modifiers)
        Teams-->>CLI: modified team
    end
    
    CLI->>Collector: collectModuleConfigNonInteractive(module)
    Collector->>EnvRes: resolveValue(CLI, ENV, default)
    EnvRes-->>Collector: resolved value
    Collector-->>CLI: module config
    
    CLI->>MGen: collectWorkflows(selectedModules, selectedWorkflows)
    MGen->>MGen: filterWorkflows(workflows, selected)
    MGen-->>CLI: filtered workflows
    
    CLI->>MGen: collectAgents(selectedModules, selectedAgents)
    MGen->>MGen: filterAgents(agents, selected)
    MGen-->>CLI: filtered agents
    
    CLI->>MGen: writeMainManifest(installMode, selected lists)
    MGen-->>CLI: manifest with metadata
    
    CLI->>Installer: proceed with config
    Installer-->>CLI: installation complete
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • env-resolver.js: Verify fallback chains and environment variable coverage across different OS/shell environments; check error handling in os.userInfo() calls.
  • options-parser.js: Validate modifier parsing logic (+/- prefix handling), profile expansion precedence, special value handling ('all', 'none', 'minimal'), and comprehensive error messages for invalid inputs.
  • manifest-generator.js: Review filtering logic for workflows/agents, ensure wildcard/special value handling is consistent, verify manifest structure augmentation doesn't break downstream consumers.
  • team-loader.js: Validate YAML parsing robustness, error handling for missing teams, glob pattern coverage across module structure, and modifier application logic.
  • config-collector.js: Ensure non-interactive resolution order matches design intent, verify preservation of existing configurations, check integration with env-resolver.
  • ui.js: Verify non-interactive config builder mirrors critical interactive flow paths, validate action detection logic, ensure team expansion and module selection handle edge cases.
  • Integration: Cross-check data flow between parser → env-resolver → config-collector → manifest-generator, particularly around how selected agents/workflows propagate through the system.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description check ⚠️ Warning No pull request description was provided by the author, making it impossible to evaluate whether it relates to the changeset. Add a pull request description explaining the purpose, scope, and key features of the non-interactive CLI installation feature.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: introduce non-interactive cli installation' directly and clearly summarizes the main change—adding non-interactive CLI installation capability—which is confirmed by the file summaries showing new CLI options, non-interactive flows, and environment-based configuration collection.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (10)
tools/cli/installers/lib/teams/team-loader.js (3)

1-4: Consider using async file operations for consistency.

The module imports synchronous fs from node:fs but the functions are declared async. For consistency with the rest of the codebase (which uses fs-extra), consider using async file operations:

const fs = require('fs-extra');
const path = require('node:path');
const yaml = require('yaml');
const { glob } = require('glob');

Then replace fs.readFileSync with await fs.readFile at lines 27 and 84.


52-55: Remove unused catch binding.

Per eslint, the error binding is unused. Either use it or remove it:

  } catch {
    // If glob fails, return empty array
    return [];
  }

131-154: Use slice() instead of substring() per project conventions.

The eslint rules prefer String#slice() over String#substring():

🔎 Apply this diff:
   for (const modifier of agentModifiers) {
     if (modifier.startsWith('+')) {
-      const agent = modifier.substring(1);
+      const agent = modifier.slice(1);
       if (!result.agents.includes(agent)) {
         result.agents.push(agent);
       }
     } else if (modifier.startsWith('-')) {
-      const agent = modifier.substring(1);
+      const agent = modifier.slice(1);
       result.agents = result.agents.filter((a) => a !== agent);
     }
   }

   // Parse and apply workflow modifiers
   for (const modifier of workflowModifiers) {
     if (modifier.startsWith('+')) {
-      const workflow = modifier.substring(1);
+      const workflow = modifier.slice(1);
       if (!result.workflows.includes(workflow)) {
         result.workflows.push(workflow);
       }
     } else if (modifier.startsWith('-')) {
-      const workflow = modifier.substring(1);
+      const workflow = modifier.slice(1);
       result.workflows = result.workflows.filter((w) => w !== workflow);
     }
   }
tools/cli/test/test-non-interactive.sh (1)

158-161: Consider preserving test artifacts on failure for debugging.

Currently, the test directory is always cleaned up. For debugging failed tests, consider preserving the directory when tests fail:

# Cleanup (only on success)
echo ""
if [ $TESTS_FAILED -eq 0 ]; then
    echo "🧹 Cleaning up test directory: $TEST_DIR"
    rm -rf "$TEST_DIR"
else
    echo "⚠️  Keeping test directory for debugging: $TEST_DIR"
fi
tools/cli/installers/lib/core/config-collector.js (1)

886-912: Consider using switch statement for core field mappings.

The eslint hint suggests using switch instead of multiple else-if. While the current code is readable, a switch statement can be cleaner for string-based dispatching:

🔎 Optional refactor using switch:
       if (moduleName === 'core') {
         // Core module has special mappings
-        if (key === 'user_name') {
-          value = resolveValue(cliOptions.userName, null, envDefaults.userName);
-        } else if (key === 'user_skill_level') {
-          value = resolveValue(cliOptions.skillLevel, null, item.default || 'intermediate');
-        } else if (key === 'communication_language') {
-          value = resolveValue(
-            cliOptions.communicationLanguage,
-            null,
-            envDefaults.communicationLanguage,
-          );
-        } else if (key === 'document_output_language') {
-          value = resolveValue(cliOptions.documentLanguage, null, envDefaults.documentLanguage);
-        } else if (key === 'output_folder') {
-          value = resolveValue(cliOptions.outputFolder, null, item.default);
-        } else if (item.default !== undefined) {
-          value = item.default;
-        }
+        switch (key) {
+          case 'user_name':
+            value = resolveValue(cliOptions.userName, null, envDefaults.userName);
+            break;
+          case 'user_skill_level':
+            value = resolveValue(cliOptions.skillLevel, null, item.default || 'intermediate');
+            break;
+          case 'communication_language':
+            value = resolveValue(
+              cliOptions.communicationLanguage,
+              null,
+              envDefaults.communicationLanguage,
+            );
+            break;
+          case 'document_output_language':
+            value = resolveValue(cliOptions.documentLanguage, null, envDefaults.documentLanguage);
+            break;
+          case 'output_folder':
+            value = resolveValue(cliOptions.outputFolder, null, item.default);
+            break;
+          default:
+            if (item.default !== undefined) {
+              value = item.default;
+            }
+        }
tools/cli/installers/lib/core/env-resolver.js (1)

29-31: Remove unused catch binding.

The error variable is declared but never used. Per eslint, remove it.

🔎 Suggested fix
-  } catch (error) {
+  } catch {
     // os.userInfo() can fail in some environments
   }
tools/cli/installers/lib/core/options-parser.js (2)

59-67: Use slice() instead of substring().

ESLint prefers String#slice() over String#substring() for consistency.

🔎 Suggested fix
   for (const item of list) {
     if (item.startsWith('+')) {
-      result.add.push(item.substring(1));
+      result.add.push(item.slice(1));
     } else if (item.startsWith('-')) {
-      result.remove.push(item.substring(1));
+      result.remove.push(item.slice(1));
     } else {
       result.base.push(item);
     }
   }

166-174: Use dynamic profile names from definitions instead of hardcoding.

The valid profiles are hardcoded but getProfileNames() is already exported from ../profiles/definitions.js. Using the dynamic list keeps validation automatically synchronized with profile definitions, ensuring new profiles added to PROFILES are included without manual updates.

-const { getProfile } = require('../profiles/definitions');
+const { getProfile, getProfileNames } = require('../profiles/definitions');

   // Validate profile
   if (options.profile) {
-    const validProfiles = ['minimal', 'full', 'solo-dev', 'team'];
+    const validProfiles = getProfileNames();
     if (!validProfiles.includes(options.profile.toLowerCase())) {
       errors.push(
-        `Invalid profile: ${options.profile}. Valid values: minimal, full, solo-dev, team`,
+        `Invalid profile: ${options.profile}. Valid values: ${validProfiles.join(', ')}`,
       );
     }
   }
tools/cli/installers/lib/core/manifest-generator.js (2)

162-166: Use replaceAll() and consider escaping regex special characters.

  1. ESLint prefers replaceAll() over replace() with global flag patterns.
  2. The wildcard pattern only handles * but doesn't escape other regex special characters (., +, ?, etc.). If a workflow name contains these characters, the match could behave unexpectedly.
🔎 Suggested fix
         // Wildcard matching: create-* matches create-prd, create-tech-spec, etc.
         if (pattern.includes('*')) {
-          const regex = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$');
+          // Escape regex special chars except *, then convert * to .*
+          const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&');
+          const regex = new RegExp('^' + escaped.replaceAll('*', '.*') + '$');
           return regex.test(workflowName);
         }

319-323: Same regex escaping concern as filterWorkflows.

Apply the same fix to escape regex special characters in agent name patterns.

🔎 Suggested fix
         // Wildcard matching: dev* matches dev, dev-story, etc.
         if (pattern.includes('*')) {
-          const regex = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$');
+          // Escape regex special chars except *, then convert * to .*
+          const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&');
+          const regex = new RegExp('^' + escaped.replaceAll('*', '.*') + '$');
           return regex.test(agentName);
         }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 363915b and 52bd250.

📒 Files selected for processing (12)
  • README.md (2 hunks)
  • docs/non-interactive-installation-guide.md (1 hunks)
  • tools/cli/commands/install.js (1 hunks)
  • tools/cli/installers/lib/core/config-collector.js (2 hunks)
  • tools/cli/installers/lib/core/env-resolver.js (1 hunks)
  • tools/cli/installers/lib/core/installer.js (1 hunks)
  • tools/cli/installers/lib/core/manifest-generator.js (6 hunks)
  • tools/cli/installers/lib/core/options-parser.js (1 hunks)
  • tools/cli/installers/lib/profiles/definitions.js (1 hunks)
  • tools/cli/installers/lib/teams/team-loader.js (1 hunks)
  • tools/cli/lib/ui.js (2 hunks)
  • tools/cli/test/test-non-interactive.sh (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*

⚙️ CodeRabbit configuration file

**/*: Focus on inconsistencies, contradictions, edge cases and serious issues.
Avoid commenting on minor issues such as linting, formatting and style issues.
When providing code suggestions, use GitHub's suggestion format:

<code changes>

Files:

  • tools/cli/installers/lib/core/installer.js
  • tools/cli/test/test-non-interactive.sh
  • tools/cli/commands/install.js
  • tools/cli/installers/lib/core/options-parser.js
  • tools/cli/installers/lib/profiles/definitions.js
  • tools/cli/installers/lib/core/manifest-generator.js
  • tools/cli/installers/lib/core/env-resolver.js
  • tools/cli/installers/lib/teams/team-loader.js
  • tools/cli/installers/lib/core/config-collector.js
  • README.md
  • docs/non-interactive-installation-guide.md
  • tools/cli/lib/ui.js
**/*.js

⚙️ CodeRabbit configuration file

**/*.js: CLI tooling code. Check for: missing error handling on fs operations,
path.join vs string concatenation, proper cleanup in error paths.
Flag any process.exit() without error message.

Files:

  • tools/cli/installers/lib/core/installer.js
  • tools/cli/commands/install.js
  • tools/cli/installers/lib/core/options-parser.js
  • tools/cli/installers/lib/profiles/definitions.js
  • tools/cli/installers/lib/core/manifest-generator.js
  • tools/cli/installers/lib/core/env-resolver.js
  • tools/cli/installers/lib/teams/team-loader.js
  • tools/cli/installers/lib/core/config-collector.js
  • tools/cli/lib/ui.js
🧬 Code graph analysis (5)
tools/cli/installers/lib/core/installer.js (3)
tools/cli/commands/install.js (1)
  • config (48-48)
tools/cli/lib/agent/installer.js (1)
  • config (26-26)
tools/cli/installers/lib/ide/shared/module-injections.js (1)
  • config (17-17)
tools/cli/commands/install.js (1)
src/modules/bmb/workflows/create-module/templates/installer.template.js (1)
  • options (16-16)
tools/cli/installers/lib/core/options-parser.js (1)
tools/cli/installers/lib/profiles/definitions.js (1)
  • profile (70-70)
tools/cli/installers/lib/profiles/definitions.js (2)
tools/cli/installers/lib/core/options-parser.js (1)
  • profile (119-119)
tools/cli/installers/lib/teams/team-loader.js (1)
  • descriptions (166-166)
tools/cli/installers/lib/core/env-resolver.js (1)
tools/cli/lib/ui.js (1)
  • os (4-4)
🪛 GitHub Actions: Quality & Validation
tools/cli/installers/lib/core/options-parser.js

[warning] 1-1: Code style issues found in 1 file. Run 'prettier --write' to fix.

tools/cli/installers/lib/profiles/definitions.js

[warning] 1-1: Code style issues found in 1 file. Run 'prettier --write' to fix.

tools/cli/installers/lib/teams/team-loader.js

[warning] 1-1: Code style issues found in 1 file. Run 'prettier --write' to fix.

tools/cli/installers/lib/core/config-collector.js

[warning] 1-1: Code style issues found in 1 file. Run 'prettier --write' to fix.

docs/non-interactive-installation-guide.md

[warning] 1-1: Code style issues found in 1 file. Run 'prettier --write' to fix.

🪛 GitHub Check: eslint
tools/cli/installers/lib/core/options-parser.js

[failure] 63-63:
Prefer String#slice() over String#substring()


[failure] 61-61:
Prefer String#slice() over String#substring()

tools/cli/installers/lib/core/manifest-generator.js

[failure] 164-164:
Prefer String#replaceAll() over String#replace()


[failure] 321-321:
Prefer String#replaceAll() over String#replace()

tools/cli/installers/lib/core/env-resolver.js

[failure] 29-29:
Remove unused catch binding error

tools/cli/installers/lib/teams/team-loader.js

[failure] 146-146:
Prefer String#slice() over String#substring()


[failure] 138-138:
Prefer String#slice() over String#substring()


[failure] 133-133:
Prefer String#slice() over String#substring()


[failure] 52-52:
Remove unused catch binding error

tools/cli/installers/lib/core/config-collector.js

[failure] 888-888:
Use switch instead of multiple else-if

🪛 LanguageTool
README.md

[style] ~125-~125: It’s more common nowadays to write this noun as one word.
Context: ..., use defaults - --user-name <name> - User name for configuration - `--skill-level <lev...

(RECOMMENDED_COMPOUNDS)

docs/non-interactive-installation-guide.md

[style] ~24-~24: It’s more common nowadays to write this noun as one word.
Context: ...``` This installs BMAD with: - Default user name from system (USER environment variable)...

(RECOMMENDED_COMPOUNDS)


[style] ~175-~175: It’s more common nowadays to write this noun as one word.
Context: ...install -y | | --user-name <name> | User name | System user | --user-name=Alice | |...

(RECOMMENDED_COMPOUNDS)

🔇 Additional comments (23)
tools/cli/commands/install.js (2)

11-45: Well-documented CLI description with comprehensive examples.

The multiline description provides clear guidance for both interactive and non-interactive usage, including examples, special values, modifiers, and available teams/profiles. This is excellent for discoverability and user experience.


46-48: Options now correctly passed to promptInstall.

The action signature and call to ui.promptInstall(options) properly propagate CLI options to the UI layer for non-interactive flow handling.

tools/cli/installers/lib/core/installer.js (1)

1078-1081: Manifest generation now tracks installation mode and selections.

The addition of selectedAgents, selectedWorkflows, and installMode to manifest generation options enables tracking of non-interactive installation choices. The optional chaining on config.cliOptions handles both interactive (undefined) and non-interactive cases gracefully.

README.md (1)

82-150: Excellent documentation for non-interactive installation.

The new sections provide comprehensive guidance for both interactive and non-interactive installation modes, including:

  • Clear examples for common scenarios
  • Full options reference
  • Team and profile descriptions
  • Modifier syntax for customization

This significantly improves the developer experience for CI/CD and automation use cases.

docs/non-interactive-installation-guide.md (1)

1-440: Comprehensive non-interactive installation guide.

This documentation is thorough and well-structured, covering:

  • Quick start and migration from interactive mode
  • Real-world use cases (CI/CD, Docker, team onboarding, IaC)
  • Complete CLI options reference with tables
  • Team and profile-based installation scenarios
  • Troubleshooting section with common issues and solutions
  • Advanced examples for reproducible and environment-based installations

This will be invaluable for teams adopting automated BMAD deployments.

tools/cli/installers/lib/profiles/definitions.js (2)

9-58: Well-structured profile definitions.

The profile definitions are clear and align with the documentation. Each profile provides a meaningful combination of modules, agents, and workflows for its use case.


65-77: Defensive copy prevents profile mutation.

Returning a shallow copy with { ...profile } prevents callers from accidentally mutating the shared PROFILES object. This is a good practice.

Note: Since profiles contain arrays (modules, agents, workflows), callers that modify these arrays would still affect the original. If this becomes an issue, consider a deep copy:

return JSON.parse(JSON.stringify(profile));

However, for current usage this is likely fine.

tools/cli/test/test-non-interactive.sh (2)

1-45: Good test infrastructure for non-interactive installation.

The test setup with colorized output, isolated temporary directories, and pass/fail tracking is well-structured. The run_test and verify_installation helpers provide reusable test utilities.


101-156: Comprehensive test coverage for non-interactive scenarios.

The 8 test cases provide good coverage:

  1. Minimal installation
  2. Custom user name
  3. Selective agent installation
  4. Selective workflow installation
  5. Team-based installation
  6. Profile-based installation
  7. Multiple CLI options combined
  8. Manifest tracking verification

This validates the core non-interactive functionality.

tools/cli/installers/lib/core/config-collector.js (3)

8-8: Environment-aware resolution utilities imported.

The new imports enable the non-interactive flow to resolve values from CLI options, environment variables, and defaults in a consistent order.


796-868: Well-structured non-interactive configuration collection.

The collectModuleConfigNonInteractive method properly:

  • Loads existing config for updates
  • Gets environment defaults
  • Handles custom module paths
  • Falls back gracefully when no config schema exists

This enables deterministic installations without user prompts.


914-968: Result template processing and existing config preservation.

The logic for processing result templates with placeholder replacement and preserving existing config values for fields not in schema ensures smooth updates and backwards compatibility.

tools/cli/installers/lib/core/env-resolver.js (3)

15-35: LGTM! Robust fallback chain for username resolution.

Good defensive coding with try-catch around os.userInfo() since it can fail in containerized or restricted environments. The fallback chain (environment variables → OS API → hardcoded default) is well-designed.


42-69: LGTM! Language detection is reasonable.

The locale parsing handles the common en_US.UTF-8 format correctly. The language map covers major languages with a sensible English fallback.


86-99: LGTM! Clean priority-based resolution.

The CLI > ENV > default priority chain is correctly implemented with proper null/undefined checks.

tools/cli/lib/ui.js (4)

1499-1545: LGTM! Team-based installation with modifier support.

The team expansion logic properly handles:

  • Loading team definitions
  • Applying agent/workflow modifiers (+add/-remove)
  • Graceful error handling with informative exit messages

The process.exit(1) on line 1544 is appropriate here since the team loading failure is unrecoverable in non-interactive mode.


1546-1565: LGTM! Module selection logic with profile support.

The fallback chain (team → modules → profile → default 'bmm') provides sensible defaults for non-interactive installations.


1583-1598: LGTM! Configuration object is well-structured.

The returned configuration mirrors the interactive flow's output structure, ensuring downstream installer logic works consistently for both modes.


1472-1491: The review comment's assumption about a missing --directory CLI option is incorrect. Examining the codebase shows:

  1. The install command in tools/cli/commands/install.js defines no --directory option
  2. The only path-related option is --output-folder, which specifies where BMAD artifacts are written, not the installation directory
  3. The parseOptions function in options-parser.js only handles: outputFolder, userName, skillLevel, modules, agents, workflows, team, profile, and language settings
  4. Using process.cwd() to determine the directory for checking existing installations is the intended behavior

The non-interactive mode does not need to support a --directory option.

Likely an incorrect or invalid review comment.

tools/cli/installers/lib/core/options-parser.js (2)

15-28: LGTM! Clean list parsing implementation.

Handles edge cases (undefined, non-string, empty items) correctly.


79-95: LGTM! Modifier application logic is correct.

Proper deduplication on add and filtering on remove.

tools/cli/installers/lib/core/manifest-generator.js (2)

134-171: LGTM! Workflow filtering with special values and wildcards.

The filtering logic correctly handles:

  • 'all' → return everything
  • 'none' → return empty array
  • Exact matches
  • Wildcard patterns

566-578: LGTM! Manifest now captures installation context.

Recording installMode, selectedAgents, and selectedWorkflows in the manifest enables reproducible installations and provides useful metadata for debugging.

@alexeyv
Copy link
Contributor

alexeyv commented Dec 18, 2025

PR Review: #1153

Title: feat: introduce non-interactive cli installation
Author: @8nevil8
Branch: main → main


Findings

1. Path duplication bug in manager.js [likely]

Severity: 🔴 Critical

The change at tools/cli/installers/lib/modules/manager.js:734 introduces an incorrect nested path:

// Changed FROM:
const cfgAgentsDir = path.join(bmadDir, '_config', 'agents');

// Changed TO:
const cfgAgentsDir = path.join(bmadDir, '_bmad', '_config', 'agents');

Since bmadDir is already the _bmad directory, this creates _bmad/_bmad/_config/agents instead of the intended _bmad/_config/agents. Other usages in the same file (lines 231, 779) correctly use path.join(bmadDir, '_config', ...).

Suggested fix: Revert this specific change to maintain consistency with other path constructions in the file.


2. Version regression in package.json [likely]

Severity: 🟡 Moderate

The PR changes package.json version from 6.0.0-alpha.19 to 6.0.0-alpha.18. This appears to be a merge/rebase issue where the PR branch was based on an older version of main.

Suggested fix: Rebase on current main and update version appropriately (likely 6.0.0-alpha.20).


3. CHANGELOG removes recent entry instead of adding new one [likely]

Severity: 🟡 Moderate

The diff shows the CHANGELOG removing the 6.0.0-alpha.19 entry rather than adding a new entry for the non-interactive installation feature. This suggests a similar merge/rebase issue.

Suggested fix: After rebasing on main, add a new changelog entry documenting the non-interactive installation feature.


4. Backup file committed [likely]

Severity: 🟡 Moderate

File tools/cli/installers/lib/core/installer.js.bak is included in the PR. Backup files should not be committed to source control.

Suggested fix: Remove this file and add *.bak to .gitignore if not already present.


5. Documentation shows only npx usage pattern

Severity: 🟢 Minor

The README and guide exclusively show npx bmad-method@alpha install -y syntax. Users who have already cloned the repo or installed globally would use bmad install -y. Adding both patterns would reduce confusion.

Suggested fix: Add a note in the documentation showing both invocation patterns.


6. Limited language map in env-resolver.js

Severity: 🟢 Minor

The language detection in tools/cli/installers/lib/core/env-resolver.js maps only 12 language codes to full names. Users with other locales (e.g., Dutch nl, Swedish sv, Polish pl) will silently default to English.

Suggested fix: Consider logging a debug message when falling back to English, or expand the language map.


7. No unit tests for new JavaScript functions

Severity: 🟢 Minor

The PR adds substantial new functionality (config-collector.js, env-resolver.js, options-parser.js) but only includes shell-based integration tests (test-non-interactive.sh). Unit tests would improve maintainability and CI compatibility across platforms.

Suggested fix: Consider adding Jest or Mocha unit tests for the new utility functions.


Summary

Critical: 1 | Moderate: 3 | Minor: 3

The non-interactive installation feature is well-designed with good separation of concerns (env-resolver, options-parser, team-loader, profiles). However, the PR has merge/rebase issues causing version regression and a critical path bug that would break agent compilation. These should be addressed before merging.


Review generated by Raven's Verdict. LLM-produced analysis - findings may be incorrect or lack context. Verify before acting.

8nevil8 and others added 7 commits December 18, 2025 12:25
- Fix ESLint errors: use switch over else-if, remove unused catch bindings, use slice over substring, use replaceAll over replace
- Fix Prettier formatting issues across all modified files
- Fix markdown lint: wrap bare URL in angle brackets

All tests passing: schemas, installation, validation, lint, markdown lint, and formatting checks.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
The ternary operators were checking `Array.isArray()` but returning the same value in both branches, making them completely pointless. Since profiles can contain both arrays (e.g., `['dev', 'architect']`) and strings (e.g., `'all'`), and both are valid, we should just assign the value directly.

Fixed lines:
- normalized.modules = profile.modules
- normalized.agents = profile.agents
- normalized.workflows = profile.workflows

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
The ternary operators WERE needed after all! Profile values can be:
- Arrays: ['dev', 'architect', 'pm']
- Strings: 'all' (special keyword)

Downstream code expects arrays:
- filterWorkflows() checks selectedWorkflows.includes('all')
- filterAgents() checks selectedAgents.includes('all')
- separateModifiers() iterates with for-of loop

Without wrapping strings in arrays:
- 'all' → stays as string → includes() doesn't work
- WITH fix: 'all' → becomes ['all'] → includes('all') works ✓

This fixes the profile workflow:
1. Profile defines: workflows: 'all'
2. Parser wraps: normalized.workflows = ['all']
3. Filter checks: selectedWorkflows.includes('all') → true ✓

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
After wrapping profile string values in arrays in options-parser, the modules handling in ui.js was still only checking for string 'all', not array ['all']. This would break profiles with `modules: 'all'`.

Added checks for both cases:
- String: `options.modules === 'all'` (original case)
- Array: `options.modules.includes('all')` (new case after wrapping)

Now modules handling is consistent with agents/workflows filtering which already used `.includes('all')`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Added IntelliJ IDEA project configuration directory to gitignore to prevent IDE-specific files from being committed.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Added BMAD output directory to gitignore to prevent generated output files from being committed.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Removed unused properties that were set but never read:
- normalized.profileModules
- normalized.profileAgents
- normalized.profileWorkflows

These values were:
1. Stored as unwrapped (strings like 'all' or arrays)
2. Never accessed anywhere in the codebase
3. Created confusion - actual values used are normalized.modules/agents/workflows
4. Inconsistent with the wrapped versions actually used downstream

The profile values are already correctly processed and stored in
normalized.modules/agents/workflows (with proper array wrapping),
which are then passed to installer via config.cliOptions.

No functional change - just removing dead code that served no purpose.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@alexeyv
Copy link
Contributor

alexeyv commented Dec 18, 2025

@8nevil8 Thanks for going through the review findings. Raven and Rabbit are both experimental. What do you think about them?

@8nevil8
Copy link
Author

8nevil8 commented Dec 19, 2025

@8nevil8 Thanks for going through the review findings. Raven and Rabbit are both experimental. What do you think about them?

used them as well. it's a matter of prompting as usual, since some comments are not relevant to PR. for ex. multi language support

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants