Skip to content

refactor(installer): restructure installer with clean separation of concerns#2129

Merged
alexeyv merged 4 commits intomainfrom
install-squashed
Mar 27, 2026
Merged

refactor(installer): restructure installer with clean separation of concerns#2129
alexeyv merged 4 commits intomainfrom
install-squashed

Conversation

@alexeyv
Copy link
Copy Markdown
Collaborator

@alexeyv alexeyv commented Mar 26, 2026

Summary

This is a ground-up restructure of the BMAD installer — the largest single refactor in project history. The 3,000-line monolithic Installer class and its tangled dependency tree have been replaced with a clean, prompt-free orchestrator backed by focused value objects and module classes.

Net result: 85 files changed, 3,703 additions, 7,715 deletions (−4,012 lines).

What changed

Directory restructure

  • tools/cli/tools/installer/ with a flattened hierarchy (installers/lib/core/core/, installers/lib/custom/handler.jscustom-handler.js, etc.)
  • All path references updated across package.json, CI, tests, and docs

New value objects replace stateful classes

  • Config — frozen, built once from user input via Config.build()
  • InstallPaths — async factory for path resolution and directory creation
  • ExistingInstall — replaces the stateful Detector class

Module system split

  • OfficialModules — source resolution, install, config collection (absorbs the old ConfigCollector)
  • CustomModules — paths Map, discoverPaths(), install
  • ExternalModuleManager — git clone/fetch, npm install

install() is now a prompt-free orchestrator

  • All user interaction happens upstream in ui.js
  • _prepareUpdateState() returns an explicit update state object instead of mutating a cloned config bag (customConfig eliminated entirely)
  • _restoreUserFiles() returns results instead of writing them back onto a mutable object

Dead code removed (~4,000 lines)

  • dependency-resolver.js (743 lines) — unused since module system redesign
  • _base-ide.js (657 lines) — _config-driven.js is the sole IDE installer
  • IdeConfigManager (157 lines), Detector (223 lines), config.js (213 lines), platform-codes.js helpers (216 lines)
  • manager.js (928 lines) — monolithic ModuleManager replaced by three focused classes
  • installer.js old (3,002 lines) → new (1,790 lines) — update(), updateCore(), promptUpdateAction(), createDirectoryStructure() all deleted as unreachable
  • npx-wrapper.jsbin in package.json points directly to bmad-cli.js
  • xml-utils.js, dead discoverPaths branch, orphaned JSDoc blocks
  • claude-workflow-yaml.md dangling symlink

