Commit Graph

14 Commits

Author SHA1 Message Date
Murat K Ozcan 2395b0e2ed
fix: bmad tea instal version (#2298)
* fix: bmad tea instal version

* fix: addressed review comments
2026-04-22 11:03:20 -05:00
Brian 0dbfae675b
feat(skills): TOML-based agent and workflow customization (#2284)
* feat(skills): TOML-based agent customization with stdlib Python resolver

Re-applies PR #2282's three-layer customization model (skill defaults →
team → user) but swaps YAML for TOML and uv for stdlib tomllib. Users
no longer need uv, pip, or a virtualenv — plain python3 (3.11+) is
sufficient, since tomllib shipped in the standard library.

## Schema changes vs PR #2282

- Flat agent schema: fields live directly under [agent], no nested
  metadata/persona sub-tables. Easier to author, less indentation.
- Non-configurable identity: name and title are declared in
  customize.toml as source-of-truth metadata (for future skill-manifest
  generation) but SKILL.md ignores overrides there — identity is
  hardcoded to preserve brand recognition.
- role redefined: now describes what the skill does for the user
  within its module phase, not a restatement of the title.
- persistent_facts replaces the activation-time file-context load AND
  the old memories concept. Entries can be literal sentences or
  file: prefixed paths/globs; avoids collision with the upcoming
  runtime memory sidecar.
- activation_steps_prepend / activation_steps_append harmonized across
  agents and workflows (replaces agent-specific critical_actions).
- [workflow] namespace mirrors [agent] for workflow customization.
  Same four structural rules, same field vocabulary.

## Resolver (src/scripts/resolve_customization.py)

Four purely structural merge rules, zero field-name hardcoding:

  - Scalars: override wins
  - Tables: deep merge
  - Arrays of tables where every item has `code` or `id`: merge by
    that key (matching keys replace, new keys append)
  - Any other array: append

No removal mechanism — overrides cannot delete base items. Fork the
skill or override by code with a no-op value to suppress defaults.

## Agents ported (6)

All six BMad agents now ship customize.toml + rewritten SKILL.md:
analyst (Mary), tech-writer (Paige), pm (John), ux-designer (Sally),
architect (Winston), dev (Amelia). Each uses the same 8-step
activation template: resolve → execute prepend → adopt persona →
load persistent facts → load config → greet (with {agent.icon}) →
execute append → dispatch or present menu.

Step 8 supports fast-path invocation: "hey Mary, let's brainstorm"
dispatches the matching menu item directly after greeting, skipping
the menu render when intent is clear. Chat, clarifying questions,
and bmad-help remain available when nothing on the menu fits.

## Installer + tooling

- _bmad/scripts/ provisioned on install (copies src/scripts/)
- _bmad/custom/ seeded with .gitignore for *.user.toml on fresh install
- Non-module-dir filter extended to skip _memory, memory, docs,
  scripts, and custom when scanning for modules
- Dead _config/agents/ directory no longer created
- metadata.capabilities removed from agent-manifest.csv and schema
- eslint config extended to cover src/scripts/**
- validate-file-refs.js knows about custom/ as install-only

## Deferred for follow-up

- bmad-product-brief workflow port (the pilot that demonstrates
  [workflow] + on_complete)
- Translated docs (cs/fr/vi-vn/zh-cn) — regenerate from English

* feat(skills): port bmad-product-brief to TOML workflow customization

Completes the customization surface rollout by giving the product-brief
workflow the same override model as the six BMad agents, under the
[workflow] namespace instead of [agent].

## customize.toml

Mirrors the agent shape under [workflow] with:
- activation_steps_prepend / activation_steps_append (harmonized across
  agents and workflows — same field names, same append semantics)
- persistent_facts with the file: convention, seeded with
  file:{project-root}/**/project-context.md
- on_complete scalar (renamed from PR #2282's skill_end for clarity —
  reads cleaner as "what runs when the workflow completes")

## SKILL.md

7-step workflow activation:
  1. Resolve workflow block
  2. Execute prepend steps
  3. Load persistent facts (file: or literal)
  4. Load config
  5. Greet if not already
  6. Execute append steps
  7. Stage 1 — Understand Intent

python3 + stdlib tomllib invocation; no uv required.

## Prompt file changes

- Path normalization: ../agents/ → agents/, ../resources/ → resources/,
  bare foo.md → prompts/foo.md. All references now resolve from the
  skill root (matches the convention documented in SKILL.md).
- Paths: meta-line added to each of the 4 prompt files that reference
  other files, reinforcing "bare paths resolve from skill root" so the
  LLM doesn't lose the convention when operating two hops into a
  prompt chain.
- finalize.md terminal stage now calls the resolver for
  workflow.on_complete — non-empty values run as the final step.

## Validation

- Resolver output verified: 4 workflow fields returned cleanly.
- validate-file-refs.js: 254 files scanned, 139 refs checked, 0 broken.
- test:refs: passing.

* docs(skills): enterprise customization recipes + workflow template variable

Three independent improvements bundled because they share the same
surface (workflow/agent customization) and landed from the same design
discussion:

## Fallback sentence disambiguated (7 SKILL.md files)

The "if the script fails" fallback used to say `{project-root}/_bmad/
custom/{skill-name}.toml` for the team override and then just `{skill-
name}.user.toml` for the user override, leaving the user file's
location implicit. LLMs could reasonably guess skill root or project
root instead. Replaced with an unambiguous numbered list that spells
out the full path for every file in the merge chain.

## Product-brief: stage promotion + brief_template variable

- Promoted `## Stage 1: Understand Intent` from a nested step inside
  "On Activation" to a top-level section. The previous "Step 7: Stage
  1 — Understand Intent → Proceed to Stage 1 below" was mechanical
  numbering pretending to be a step. Activation now ends cleanly at
  Step 6; Stage 1 is a peer section.
- Added `brief_template` as a workflow-level scalar customization
  defaulting to `resources/brief-template.md`. Stage 4 reads
  `{workflow.brief_template}` instead of the hardcoded path, so orgs
  can point at their own template under `{project-root}/...` without
  forking the skill.

## New doc: docs/how-to/extend-bmad-for-your-org.md

Four worked recipes that together cover most enterprise scenarios:

1. Shape an agent across every workflow it dispatches (dev agent +
   Context7 MCP + Linear search — the highest-leverage pattern)
2. Enforce org conventions inside a specific workflow (product-brief
   + compliance-field persistent_facts)
3. Publish completed outputs to external systems (product-brief +
   Confluence + Jira via MCP, gated on user confirmation for Jira)
4. Swap in your own output template (product-brief + brief_template
   variable swap)

Opens with the two-layer mental model (agent spans workflows,
workflow is local) so readers pick the right granularity before
reading any recipe. Closes with a "Combining Recipes" section
showing all four composed. Cross-linked from customize-bmad.md.

## Validation

- Resolver: workflow.brief_template returns the default cleanly.
- validate-file-refs.js: 254 files scanned, 146 refs checked
  (+7 from this commit), 0 broken.

* docs(skills): encourage CLAUDE.md/AGENTS.md reinforcement of critical rules

Added a "Reinforce Global Rules in Your IDE's Session File" section to
extend-bmad-for-your-org.md. BMad customizations only load when a
skill activates, but IDE session files (CLAUDE.md, AGENTS.md, cursor
rules, copilot-instructions) load every turn — worth restating the
most critical rules there too so they survive ad-hoc chat outside a
BMad skill.

Includes a one-line example reinforcing the Recipe 1 Context7 rule,
plus a scope table that clarifies what each layer is for:
  - IDE session file: universal, every session, keep succinct
  - Agent customization: persona-specific, every dispatched workflow
  - Workflow customization: one workflow run

Emphasizes brevity — noise in the session file crowds out signal.

* docs(skills): add Named Agents explanation doc

New docs/explanation/named-agents.md walking through the three-legged
stool (skills + named agents + customization) with the "Hey Mary,
let's brainstorm" activation flow as the narrative thread.

Covers:
- Why named agents vs menu-driven or prompt-driven alternatives
- The 8-step activation flow and what each step contributes
- How customization scales the model beyond a single developer
- Cross-links to the how-to docs for implementation details

Sits alongside brainstorming.md, quick-dev.md, party-mode.md in the
explanation folder — feature narratives for users who want to
understand why BMad is designed the way it is, not just how to use it.

* docs(skills): clarify that keyed-merge requires a single identifier key per array

Review feedback (PR #2284) flagged that the merge-rules wording was
ambiguous: "every item has a `code` or `id` field" could reasonably
be read as "each item individually has at least one of the two",
allowing arrays to mix `code` and `id` across items.

The resolver has always required all items share the *same* identifier
key (all `code`, or all `id`). Mixed arrays fall through to append —
intentional, because mixing identifier keys within one array is a
schema smell and any guess about which key should merge creates a
worse trap than the append-fallback.

Clarified in three places:
- Merge-rules table in customize-bmad.md: "every item shares the
  **same** identifier field"
- `code`/`id` convention paragraph: "pick **one** convention ... and
  stick with it across the whole array"
- Resolver docstring and `_detect_keyed_merge_field` docstring:
  explicit note that mixed arrays fall through with rationale

No behavior change.

* docs(skills): address CodeRabbit review — fallback rules, OS claim, headless greeting

Three fixes from PR #2284 review feedback:

## 1. Fallback merge wording (7 SKILL.md files)

Every SKILL.md told the LLM to merge the three customization files
"in priority order (later wins)" when the resolver fails. That reads
as shallow last-write-wins — but the resolver does structural merge
(scalars override, tables deep-merge, code/id-keyed arrays merge by
key, other arrays append). Following the old wording manually would
have silently stripped base `principles`, `persistent_facts`, and
`menu` items whenever a team override was present.

Expanded the fallback sentence to restate the four structural rules
explicitly, matching the resolver's behavior.

Applied to all 6 agents + bmad-product-brief workflow.

## 2. Python 3.11 / OS shipping claim (customize-bmad.md)

The docs claimed "macOS 13+, Ubuntu 22.04+, Debian 12+, Fedora 37+
all ship 3.11 or newer." Inaccurate — Ubuntu 22.04 defaults `python3`
to 3.10.6 (3.11 is a separate package), and macOS doesn't really
ship Python by default anymore.

Replaced with honest guidance: check `python3 --version` and note
that macOS without Homebrew and Ubuntu 22.04 default to 3.10 or
earlier.

## 3. Autonomous mode greeting gate (bmad-product-brief)

Product-brief's activation-mode detection documents autonomous mode
as "produce complete brief without interaction" — but Step 5 greeted
unconditionally, adding conversational output before the headless
artifact. Gated the greeting on `{mode}` != `autonomous`.

## Dismissed (replied on thread)

- `.gitignore` migration from *.user.yaml to *.user.toml: YAML
  installer code was in reverted #2282, never released. No users
  affected. Same rationale as Augment's earlier thread.

Validated: 254 files, 146 refs, 0 broken. test:refs 7/7,
test:install 242/242.

* docs: rename Extend to Expand throughout customization docs
2026-04-19 19:30:29 -05:00
Brian e550df2474
Revert "feat(skills): YAML-based agent customization with Python resolver (#2282)" (#2283)
This reverts commit bd1c0053d5.
2026-04-19 11:09:21 -05:00
Brian bd1c0053d5
feat(skills): YAML-based agent customization with Python resolver (#2282)
Three-layer customization (skill defaults → team → user) for BMad agents    
  and any skill that opts in. Users edit `_bmad/custom/{skill-name}.yaml`
  (team, committed) or `{skill-name}.user.yaml` (personal, gitignored);       
  customizations survive updates.                                             
                                                                              
  Resolver is a Python script using PEP 723 inline metadata, invoked via      
  `uv run` so deps auto-install into a cached isolated env on first call.     
  This aligns with Anthropic's Agent Skills spec and BMB conventions, and     
  keeps the dependency declared (scannable by pip-audit/Dependabot) rather    
  than vendored.                                                              
                                                                              
  ## Design choices                                                           
                                                                              
  - **Agent identity is hardcoded** in SKILL.md (name, title, Overview prose) 
    so skills can be invoked reliably by role *or* default name. Brand
    recognition is preserved; customization shapes behavior, not identity.    
  - **Luminary-anchored personas** (e.g. "Channels Martin Fowler's            
    pragmatism and Werner Vogels's cloud-scale realism") deliver ~55%
    token savings per agent while preserving distinctive voice beats.         
  - **Universal per-field merge rules** with v6.1-compatible agent            
    semantics: metadata shallow-merge, persona replace, critical_actions      
    and memories append, menu merge-by-code, all else deep-merge.             
  - **Workflow customization** shares the same surface — `bmad-product-brief`
    pilots `activation_steps_prepend`, `activation_steps_append`, and         
    `skill_end` hooks that any workflow-style skill can adopt.                
                                                                              
  ## Infrastructure                                                           
                                                                              
  - `_bmad/scripts/` houses shared Python scripts (resolver + future).        
  - `_bmad/custom/` is provisioned empty with a seeded `.gitignore` for
    `*.user.yaml` on fresh installs.                                          
  - Installer filters ensure `scripts/`, `custom/`, and sidecar-generated   
    `memory/` directories are never treated as modules.                       
  - Dead v6.1 code cleaned up: `_config/agents/` no longer created,         
    `metadata.capabilities` removed from schema and CSV manifest.
2026-04-18 23:13:31 -05:00
Alex Verkhovsky d09363b1b2
feat(installer): use GitHub API as primary fetch with raw CDN fallback (#2248)
* feat(installer): use GitHub API as primary fetch with raw CDN fallback

Corporate proxies commonly block raw.githubusercontent.com while allowing
api.github.com. Add fetchGitHubFile() to RegistryClient that tries the
GitHub Contents API first, falling back to the raw CDN transparently.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(installer): cap redirect depth and preserve dual-fallback errors

Add maxRedirects parameter to fetch() and _fetchWithHeaders() to prevent
unbounded redirect recursion. Wrap CDN fallback in try/catch and throw
AggregateError with both API and CDN errors for better diagnostics.
Extract marketplace repo coordinates into named constants in
external-manager.

* chore(installer): drop unused fetchJson and fetchGitHubJson

Neither method has any callers. Also drop the corresponding test.

* refactor(test): fold registry tests into test-installation-components

No reason for RegistryClient tests to be a separate runner — the same
file already tests the registry consumers in Suite 33. Drop test:registry
from package.json scripts and quality gate.

* fix(installer): include URL, API message, and rate-limit info in HTTP errors

Non-2xx responses previously yielded bare `HTTP 403`. Now surface the
request URL, GitHub's JSON error message (or body snippet), X-RateLimit-Reset
when quota is exhausted, and Retry-After. Turns a mystery 403 into
'rate limit exhausted; resets at 2026-04-15T18:00:00Z' — the difference
between 'try GITHUB_TOKEN' and a wild goose chase.

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-18 08:53:23 -07:00
Brian Madison a6d075bd0b fix(installer): replace fs-extra with native node:fs to prevent file loss
fs-extra routes all operations through graceful-fs, which globally
monkey-patches node:fs with a deferred retry queue. During multi-module
installs (~500+ file ops), retried unlink operations from one module's
remove phase can fire after the next module's copy phase has written
files, silently deleting them non-deterministically.

Replace fs-extra with a thin fs-native.js wrapper over node:fs/promises
and node:fs. All 21 consumers now use native APIs with no global
monkey-patching, eliminating the retry-queue race condition entirely.

Closes #1779
2026-04-13 00:44:28 -05:00
Alex Verkhovsky ea99b7ece5
chore(installer): remove 1,683 lines of dead code (#2247)
* chore(installer): remove dead code across installer modules

Delete 3 entirely dead files (agent-command-generator, bmad-artifacts,
module-injections) and remove ~50 unused exports from manifest.js,
cli-utils.js, prompts.js, path-utils.js, official-modules.js,
external-manager.js, custom-module-manager.js, and registry-client.js.
Removes corresponding dead tests.

* fix(installer): restore currentProjectDir writes for placeholder expansion

The previous commit removed the three assignments to
OfficialModules.currentProjectDir as dead code, but buildQuestion()
still reads the property to resolve {directory_name} placeholders in
module config defaults during interactive collection. Without the
writes, any module default containing {directory_name} would surface
the literal placeholder to users.
2026-04-10 20:24:50 -07:00
Brian 97d32405d0
feat(installer): universal source support for custom module installs (#2233)
* feat(installer): add plugin resolution strategies for custom URL installs

When installing from a custom GitHub URL, the installer now analyzes
marketplace.json plugin structures to determine how to locate module
registration files (module.yaml, module-help.csv). Five strategies
are tried in cascade:

1. Root module files at the common parent of listed skills
2. A -setup skill with registration files in its assets/
3. Single standalone skill with registration files in assets/
4. Multiple standalone skills, each with their own registration files
5. Fallback: synthesize registration from marketplace.json metadata
   and SKILL.md frontmatter

Also changes the custom URL flow from confirm-all to multiselect,
letting users pick which plugins to install. Already-installed modules
are pre-checked for update; new modules are unchecked for opt-in.

New file: tools/installer/modules/plugin-resolver.js
Modified: custom-module-manager.js, official-modules.js, ui.js

* fix(installer): address PR review findings for plugin resolver

- Guard against path traversal in plugin-resolver.js: skill paths from
  unverified marketplace.json are now constrained to the repo root using
  path.resolve() + startsWith check
- Skip npm install during browsing phase: cloneRepo() accepts
  skipInstall option, used in ui.js before user confirms selection,
  preventing arbitrary lifecycle script execution from untrusted repos
- Add createModuleDirectories() call to installFromResolution() so
  modules with declarative directory config are fully set up
- Fix ESLint: use replaceAll instead of replace with global regex

* fix(installer): pass version and repoUrl to manifest for custom plugins

installFromResolution was passing empty strings for version and repoUrl,
which the manifest stores as null. Now threads the repo URL from ui.js
through resolvePlugin into each ResolvedModule, and passes the plugin
version and URL to the manifest correctly.

* fix(installer): manifest-generator overwrites custom module version/repoUrl

ManifestGenerator rebuilds the entire manifest via getModuleVersionInfo
for every module. For custom modules, this returned null for version and
repoUrl because it only checked _readMarketplaceVersion (which searches
for marketplace.json on disk) and hardcoded repoUrl to null. Now checks
the resolution cache first to get the correct version and repo URL.

* fix(installer): resolve custom modules from disk cache on quick update

When the resolution cache is empty (fresh CLI process, e.g. quick
update), findModuleSourceByCode only matched plugin.name against the
module code. This failed for modules like "sam" and "dw" where the
code comes from module.yaml inside a setup/standalone skill, not from
the plugin name in marketplace.json.

Now runs the PluginResolver on cached repos when the direct name match
fails, finding the correct module source and re-populating the cache
for the install pipeline.

* feat(installer): universal source support for custom modules

Replace GitHub-only custom module installation with support for any Git
host (GitHub, GitLab, Bitbucket, self-hosted) and local file paths.

- Add parseSource() universal input parser (local paths, SSH, HTTPS with
  deep path/subdir extraction for GitHub, GitLab, Gitea)
- Add resolveSource() coordinator: parse -> clone if URL -> detect
  discovery vs direct mode (marketplace.json present or not)
- Clone-first approach eliminates host-specific raw URL fetching
- 3-level cache structure (host/owner/repo) with .bmad-source.json
  metadata for URL reconstruction
- Local paths install directly without caching; localPath persisted in
  manifest for quick-update source lookup
- Direct mode scans target directory for SKILL.md when no marketplace.json
- Fix version display bug where walk-up found parent repo marketplace.json
  and reported wrong version for custom modules

* fix(installer): harden readMarketplaceJsonFromDisk and hoist require

- Add try/catch to readMarketplaceJsonFromDisk so malformed JSON returns
  null instead of throwing an unhandled parse error
- Hoist CustomModuleManager require outside the per-module loop in
  _installOfficialModules

* fix(installer): restore validateGitHubUrl strictness and fix prettier

- Restore original GitHub-only regex in deprecated validateGitHubUrl
  wrapper so existing tests pass (rejects non-GitHub URLs, trailing
  slashes)
- Run prettier to fix formatting in custom-module-manager.js

* feat(installer): add --custom-source CLI flag for non-interactive installs

Allows installing custom modules from Git URLs or local paths directly
from the command line without interactive prompts:

  npx bmad-method install --custom-source /path/to/module
  npx bmad-method install --custom-source https://gitlab.com/org/repo
  npx bmad-method install --custom-source /path/one,https://host/org/repo

Works alongside --modules and --yes flags. All discovered modules from
each source are auto-selected.

* docs: add custom and community module installation guide

New how-to page covering community module browsing, custom sources (any
Git host, local paths), discovery vs direct mode, local development
workflow, and the --custom-source CLI flag. Clarifies that
.claude-plugin/ is a cross-tool convention, not Claude-specific.

Also updates non-interactive installation docs with the new flag and
examples, bumps sidebar ordering, and fixes --custom-source to install
only core + custom modules when --modules is not specified.
2026-04-09 18:44:40 -05:00
Brian b744408783
feat(installer): community module browser and custom URL support (#2229)
* feat(installer): add community module browser and custom URL support

Three-tier module selection: official, community (category drill-down
with featured/search), and custom GitHub URL.

- Add RegistryClient shared fetch utility
- Add CommunityModuleManager with SHA-pinned cloning (refuses install
  if approved SHA cannot be reached; uses HEAD when no SHA set)
- Add CustomModuleManager for arbitrary GitHub repo installation
- Extend findModuleSource chain with community and custom fallthrough
- Extend manifest to detect community and custom source types
- Add Config.customModulesMeta for custom module metadata

* fix: resolve review findings for community/custom module support

- Remove redundant CommunityModuleManager instantiation in UI display
- Remove dead customModulesMeta field from Config (never populated)
- Add 35 unit tests for CustomModuleManager and CommunityModuleManager
  pure functions: URL validation, normalization, search, featured, categories

* fix: preserve installed community/custom modules in modify flow

When a user does "Modify Installation" and declines to browse community
modules, previously installed community/custom modules are now auto-kept.
If the user does browse, their selections are trusted (they can deselect).

Also fix stale docs: class doc for SHA pinning, JSDoc return type.

* fix: include community and custom modules in quick update

Quick update now checks community registry and custom cache so installed
community/custom modules are updated instead of skipped.

* fix: use defaults for new config fields during quick update

When quick update encounters new config fields (e.g., from a newly
supported community module), use schema defaults silently instead of
prompting the user. Quick update should be non-interactive.

* test: add unit tests for SHA pinning, category filtering, and URL edge cases

Cover SHA normalization (set vs null/trusted), listByCategory,
getModuleByCode, and URL validation edge cases (HTTP, trailing slash,
SSH without .git). Total: 243 tests.
2026-04-08 00:50:04 -05:00
Brian 5e038a8ce4
feat(installer): remote registry + remove custom content (#2228)
* refactor(installer): remove custom content installation feature

Remove the entire local filesystem custom content feature from the
installer to make way for marketplace-based plugin installation.

Deleted: custom-handler.js, custom-module-cache.js, custom-modules.js
Removed: --custom-content CLI flag, interactive custom content prompts,
custom module caching, manifest tracking, missing-source resolution,
and related test suites. Updated docs across all translations.

* fix: address review findings from Augment

Fix admonition syntax (remove accidental space in :::note) across 4
translated docs files, and update stale JSDoc on listAvailable().

* feat(installer): fetch module list from marketplace registry

Switch module list source of truth from bundled
external-official-modules.yaml to the remote marketplace registry
(registry/official.yaml) fetched via raw.githubusercontent.com.

- Rewrite ExternalModuleManager to fetch from GitHub with local fallback
- Simplify selectAllModules/getDefaultModules to use registry as single source
- Registry order controls display order; built_in flag prevents cloning
- Rename fallback file to registry-fallback.yaml in modules/
- Only show legacy migration message when legacy dirs actually exist
2026-04-07 22:45:01 -05:00
Brian 5dbfb588ee
refactor(installer): remove custom content installation feature (#2227)
* refactor(installer): remove custom content installation feature

Remove the entire local filesystem custom content feature from the
installer to make way for marketplace-based plugin installation.

Deleted: custom-handler.js, custom-module-cache.js, custom-modules.js
Removed: --custom-content CLI flag, interactive custom content prompts,
custom module caching, manifest tracking, missing-source resolution,
and related test suites. Updated docs across all translations.

* fix: address review findings from Augment

Fix admonition syntax (remove accidental space in :::note) across 4
translated docs files, and update stale JSDoc on listAvailable().
2026-04-07 21:41:03 -05:00
Taras Romaniv 1f99eb0496
fix: preserve local custom module sources during quick update (#2172)
* fix: preserve local custom module sources during quick update

Keep customModules in the generated main manifest so local custom
module source paths survive update runs. Load those preserved source
paths during stock quick update before falling back to the custom
cache directory.

This fixes the case where BMAD would drop customModules, lose the
original source path for a local module, and then skip the module or
try to re-cache from _bmad/_config/custom/<module>, which could fail
with ENOENT after the cache directory was removed.

Also adds an installation component regression test to verify
customModules and sourcePath are preserved in manifest generation.

Fixes #1582

* fix: ensure consistent formatting

* refactor: extract quick update custom source assembly

Move quick-update custom module source collection out of Installer and into
CustomModules as assembleQuickUpdateSources(). This keeps discoverPaths() focused on consuming prepared install inputs while
making the quick-update source assembly step explicit and easier to evolve.

Also:
- preserve customModules metadata in manifest regeneration for installed modules
- drop stale customModules entries when modules are no longer installed
- cover manifest preservation and manifest-backed quick-update sources in tests
2026-03-30 17:49:05 -07:00
Brian e0ea6a0500
fix: support skills/ folder as module source location (#2149)
The installer now finds module.yaml in both skills/ and src/ directories,
including one level deep in subfolders. Updates bmb module-definition to
skills/module.yaml to match its actual structure.
2026-03-28 00:33:10 -05:00
Alex Verkhovsky 513f440a23
refactor(installer): restructure installer with clean separation of concerns (#2129)
* refactor(installer): restructure installer with clean separation of concerns

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

* fix(installer): guard ExistingInstall.version and surface module.yaml 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.
2026-03-27 06:50:07 -06:00