BMAD-METHOD is an open-source framework for structured, agent-driven software development — a methodology that uses carefully designed prompt templates and modular agent configurations to guide AI-assisted engineering workflows. Its installer tooling is the operational core that makes the methodology usable: it resolves, fetches, and configures modules ranging from official BMAD templates to externally hosted custom agents. That breadth of responsibility — handling many module types, sources, and update strategies in a single codebase — is exactly what concentrates complexity in the five functions identified below.
The table below ranks functions by activity-weighted risk — a score that multiplies structural complexity by recent commit frequency. A function that is both hard to understand (high cyclomatic complexity) and actively changing is a higher priority than one that is complex but untouched. CC = cyclomatic complexity (independent execution paths); ND = max nesting depth; FO = fan-out (distinct callees).
Top 5 Hotspots
| Function | File | Risk | CC | ND | FO |
|---|---|---|---|---|---|
collectModuleConfig | tools/installer/modules/official-modules.js | 19.4 | 68 | 9 | 23 |
findModuleSourceByCode | tools/installer/modules/custom-module-manager.js | 17.8 | 34 | 9 | 9 |
parseSource | tools/installer/modules/custom-module-manager.js | 17.6 | 40 | 6 | 18 |
cloneExternalModule | tools/installer/modules/external-manager.js | 17.5 | 73 | 5 | 26 |
_resolveUpdateChannels | tools/installer/ui.js | 17.5 | 62 | 5 | 25 |
Hotspot Analysis
collectModuleConfig — tools/installer/modules/official-modules.js
At a cyclomatic complexity of 68 and a max nesting depth of 9, collectModuleConfig is the most structurally demanding function in this dataset. The name and path suggest it is responsible for assembling configuration objects for each official BMAD module — a task that is structurally expensive when many module types must each be handled with distinct logic. A CC of 68 implies 68 independent execution paths; at ND 9 the innermost logic sits behind nine layers of control flow that a reader must hold simultaneously. A fan-out of 23 means the function also coordinates 23 distinct downstream callees, distributing its logic widely across the module layer. All five structural antipatterns — complex_branching, deeply_nested, exit_heavy, god_function, long_function — are present, which is typical of a function that has absorbed every new module type as a new conditional block rather than as an extension point.
Recommendation: Introduce a per-module-type configuration handler — either a dispatch table or a set of small named functions — so that each module’s configuration logic lives in isolation. This breaks the god-function pattern directly, reduces CC by eliminating the cross-module branching, and brings nesting depth down by removing the outer dispatch conditionals.
findModuleSourceByCode — tools/installer/modules/custom-module-manager.js
findModuleSourceByCode has a lower CC (34) than collectModuleConfig but the same max nesting depth (9), which signals that the complexity here is driven more by deeply nested conditionals than by a large number of sequential branches. A function that reaches ND 9 with CC 34 likely contains several tightly nested if/else chains — each level validating or narrowing a condition — rather than a wide flat branching structure. Its fan-out of 9 is the lowest in the top 5, suggesting the function is relatively self-contained in terms of downstream coupling; the risk comes from its internal structure rather than from the breadth of what it touches.
Recommendation: Apply extract-method refactoring to the innermost nested blocks first, targeting the conditions responsible for driving depth beyond ND 4. Naming these extracted helpers after what they assert or validate will make the remaining structure at each level readable without reference to the inner logic.
parseSource — tools/installer/modules/custom-module-manager.js
parseSource shares a file with findModuleSourceByCode, suggesting both functions operate on the same custom-module data layer. Its CC of 40 with ND 6 and FO 18 points to a function that both branches across many source formats and reaches into 18 downstream callees to handle them — a broader coupling footprint than findModuleSourceByCode despite shallower nesting. Source parsing functions commonly accumulate this shape when handling a growing set of input formats: each new format adds a branch and typically a new callee, so CC and FO grow together. The exit_heavy pattern indicates those branches resolve through many different return points rather than a unified exit path, which makes tracing what the function produces for any given input non-trivial.
Recommendation: Separate format detection from format handling. A small dispatcher that identifies the source type and delegates to a dedicated parser per type keeps CC at the dispatch level low and makes each format’s handling independently testable. This also makes adding new source formats additive rather than requiring edits to a shared branching structure.
cloneExternalModule — tools/installer/modules/external-manager.js
cloneExternalModule has the highest CC in the top 5 at 73, despite a nesting depth of only 5 — a pattern that indicates a large number of sequential or parallel branches rather than deeply nested conditionals. Cloning external modules involves many failure modes, protocol variants, and configuration paths (different VCS hosts, authentication schemes, directory structures, error states), and this function appears to have absorbed them all inline. A fan-out of 26 confirms that the branching is backed by a wide set of downstream callees, each presumably responsible for a specific cloning strategy or side effect.
Recommendation: The 73 execution paths are the priority. Identify the major categories of cloning strategy — by protocol, by host type, by authentication method — and extract each into a dedicated function. A strategy or command pattern would make each path independently named, testable, and replaceable without touching the others. Reducing CC below 20 should be the initial target.
_resolveUpdateChannels — tools/installer/ui.js
_resolveUpdateChannels sits in ui.js rather than in the module management layer, which suggests it is responsible for determining which update channels or notification paths are visible to the user based on the current installer state. With CC 62, ND 5, and FO 25, it mirrors the shape of cloneExternalModule — broad branching across many cases with moderate nesting and wide downstream coupling — but in the UI coordination layer rather than the file-system layer. UI state-resolution functions commonly reach this complexity when they must reflect the combined state of many modules, installation phases, and configuration options simultaneously.
Recommendation: Decompose by concern: separate the logic that determines which channels are eligible from the logic that determines which are active and from the logic that determines how they are presented. Each of these is a distinct decision with its own inputs, and keeping them in a single function conflates what should be three independently testable operations.
Patterns Found
Antipatterns detected across the top functions in this snapshot:
| Pattern | Occurrences |
|---|---|
exit_heavy | 10 |
complex_branching | 9 |
deeply_nested | 9 |
god_function | 9 |
long_function | 9 |
These labels belong to two tiers — Tier 1 (structural): complex_branching, deeply_nested, exit_heavy, long_function, god_function. Tier 2 (relational/temporal): hub_function, cyclic_hub, middle_man, neighbor_risk, stale_complex, churn_magnet, shotgun_target, volatile_god.
Key Takeaways
cloneExternalModulehas the highest cyclomatic complexity at CC 73 despite only ND 5, meaning its 73 paths are broad and sequential rather than deeply nested — each additional cloning strategy or error case added inline expands this surface further; extracting per-strategy handlers is the highest-leverage structural fix.collectModuleConfigandfindModuleSourceByCodeboth reach ND 9 — the maximum nesting depth in this dataset — and both live in the module management layer; the concentration of deep nesting in one subsystem signals that the module-type dispatch pattern used here is the root cause, not individual function choices.- All five functions share the
exit_heavypattern, meaning they resolve through many different return points; before any refactoring, add characterization tests that capture the current return values for representative inputs so you have a behavioral baseline to verify against after each extraction.
Reproduce This Analysis
git clone https://github.com/bmad-code-org/BMAD-METHOD
cd BMAD-METHOD
git checkout 5090cfb09617eeb9c5fb547d4d10529d9886adcd
hotspots analyze . --mode snapshot --explain-patterns --force
To run the same analysis on your own codebase, run hotspots analyze . --mode snapshot in any local git repo — no configuration required.
Hotspots highlights structural and activity risk — not “bad code.” Findings are a prioritization aid, not a bug predictor. Editorial policy →