Bug fixes included

  • ui.js package.json path was 3 levels up instead of 2 (broke quick-update detection)
  • bmb external module module-definition path updated for skills/ restructure (fix: update bmb module-definition path #2126 port)
  • All main-branch installer fixes verified as already ported (agent-manifest recursive walk, config paths issue 55, dead fallback removal)

Design principles

  1. Two configs, two paths — clean Config for official modules, separate custom content handling
  2. Core is just another module — in config.modules like any other, goes through OfficialModules.install()
  3. install() is a pure executor — never prompts, module/IDE removal is unconditional
  4. Explicit state, no mutation — update state is returned, not smuggled through object mutation

What this enables next

  • quickUpdate() simplification into a thinner config-assembly wrapper
  • Elimination of remaining _preserveModules / _existingModules pass-through fields
  • Cleaner test isolation (value objects are easy to construct in tests)

Test plan

  • npm ci && npm run quality — all checks pass (lint, format, markdown, docs build, 205 component tests, file ref validation, skill validation)
  • Fresh install: bmad-cli.js install --directory /tmp/test --modules bmm --tools claude-code --yes → 43 skills
  • Quick update on existing install → preserves settings, 43 skills
  • Custom module install with --custom-content → 46 skills (3 from custom modules)
  • All-modules install (bmm,bmb,cis,gds,tea,wds) → 113 skills
  • Status command on installed directory
  • Uninstall command
  • Skill/agent manifest diff against main baseline — identical output
  • No remaining tools/cli references in JS, JSON, YAML, or CI files

…oncerns

Move tools/cli/ to tools/installer/ with major structural cleanup:

- InstallPaths async factory for path resolution and directory creation
- Config value object (frozen) replaces mutable config bag
- ExistingInstall value object replaces stateful Detector class
- OfficialModules + CustomModules + ExternalModuleManager replace monolithic ModuleManager
- install() is prompt-free; all user interaction in ui.js
- Update state returned explicitly instead of mutating customConfig
- Delete dead code: dependency-resolver, _base-ide, IdeConfigManager,
  platform-codes helpers, npx wrapper, xml-utils
- Flatten directory structure: custom/handler → custom-handler,
  tools/cli/ → tools/installer/, lib/ directories removed
- Update all path references in package.json, tests, CI, and docs
Copy link
Copy Markdown

@augmentcode augmentcode bot left a comment

Choose a reason for hiding this comment

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

Review completed. 5 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 26, 2026

📝 Walkthrough

Walkthrough

This PR reorganizes the installer codebase from tools/cli/installers/lib/ to a new tools/installer/ directory structure. Old installer modules are removed and replaced with new implementations that consolidate installation logic. Package.json entrypoints, documentation links, and workflow triggers are updated to reference the new paths.

Changes

Cohort / File(s) Summary
Workflow & Configuration
.github/workflows/publish.yaml, package.json
Updated publish workflow trigger and npm entrypoints to use tools/installer/ paths and scripts instead of tools/cli/.
Documentation Path Updates
docs/how-to/non-interactive-installation.md, docs/fr/..., docs/zh-cn/..., src/core-skills/.../distillate-format-reference.md, tools/docs/_prompt-external-modules-page.md
Updated documentation references to point to tools/installer/ platform codes and external modules configuration.
Removed Old Installer Modules
tools/cli/installers/lib/core/dependency-resolver.js, tools/cli/installers/lib/core/detector.js, tools/cli/installers/lib/core/ide-config-manager.js, tools/cli/installers/lib/core/..., tools/cli/installers/lib/modules/..., tools/cli/installers/lib/ide/*, tools/cli/lib/...
Deleted entire legacy installer module implementations including dependency resolution, device detection, IDE configuration management, base IDE setup, module/platform code handling, and configuration utilities.
New Installer Core Implementation
tools/installer/core/installer.js, tools/installer/core/config.js, tools/installer/core/manifest.js, tools/installer/core/install-paths.js, tools/installer/core/existing-install.js
Added new consolidated installer orchestration logic with configuration building, path resolution, installation state detection, and lifecycle management for install/update/uninstall operations.
New Module Management
tools/installer/modules/official-modules.js, tools/installer/modules/custom-modules.js, tools/installer/modules/external-manager.js
Added new module discovery, installation, and configuration logic for official, custom, and externally hosted modules with file filtering and source resolution.
New IDE Management & Platform Configuration
tools/installer/ide/platform-codes.js, tools/installer/ide/platform-codes.yaml, tools/installer/ide/_config-driven.js, tools/installer/ide/manager.js
Moved and rewrote platform code configuration; refactored IDE setup to remove multi-target and template-based artifact generation, simplifying to single-target configuration-driven installation.
CLI Entry Point & UI Updates
tools/installer/bmad-cli.js, tools/installer/ui.js, tools/installer/cli-utils.js, tools/installer/custom-handler.js, tools/installer/message-loader.js
Updated main CLI entrypoint with shebang and import paths; rewrote UI to use new ExistingInstall detection, OfficialModules config collection, and removed legacy v4 migration flows.
Import Path Updates Across Tests & Supporting Files
test/test-install-to-bmad.js, test/test-installation-components.js, test/test-workflow-path-regex.js, tools/javascript-conventions.md, tools/lib/xml-utils.js, tools/installer/core/manifest-generator.js, tools/installer/ide/shared/..., tools/installer/custom-module-cache.js
Updated import paths to reference new tools/installer/ locations; removed obsolete XML utility function; deleted extensive test assertions for deprecated installer configuration flags and config-collector functionality.
Removed Entry Point
tools/bmad-npx-wrapper.js
Deleted NPX wrapper that handled npx-specific execution paths; CLI execution now delegated directly to new tools/installer/bmad-cli.js.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Suggested reviewers

  • bmadcode
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title directly reflects the main change: a major restructuring of the installer architecture with improved separation of concerns, replacing the monolithic design with focused value objects and cleaner module organization.
Description check ✅ Passed The description is comprehensive and well-related to the changeset, covering the restructure, new value objects, module system split, dead code removal, bug fixes, and extensive testing performed.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch install-squashed

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown

@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: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tools/installer/modules/official-modules.js (1)

1405-1414: ⚠️ Potential issue | 🟠 Major

--yes skips function-backed defaults.

buildQuestion() turns same-module templated defaults into functions, but this branch only copies literal defaults. Headless installs/tests will therefore drop those fields instead of resolving them.

🤖 Proposed fix
         for (const question of questions) {
           const hasDefault = question.default !== undefined && question.default !== null && question.default !== '';
-          if (hasDefault && typeof question.default !== 'function') {
-            allAnswers[question.name] = question.default;
-          }
+          if (!hasDefault) continue;
+          allAnswers[question.name] =
+            typeof question.default === 'function' ? question.default(allAnswers) : question.default;
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tools/installer/modules/official-modules.js` around lines 1405 - 1414, The
skip-prompts branch currently only copies literal defaults and ignores
function-backed defaults produced by buildQuestion, causing templated defaults
to be dropped; update the loop in the this.skipPrompts branch that iterates over
questions to check if question.default is a function and, if so, invoke it
(awaiting the result if it returns a Promise) with the current answers/context
(e.g., pass allAnswers or the same args buildQuestion expects) and assign the
returned value to allAnswers[question.name]; keep the existing behavior for
non-function defaults and preserve the hasDefault guard and logging using
moduleDisplayName.
🧹 Nitpick comments (4)
tools/installer/ide/_config-driven.js (2)

44-57: Unify the BMAD-entry predicate across detection paths.

detect(), cleanupTarget(), and findAncestorConflict() now each define “BMAD-owned entry” a little differently. Pull that into one helper so bmad-os-* handling and case-folding stay consistent across detect/cleanup/conflict flows.

♻️ Suggested refactor
+  isManagedBmadEntry(entry) {
+    const name = typeof entry === 'string' ? entry.toLowerCase() : '';
+    return name.startsWith('bmad') && !name.startsWith('bmad-os-');
+  }
+
   async detect(projectDir) {
     if (!this.configDir) return false;
 
     const dir = path.join(projectDir || process.cwd(), this.configDir);
     if (await fs.pathExists(dir)) {
       try {
         const entries = await fs.readdir(dir);
-        return entries.some((e) => typeof e === 'string' && e.startsWith('bmad'));
+        return entries.some((e) => this.isManagedBmadEntry(e));
       } catch {
         return false;
       }
     }
     return false;
   }
-      if (entry.startsWith('bmad') && !entry.startsWith('bmad-os-')) {
+      if (this.isManagedBmadEntry(entry)) {
         const entryPath = path.join(targetPath, entry);
         try {
           await fs.remove(entryPath);
           removedCount++;
-          const hasBmad = entries.some(
-            (e) => typeof e === 'string' && e.toLowerCase().startsWith('bmad') && !e.toLowerCase().startsWith('bmad-os-'),
-          );
+          const hasBmad = entries.some((e) => this.isManagedBmadEntry(e));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tools/installer/ide/_config-driven.js` around lines 44 - 57, Create a single
helper (e.g., isBmadEntry(name)) in this module that normalizes the entry name
(toLowerCase()) and returns true for names that start with 'bmad' or the fuller
prefix 'bmad-os-' (so both 'bmad*' and 'bmad-os-*' are handled consistently);
then replace the ad-hoc predicates in detect(), cleanupTarget(), and
findAncestorConflict() to call that helper so case-folding and the 'bmad-os-'
rule are unified across all detection/cleanup/conflict flows.

48-53: Don't hide unreadable config dirs behind false.

A permissions/I/O failure here is indistinguishable from “not installed”, so higher layers can take the wrong success path. Consider logging a warning or propagating a structured error instead of swallowing the exception.

As per coding guidelines, tools/**: Build script/tooling. Check error handling and proper exit codes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tools/installer/ide/_config-driven.js` around lines 48 - 53, The current
check in the config-driven installer swallows all errors in the fs.readdir block
(when checking dir via fs.pathExists and fs.readdir) and returns false, making
permission/I/O failures indistinguishable from "not installed"; change the catch
to either propagate a structured error (e.g., throw a new Error with context
including dir and the original error) or at minimum log a warning with dir and
error details (using the project's logger or console.warn) before rethrowing or
returning a distinct error result so callers don't treat unreadable config dirs
as absent.
tools/installer/ide/platform-codes.js (1)

13-25: Consider wrapping yaml.parse in try/catch for better error messages.

If platform-codes.yaml contains malformed YAML, yaml.parse() will throw a parsing error with a potentially cryptic message. Wrapping it in try/catch would allow a more informative error like "Failed to parse platform-codes.yaml: ".

♻️ Optional improvement for clearer error messages
   const content = await fs.readFile(PLATFORM_CODES_PATH, 'utf8');
-  _cachedPlatformCodes = yaml.parse(content);
+  try {
+    _cachedPlatformCodes = yaml.parse(content);
+  } catch (error) {
+    throw new Error(`Failed to parse platform-codes.yaml: ${error.message}`);
+  }
   return _cachedPlatformCodes;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tools/installer/ide/platform-codes.js` around lines 13 - 25, Wrap the
yaml.parse call in loadPlatformCodes in a try/catch to surface a clearer error:
catch parsing exceptions thrown by yaml.parse(content) and rethrow a new Error
that prefixes a helpful message like "Failed to parse platform-codes.yaml at
<PLATFORM_CODES_PATH>:" and include the original error message (or error.stack)
to preserve details; ensure _cachedPlatformCodes is only set on successful parse
and still returned from loadPlatformCodes.
tools/installer/modules/external-manager.js (1)

251-292: Dependency installation logic is correct but has minor duplication.

The npm install command and spinner handling are duplicated between the two conditional branches (Lines 255 and 281). While functional, this could be refactored to a helper function.

♻️ Optional: Extract npm install to helper
+  async _runNpmInstall(moduleCacheDir, moduleInfo, silent) {
+    const installSpinner = silent
+      ? { start() {}, stop() {}, error() {} }
+      : await prompts.spinner();
+    installSpinner.start(`Installing dependencies for ${moduleInfo.name}...`);
+    try {
+      execSync('npm install --omit=dev --no-audit --no-fund --no-progress --legacy-peer-deps', {
+        cwd: moduleCacheDir,
+        stdio: ['ignore', 'pipe', 'pipe'],
+        timeout: 120_000,
+      });
+      installSpinner.stop(`Installed dependencies for ${moduleInfo.name}`);
+    } catch (error) {
+      installSpinner.error(`Failed to install dependencies for ${moduleInfo.name}`);
+      if (!silent) await prompts.log.warn(`  ${error.message}`);
+    }
+  }

Then call await this._runNpmInstall(moduleCacheDir, moduleInfo, silent) in both branches.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tools/installer/modules/external-manager.js` around lines 251 - 292, The npm
install + spinner block is duplicated in the two branches; extract that logic
into a single helper (e.g., _runNpmInstall or runNpmInstall) that accepts
(moduleCacheDir, moduleInfo, silent) and performs: createSpinner(), start
message using moduleInfo.name, execSync with the same args and timeout, stop on
success, and error handling that calls installSpinner.error and, if !silent,
awaits prompts.log.warn with error.message; then replace both duplicated blocks
with a single await this._runNpmInstall(moduleCacheDir, moduleInfo, silent) call
so all install behavior is centralized and consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tools/installer/core/installer.js`:
- Around line 816-859: The current approach splits yamlContent into lines and
repartitions them, which breaks multi-line scalars/objects; instead, operate on
the data structure before stringifying: build a new plain object by taking
finalConfig (or structuredClone(finalConfig)), iterate its top-level keys and
construct an ordered object where keys not in coreConfig (module-specific) are
added first and keys present in coreConfig are added afterward, then
yaml.stringify that ordered object into yamlContent and prepend coreSection as a
comment if needed; reference symbols: coreConfig, finalConfig, yamlContent,
coreSection, moduleName.
- Around line 1683-1685: findBmadDir currently always returns
path.join(projectDir, BMAD_FOLDER_NAME) which breaks detection if the BMAD
folder was renamed; update findBmadDir to first scan projectDir for any
subdirectory that contains the marker file "_config/manifest.yaml" and return
that directory as bmadDir if found (use async fs access/stat or readdir +
access), otherwise fall back to path.join(projectDir, BMAD_FOLDER_NAME); ensure
the return shape stays { bmadDir } so callers like quickUpdate, uninstall,
getStatus, and getOutputFolder continue to work.
- Around line 40-45: Move the custom module discovery before building official
modules and update OfficialModules.build to accept and forward custom module
paths into its configuration collection: call
this.customModules.discoverPaths(originalConfig, paths) before invoking
OfficialModules.build(config, paths), and change OfficialModules.build(...) (and
inside it, the call to collectAllConfigurations(...)) to accept a
customModulePaths argument (pass this.customModules.paths) and propagate that
into collectAllConfigurations so module.yaml schemas for selected custom modules
are available during headless config collection.

In `@tools/installer/modules/custom-modules.js`:
- Around line 92-98: The current checks using file.startsWith('sub-modules/')
and path.dirname(file).split('/').some(dir =>
dir.toLowerCase().endsWith('-sidecar')) break on Windows because they assume '/'
separators; normalize the path first (e.g., const normalized =
path.normalize(file)) and then use normalized.split(path.sep) to test the first
segment for 'sub-modules' instead of startsWith, and use
path.dirname(normalized).split(path.sep).some(dir =>
dir.toLowerCase().endsWith('-sidecar')) so both the initial sub-module check and
the sidecar detection handle platform-specific separators; replace uses of
file.startsWith and path.dirname(file).split('/') with these
normalized/path.sep-based checks.

In `@tools/installer/modules/official-modules.js`:
- Around line 483-489: The current catch block around reading/parsing moduleYaml
swallows errors and returns emptyResult, hiding invalid module.yaml; change the
catch to capture the error (e.g., catch (err)) and surface it instead of
silently returning: either log a warning/error including moduleYamlPath and err
via the module installer logger or throw an explicit Error so the CLI can fail
appropriately; update the code paths that reference emptyResult to handle the
thrown error or the logged failure so installs of broken module definitions are
not treated as successful.
- Around line 678-690: getFileList() returns platform-specific separators from
path.relative which breaks downstream POSIX-based filters in
copyModuleWithFiltering; normalize each pushed path to use forward slashes.
Update getFileList (the function named getFileList) so that after computing rel
= path.relative(baseDir, fullPath) you convert backslashes to '/', e.g. replace
all '\\' with '/' (or use path.posix equivalents) before pushing to files,
ensuring all returned entries are POSIX-style paths expected by
copyModuleWithFiltering and its sub-modules/ and agents/ filters.

---

Outside diff comments:
In `@tools/installer/modules/official-modules.js`:
- Around line 1405-1414: The skip-prompts branch currently only copies literal
defaults and ignores function-backed defaults produced by buildQuestion, causing
templated defaults to be dropped; update the loop in the this.skipPrompts branch
that iterates over questions to check if question.default is a function and, if
so, invoke it (awaiting the result if it returns a Promise) with the current
answers/context (e.g., pass allAnswers or the same args buildQuestion expects)
and assign the returned value to allAnswers[question.name]; keep the existing
behavior for non-function defaults and preserve the hasDefault guard and logging
using moduleDisplayName.

---

Nitpick comments:
In `@tools/installer/ide/_config-driven.js`:
- Around line 44-57: Create a single helper (e.g., isBmadEntry(name)) in this
module that normalizes the entry name (toLowerCase()) and returns true for names
that start with 'bmad' or the fuller prefix 'bmad-os-' (so both 'bmad*' and
'bmad-os-*' are handled consistently); then replace the ad-hoc predicates in
detect(), cleanupTarget(), and findAncestorConflict() to call that helper so
case-folding and the 'bmad-os-' rule are unified across all
detection/cleanup/conflict flows.
- Around line 48-53: The current check in the config-driven installer swallows
all errors in the fs.readdir block (when checking dir via fs.pathExists and
fs.readdir) and returns false, making permission/I/O failures indistinguishable
from "not installed"; change the catch to either propagate a structured error
(e.g., throw a new Error with context including dir and the original error) or
at minimum log a warning with dir and error details (using the project's logger
or console.warn) before rethrowing or returning a distinct error result so
callers don't treat unreadable config dirs as absent.

In `@tools/installer/ide/platform-codes.js`:
- Around line 13-25: Wrap the yaml.parse call in loadPlatformCodes in a
try/catch to surface a clearer error: catch parsing exceptions thrown by
yaml.parse(content) and rethrow a new Error that prefixes a helpful message like
"Failed to parse platform-codes.yaml at <PLATFORM_CODES_PATH>:" and include the
original error message (or error.stack) to preserve details; ensure
_cachedPlatformCodes is only set on successful parse and still returned from
loadPlatformCodes.

In `@tools/installer/modules/external-manager.js`:
- Around line 251-292: The npm install + spinner block is duplicated in the two
branches; extract that logic into a single helper (e.g., _runNpmInstall or
runNpmInstall) that accepts (moduleCacheDir, moduleInfo, silent) and performs:
createSpinner(), start message using moduleInfo.name, execSync with the same
args and timeout, stop on success, and error handling that calls
installSpinner.error and, if !silent, awaits prompts.log.warn with
error.message; then replace both duplicated blocks with a single await
this._runNpmInstall(moduleCacheDir, moduleInfo, silent) call so all install
behavior is centralized and consistent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8309a8e7-0533-4ae2-8c11-0f52d1d629f9

📥 Commits

Reviewing files that changed from the base of the PR and between 819d373 and f0e81fd.

📒 Files selected for processing (85)
  • .github/workflows/publish.yaml
  • docs/fr/how-to/non-interactive-installation.md
  • docs/how-to/non-interactive-installation.md
  • docs/zh-cn/how-to/non-interactive-installation.md
  • package.json
  • src/core-skills/bmad-distillator/resources/distillate-format-reference.md
  • test/test-install-to-bmad.js
  • test/test-installation-components.js
  • test/test-workflow-path-regex.js
  • tools/bmad-npx-wrapper.js
  • tools/cli/installers/lib/core/dependency-resolver.js
  • tools/cli/installers/lib/core/detector.js
  • tools/cli/installers/lib/core/ide-config-manager.js
  • tools/cli/installers/lib/core/installer.js
  • tools/cli/installers/lib/ide/_base-ide.js
  • tools/cli/installers/lib/ide/platform-codes.js
  • tools/cli/installers/lib/ide/platform-codes.yaml
  • tools/cli/installers/lib/ide/templates/combined/claude-workflow-yaml.md
  • tools/cli/installers/lib/modules/external-manager.js
  • tools/cli/installers/lib/modules/manager.js
  • tools/cli/lib/config.js
  • tools/cli/lib/platform-codes.js
  • tools/docs/_prompt-external-modules-page.md
  • tools/installer/README.md
  • tools/installer/bmad-cli.js
  • tools/installer/cli-utils.js
  • tools/installer/commands/install.js
  • tools/installer/commands/status.js
  • tools/installer/commands/uninstall.js
  • tools/installer/core/config.js
  • tools/installer/core/custom-module-cache.js
  • tools/installer/core/existing-install.js
  • tools/installer/core/install-paths.js
  • tools/installer/core/installer.js
  • tools/installer/core/manifest-generator.js
  • tools/installer/core/manifest.js
  • tools/installer/custom-handler.js
  • tools/installer/external-official-modules.yaml
  • tools/installer/file-ops.js
  • tools/installer/ide/_config-driven.js
  • tools/installer/ide/manager.js
  • tools/installer/ide/platform-codes.js
  • tools/installer/ide/platform-codes.yaml
  • tools/installer/ide/shared/agent-command-generator.js
  • tools/installer/ide/shared/bmad-artifacts.js
  • tools/installer/ide/shared/module-injections.js
  • tools/installer/ide/shared/path-utils.js
  • tools/installer/ide/shared/skill-manifest.js
  • tools/installer/ide/templates/agent-command-template.md
  • tools/installer/ide/templates/combined/antigravity.md
  • tools/installer/ide/templates/combined/claude-agent.md
  • tools/installer/ide/templates/combined/claude-workflow.md
  • tools/installer/ide/templates/combined/default-agent.md
  • tools/installer/ide/templates/combined/default-task.md
  • tools/installer/ide/templates/combined/default-tool.md
  • tools/installer/ide/templates/combined/default-workflow.md
  • tools/installer/ide/templates/combined/gemini-agent.toml
  • tools/installer/ide/templates/combined/gemini-task.toml
  • tools/installer/ide/templates/combined/gemini-tool.toml
  • tools/installer/ide/templates/combined/gemini-workflow-yaml.toml
  • tools/installer/ide/templates/combined/gemini-workflow.toml
  • tools/installer/ide/templates/combined/kiro-agent.md
  • tools/installer/ide/templates/combined/kiro-task.md
  • tools/installer/ide/templates/combined/kiro-tool.md
  • tools/installer/ide/templates/combined/kiro-workflow.md
  • tools/installer/ide/templates/combined/opencode-agent.md
  • tools/installer/ide/templates/combined/opencode-task.md
  • tools/installer/ide/templates/combined/opencode-tool.md
  • tools/installer/ide/templates/combined/opencode-workflow-yaml.md
  • tools/installer/ide/templates/combined/opencode-workflow.md
  • tools/installer/ide/templates/combined/rovodev.md
  • tools/installer/ide/templates/combined/trae.md
  • tools/installer/ide/templates/combined/windsurf-workflow.md
  • tools/installer/ide/templates/split/.gitkeep
  • tools/installer/install-messages.yaml
  • tools/installer/message-loader.js
  • tools/installer/modules/custom-modules.js
  • tools/installer/modules/external-manager.js
  • tools/installer/modules/official-modules.js
  • tools/installer/project-root.js
  • tools/installer/prompts.js
  • tools/installer/ui.js
  • tools/installer/yaml-format.js
  • tools/javascript-conventions.md
  • tools/lib/xml-utils.js
💤 Files with no reviewable changes (13)
  • tools/lib/xml-utils.js
  • tools/cli/installers/lib/core/ide-config-manager.js
  • tools/cli/installers/lib/modules/external-manager.js
  • tools/bmad-npx-wrapper.js
  • tools/cli/installers/lib/ide/platform-codes.yaml
  • tools/cli/lib/platform-codes.js
  • tools/cli/installers/lib/modules/manager.js
  • tools/cli/installers/lib/ide/platform-codes.js
  • tools/cli/installers/lib/core/dependency-resolver.js
  • tools/cli/installers/lib/core/detector.js
  • tools/cli/lib/config.js
  • tools/cli/installers/lib/ide/templates/combined/claude-workflow-yaml.md
  • tools/cli/installers/lib/ide/_base-ide.js

alexeyv added 3 commits March 26, 2026 19:49
… errors

Guard ExistingInstall.version access with .installed check in
uninstall.js, ui.js, and installer.js to prevent throwing on
empty/partial _bmad dirs. Surface invalid module.yaml parse errors
as warnings instead of silently returning empty results.
@alexeyv
Copy link
Copy Markdown
Collaborator Author

alexeyv commented Mar 27, 2026

Triage Summary — 11 findings reviewed

FIX: 4 | DISMISS: 3 | DEFER: 4

# Severity Finding Decision
F1 Medium ExistingInstall.version throws in uninstall.js FIX
F2 Medium ExistingInstall.version throws in ui.js FIX
F3 Medium CSV split(',') misreads quoted fields DEFER — pre-existing, filed as #2141
F4 Medium Windows path separators break filtering DEFER — pre-existing
F5 Medium ExistingInstall.version throws during uninstall return FIX
F6 High Custom module paths not discovered before headless config DEFER — no practical impact, all call paths pre-collect via UI
F7 High YAML line-splitting breaks multi-line values DEFER — latent, all current configs are simple strings
F8 High Regresses renamed BMAD folder detection DISMISS — old installer has identical hardcoded lookup
F9 Medium Cross-platform path separator (dup of F4) DISMISS — duplicate
F10 High Silent ignore of invalid module.yaml FIX
F11 High getFileList() cross-platform normalization DISMISS — duplicate of F4

Fixes applied in commit 1735c080.

@alexeyv alexeyv merged commit 513f440 into main Mar 27, 2026
5 checks passed
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.

1 participant