Compare commits

...

13 Commits

Author SHA1 Message Date
Jacob du Toit 32e56513df
Merge 9bcafdef51 into 97d32405d0 2026-04-09 17:43:45 -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
Alex Verkhovsky 3ba51e1bac
feat(quick-dev): add epic context compilation to step-01 (#2218)
* feat(quick-dev): add epic context compilation to step-01

Fork step-01 context loading: epic stories get a sub-agent that
compiles planning docs into a cached epic-{N}-context.md, while
freeform intents keep the lightweight directory-listing path.

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

* fix(quick-dev): tighten epic context loading per PR review

- Validate cached epic-<N>-context.md is non-empty and starts with the
  expected header before loading; treat invalid cache as missing.
- Replace inline {N} placeholders with <N> so the skill validator does
  not flag them as unresolved workflow variables.
- Replace ambiguous "fall back to path B" with an explicit instruction
  to scan/load planning artifacts using path B's procedure, with a note
  not to re-evaluate path B's gating clause.

Addresses CodeRabbit and Augment review comments on PR #2218.

* refactor(quick-dev): tighten compile-epic-context prompt

- Restructure with Task/Steps opening and Exact Output Format section.
- Switch Stories template to bullet form for clarity.
- Add "no hallucination" and explicit "omit empty sections except Goal
  and Stories" rules.
- Use <N> instead of {N} in the filename for consistency with step-01.

* refactor(quick-dev): restructure epic-story context loading

Reshape path A of step-01 into five explicit numbered steps and add an
inline-compilation fallback for runtimes that cannot spawn sub-agents
(Copilot, Codex, local Ollama, older Claude).

- Pull cache validity, compilation, verification, and continuity into
  separate numbered steps instead of nested paragraphs.
- Define "valid cached context" upfront: non-empty and starts with
  `# Epic <N> Context:`.
- Add inline-compilation fallback: runtimes without sub-agent support
  read compile-epic-context.md and follow it directly.
- Make previous-story continuity run regardless of which context source
  succeeded (cache hit, fresh compilation, or path-B raw fallback).

* fix(quick-dev): address review findings on epic context compilation

- Add freshness check to cached epic-N-context.md (invalidate when any
  planning artifact is newer)
- Remove the silent fall-back-to-raw-planning-docs path on compile
  failure; HALT and report instead
- Add explicit "ambiguous → freeform" tiebreakers for both the path A
  header and the epic-number identification step
- Drop "verbatim" from compile-epic-context.md format header to resolve
  the verbatim-vs-omit-empty contradiction

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 18:29:17 -07:00
Jacob du Toit 9bcafdef51
Merge branch 'main' into feat/expand-advanced-elicitation-methods 2026-04-03 11:04:11 +02:00
Jacob du Toit 92498ebb52
Merge branch 'main' into feat/expand-advanced-elicitation-methods 2026-03-26 05:31:03 +02:00
Jacob du Toit fdfe23fc22
Merge branch 'main' into feat/expand-advanced-elicitation-methods 2026-03-23 04:01:03 +02:00
Jacob du Toit f32d1d4e8d
Merge branch 'main' into feat/expand-advanced-elicitation-methods 2026-03-22 06:03:59 +02:00
Alex Verkhovsky f9e7d65cf9
Merge branch 'main' into feat/expand-advanced-elicitation-methods 2026-03-20 15:20:27 -06:00
Jacob du Toit bfdeef0453
Merge branch 'main' into feat/expand-advanced-elicitation-methods 2026-03-20 11:00:45 +02:00
Jacob du Toit 3ac8736756
Merge branch 'main' into feat/expand-advanced-elicitation-methods 2026-03-20 06:25:14 +02:00
Brian 417fc44a98
Merge branch 'main' into feat/expand-advanced-elicitation-methods 2026-03-19 19:29:28 -05:00
Jacob du Toit 4e96a50515
Merge branch 'main' into feat/expand-advanced-elicitation-methods 2026-03-19 07:53:14 +02:00
de Villiers du Toit ad77c8e1c6 feat: expand advanced elicitation methods with 19 new techniques
Adds 19 new elicitation methods across 7 categories including a new
'framing' category. All existing 50 methods preserved. Entries sorted
alphabetically by category then method name.

New methods added:
- advanced: Chain-of-Thought Scaffolding, Few-Shot Exemplar Priming
- collaboration: Six Thinking Hats, Delphi Method
- core: Second-Order Thinking, Inversion Analysis, Problem Decomposition,
  Analogy Mapping, Steelmanning
- creative: Constraint Injection, Morphological Analysis
- framing (new): Abstraction Laddering, Reframe the Question,
  Stakeholder Lens Rotation
- learning: Deliberate Practice Loop
- research: Source Triangulation
- risk: Assumption Audit, Cascading Failure Simulation
- technical: Boundary & Edge Case Sweep

Closes #2061
2026-03-19 05:28:52 +02:00
21 changed files with 1652 additions and 287 deletions

View File

@ -1,8 +1,8 @@
---
title: "How to Customize BMad"
title: 'How to Customize BMad'
description: Customize agents, workflows, and modules while preserving update compatibility
sidebar:
order: 7
order: 8
---
Use the `.customize.yaml` files to tailor agent behavior, personas, and menus while preserving your changes across updates.
@ -15,9 +15,10 @@ Use the `.customize.yaml` files to tailor agent behavior, personas, and menus wh
- You want agents to perform specific actions every time they start up
:::note[Prerequisites]
- BMad installed in your project (see [How to Install BMad](./install-bmad.md))
- A text editor for YAML files
:::
:::
:::caution[Keep Your Customizations Safe]
Always use the `.customize.yaml` files described here rather than editing agent files directly. The installer overwrites agent files during updates, but preserves your `.customize.yaml` changes.
@ -136,10 +137,10 @@ npx bmad-method install
The installer detects the existing installation and offers these options:
| Option | What It Does |
| ---------------------------- | ------------------------------------------------------------------- |
| Option | What It Does |
| ---------------------------- | -------------------------------------------------------------------- |
| **Quick Update** | Updates all modules to the latest version and applies customizations |
| **Modify BMad Installation** | Full installation flow for adding or removing modules |
| **Modify BMad Installation** | Full installation flow for adding or removing modules |
For customization-only changes, **Quick Update** is the fastest option.

View File

@ -1,8 +1,8 @@
---
title: "Established Projects"
title: 'Established Projects'
description: How to use BMad Method on existing codebases
sidebar:
order: 6
order: 7
---
Use BMad Method effectively when working on existing projects and legacy codebases.
@ -10,10 +10,11 @@ Use BMad Method effectively when working on existing projects and legacy codebas
This guide covers the essential workflow for onboarding to existing projects with BMad Method.
:::note[Prerequisites]
- BMad Method installed (`npx bmad-method install`)
- An existing codebase you want to work on
- Access to an AI-powered IDE (Claude Code or Cursor)
:::
:::
## Step 1: Clean Up Completed Planning Artifacts
@ -36,6 +37,7 @@ bmad-generate-project-context
```
This scans your codebase to identify:
- Technology stack and versions
- Code organization patterns
- Naming conventions
@ -79,10 +81,10 @@ BMad-Help also **automatically runs at the end of every workflow**, providing cl
You have two primary options depending on the scope of changes:
| Scope | Recommended Approach |
| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------- |
| Scope | Recommended Approach |
| ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- |
| **Small updates or additions** | Run `bmad-quick-dev` to clarify intent, plan, implement, and review in a single workflow. The full four-phase BMad Method is likely overkill. |
| **Major changes or additions** | Start with the BMad Method, applying as much or as little rigor as needed. |
| **Major changes or additions** | Start with the BMad Method, applying as much or as little rigor as needed. |
### During PRD Creation

View File

@ -1,8 +1,8 @@
---
title: "How to Get Answers About BMad"
title: 'How to Get Answers About BMad'
description: Use an LLM to quickly answer your own BMad questions
sidebar:
order: 4
order: 5
---
Use BMad's built-in help, source docs, or the community to get answers — from quickest to most thorough.
@ -46,35 +46,35 @@ If your AI can't read local files (ChatGPT, Claude.ai, etc.), fetch [llms-full.t
If neither BMad-Help nor the source answered your question, you now have a much better question to ask.
| Channel | Use For |
| ------------------------- | ------------------------------------------- |
| `help-requests` forum | Questions |
| `#suggestions-feedback` | Ideas and feature requests |
| Channel | Use For |
| ----------------------- | -------------------------- |
| `help-requests` forum | Questions |
| `#suggestions-feedback` | Ideas and feature requests |
**Discord:** [discord.gg/gk8jAdXWmj](https://discord.gg/gk8jAdXWmj)
**GitHub Issues:** [github.com/bmad-code-org/BMAD-METHOD/issues](https://github.com/bmad-code-org/BMAD-METHOD/issues)
*You!*
*Stuck*
*in the queue—*
*waiting*
*for who?*
_You!_
_Stuck_
_in the queue—_
_waiting_
_for who?_
*The source*
*is there,*
*plain to see!*
_The source_
_is there,_
_plain to see!_
*Point*
*your machine.*
*Set it free.*
_Point_
_your machine._
_Set it free._
*It reads.*
*It speaks.*
*Ask away—*
_It reads._
_It speaks._
_Ask away—_
*Why wait*
*for tomorrow*
*when you have*
*today?*
_Why wait_
_for tomorrow_
_when you have_
_today?_
*—Claude*
_—Claude_

View File

@ -1,5 +1,5 @@
---
title: "How to Install BMad"
title: 'How to Install BMad'
description: Step-by-step guide to installing BMad in your project
sidebar:
order: 1
@ -16,10 +16,11 @@ If you want to use a non interactive installer and provide all install options o
- Update the existing BMad Installation
:::note[Prerequisites]
- **Node.js** 20+ (required for the installer)
- **Git** (recommended)
- **AI tool** (Claude Code, Cursor, or similar)
:::
:::
## Steps
@ -31,6 +32,7 @@ npx bmad-method install
:::tip[Want the newest prerelease build?]
Use the `next` dist-tag:
```bash
npx bmad-method@next install
```
@ -40,9 +42,11 @@ This gets you newer changes earlier, with a higher chance of churn than the defa
:::tip[Bleeding edge]
To install the latest from the main branch (may be unstable):
```bash
npx github:bmad-code-org/BMAD-METHOD install
```
:::
### 2. Choose Installation Location
@ -99,11 +103,13 @@ your-project/
Run `bmad-help` to verify everything works and see what to do next.
**BMad-Help is your intelligent guide** that will:
- Confirm your installation is working
- Show what's available based on your installed modules
- Recommend your first step
You can also ask it questions:
```
bmad-help I just installed, what should I do first?
bmad-help What are my options for a SaaS project?

View File

@ -0,0 +1,180 @@
---
title: 'Install Custom and Community Modules'
description: Install third-party modules from the community registry, Git repositories, or local paths
sidebar:
order: 3
---
Use the BMad installer to add modules from the community registry, third-party Git repositories, or local file paths.
## When to Use This
- Installing a community-contributed module from the BMad registry
- Installing a module from a third-party Git repository (GitHub, GitLab, Bitbucket, self-hosted)
- Testing a module you are developing locally with BMad Builder
- Installing modules from a private or self-hosted Git server
:::note[Prerequisites]
Requires [Node.js](https://nodejs.org) v20+ and `npx` (included with npm). Custom and community modules can be selected during a fresh install or added to an existing installation.
:::
## Community Modules
Community modules are curated in the [BMad plugins marketplace](https://github.com/bmad-code-org/bmad-plugins-marketplace). They are organized by category and are pinned to an approved commit for safety.
### 1. Run the Installer
```bash
npx bmad-method install
```
### 2. Browse the Community Catalog
After selecting official modules, the installer asks:
```
Would you like to browse community modules?
```
Select **Yes** to enter the catalog browser. You can:
- Browse by category
- View featured modules
- View all available modules
- Search by keyword
### 3. Select Modules
Pick modules from any category. The installer shows descriptions, versions, and trust tiers. Already-installed modules are pre-checked for update.
### 4. Continue with Installation
After selecting community modules, the installer proceeds to custom sources, then tool/IDE configuration and the rest of the install flow.
## Custom Sources (Git URLs and Local Paths)
Custom modules can come from any Git repository or a local directory on your machine. The installer resolves the source, analyzes the module structure, and installs it alongside your other modules.
### Interactive Installation
During installation, after the community module step, the installer asks:
```
Would you like to install from a custom source (Git URL or local path)?
```
Select **Yes**, then provide a source:
| Input Type | Example |
| --------------------- | ------------------------------------------------- |
| HTTPS URL (any host) | `https://github.com/org/repo` |
| HTTPS URL with subdir | `https://github.com/org/repo/tree/main/my-module` |
| SSH URL | `git@github.com:org/repo.git` |
| Local path | `/Users/me/projects/my-module` |
| Local path with tilde | `~/projects/my-module` |
The installer clones the repository (for URLs) or reads directly from disk (for local paths), then presents the discovered modules for selection.
### Non-Interactive Installation
Use the `--custom-source` flag to install custom modules from the command line:
```bash
npx bmad-method install \
--directory . \
--custom-source /path/to/my-module \
--tools claude-code \
--yes
```
When `--custom-source` is provided without `--modules`, only core and the custom modules are installed. To include official modules as well, add `--modules`:
```bash
npx bmad-method install \
--directory . \
--modules bmm \
--custom-source https://gitlab.com/myorg/my-module \
--tools claude-code \
--yes
```
Multiple sources can be comma-separated:
```bash
--custom-source /path/one,https://github.com/org/repo,/path/two
```
## How Module Discovery Works
The installer uses two modes to find installable modules in a source:
| Mode | Trigger | Behavior |
| --------- | ------------------------------------------------- | -------------------------------------------------------------------------------------------- |
| Discovery | Source contains `.claude-plugin/marketplace.json` | Lists all plugins from the manifest; you pick which to install |
| Direct | No marketplace.json found | Scans the directory for skills (subdirectories with `SKILL.md`), resolves as a single module |
Discovery mode is typical for published modules. Direct mode is convenient when pointing at a skills directory during local development.
:::note[About `.claude-plugin/`]
The `.claude-plugin/marketplace.json` path is a standard convention adopted across multiple AI tool installers for plugin discoverability. It does not require Claude, does not use Claude APIs, and has no effect on which AI tool you use. Any module with this file can be discovered by any installer that follows the convention.
:::
## Local Development Workflow
If you are building a module with [BMad Builder](https://github.com/bmad-code-org/bmad-builder), you can install it directly from your working directory:
```bash
npx bmad-method install \
--directory ~/my-project \
--custom-source ~/my-module-repo/skills \
--tools claude-code \
--yes
```
Local sources are referenced by path, not copied to a cache. When you update your module source and reinstall, the installer picks up the latest changes.
:::caution[Source Removal]
If you delete the local source directory after installation, the installed module files in `_bmad/` are preserved. The module will be skipped during updates until the source path is restored.
:::
## What You Get
After installation, custom modules appear in `_bmad/` alongside official modules:
```
your-project/
├── _bmad/
│ ├── core/ # Built-in core module
│ ├── bmm/ # Official module (if selected)
│ ├── my-module/ # Your custom module
│ │ ├── my-skill/
│ │ │ └── SKILL.md
│ │ └── module-help.csv
│ └── _config/
│ └── manifest.yaml # Tracks all modules, versions, and sources
└── ...
```
The manifest records the source of each custom module (`repoUrl` for Git sources, `localPath` for local sources) so that quick updates can locate the source again.
## Updating Custom Modules
Custom modules participate in the normal update flow:
- **Quick update** (`--action quick-update`): Refreshes all modules from their original sources. Git-based modules are re-fetched; local modules are re-read from their source path.
- **Full update**: Re-runs module selection so you can add or remove custom modules.
## Creating Your Own Modules
Use [BMad Builder](https://github.com/bmad-code-org/bmad-builder) to create modules that others can install:
1. Run `bmad-module-builder` to scaffold your module structure
2. Add skills, agents, and workflows with the various bmad builder tools
3. Publish to a Git repository or share the folder collection
4. Others install with `--custom-source <your-repo-url>`
For modules to support discovery mode, include a `.claude-plugin/marketplace.json` in your repository root (this is a cross-tool convention, not Claude-specific). See the [BMad Builder documentation](https://github.com/bmad-code-org/bmad-builder) for the marketplace.json format.
:::tip[Testing Locally First]
During development, install your module with a local path to iterate quickly before publishing to a Git repository.
:::

View File

@ -22,39 +22,40 @@ Requires [Node.js](https://nodejs.org) v20+ and `npx` (included with npm).
### Installation Options
| Flag | Description | Example |
|------|-------------|---------|
| `--directory <path>` | Installation directory | `--directory ~/projects/myapp` |
| `--modules <modules>` | Comma-separated module IDs | `--modules bmm,bmb` |
| `--tools <tools>` | Comma-separated tool/IDE IDs (use `none` to skip) | `--tools claude-code,cursor` or `--tools none` |
| `--action <type>` | Action for existing installations: `install` (default), `update`, or `quick-update` | `--action quick-update` |
| Flag | Description | Example |
| --------------------------- | ----------------------------------------------------------------------------------- | ---------------------------------------------- |
| `--directory <path>` | Installation directory | `--directory ~/projects/myapp` |
| `--modules <modules>` | Comma-separated module IDs | `--modules bmm,bmb` |
| `--tools <tools>` | Comma-separated tool/IDE IDs (use `none` to skip) | `--tools claude-code,cursor` or `--tools none` |
| `--action <type>` | Action for existing installations: `install` (default), `update`, or `quick-update` | `--action quick-update` |
| `--custom-source <sources>` | Comma-separated Git URLs or local paths for custom modules | `--custom-source /path/to/module` |
### Core Configuration
| Flag | Description | Default |
|------|-------------|---------|
| `--user-name <name>` | Name for agents to use | System username |
| `--communication-language <lang>` | Agent communication language | English |
| `--document-output-language <lang>` | Document output language | English |
| `--output-folder <path>` | Output folder path (see resolution rules below) | `_bmad-output` |
| Flag | Description | Default |
| ----------------------------------- | ----------------------------------------------- | --------------- |
| `--user-name <name>` | Name for agents to use | System username |
| `--communication-language <lang>` | Agent communication language | English |
| `--document-output-language <lang>` | Document output language | English |
| `--output-folder <path>` | Output folder path (see resolution rules below) | `_bmad-output` |
#### Output Folder Path Resolution
The value passed to `--output-folder` (or entered interactively) is resolved according to these rules:
| Input type | Example | Resolved as |
|------------|---------|-------------|
| Relative path (default) | `_bmad-output` | `<project-root>/_bmad-output` |
| Relative path with traversal | `../../shared-outputs` | Normalized absolute path — e.g. `/Users/me/shared-outputs` |
| Absolute path | `/Users/me/shared-outputs` | Used as-is — project root is **not** prepended |
| Input type | Example | Resolved as |
| ---------------------------- | -------------------------- | ---------------------------------------------------------- |
| Relative path (default) | `_bmad-output` | `<project-root>/_bmad-output` |
| Relative path with traversal | `../../shared-outputs` | Normalized absolute path — e.g. `/Users/me/shared-outputs` |
| Absolute path | `/Users/me/shared-outputs` | Used as-is — project root is **not** prepended |
The resolved path is what agents and workflows use at runtime when writing output files. Using an absolute path or a traversal-based relative path lets you direct all generated artifacts to a directory outside your project tree — useful for shared or monorepo setups.
### Other Options
| Flag | Description |
|------|-------------|
| `-y, --yes` | Accept all defaults and skip prompts |
| Flag | Description |
| ------------- | ------------------------------------------- |
| `-y, --yes` | Accept all defaults and skip prompts |
| `-d, --debug` | Enable debug output for manifest generation |
## Module IDs
@ -76,12 +77,13 @@ Run `npx bmad-method install` interactively once to see the full current list of
## Installation Modes
| Mode | Description | Example |
|------|-------------|---------|
| Fully non-interactive | Provide all flags to skip all prompts | `npx bmad-method install --directory . --modules bmm --tools claude-code --yes` |
| Semi-interactive | Provide some flags; BMad prompts for the rest | `npx bmad-method install --directory . --modules bmm` |
| Defaults only | Accept all defaults with `-y` | `npx bmad-method install --yes` |
| Without tools | Skip tool/IDE configuration | `npx bmad-method install --modules bmm --tools none` |
| Mode | Description | Example |
| --------------------- | --------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| Fully non-interactive | Provide all flags to skip all prompts | `npx bmad-method install --directory . --modules bmm --tools claude-code --yes` |
| Semi-interactive | Provide some flags; BMad prompts for the rest | `npx bmad-method install --directory . --modules bmm` |
| Defaults only | Accept all defaults with `-y` | `npx bmad-method install --yes` |
| Custom source only | Install core + custom module(s) | `npx bmad-method install --directory . --custom-source /path/to/module --tools claude-code --yes` |
| Without tools | Skip tool/IDE configuration | `npx bmad-method install --modules bmm --tools none` |
## Examples
@ -119,6 +121,33 @@ npx bmad-method install \
--action quick-update
```
### Install from Custom Source
Install a module from a local path or any Git host:
```bash
npx bmad-method install \
--directory . \
--custom-source /path/to/my-module \
--tools claude-code \
--yes
```
Combine with official modules:
```bash
npx bmad-method install \
--directory . \
--modules bmm \
--custom-source https://gitlab.com/myorg/my-module \
--tools claude-code \
--yes
```
:::note[Custom source behavior]
When `--custom-source` is used without `--modules`, only core and the custom modules are installed. Add `--modules` to include official modules as well. See [Install Custom and Community Modules](./install-custom-modules.md) for details.
:::
## What You Get
- A fully configured `_bmad/` directory in your project
@ -135,17 +164,19 @@ BMad validates all provided flags:
- **Action** — Must be one of: `install`, `update`, `quick-update`
Invalid values will either:
1. Show an error and exit (for critical options like directory)
2. Show a warning and skip (for optional items)
3. Fall back to interactive prompts (for missing required values)
:::tip[Best Practices]
- Use absolute paths for `--directory` to avoid ambiguity
- Use an absolute path for `--output-folder` when you want artifacts written outside the project tree (e.g. a shared monorepo outputs directory)
- Test flags locally before using in CI/CD pipelines
- Combine with `-y` for truly unattended installations
- Use `--debug` if you encounter issues during installation
:::
:::
## Troubleshooting

View File

@ -1,16 +1,17 @@
---
title: "Manage Project Context"
title: 'Manage Project Context'
description: Create and maintain project-context.md to guide AI agents
sidebar:
order: 8
order: 9
---
Use the `project-context.md` file to ensure AI agents follow your project's technical preferences and implementation rules throughout all workflows. To make sure this is always available, you can also add the line `Important project context and conventions are located in [path to project context]/project-context.md` to your tools context or always rules file (such as `AGENTS.md`)
:::note[Prerequisites]
- BMad Method installed
- Understanding of your project's technology stack and conventions
:::
:::
## When to Use This
@ -60,14 +61,17 @@ sections_completed: ['technology_stack', 'critical_rules']
## Critical Implementation Rules
**TypeScript:**
- Strict mode enabled, no `any` types
- Use `interface` for public APIs, `type` for unions
**Code Organization:**
- Components in `/src/components/` with co-located tests
- API calls use `apiClient` singleton — never fetch directly
**Testing:**
- Unit tests focus on business logic
- Integration tests use MSW for API mocking
```
@ -115,11 +119,12 @@ A `project-context.md` file that:
## Tips
:::tip[Best Practices]
- **Focus on the unobvious** — Document patterns agents might miss (e.g., "Use JSDoc on every public class"), not universal practices like "use meaningful variable names."
- **Keep it lean** — This file is loaded by every implementation workflow. Long files waste context. Exclude content that only applies to narrow scope or specific stories.
- **Update as needed** — Edit manually when patterns change, or re-generate after significant architecture changes.
- Works for Quick Flow and full BMad Method projects alike.
:::
:::
## Next Steps

View File

@ -1,8 +1,8 @@
---
title: "Quick Fixes"
title: 'Quick Fixes'
description: How to make quick fixes and ad-hoc changes
sidebar:
order: 5
order: 6
---
Use **Quick Dev** for bug fixes, refactorings, or small targeted changes that don't require the full BMad Method.
@ -15,9 +15,10 @@ Use **Quick Dev** for bug fixes, refactorings, or small targeted changes that do
- Dependency updates
:::note[Prerequisites]
- BMad Method installed (`npx bmad-method install`)
- An AI-powered IDE (Claude Code, Cursor, or similar)
:::
:::
## Steps

View File

@ -1,8 +1,8 @@
---
title: "Document Sharding Guide"
title: 'Document Sharding Guide'
description: Split large markdown files into smaller organized files for better context management
sidebar:
order: 9
order: 10
---
Use the `bmad-shard-doc` tool if you need to split large markdown files into smaller, organized files for better context management.

View File

@ -1,8 +1,8 @@
---
title: "How to Upgrade to v6"
title: 'How to Upgrade to v6'
description: Migrate from BMad v4 to v6
sidebar:
order: 3
order: 4
---
Use the BMad installer to upgrade from v4 to v6, which includes automatic detection of legacy installations and migration assistance.
@ -14,9 +14,10 @@ Use the BMad installer to upgrade from v4 to v6, which includes automatic detect
- You have existing planning artifacts to preserve
:::note[Prerequisites]
- Node.js 20+
- Existing BMad v4 installation
:::
:::
## Steps

View File

@ -0,0 +1,62 @@
# Compile Epic Context
**Task**
Given an epic number, the epics file, the planning artifacts directory, and a desired output path, compile a clean, focused, developer-ready context file (`epic-<N>-context.md`).
**Steps**
1. Read the epics file and extract the target epic's title, goal, and list of stories.
2. Scan the planning artifacts directory for the standard files (PRD, architecture, UX/design, product brief).
3. Pull only the information relevant to this epic.
4. Write the compiled context to the exact output path using the format below.
## Exact Output Format
Use these headings:
```markdown
# Epic {N} Context: {Epic Title}
<!-- Compiled from planning artifacts. Edit freely. Regenerate with compile-epic-context if planning docs change. -->
## Goal
{One clear paragraph: what this epic achieves and why it matters.}
## Stories
- Story X.Y: Brief title only
- ...
## Requirements & Constraints
{Relevant functional/non-functional requirements and success criteria for this epic (describe by purpose, not source).}
## Technical Decisions
{Key architecture decisions, constraints, patterns, data models, and conventions relevant to this epic.}
## UX & Interaction Patterns
{Relevant UX flows, interaction patterns, and design constraints (omit section entirely if nothing relevant).}
## Cross-Story Dependencies
{Dependencies between stories in this epic or with other epics/systems (omit if none).}
```
## Rules
- **Scope aggressively.** Include only what a developer working on any story in this epic actually needs. When in doubt, leave it out — the developer can always read the full planning doc.
- **Describe by purpose, not by source.** Write "API responses must include pagination metadata" not "Per PRD section 3.2.1, pagination is required." Planning doc internals will change; the constraint won't.
- **No full copies.** Never quote source documents, section numbers, or paste large blocks verbatim. Always distill.
- **No story-level details.** The story list is for orientation only. Individual story specs handle the details.
- **Nothing derivable from the codebase.** Don't document what a developer can learn by reading the code.
- **Be concise and actionable.** Target 8001500 tokens total. This file loads into quick-dev's context alongside other material.
- **Never hallucinate content.** If source material doesn't say something, don't invent it.
- **Omit empty sections entirely**, except Goal and Stories, which are always required.
## Error handling
- **If the epics file is missing or the target epic is not found:** write nothing and report the problem to the calling agent. Goal and Stories cannot be populated without a usable epics file.
- **If planning artifacts are missing or empty:** still produce the file with Goal and Stories populated from the epics file, and note the gap in the Goal section. Never hallucinate content to fill missing sections.

View File

@ -41,19 +41,32 @@ Never ask extra questions if you already understand what the user intends.
1. Load context.
- List files in `{planning_artifacts}` and `{implementation_artifacts}`.
- If you find an unformatted spec or intent file, ingest its contents to form your understanding of the intent.
- Planning artifacts are the output of BMAD phases 1-3. Typical files include:
- **PRD** (`*prd*`) — product requirements and success criteria
- **Architecture** (`*architecture*`) — technical design decisions and constraints
- **UX/Design** (`*ux*`) — user experience and interaction design
- **Epics** (`*epic*`) — feature breakdown into implementable stories
- **Product Brief** (`*brief*`) — project vision and scope
- Scan the listing for files matching these patterns. If any look relevant to the current intent, load them selectively — you don't need all of them, but you need the right constraints and requirements rather than guessing from code alone.
- **Previous story continuity.** Using the intent and loaded context (especially any epics file), infer whether the current work is a story from an epic. Do not rely on filename patterns or regex — reason about the intent, the artifact listing, and epics content together. If the intent is an epic story:
1. Identify the epic and story number.
2. Scan `{implementation_artifacts}` for specs from the same epic with `status: done` and a lower story number.
3. Load the most recent one (highest story number below current).
4. Extract its **Code Map**, **Design Notes**, **Spec Change Log**, and **task list** as continuity context for step-02 planning.
If no `done` spec is found but an `in-review` spec exists for the same epic with a lower story number, note it to the user and ask whether to load it. If the intent is not an epic story, or no previous spec exists, skip this silently.
- **Determine context strategy.** Using the intent and the artifact listing, infer whether the current work is a story from an epic. Do not rely on filename patterns or regex — reason about the intent, the listing, and any epics file content together.
**A) Epic story path** — if the intent is clearly an epic story:
1. Identify the epic number and (if present) the story number. If you can't identify an epic number, use path B.
2. **Check for a valid cached epic context.** Look for `{implementation_artifacts}/epic-<N>-context.md` (where `<N>` is the epic number). A file is **valid** when it exists, is non-empty, starts with `# Epic <N> Context:` (with the correct epic number), and no file in `{planning_artifacts}` is newer.
- **If valid:** load it as the primary planning context. Do not load raw planning docs (PRD, architecture, UX, etc.). Skip to step 5.
- **If missing, empty, or invalid:** continue to step 3.
3. **Compile epic context.** Produce `{implementation_artifacts}/epic-<N>-context.md` by following `./compile-epic-context.md`, in order of preference:
- **Preferred — sub-agent:** spawn a sub-agent with `./compile-epic-context.md` as its prompt. Pass it the epic number, the epics file path, the `{planning_artifacts}` directory, and the output path `{implementation_artifacts}/epic-<N>-context.md`.
- **Fallback — inline** (for runtimes without sub-agent support, e.g. Copilot, Codex, local Ollama, older Claude): if your runtime cannot spawn sub-agents, or the spawn fails/times out, read `./compile-epic-context.md` yourself and follow its instructions to produce the same output file.
4. **Verify.** After compilation, verify the output file exists, is non-empty, and starts with `# Epic <N> Context:`. If valid, load it. If verification fails, HALT and report the failure.
5. **Previous story continuity.** Regardless of which context source succeeded above, scan `{implementation_artifacts}` for specs from the same epic with `status: done` and a lower story number. Load the most recent one (highest story number below current). Extract its **Code Map**, **Design Notes**, **Spec Change Log**, and **task list** as continuity context for step-02 planning. If no `done` spec is found but an `in-review` spec exists for the same epic with a lower story number, note it to the user and ask whether to load it.
**B) Freeform path** — if the intent is not an epic story:
- Planning artifacts are the output of BMAD phases 1-3. Typical files include:
- **PRD** (`*prd*`) — product requirements and success criteria
- **Architecture** (`*architecture*`) — technical design decisions and constraints
- **UX/Design** (`*ux*`) — user experience and interaction design
- **Epics** (`*epic*`) — feature breakdown into implementable stories
- **Product Brief** (`*brief*`) — project vision and scope
- Scan the listing for files matching these patterns. If any look relevant to the current intent, load them selectively — you don't need all of them, but you need the right constraints and requirements rather than guessing from code alone.
2. Clarify intent. Do not fantasize, do not leave open questions. If you must ask questions, ask them as a numbered list. When the human replies, verify that every single numbered question was answered. If any were ignored, HALT and re-ask only the missing questions before proceeding. Keep looping until intent is clear enough to implement.
3. Version control sanity check. Is the working tree clean? Does the current branch make sense for this intent — considering its name and recent history? If the tree is dirty or the branch is an obvious mismatch, HALT and ask the human before proceeding. If version control is unavailable, skip this check.
4. Multi-goal check (see SCOPE STANDARD). If the intent fails the single-goal criteria:

View File

@ -1,51 +1,70 @@
num,category,method_name,description,output_pattern
1,collaboration,Stakeholder Round Table,Convene multiple personas to contribute diverse perspectives - essential for requirements gathering and finding balanced solutions across competing interests,perspectives → synthesis → alignment
2,collaboration,Expert Panel Review,Assemble domain experts for deep specialized analysis - ideal when technical depth and peer review quality are needed,expert views → consensus → recommendations
3,collaboration,Debate Club Showdown,Two personas argue opposing positions while a moderator scores points - great for exploring controversial decisions and finding middle ground,thesis → antithesis → synthesis
4,collaboration,User Persona Focus Group,Gather your product's user personas to react to proposals and share frustrations - essential for validating features and discovering unmet needs,reactions → concerns → priorities
5,collaboration,Time Traveler Council,Past-you and future-you advise present-you on decisions - powerful for gaining perspective on long-term consequences vs short-term pressures,past wisdom → present choice → future impact
6,collaboration,Cross-Functional War Room,Product manager + engineer + designer tackle a problem together - reveals trade-offs between feasibility desirability and viability,constraints → trade-offs → balanced solution
7,collaboration,Mentor and Apprentice,Senior expert teaches junior while junior asks naive questions - surfaces hidden assumptions through teaching,explanation → questions → deeper understanding
8,collaboration,Good Cop Bad Cop,Supportive persona and critical persona alternate - finds both strengths to build on and weaknesses to address,encouragement → criticism → balanced view
9,collaboration,Improv Yes-And,Multiple personas build on each other's ideas without blocking - generates unexpected creative directions through collaborative building,idea → build → build → surprising result
10,collaboration,Customer Support Theater,Angry customer and support rep roleplay to find pain points - reveals real user frustrations and service gaps,complaint → investigation → resolution → prevention
11,advanced,Tree of Thoughts,Explore multiple reasoning paths simultaneously then evaluate and select the best - perfect for complex problems with multiple valid approaches,paths → evaluation → selection
12,advanced,Graph of Thoughts,Model reasoning as an interconnected network of ideas to reveal hidden relationships - ideal for systems thinking and discovering emergent patterns,nodes → connections → patterns
13,advanced,Thread of Thought,Maintain coherent reasoning across long contexts by weaving a continuous narrative thread - essential for RAG systems and maintaining consistency,context → thread → synthesis
14,advanced,Self-Consistency Validation,Generate multiple independent approaches then compare for consistency - crucial for high-stakes decisions where verification matters,approaches → comparison → consensus
15,advanced,Meta-Prompting Analysis,Step back to analyze the approach structure and methodology itself - valuable for optimizing prompts and improving problem-solving,current → analysis → optimization
16,advanced,Reasoning via Planning,Build a reasoning tree guided by world models and goal states - excellent for strategic planning and sequential decision-making,model → planning → strategy
17,competitive,Red Team vs Blue Team,Adversarial attack-defend analysis to find vulnerabilities - critical for security testing and building robust solutions,defense → attack → hardening
18,competitive,Shark Tank Pitch,Entrepreneur pitches to skeptical investors who poke holes - stress-tests business viability and forces clarity on value proposition,pitch → challenges → refinement
19,competitive,Code Review Gauntlet,Senior devs with different philosophies review the same code - surfaces style debates and finds consensus on best practices,reviews → debates → standards
20,technical,Architecture Decision Records,Multiple architect personas propose and debate architectural choices with explicit trade-offs - ensures decisions are well-reasoned and documented,options → trade-offs → decision → rationale
21,technical,Rubber Duck Debugging Evolved,Explain your code to progressively more technical ducks until you find the bug - forces clarity at multiple abstraction levels,simple → detailed → technical → aha
22,technical,Algorithm Olympics,Multiple approaches compete on the same problem with benchmarks - finds optimal solution through direct comparison,implementations → benchmarks → winner
23,technical,Security Audit Personas,Hacker + defender + auditor examine system from different threat models - comprehensive security review from multiple angles,vulnerabilities → defenses → compliance
24,technical,Performance Profiler Panel,Database expert + frontend specialist + DevOps engineer diagnose slowness - finds bottlenecks across the full stack,symptoms → analysis → optimizations
25,creative,SCAMPER Method,Apply seven creativity lenses (Substitute/Combine/Adapt/Modify/Put/Eliminate/Reverse) - systematic ideation for product innovation,S→C→A→M→P→E→R
26,creative,Reverse Engineering,Work backwards from desired outcome to find implementation path - powerful for goal achievement and understanding endpoints,end state → steps backward → path forward
27,creative,What If Scenarios,Explore alternative realities to understand possibilities and implications - valuable for contingency planning and exploration,scenarios → implications → insights
28,creative,Random Input Stimulus,Inject unrelated concepts to spark unexpected connections - breaks creative blocks through forced lateral thinking,random word → associations → novel ideas
29,creative,Exquisite Corpse Brainstorm,Each persona adds to the idea seeing only the previous contribution - generates surprising combinations through constrained collaboration,contribution → handoff → contribution → surprise
30,creative,Genre Mashup,Combine two unrelated domains to find fresh approaches - innovation through unexpected cross-pollination,domain A + domain B → hybrid insights
31,research,Literature Review Personas,Optimist researcher + skeptic researcher + synthesizer review sources - balanced assessment of evidence quality,sources → critiques → synthesis
32,research,Thesis Defense Simulation,Student defends hypothesis against committee with different concerns - stress-tests research methodology and conclusions,thesis → challenges → defense → refinements
33,research,Comparative Analysis Matrix,Multiple analysts evaluate options against weighted criteria - structured decision-making with explicit scoring,options → criteria → scores → recommendation
34,risk,Pre-mortem Analysis,Imagine future failure then work backwards to prevent it - powerful technique for risk mitigation before major launches,failure scenario → causes → prevention
35,risk,Failure Mode Analysis,Systematically explore how each component could fail - critical for reliability engineering and safety-critical systems,components → failures → prevention
36,risk,Challenge from Critical Perspective,Play devil's advocate to stress-test ideas and find weaknesses - essential for overcoming groupthink,assumptions → challenges → strengthening
37,risk,Identify Potential Risks,Brainstorm what could go wrong across all categories - fundamental for project planning and deployment preparation,categories → risks → mitigations
38,risk,Chaos Monkey Scenarios,Deliberately break things to test resilience and recovery - ensures systems handle failures gracefully,break → observe → harden
39,core,First Principles Analysis,Strip away assumptions to rebuild from fundamental truths - breakthrough technique for innovation and solving impossible problems,assumptions → truths → new approach
40,core,5 Whys Deep Dive,Repeatedly ask why to drill down to root causes - simple but powerful for understanding failures,why chain → root cause → solution
41,core,Socratic Questioning,Use targeted questions to reveal hidden assumptions and guide discovery - excellent for teaching and self-discovery,questions → revelations → understanding
42,core,Critique and Refine,Systematic review to identify strengths and weaknesses then improve - standard quality check for drafts,strengths/weaknesses → improvements → refined
43,core,Explain Reasoning,Walk through step-by-step thinking to show how conclusions were reached - crucial for transparency,steps → logic → conclusion
44,core,Expand or Contract for Audience,Dynamically adjust detail level and technical depth for target audience - matches content to reader capabilities,audience → adjustments → refined content
45,learning,Feynman Technique,Explain complex concepts simply as if teaching a child - the ultimate test of true understanding,complex → simple → gaps → mastery
46,learning,Active Recall Testing,Test understanding without references to verify true knowledge - essential for identifying gaps,test → gaps → reinforcement
47,philosophical,Occam's Razor Application,Find the simplest sufficient explanation by eliminating unnecessary complexity - essential for debugging,options → simplification → selection
48,philosophical,Trolley Problem Variations,Explore ethical trade-offs through moral dilemmas - valuable for understanding values and difficult decisions,dilemma → analysis → decision
49,retrospective,Hindsight Reflection,Imagine looking back from the future to gain perspective - powerful for project reviews,future view → insights → application
50,retrospective,Lessons Learned Extraction,Systematically identify key takeaways and actionable improvements - essential for continuous improvement,experience → lessons → actions
1,advanced,Tree of Thoughts,Explore multiple reasoning paths simultaneously then evaluate and select the best - perfect for complex problems with multiple valid approaches,paths → evaluation → selection
2,advanced,Graph of Thoughts,Model reasoning as an interconnected network of ideas to reveal hidden relationships - ideal for systems thinking and discovering emergent patterns,nodes → connections → patterns
3,advanced,Thread of Thought,Maintain coherent reasoning across long contexts by weaving a continuous narrative thread - essential for RAG systems and maintaining consistency,context → thread → synthesis
4,advanced,Self-Consistency Validation,Generate multiple independent approaches then compare for consistency - crucial for high-stakes decisions where verification matters,approaches → comparison → consensus
5,advanced,Meta-Prompting Analysis,Step back to analyze the approach structure and methodology itself - valuable for optimizing prompts and improving problem-solving,current → analysis → optimization
6,advanced,Reasoning via Planning,Build a reasoning tree guided by world models and goal states - excellent for strategic planning and sequential decision-making,model → planning → strategy
7,advanced,Chain-of-Thought Scaffolding,Force explicit intermediate reasoning steps before any conclusion — prevents intuitive leaps that skip flawed logic,premise → step → step → conclusion
8,advanced,Few-Shot Exemplar Priming,Provide 2-3 worked examples of the desired reasoning pattern before the real task — aligns output format and depth through demonstration,examples → pattern recognition → application
9,collaboration,Stakeholder Round Table,Convene multiple personas to contribute diverse perspectives - essential for requirements gathering and finding balanced solutions across competing interests,perspectives → synthesis → alignment
10,collaboration,Expert Panel Review,Assemble domain experts for deep specialized analysis - ideal when technical depth and peer review quality are needed,expert views → consensus → recommendations
11,collaboration,Debate Club Showdown,Two personas argue opposing positions while a moderator scores points - great for exploring controversial decisions and finding middle ground,thesis → antithesis → synthesis
12,collaboration,User Persona Focus Group,Gather your product's user personas to react to proposals and share frustrations - essential for validating features and discovering unmet needs,reactions → concerns → priorities
13,collaboration,Time Traveler Council,Past-you and future-you advise present-you on decisions - powerful for gaining perspective on long-term consequences vs short-term pressures,past wisdom → present choice → future impact
14,collaboration,Cross-Functional War Room,Product manager + engineer + designer tackle a problem together - reveals trade-offs between feasibility desirability and viability,constraints → trade-offs → balanced solution
15,collaboration,Mentor and Apprentice,Senior expert teaches junior while junior asks naive questions - surfaces hidden assumptions through teaching,explanation → questions → deeper understanding
16,collaboration,Good Cop Bad Cop,Supportive persona and critical persona alternate - finds both strengths to build on and weaknesses to address,encouragement → criticism → balanced view
17,collaboration,Improv Yes-And,Multiple personas build on each other's ideas without blocking - generates unexpected creative directions through collaborative building,idea → build → build → surprising result
18,collaboration,Customer Support Theater,Angry customer and support rep roleplay to find pain points - reveals real user frustrations and service gaps,complaint → investigation → resolution → prevention
19,collaboration,Six Thinking Hats,Rotate through six modes (facts - feelings - caution - optimism - creativity - process) to ensure a group covers every angle without crosstalk,white → red → black → yellow → green → blue
20,collaboration,Delphi Method,Experts give independent estimates - see anonymized results - then revise — converges on calibrated group judgment while avoiding anchoring bias,independent estimates → reveal → revise → converge
21,competitive,Red Team vs Blue Team,Adversarial attack-defend analysis to find vulnerabilities - critical for security testing and building robust solutions,defense → attack → hardening
22,competitive,Shark Tank Pitch,Entrepreneur pitches to skeptical investors who poke holes - stress-tests business viability and forces clarity on value proposition,pitch → challenges → refinement
23,competitive,Code Review Gauntlet,Senior devs with different philosophies review the same code - surfaces style debates and finds consensus on best practices,reviews → debates → standards
24,core,First Principles Analysis,Strip away assumptions to rebuild from fundamental truths - breakthrough technique for innovation and solving impossible problems,assumptions → truths → new approach
25,core,5 Whys Deep Dive,Repeatedly ask why to drill down to root causes - simple but powerful for understanding failures,why chain → root cause → solution
26,core,Socratic Questioning,Use targeted questions to reveal hidden assumptions and guide discovery - excellent for teaching and self-discovery,questions → revelations → understanding
27,core,Critique and Refine,Systematic review to identify strengths and weaknesses then improve - standard quality check for drafts,strengths/weaknesses → improvements → refined
28,core,Explain Reasoning,Walk through step-by-step thinking to show how conclusions were reached - crucial for transparency,steps → logic → conclusion
29,core,Expand or Contract for Audience,Dynamically adjust detail level and technical depth for target audience - matches content to reader capabilities,audience → adjustments → refined content
30,core,Second-Order Thinking,Think beyond immediate consequences to anticipate cascading effects and long-term implications - essential for strategic decisions where first-order solutions create hidden downstream problems,action → consequences → second-order effects → informed choice
31,core,Inversion Analysis,Flip the problem by asking what would guarantee failure instead of how to succeed - reveals hidden obstacles and blind spots by approaching challenges from the opposite direction,goal → invert → failure paths → avoidance → solution
32,core,Problem Decomposition,Break a complex problem into independent sub-problems - solve each - then reassemble — essential when a task is too large or tangled to tackle whole,whole → parts → solutions → reassembly
33,core,Analogy Mapping,Find a well-understood parallel domain and transfer its structure to the current problem — unlocks insight by borrowing proven mental models,source domain → mapping → target insight
34,core,Steelmanning,Construct the strongest possible version of an opposing argument before responding — builds credibility and catches blind spots that strawmanning misses,opposing view → strongest form → honest rebuttal
35,creative,SCAMPER Method,Apply seven creativity lenses (Substitute/Combine/Adapt/Modify/Put/Eliminate/Reverse) - systematic ideation for product innovation,S→C→A→M→P→E→R
36,creative,Reverse Engineering,Work backwards from desired outcome to find implementation path - powerful for goal achievement and understanding endpoints,end state → steps backward → path forward
37,creative,What If Scenarios,Explore alternative realities to understand possibilities and implications - valuable for contingency planning and exploration,scenarios → implications → insights
38,creative,Random Input Stimulus,Inject unrelated concepts to spark unexpected connections - breaks creative blocks through forced lateral thinking,random word → associations → novel ideas
39,creative,Exquisite Corpse Brainstorm,Each persona adds to the idea seeing only the previous contribution - generates surprising combinations through constrained collaboration,contribution → handoff → contribution → surprise
40,creative,Genre Mashup,Combine two unrelated domains to find fresh approaches - innovation through unexpected cross-pollination,domain A + domain B → hybrid insights
41,creative,Constraint Injection,Deliberately add an artificial limitation (budget - time - technology) to force novel solutions — creativity thrives under pressure,add constraint → forced creativity → remove constraint → evaluate
42,creative,Morphological Analysis,List independent parameters of a problem - enumerate options for each - then systematically combine — ensures you don't miss non-obvious configurations,parameters → options grid → combinations → evaluation
43,framing,Abstraction Laddering,"Move up (""why?"") for strategic clarity or down (""how?"") for tactical detail — ensures you're solving at the right altitude",concrete ↔ abstract → right level
44,framing,Reframe the Question,Challenge whether the stated problem is the real problem — often the question itself is wrong and a better framing unlocks an easy answer,stated problem → reframe → true problem → solution
45,framing,Stakeholder Lens Rotation,Serially adopt each stakeholder's world-view to see the same situation differently — reveals whose needs are being overlooked,perspective A → B → C → gaps found
46,learning,Feynman Technique,Explain complex concepts simply as if teaching a child - the ultimate test of true understanding,complex → simple → gaps → mastery
47,learning,Active Recall Testing,Test understanding without references to verify true knowledge - essential for identifying gaps,test → gaps → reinforcement
48,learning,Deliberate Practice Loop,Identify a specific sub-skill - drill it with immediate feedback - adjust - repeat — targeted improvement beats general repetition,isolate → drill → feedback → adjust → repeat
49,philosophical,Occam's Razor Application,Find the simplest sufficient explanation by eliminating unnecessary complexity - essential for debugging,options → simplification → selection
50,philosophical,Trolley Problem Variations,Explore ethical trade-offs through moral dilemmas - valuable for understanding values and difficult decisions,dilemma → analysis → decision
51,research,Literature Review Personas,Optimist researcher + skeptic researcher + synthesizer review sources - balanced assessment of evidence quality,sources → critiques → synthesis
52,research,Thesis Defense Simulation,Student defends hypothesis against committee with different concerns - stress-tests research methodology and conclusions,thesis → challenges → defense → refinements
53,research,Comparative Analysis Matrix,Multiple analysts evaluate options against weighted criteria - structured decision-making with explicit scoring,options → criteria → scores → recommendation
54,research,Source Triangulation,Require at least three independent source types (quantitative - qualitative - expert) before accepting a claim — guards against single-source bias,claim → source A → source B → source C → confidence rating
55,retrospective,Hindsight Reflection,Imagine looking back from the future to gain perspective - powerful for project reviews,future view → insights → application
56,retrospective,Lessons Learned Extraction,Systematically identify key takeaways and actionable improvements - essential for continuous improvement,experience → lessons → actions
57,risk,Pre-mortem Analysis,Imagine future failure then work backwards to prevent it - powerful technique for risk mitigation before major launches,failure scenario → causes → prevention
58,risk,Failure Mode Analysis,Systematically explore how each component could fail - critical for reliability engineering and safety-critical systems,components → failures → prevention
59,risk,Challenge from Critical Perspective,Play devil's advocate to stress-test ideas and find weaknesses - essential for overcoming groupthink,assumptions → challenges → strengthening
60,risk,Identify Potential Risks,Brainstorm what could go wrong across all categories - fundamental for project planning and deployment preparation,categories → risks → mitigations
61,risk,Chaos Monkey Scenarios,Deliberately break things to test resilience and recovery - ensures systems handle failures gracefully,break → observe → harden
62,risk,Assumption Audit,Explicitly list every assumption underlying a plan - rate each by confidence and impact - then stress-test the weakest — prevents building on shaky foundations,list → rate → stress-test → shore up
63,risk,Cascading Failure Simulation,Trace how one component's failure propagates through dependencies — reveals hidden coupling and single points of failure,trigger failure → trace propagation → find amplifiers → decouple
64,technical,Architecture Decision Records,Multiple architect personas propose and debate architectural choices with explicit trade-offs - ensures decisions are well-reasoned and documented,options → trade-offs → decision → rationale
65,technical,Rubber Duck Debugging Evolved,Explain your code to progressively more technical ducks until you find the bug - forces clarity at multiple abstraction levels,simple → detailed → technical → aha
66,technical,Algorithm Olympics,Multiple approaches compete on the same problem with benchmarks - finds optimal solution through direct comparison,implementations → benchmarks → winner
67,technical,Security Audit Personas,Hacker + defender + auditor examine system from different threat models - comprehensive security review from multiple angles,vulnerabilities → defenses → compliance
68,technical,Performance Profiler Panel,Database expert + frontend specialist + DevOps engineer diagnose slowness - finds bottlenecks across the full stack,symptoms → analysis → optimizations
69,technical,Boundary & Edge Case Sweep,Systematically test extremes - zeros - nulls - maximums - and type mismatches — catches the failures that happy-path thinking always misses,inputs → boundaries → edge cases → failures found

1 num category method_name description output_pattern
2 1 collaboration advanced Stakeholder Round Table Tree of Thoughts Convene multiple personas to contribute diverse perspectives - essential for requirements gathering and finding balanced solutions across competing interests Explore multiple reasoning paths simultaneously then evaluate and select the best - perfect for complex problems with multiple valid approaches perspectives → synthesis → alignment paths → evaluation → selection
3 2 collaboration advanced Expert Panel Review Graph of Thoughts Assemble domain experts for deep specialized analysis - ideal when technical depth and peer review quality are needed Model reasoning as an interconnected network of ideas to reveal hidden relationships - ideal for systems thinking and discovering emergent patterns expert views → consensus → recommendations nodes → connections → patterns
4 3 collaboration advanced Debate Club Showdown Thread of Thought Two personas argue opposing positions while a moderator scores points - great for exploring controversial decisions and finding middle ground Maintain coherent reasoning across long contexts by weaving a continuous narrative thread - essential for RAG systems and maintaining consistency thesis → antithesis → synthesis context → thread → synthesis
5 4 collaboration advanced User Persona Focus Group Self-Consistency Validation Gather your product's user personas to react to proposals and share frustrations - essential for validating features and discovering unmet needs Generate multiple independent approaches then compare for consistency - crucial for high-stakes decisions where verification matters reactions → concerns → priorities approaches → comparison → consensus
6 5 collaboration advanced Time Traveler Council Meta-Prompting Analysis Past-you and future-you advise present-you on decisions - powerful for gaining perspective on long-term consequences vs short-term pressures Step back to analyze the approach structure and methodology itself - valuable for optimizing prompts and improving problem-solving past wisdom → present choice → future impact current → analysis → optimization
7 6 collaboration advanced Cross-Functional War Room Reasoning via Planning Product manager + engineer + designer tackle a problem together - reveals trade-offs between feasibility desirability and viability Build a reasoning tree guided by world models and goal states - excellent for strategic planning and sequential decision-making constraints → trade-offs → balanced solution model → planning → strategy
8 7 collaboration advanced Mentor and Apprentice Chain-of-Thought Scaffolding Senior expert teaches junior while junior asks naive questions - surfaces hidden assumptions through teaching Force explicit intermediate reasoning steps before any conclusion — prevents intuitive leaps that skip flawed logic explanation → questions → deeper understanding premise → step → step → conclusion
9 8 collaboration advanced Good Cop Bad Cop Few-Shot Exemplar Priming Supportive persona and critical persona alternate - finds both strengths to build on and weaknesses to address Provide 2-3 worked examples of the desired reasoning pattern before the real task — aligns output format and depth through demonstration encouragement → criticism → balanced view examples → pattern recognition → application
10 9 collaboration Improv Yes-And Stakeholder Round Table Multiple personas build on each other's ideas without blocking - generates unexpected creative directions through collaborative building Convene multiple personas to contribute diverse perspectives - essential for requirements gathering and finding balanced solutions across competing interests idea → build → build → surprising result perspectives → synthesis → alignment
11 10 collaboration Customer Support Theater Expert Panel Review Angry customer and support rep roleplay to find pain points - reveals real user frustrations and service gaps Assemble domain experts for deep specialized analysis - ideal when technical depth and peer review quality are needed complaint → investigation → resolution → prevention expert views → consensus → recommendations
12 11 advanced collaboration Tree of Thoughts Debate Club Showdown Explore multiple reasoning paths simultaneously then evaluate and select the best - perfect for complex problems with multiple valid approaches Two personas argue opposing positions while a moderator scores points - great for exploring controversial decisions and finding middle ground paths → evaluation → selection thesis → antithesis → synthesis
13 12 advanced collaboration Graph of Thoughts User Persona Focus Group Model reasoning as an interconnected network of ideas to reveal hidden relationships - ideal for systems thinking and discovering emergent patterns Gather your product's user personas to react to proposals and share frustrations - essential for validating features and discovering unmet needs nodes → connections → patterns reactions → concerns → priorities
14 13 advanced collaboration Thread of Thought Time Traveler Council Maintain coherent reasoning across long contexts by weaving a continuous narrative thread - essential for RAG systems and maintaining consistency Past-you and future-you advise present-you on decisions - powerful for gaining perspective on long-term consequences vs short-term pressures context → thread → synthesis past wisdom → present choice → future impact
15 14 advanced collaboration Self-Consistency Validation Cross-Functional War Room Generate multiple independent approaches then compare for consistency - crucial for high-stakes decisions where verification matters Product manager + engineer + designer tackle a problem together - reveals trade-offs between feasibility desirability and viability approaches → comparison → consensus constraints → trade-offs → balanced solution
16 15 advanced collaboration Meta-Prompting Analysis Mentor and Apprentice Step back to analyze the approach structure and methodology itself - valuable for optimizing prompts and improving problem-solving Senior expert teaches junior while junior asks naive questions - surfaces hidden assumptions through teaching current → analysis → optimization explanation → questions → deeper understanding
17 16 advanced collaboration Reasoning via Planning Good Cop Bad Cop Build a reasoning tree guided by world models and goal states - excellent for strategic planning and sequential decision-making Supportive persona and critical persona alternate - finds both strengths to build on and weaknesses to address model → planning → strategy encouragement → criticism → balanced view
18 17 competitive collaboration Red Team vs Blue Team Improv Yes-And Adversarial attack-defend analysis to find vulnerabilities - critical for security testing and building robust solutions Multiple personas build on each other's ideas without blocking - generates unexpected creative directions through collaborative building defense → attack → hardening idea → build → build → surprising result
19 18 competitive collaboration Shark Tank Pitch Customer Support Theater Entrepreneur pitches to skeptical investors who poke holes - stress-tests business viability and forces clarity on value proposition Angry customer and support rep roleplay to find pain points - reveals real user frustrations and service gaps pitch → challenges → refinement complaint → investigation → resolution → prevention
20 19 competitive collaboration Code Review Gauntlet Six Thinking Hats Senior devs with different philosophies review the same code - surfaces style debates and finds consensus on best practices Rotate through six modes (facts - feelings - caution - optimism - creativity - process) to ensure a group covers every angle without crosstalk reviews → debates → standards white → red → black → yellow → green → blue
21 20 technical collaboration Architecture Decision Records Delphi Method Multiple architect personas propose and debate architectural choices with explicit trade-offs - ensures decisions are well-reasoned and documented Experts give independent estimates - see anonymized results - then revise — converges on calibrated group judgment while avoiding anchoring bias options → trade-offs → decision → rationale independent estimates → reveal → revise → converge
22 21 technical competitive Rubber Duck Debugging Evolved Red Team vs Blue Team Explain your code to progressively more technical ducks until you find the bug - forces clarity at multiple abstraction levels Adversarial attack-defend analysis to find vulnerabilities - critical for security testing and building robust solutions simple → detailed → technical → aha defense → attack → hardening
23 22 technical competitive Algorithm Olympics Shark Tank Pitch Multiple approaches compete on the same problem with benchmarks - finds optimal solution through direct comparison Entrepreneur pitches to skeptical investors who poke holes - stress-tests business viability and forces clarity on value proposition implementations → benchmarks → winner pitch → challenges → refinement
24 23 technical competitive Security Audit Personas Code Review Gauntlet Hacker + defender + auditor examine system from different threat models - comprehensive security review from multiple angles Senior devs with different philosophies review the same code - surfaces style debates and finds consensus on best practices vulnerabilities → defenses → compliance reviews → debates → standards
25 24 technical core Performance Profiler Panel First Principles Analysis Database expert + frontend specialist + DevOps engineer diagnose slowness - finds bottlenecks across the full stack Strip away assumptions to rebuild from fundamental truths - breakthrough technique for innovation and solving impossible problems symptoms → analysis → optimizations assumptions → truths → new approach
26 25 creative core SCAMPER Method 5 Whys Deep Dive Apply seven creativity lenses (Substitute/Combine/Adapt/Modify/Put/Eliminate/Reverse) - systematic ideation for product innovation Repeatedly ask why to drill down to root causes - simple but powerful for understanding failures S→C→A→M→P→E→R why chain → root cause → solution
27 26 creative core Reverse Engineering Socratic Questioning Work backwards from desired outcome to find implementation path - powerful for goal achievement and understanding endpoints Use targeted questions to reveal hidden assumptions and guide discovery - excellent for teaching and self-discovery end state → steps backward → path forward questions → revelations → understanding
28 27 creative core What If Scenarios Critique and Refine Explore alternative realities to understand possibilities and implications - valuable for contingency planning and exploration Systematic review to identify strengths and weaknesses then improve - standard quality check for drafts scenarios → implications → insights strengths/weaknesses → improvements → refined
29 28 creative core Random Input Stimulus Explain Reasoning Inject unrelated concepts to spark unexpected connections - breaks creative blocks through forced lateral thinking Walk through step-by-step thinking to show how conclusions were reached - crucial for transparency random word → associations → novel ideas steps → logic → conclusion
30 29 creative core Exquisite Corpse Brainstorm Expand or Contract for Audience Each persona adds to the idea seeing only the previous contribution - generates surprising combinations through constrained collaboration Dynamically adjust detail level and technical depth for target audience - matches content to reader capabilities contribution → handoff → contribution → surprise audience → adjustments → refined content
31 30 creative core Genre Mashup Second-Order Thinking Combine two unrelated domains to find fresh approaches - innovation through unexpected cross-pollination Think beyond immediate consequences to anticipate cascading effects and long-term implications - essential for strategic decisions where first-order solutions create hidden downstream problems domain A + domain B → hybrid insights action → consequences → second-order effects → informed choice
32 31 research core Literature Review Personas Inversion Analysis Optimist researcher + skeptic researcher + synthesizer review sources - balanced assessment of evidence quality Flip the problem by asking what would guarantee failure instead of how to succeed - reveals hidden obstacles and blind spots by approaching challenges from the opposite direction sources → critiques → synthesis goal → invert → failure paths → avoidance → solution
33 32 research core Thesis Defense Simulation Problem Decomposition Student defends hypothesis against committee with different concerns - stress-tests research methodology and conclusions Break a complex problem into independent sub-problems - solve each - then reassemble — essential when a task is too large or tangled to tackle whole thesis → challenges → defense → refinements whole → parts → solutions → reassembly
34 33 research core Comparative Analysis Matrix Analogy Mapping Multiple analysts evaluate options against weighted criteria - structured decision-making with explicit scoring Find a well-understood parallel domain and transfer its structure to the current problem — unlocks insight by borrowing proven mental models options → criteria → scores → recommendation source domain → mapping → target insight
35 34 risk core Pre-mortem Analysis Steelmanning Imagine future failure then work backwards to prevent it - powerful technique for risk mitigation before major launches Construct the strongest possible version of an opposing argument before responding — builds credibility and catches blind spots that strawmanning misses failure scenario → causes → prevention opposing view → strongest form → honest rebuttal
36 35 risk creative Failure Mode Analysis SCAMPER Method Systematically explore how each component could fail - critical for reliability engineering and safety-critical systems Apply seven creativity lenses (Substitute/Combine/Adapt/Modify/Put/Eliminate/Reverse) - systematic ideation for product innovation components → failures → prevention S→C→A→M→P→E→R
37 36 risk creative Challenge from Critical Perspective Reverse Engineering Play devil's advocate to stress-test ideas and find weaknesses - essential for overcoming groupthink Work backwards from desired outcome to find implementation path - powerful for goal achievement and understanding endpoints assumptions → challenges → strengthening end state → steps backward → path forward
38 37 risk creative Identify Potential Risks What If Scenarios Brainstorm what could go wrong across all categories - fundamental for project planning and deployment preparation Explore alternative realities to understand possibilities and implications - valuable for contingency planning and exploration categories → risks → mitigations scenarios → implications → insights
39 38 risk creative Chaos Monkey Scenarios Random Input Stimulus Deliberately break things to test resilience and recovery - ensures systems handle failures gracefully Inject unrelated concepts to spark unexpected connections - breaks creative blocks through forced lateral thinking break → observe → harden random word → associations → novel ideas
40 39 core creative First Principles Analysis Exquisite Corpse Brainstorm Strip away assumptions to rebuild from fundamental truths - breakthrough technique for innovation and solving impossible problems Each persona adds to the idea seeing only the previous contribution - generates surprising combinations through constrained collaboration assumptions → truths → new approach contribution → handoff → contribution → surprise
41 40 core creative 5 Whys Deep Dive Genre Mashup Repeatedly ask why to drill down to root causes - simple but powerful for understanding failures Combine two unrelated domains to find fresh approaches - innovation through unexpected cross-pollination why chain → root cause → solution domain A + domain B → hybrid insights
42 41 core creative Socratic Questioning Constraint Injection Use targeted questions to reveal hidden assumptions and guide discovery - excellent for teaching and self-discovery Deliberately add an artificial limitation (budget - time - technology) to force novel solutions — creativity thrives under pressure questions → revelations → understanding add constraint → forced creativity → remove constraint → evaluate
43 42 core creative Critique and Refine Morphological Analysis Systematic review to identify strengths and weaknesses then improve - standard quality check for drafts List independent parameters of a problem - enumerate options for each - then systematically combine — ensures you don't miss non-obvious configurations strengths/weaknesses → improvements → refined parameters → options grid → combinations → evaluation
44 43 core framing Explain Reasoning Abstraction Laddering Walk through step-by-step thinking to show how conclusions were reached - crucial for transparency Move up ("why?") for strategic clarity or down ("how?") for tactical detail — ensures you're solving at the right altitude steps → logic → conclusion concrete ↔ abstract → right level
45 44 core framing Expand or Contract for Audience Reframe the Question Dynamically adjust detail level and technical depth for target audience - matches content to reader capabilities Challenge whether the stated problem is the real problem — often the question itself is wrong and a better framing unlocks an easy answer audience → adjustments → refined content stated problem → reframe → true problem → solution
46 45 learning framing Feynman Technique Stakeholder Lens Rotation Explain complex concepts simply as if teaching a child - the ultimate test of true understanding Serially adopt each stakeholder's world-view to see the same situation differently — reveals whose needs are being overlooked complex → simple → gaps → mastery perspective A → B → C → gaps found
47 46 learning Active Recall Testing Feynman Technique Test understanding without references to verify true knowledge - essential for identifying gaps Explain complex concepts simply as if teaching a child - the ultimate test of true understanding test → gaps → reinforcement complex → simple → gaps → mastery
48 47 philosophical learning Occam's Razor Application Active Recall Testing Find the simplest sufficient explanation by eliminating unnecessary complexity - essential for debugging Test understanding without references to verify true knowledge - essential for identifying gaps options → simplification → selection test → gaps → reinforcement
49 48 philosophical learning Trolley Problem Variations Deliberate Practice Loop Explore ethical trade-offs through moral dilemmas - valuable for understanding values and difficult decisions Identify a specific sub-skill - drill it with immediate feedback - adjust - repeat — targeted improvement beats general repetition dilemma → analysis → decision isolate → drill → feedback → adjust → repeat
50 49 retrospective philosophical Hindsight Reflection Occam's Razor Application Imagine looking back from the future to gain perspective - powerful for project reviews Find the simplest sufficient explanation by eliminating unnecessary complexity - essential for debugging future view → insights → application options → simplification → selection
51 50 retrospective philosophical Lessons Learned Extraction Trolley Problem Variations Systematically identify key takeaways and actionable improvements - essential for continuous improvement Explore ethical trade-offs through moral dilemmas - valuable for understanding values and difficult decisions experience → lessons → actions dilemma → analysis → decision
52 51 research Literature Review Personas Optimist researcher + skeptic researcher + synthesizer review sources - balanced assessment of evidence quality sources → critiques → synthesis
53 52 research Thesis Defense Simulation Student defends hypothesis against committee with different concerns - stress-tests research methodology and conclusions thesis → challenges → defense → refinements
54 53 research Comparative Analysis Matrix Multiple analysts evaluate options against weighted criteria - structured decision-making with explicit scoring options → criteria → scores → recommendation
55 54 research Source Triangulation Require at least three independent source types (quantitative - qualitative - expert) before accepting a claim — guards against single-source bias claim → source A → source B → source C → confidence rating
56 55 retrospective Hindsight Reflection Imagine looking back from the future to gain perspective - powerful for project reviews future view → insights → application
57 56 retrospective Lessons Learned Extraction Systematically identify key takeaways and actionable improvements - essential for continuous improvement experience → lessons → actions
58 57 risk Pre-mortem Analysis Imagine future failure then work backwards to prevent it - powerful technique for risk mitigation before major launches failure scenario → causes → prevention
59 58 risk Failure Mode Analysis Systematically explore how each component could fail - critical for reliability engineering and safety-critical systems components → failures → prevention
60 59 risk Challenge from Critical Perspective Play devil's advocate to stress-test ideas and find weaknesses - essential for overcoming groupthink assumptions → challenges → strengthening
61 60 risk Identify Potential Risks Brainstorm what could go wrong across all categories - fundamental for project planning and deployment preparation categories → risks → mitigations
62 61 risk Chaos Monkey Scenarios Deliberately break things to test resilience and recovery - ensures systems handle failures gracefully break → observe → harden
63 62 risk Assumption Audit Explicitly list every assumption underlying a plan - rate each by confidence and impact - then stress-test the weakest — prevents building on shaky foundations list → rate → stress-test → shore up
64 63 risk Cascading Failure Simulation Trace how one component's failure propagates through dependencies — reveals hidden coupling and single points of failure trigger failure → trace propagation → find amplifiers → decouple
65 64 technical Architecture Decision Records Multiple architect personas propose and debate architectural choices with explicit trade-offs - ensures decisions are well-reasoned and documented options → trade-offs → decision → rationale
66 65 technical Rubber Duck Debugging Evolved Explain your code to progressively more technical ducks until you find the bug - forces clarity at multiple abstraction levels simple → detailed → technical → aha
67 66 technical Algorithm Olympics Multiple approaches compete on the same problem with benchmarks - finds optimal solution through direct comparison implementations → benchmarks → winner
68 67 technical Security Audit Personas Hacker + defender + auditor examine system from different threat models - comprehensive security review from multiple angles vulnerabilities → defenses → compliance
69 68 technical Performance Profiler Panel Database expert + frontend specialist + DevOps engineer diagnose slowness - finds bottlenecks across the full stack symptoms → analysis → optimizations
70 69 technical Boundary & Edge Case Sweep Systematically test extremes - zeros - nulls - maximums - and type mismatches — catches the failures that happy-path thinking always misses inputs → boundaries → edge cases → failures found

View File

@ -22,6 +22,7 @@ module.exports = {
['--communication-language <lang>', 'Language for agent communication (default: English)'],
['--document-output-language <lang>', 'Language for document output (default: English)'],
['--output-folder <path>', 'Output folder path relative to project root (default: _bmad-output)'],
['--custom-source <sources>', 'Comma-separated Git URLs or local paths to install custom modules from'],
['-y, --yes', 'Accept all defaults and skip prompts where possible'],
],
action: async (options) => {

View File

@ -569,6 +569,7 @@ class Installer {
*/
async _installOfficialModules(config, paths, officialModuleIds, addResult, isQuickUpdate, officialModules, ctx) {
const { message, installedModuleNames } = ctx;
const { CustomModuleManager } = require('../modules/custom-module-manager');
for (const moduleName of officialModuleIds) {
if (installedModuleNames.has(moduleName)) continue;
@ -591,11 +592,15 @@ class Installer {
},
);
// Get display name from source module.yaml; version from marketplace.json
// Get display name from source module.yaml; version from resolution cache or marketplace.json
const sourcePath = await officialModules.findModuleSource(moduleName, { silent: true });
const moduleInfo = sourcePath ? await officialModules.getModuleInfo(sourcePath, moduleName, '') : null;
const displayName = moduleInfo?.name || moduleName;
const version = sourcePath ? await this._getMarketplaceVersion(sourcePath) : '';
// Prefer version from resolution cache (accurate for custom/local modules),
// fall back to marketplace.json walk-up for official modules
const cachedResolution = CustomModuleManager._resolutionCache.get(moduleName);
const version = cachedResolution?.version || (sourcePath ? await this._getMarketplaceVersion(sourcePath) : '');
addResult(displayName, 'ok', '', { moduleCode: moduleName, newVersion: version });
}
}
@ -1189,7 +1194,7 @@ class Installer {
const customMgr = new CustomModuleManager();
for (const moduleId of installedModules) {
if (!availableModules.some((m) => m.id === moduleId)) {
const customSource = await customMgr.findModuleSourceByCode(moduleId);
const customSource = await customMgr.findModuleSourceByCode(moduleId, { bmadDir });
if (customSource) {
availableModules.push({
id: moduleId,

View File

@ -412,7 +412,7 @@ class ManifestGenerator {
// Get existing install date if available
const existing = existingModulesMap.get(moduleName);
updatedModules.push({
const moduleEntry = {
name: moduleName,
version: versionInfo.version,
installDate: existing?.installDate || new Date().toISOString(),
@ -420,7 +420,9 @@ class ManifestGenerator {
source: versionInfo.source,
npmPackage: versionInfo.npmPackage,
repoUrl: versionInfo.repoUrl,
});
};
if (versionInfo.localPath) moduleEntry.localPath = versionInfo.localPath;
updatedModules.push(moduleEntry);
}
const manifest = {

View File

@ -181,10 +181,10 @@ class Manifest {
// Handle adding a new module with version info
if (updates.addModule) {
const { name, version, source, npmPackage, repoUrl } = updates.addModule;
const { name, version, source, npmPackage, repoUrl, localPath } = updates.addModule;
const existing = manifest.modules.find((m) => m.name === name);
if (!existing) {
manifest.modules.push({
const entry = {
name,
version: version || null,
installDate: new Date().toISOString(),
@ -192,7 +192,9 @@ class Manifest {
source: source || 'external',
npmPackage: npmPackage || null,
repoUrl: repoUrl || null,
});
};
if (localPath) entry.localPath = localPath;
manifest.modules.push(entry);
}
}
@ -280,7 +282,7 @@ class Manifest {
if (existingIndex === -1) {
// Module doesn't exist, add it
manifest.modules.push({
const entry = {
name: moduleName,
version: options.version || null,
installDate: new Date().toISOString(),
@ -288,7 +290,9 @@ class Manifest {
source: options.source || 'unknown',
npmPackage: options.npmPackage || null,
repoUrl: options.repoUrl || null,
});
};
if (options.localPath) entry.localPath = options.localPath;
manifest.modules.push(entry);
} else {
// Module exists, update its version info
const existing = manifest.modules[existingIndex];
@ -298,6 +302,7 @@ class Manifest {
source: options.source || existing.source,
npmPackage: options.npmPackage === undefined ? existing.npmPackage : options.npmPackage,
repoUrl: options.repoUrl === undefined ? existing.repoUrl : options.repoUrl,
localPath: options.localPath === undefined ? existing.localPath : options.localPath,
lastUpdated: new Date().toISOString(),
};
}
@ -832,17 +837,19 @@ class Manifest {
};
}
// Check if this is a custom module (from user-provided URL)
// Check if this is a custom module (from user-provided URL or local path)
const { CustomModuleManager } = require('../modules/custom-module-manager');
const customMgr = new CustomModuleManager();
const customSource = await customMgr.findModuleSourceByCode(moduleName);
if (customSource) {
const customVersion = await this._readMarketplaceVersion(moduleName, moduleSourcePath);
const resolved = customMgr.getResolution(moduleName);
const customSource = await customMgr.findModuleSourceByCode(moduleName, { bmadDir });
if (customSource || resolved) {
const customVersion = resolved?.version || (await this._readMarketplaceVersion(moduleName, moduleSourcePath));
return {
version: customVersion,
source: 'custom',
npmPackage: null,
repoUrl: null,
repoUrl: resolved?.repoUrl || null,
localPath: resolved?.localPath || null,
};
}

View File

@ -3,22 +3,161 @@ const os = require('node:os');
const path = require('node:path');
const { execSync } = require('node:child_process');
const prompts = require('../prompts');
const { RegistryClient } = require('./registry-client');
/**
* Manages custom modules installed from user-provided GitHub URLs.
* Validates URLs, fetches .claude-plugin/marketplace.json, clones repos.
* Manages custom modules installed from user-provided sources.
* Supports any Git host (GitHub, GitLab, Bitbucket, self-hosted) and local file paths.
* Validates input, clones repos, reads .claude-plugin/marketplace.json, resolves plugins.
*/
class CustomModuleManager {
constructor() {
this._client = new RegistryClient();
}
/** @type {Map<string, Object>} Shared across all instances: module code -> ResolvedModule */
static _resolutionCache = new Map();
// ─── URL Validation ───────────────────────────────────────────────────────
// ─── Source Parsing ───────────────────────────────────────────────────────
/**
* Parse a user-provided source input into a structured descriptor.
* Accepts local file paths, HTTPS Git URLs, and SSH Git URLs.
* For HTTPS URLs with deep paths (e.g., /tree/main/subdir), extracts the subdir.
*
* @param {string} input - URL or local file path
* @returns {Object} Parsed source descriptor:
* { type: 'url'|'local', cloneUrl, subdir, localPath, cacheKey, displayName, isValid, error }
*/
parseSource(input) {
if (!input || typeof input !== 'string') {
return {
type: null,
cloneUrl: null,
subdir: null,
localPath: null,
cacheKey: null,
displayName: null,
isValid: false,
error: 'Source is required',
};
}
const trimmed = input.trim();
if (!trimmed) {
return {
type: null,
cloneUrl: null,
subdir: null,
localPath: null,
cacheKey: null,
displayName: null,
isValid: false,
error: 'Source is required',
};
}
// Local path detection: starts with /, ./, ../, or ~
if (trimmed.startsWith('/') || trimmed.startsWith('./') || trimmed.startsWith('../') || trimmed.startsWith('~')) {
return this._parseLocalPath(trimmed);
}
// SSH URL: git@host:owner/repo.git
const sshMatch = trimmed.match(/^git@([^:]+):([^/]+)\/([^/.]+?)(?:\.git)?$/);
if (sshMatch) {
const [, host, owner, repo] = sshMatch;
return {
type: 'url',
cloneUrl: trimmed,
subdir: null,
localPath: null,
cacheKey: `${host}/${owner}/${repo}`,
displayName: `${owner}/${repo}`,
isValid: true,
error: null,
};
}
// HTTPS URL: https://host/owner/repo[/tree/branch/subdir][.git]
const httpsMatch = trimmed.match(/^https?:\/\/([^/]+)\/([^/]+)\/([^/.]+?)(?:\.git)?(\/.*)?$/);
if (httpsMatch) {
const [, host, owner, repo, remainder] = httpsMatch;
const cloneUrl = `https://${host}/${owner}/${repo}`;
let subdir = null;
if (remainder) {
// Extract subdir from deep path patterns used by various Git hosts
const deepPathPatterns = [
/^\/(?:-\/)?tree\/[^/]+\/(.+)$/, // GitHub /tree/branch/path, GitLab /-/tree/branch/path
/^\/(?:-\/)?blob\/[^/]+\/(.+)$/, // /blob/branch/path (treat same as tree)
/^\/src\/[^/]+\/(.+)$/, // Gitea/Forgejo /src/branch/path
];
for (const pattern of deepPathPatterns) {
const match = remainder.match(pattern);
if (match) {
subdir = match[1].replace(/\/$/, ''); // strip trailing slash
break;
}
}
}
return {
type: 'url',
cloneUrl,
subdir,
localPath: null,
cacheKey: `${host}/${owner}/${repo}`,
displayName: `${owner}/${repo}`,
isValid: true,
error: null,
};
}
return {
type: null,
cloneUrl: null,
subdir: null,
localPath: null,
cacheKey: null,
displayName: null,
isValid: false,
error: 'Not a valid Git URL or local path',
};
}
/**
* Parse a local filesystem path.
* @param {string} rawPath - Path string (may contain ~ for home)
* @returns {Object} Parsed source descriptor
*/
_parseLocalPath(rawPath) {
const expanded = rawPath.startsWith('~') ? path.join(os.homedir(), rawPath.slice(1)) : rawPath;
const resolved = path.resolve(expanded);
if (!fs.pathExistsSync(resolved)) {
return {
type: 'local',
cloneUrl: null,
subdir: null,
localPath: resolved,
cacheKey: null,
displayName: path.basename(resolved),
isValid: false,
error: `Path does not exist: ${resolved}`,
};
}
return {
type: 'local',
cloneUrl: null,
subdir: null,
localPath: resolved,
cacheKey: null,
displayName: path.basename(resolved),
isValid: true,
error: null,
};
}
/**
* @deprecated Use parseSource() instead. Kept for backward compatibility.
* Parse and validate a GitHub repository URL.
* Supports HTTPS and SSH formats.
* @param {string} url - GitHub URL to validate
* @returns {Object} { owner, repo, isValid, error }
*/
@ -26,16 +165,15 @@ class CustomModuleManager {
if (!url || typeof url !== 'string') {
return { owner: null, repo: null, isValid: false, error: 'URL is required' };
}
const trimmed = url.trim();
// HTTPS format: https://github.com/owner/repo[.git]
// HTTPS format: https://github.com/owner/repo[.git] (strict, no trailing path)
const httpsMatch = trimmed.match(/^https?:\/\/github\.com\/([^/]+)\/([^/.]+?)(?:\.git)?$/);
if (httpsMatch) {
return { owner: httpsMatch[1], repo: httpsMatch[2], isValid: true, error: null };
}
// SSH format: git@github.com:owner/repo.git
// SSH format: git@github.com:owner/repo[.git]
const sshMatch = trimmed.match(/^git@github\.com:([^/]+)\/([^/.]+?)(?:\.git)?$/);
if (sshMatch) {
return { owner: sshMatch[1], repo: sshMatch[2], isValid: true, error: null };
@ -44,46 +182,75 @@ class CustomModuleManager {
return { owner: null, repo: null, isValid: false, error: 'Not a valid GitHub URL (expected https://github.com/owner/repo)' };
}
// ─── Discovery ────────────────────────────────────────────────────────────
// ─── Marketplace JSON ─────────────────────────────────────────────────────
/**
* Fetch .claude-plugin/marketplace.json from a GitHub repository.
* @param {string} repoUrl - GitHub repository URL
* @returns {Object} Parsed marketplace.json content
* Read .claude-plugin/marketplace.json from a local directory.
* @param {string} dirPath - Directory to read from
* @returns {Object|null} Parsed marketplace.json or null if not found
*/
async fetchMarketplaceJson(repoUrl) {
const { owner, repo, isValid, error } = this.validateGitHubUrl(repoUrl);
if (!isValid) throw new Error(error);
const rawUrl = `https://raw.githubusercontent.com/${owner}/${repo}/HEAD/.claude-plugin/marketplace.json`;
async readMarketplaceJsonFromDisk(dirPath) {
const marketplacePath = path.join(dirPath, '.claude-plugin', 'marketplace.json');
if (!(await fs.pathExists(marketplacePath))) return null;
try {
return await this._client.fetchJson(rawUrl);
} catch (error_) {
if (error_.message.includes('404')) {
throw new Error(`No .claude-plugin/marketplace.json found in ${owner}/${repo}. This repository may not be a BMad module.`);
}
if (error_.message.includes('403')) {
throw new Error(`Repository ${owner}/${repo} is not accessible. Make sure it is public.`);
}
throw new Error(`Failed to fetch marketplace.json from ${owner}/${repo}: ${error_.message}`);
return JSON.parse(await fs.readFile(marketplacePath, 'utf8'));
} catch {
return null;
}
}
// ─── Discovery ────────────────────────────────────────────────────────────
/**
* Discover modules from a GitHub repository's marketplace.json.
* @param {string} repoUrl - GitHub repository URL
* Discover modules from pre-read marketplace.json data.
* @param {Object} marketplaceData - Parsed marketplace.json content
* @param {string|null} sourceUrl - Source URL for tracking (null for local paths)
* @returns {Array<Object>} Normalized plugin list
*/
async discoverModules(repoUrl) {
const data = await this.fetchMarketplaceJson(repoUrl);
const plugins = data?.plugins;
async discoverModules(marketplaceData, sourceUrl) {
const plugins = marketplaceData?.plugins;
if (!Array.isArray(plugins) || plugins.length === 0) {
throw new Error('marketplace.json contains no plugins');
}
return plugins.map((plugin) => this._normalizeCustomModule(plugin, repoUrl, data));
return plugins.map((plugin) => this._normalizeCustomModule(plugin, sourceUrl, marketplaceData));
}
// ─── Source Resolution ────────────────────────────────────────────────────
/**
* High-level coordinator: parse input, clone if URL, determine discovery vs direct mode.
* @param {string} input - URL or local path
* @param {Object} [options] - Options passed to cloneRepo
* @returns {Object} { parsed, rootDir, repoPath, sourceUrl, marketplace, mode: 'discovery'|'direct' }
*/
async resolveSource(input, options = {}) {
const parsed = this.parseSource(input);
if (!parsed.isValid) throw new Error(parsed.error);
let rootDir;
let repoPath;
let sourceUrl;
if (parsed.type === 'local') {
rootDir = parsed.localPath;
repoPath = null;
sourceUrl = null;
} else {
repoPath = await this.cloneRepo(input, options);
sourceUrl = parsed.cloneUrl;
rootDir = parsed.subdir ? path.join(repoPath, parsed.subdir) : repoPath;
if (parsed.subdir && !(await fs.pathExists(rootDir))) {
throw new Error(`Subdirectory '${parsed.subdir}' not found in cloned repository`);
}
}
const marketplace = await this.readMarketplaceJsonFromDisk(rootDir);
const mode = marketplace ? 'discovery' : 'direct';
return { parsed, rootDir, repoPath, sourceUrl, marketplace, mode };
}
// ─── Clone ────────────────────────────────────────────────────────────────
@ -98,20 +265,24 @@ class CustomModuleManager {
/**
* Clone a custom module repository to cache.
* @param {string} repoUrl - GitHub repository URL
* Supports any Git host (GitHub, GitLab, Bitbucket, self-hosted, etc.).
* @param {string} sourceInput - Git URL (HTTPS or SSH)
* @param {Object} [options] - Clone options
* @param {boolean} [options.silent] - Suppress spinner output
* @param {boolean} [options.skipInstall] - Skip npm install (for browsing before user confirms)
* @returns {string} Path to the cloned repository
*/
async cloneRepo(repoUrl, options = {}) {
const { owner, repo, isValid, error } = this.validateGitHubUrl(repoUrl);
if (!isValid) throw new Error(error);
async cloneRepo(sourceInput, options = {}) {
const parsed = this.parseSource(sourceInput);
if (!parsed.isValid) throw new Error(parsed.error);
if (parsed.type === 'local') throw new Error('cloneRepo does not accept local paths');
const cacheDir = this.getCacheDir();
const repoCacheDir = path.join(cacheDir, owner, repo);
const repoCacheDir = path.join(cacheDir, ...parsed.cacheKey.split('/'));
const silent = options.silent || false;
const displayName = parsed.displayName;
await fs.ensureDir(path.join(cacheDir, owner));
await fs.ensureDir(path.dirname(repoCacheDir));
const createSpinner = async () => {
if (silent) {
@ -123,7 +294,7 @@ class CustomModuleManager {
if (await fs.pathExists(repoCacheDir)) {
// Update existing clone
const fetchSpinner = await createSpinner();
fetchSpinner.start(`Updating ${owner}/${repo}...`);
fetchSpinner.start(`Updating ${displayName}...`);
try {
execSync('git fetch origin --depth 1', {
cwd: repoCacheDir,
@ -134,42 +305,51 @@ class CustomModuleManager {
cwd: repoCacheDir,
stdio: ['ignore', 'pipe', 'pipe'],
});
fetchSpinner.stop(`Updated ${owner}/${repo}`);
fetchSpinner.stop(`Updated ${displayName}`);
} catch {
fetchSpinner.error(`Update failed, re-downloading ${owner}/${repo}`);
fetchSpinner.error(`Update failed, re-downloading ${displayName}`);
await fs.remove(repoCacheDir);
}
}
if (!(await fs.pathExists(repoCacheDir))) {
const fetchSpinner = await createSpinner();
fetchSpinner.start(`Cloning ${owner}/${repo}...`);
fetchSpinner.start(`Cloning ${displayName}...`);
try {
execSync(`git clone --depth 1 "${repoUrl}" "${repoCacheDir}"`, {
execSync(`git clone --depth 1 "${parsed.cloneUrl}" "${repoCacheDir}"`, {
stdio: ['ignore', 'pipe', 'pipe'],
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
});
fetchSpinner.stop(`Cloned ${owner}/${repo}`);
fetchSpinner.stop(`Cloned ${displayName}`);
} catch (error_) {
fetchSpinner.error(`Failed to clone ${owner}/${repo}`);
throw new Error(`Failed to clone ${repoUrl}: ${error_.message}`);
fetchSpinner.error(`Failed to clone ${displayName}`);
throw new Error(`Failed to clone ${parsed.cloneUrl}: ${error_.message}`);
}
}
// Install dependencies if package.json exists
// Write source metadata for later URL reconstruction
const metadataPath = path.join(repoCacheDir, '.bmad-source.json');
await fs.writeJson(metadataPath, {
cloneUrl: parsed.cloneUrl,
cacheKey: parsed.cacheKey,
displayName: parsed.displayName,
clonedAt: new Date().toISOString(),
});
// Install dependencies if package.json exists (skip during browsing/analysis)
const packageJsonPath = path.join(repoCacheDir, 'package.json');
if (await fs.pathExists(packageJsonPath)) {
if (!options.skipInstall && (await fs.pathExists(packageJsonPath))) {
const installSpinner = await createSpinner();
installSpinner.start(`Installing dependencies for ${owner}/${repo}...`);
installSpinner.start(`Installing dependencies for ${displayName}...`);
try {
execSync('npm install --omit=dev --no-audit --no-fund --no-progress --legacy-peer-deps', {
cwd: repoCacheDir,
stdio: ['ignore', 'pipe', 'pipe'],
timeout: 120_000,
});
installSpinner.stop(`Installed dependencies for ${owner}/${repo}`);
installSpinner.stop(`Installed dependencies for ${displayName}`);
} catch (error_) {
installSpinner.error(`Failed to install dependencies for ${owner}/${repo}`);
installSpinner.error(`Failed to install dependencies for ${displayName}`);
if (!silent) await prompts.log.warn(` ${error_.message}`);
}
}
@ -177,23 +357,65 @@ class CustomModuleManager {
return repoCacheDir;
}
// ─── Plugin Resolution ────────────────────────────────────────────────────
/**
* Resolve a plugin to determine installation strategy and module registration files.
* Results are cached in _resolutionCache keyed by module code.
* @param {string} repoPath - Absolute path to the cloned repository or local directory
* @param {Object} plugin - Raw plugin object from marketplace.json
* @param {string} [sourceUrl] - Original URL for manifest tracking (null for local)
* @param {string} [localPath] - Local source path for manifest tracking (null for URLs)
* @returns {Promise<Array<Object>>} Array of ResolvedModule objects
*/
async resolvePlugin(repoPath, plugin, sourceUrl, localPath) {
const { PluginResolver } = require('./plugin-resolver');
const resolver = new PluginResolver();
const resolved = await resolver.resolve(repoPath, plugin);
// Stamp source info onto each resolved module for manifest tracking
for (const mod of resolved) {
if (sourceUrl) mod.repoUrl = sourceUrl;
if (localPath) mod.localPath = localPath;
CustomModuleManager._resolutionCache.set(mod.code, mod);
}
return resolved;
}
/**
* Get a cached resolution result by module code.
* @param {string} moduleCode - Module code to look up
* @returns {Object|null} ResolvedModule or null if not cached
*/
getResolution(moduleCode) {
return CustomModuleManager._resolutionCache.get(moduleCode) || null;
}
// ─── Source Finding ───────────────────────────────────────────────────────
/**
* Find the module source path within a cloned custom repo.
* @param {string} repoUrl - GitHub repository URL (for cache location)
* Find the module source path within a cached or local source directory.
* @param {string} sourceInput - Git URL or local path (used to locate cached clone)
* @param {string} [pluginSource] - Plugin source path from marketplace.json
* @returns {string|null} Path to directory containing module.yaml
*/
async findModuleSource(repoUrl, pluginSource) {
const { owner, repo } = this.validateGitHubUrl(repoUrl);
const repoCacheDir = path.join(this.getCacheDir(), owner, repo);
async findModuleSource(sourceInput, pluginSource) {
const parsed = this.parseSource(sourceInput);
if (!parsed.isValid) return null;
if (!(await fs.pathExists(repoCacheDir))) return null;
let baseDir;
if (parsed.type === 'local') {
baseDir = parsed.localPath;
} else {
baseDir = path.join(this.getCacheDir(), ...parsed.cacheKey.split('/'));
}
if (!(await fs.pathExists(baseDir))) return null;
// Try plugin source path first (e.g., "./src/pro-skills")
if (pluginSource) {
const sourcePath = path.join(repoCacheDir, pluginSource);
const sourcePath = path.join(baseDir, pluginSource);
const moduleYaml = path.join(sourcePath, 'module.yaml');
if (await fs.pathExists(moduleYaml)) {
return sourcePath;
@ -202,11 +424,11 @@ class CustomModuleManager {
// Fallback: search skills/ and src/ directories
for (const dir of ['skills', 'src']) {
const rootCandidate = path.join(repoCacheDir, dir, 'module.yaml');
const rootCandidate = path.join(baseDir, dir, 'module.yaml');
if (await fs.pathExists(rootCandidate)) {
return path.dirname(rootCandidate);
}
const dirPath = path.join(repoCacheDir, dir);
const dirPath = path.join(baseDir, dir);
if (await fs.pathExists(dirPath)) {
const entries = await fs.readdir(dirPath, { withFileTypes: true });
for (const entry of entries) {
@ -220,10 +442,10 @@ class CustomModuleManager {
}
}
// Check repo root
const rootCandidate = path.join(repoCacheDir, 'module.yaml');
// Check base directory root
const rootCandidate = path.join(baseDir, 'module.yaml');
if (await fs.pathExists(rootCandidate)) {
return repoCacheDir;
return baseDir;
}
return null;
@ -231,51 +453,163 @@ class CustomModuleManager {
/**
* Find module source by module code, searching the custom cache.
* Handles both new 3-level cache structure (host/owner/repo) and
* legacy 2-level structure (owner/repo).
* @param {string} moduleCode - Module code to search for
* @param {Object} [options] - Options
* @returns {string|null} Path to the module source or null
*/
async findModuleSourceByCode(moduleCode, options = {}) {
// Check resolution cache first (populated by resolvePlugin)
const resolved = CustomModuleManager._resolutionCache.get(moduleCode);
if (resolved) {
// For strategies 1-2: the common parent or setup skill's parent has the module files
if (resolved.moduleYamlPath) {
return path.dirname(resolved.moduleYamlPath);
}
// For strategy 5 (synthesized): return the first skill's parent as a reference path
if (resolved.skillPaths && resolved.skillPaths.length > 0) {
return path.dirname(resolved.skillPaths[0]);
}
}
const cacheDir = this.getCacheDir();
if (!(await fs.pathExists(cacheDir))) return null;
// Search through all custom repo caches
// Search through all cached repo roots
try {
const owners = await fs.readdir(cacheDir, { withFileTypes: true });
for (const ownerEntry of owners) {
if (!ownerEntry.isDirectory()) continue;
const ownerPath = path.join(cacheDir, ownerEntry.name);
const repos = await fs.readdir(ownerPath, { withFileTypes: true });
for (const repoEntry of repos) {
if (!repoEntry.isDirectory()) continue;
const repoPath = path.join(ownerPath, repoEntry.name);
const { PluginResolver } = require('./plugin-resolver');
const resolver = new PluginResolver();
const repoRoots = await this._findCacheRepoRoots(cacheDir);
// Check marketplace.json for matching module code
const marketplacePath = path.join(repoPath, '.claude-plugin', 'marketplace.json');
if (await fs.pathExists(marketplacePath)) {
try {
const data = JSON.parse(await fs.readFile(marketplacePath, 'utf8'));
for (const plugin of data.plugins || []) {
if (plugin.name === moduleCode) {
// Found the module - find its source
const sourcePath = plugin.source ? path.join(repoPath, plugin.source) : repoPath;
const moduleYaml = path.join(sourcePath, 'module.yaml');
if (await fs.pathExists(moduleYaml)) {
return sourcePath;
for (const { repoPath, metadata } of repoRoots) {
// Check marketplace.json for matching module code
const marketplacePath = path.join(repoPath, '.claude-plugin', 'marketplace.json');
if (!(await fs.pathExists(marketplacePath))) continue;
try {
const data = JSON.parse(await fs.readFile(marketplacePath, 'utf8'));
for (const plugin of data.plugins || []) {
// Direct name match (legacy behavior)
if (plugin.name === moduleCode) {
const sourcePath = plugin.source ? path.join(repoPath, plugin.source) : repoPath;
const moduleYaml = path.join(sourcePath, 'module.yaml');
if (await fs.pathExists(moduleYaml)) {
return sourcePath;
}
}
// Resolve plugin to check if any module.yaml code matches
if (plugin.skills && plugin.skills.length > 0) {
try {
const resolvedMods = await resolver.resolve(repoPath, plugin);
for (const mod of resolvedMods) {
if (mod.code === moduleCode) {
// Use metadata for URL reconstruction instead of deriving from path
mod.repoUrl = metadata?.cloneUrl || null;
CustomModuleManager._resolutionCache.set(mod.code, mod);
if (mod.moduleYamlPath) {
return path.dirname(mod.moduleYamlPath);
}
if (mod.skillPaths && mod.skillPaths.length > 0) {
return path.dirname(mod.skillPaths[0]);
}
}
}
} catch {
// Skip unresolvable plugins
}
} catch {
// Skip malformed marketplace.json
}
}
} catch {
// Skip malformed marketplace.json
}
}
} catch {
// Cache doesn't exist or is inaccessible
}
return null;
// Fallback: check manifest for localPath (local-source modules not in cache)
return this._findLocalSourceFromManifest(moduleCode, options);
}
/**
* Check the installation manifest for a localPath entry for this module.
* Used as fallback when the module was installed from a local source (no cache entry).
* Returns the path only if it still exists on disk; never removes installed files.
* @param {string} moduleCode - Module code to search for
* @param {Object} [options] - Options (must include bmadDir or will search common locations)
* @returns {string|null} Path to the local module source or null
*/
async _findLocalSourceFromManifest(moduleCode, options = {}) {
try {
const { Manifest } = require('../core/manifest');
const manifestObj = new Manifest();
// Try to find bmadDir from options or common locations
const bmadDir = options.bmadDir;
if (!bmadDir) return null;
const manifestData = await manifestObj.read(bmadDir);
if (!manifestData?.modulesDetailed) return null;
const moduleEntry = manifestData.modulesDetailed.find((m) => m.name === moduleCode);
if (!moduleEntry?.localPath) return null;
// Only return the path if it still exists (source not removed)
if (await fs.pathExists(moduleEntry.localPath)) {
return moduleEntry.localPath;
}
return null;
} catch {
return null;
}
}
/**
* Recursively find repo root directories within the cache.
* A repo root is identified by containing .bmad-source.json (new) or .claude-plugin/ (legacy).
* Handles both 3-level (host/owner/repo) and legacy 2-level (owner/repo) cache layouts.
* @param {string} dir - Directory to search
* @param {number} [depth=0] - Current recursion depth
* @param {number} [maxDepth=4] - Maximum recursion depth
* @returns {Promise<Array<{repoPath: string, metadata: Object|null}>>}
*/
async _findCacheRepoRoots(dir, depth = 0, maxDepth = 4) {
const results = [];
// Check if this directory is a repo root
const metadataPath = path.join(dir, '.bmad-source.json');
const claudePluginDir = path.join(dir, '.claude-plugin');
if (await fs.pathExists(metadataPath)) {
try {
const metadata = JSON.parse(await fs.readFile(metadataPath, 'utf8'));
results.push({ repoPath: dir, metadata });
} catch {
results.push({ repoPath: dir, metadata: null });
}
return results; // Don't recurse into repo contents
}
if (await fs.pathExists(claudePluginDir)) {
results.push({ repoPath: dir, metadata: null });
return results;
}
// Recurse into subdirectories
if (depth >= maxDepth) return results;
try {
const entries = await fs.readdir(dir, { withFileTypes: true });
for (const entry of entries) {
if (!entry.isDirectory() || entry.name.startsWith('.')) continue;
const subResults = await this._findCacheRepoRoots(path.join(dir, entry.name), depth + 1, maxDepth);
results.push(...subResults);
}
} catch {
// Directory not readable
}
return results;
}
// ─── Normalization ────────────────────────────────────────────────────────
@ -283,11 +617,11 @@ class CustomModuleManager {
/**
* Normalize a plugin from marketplace.json to a consistent shape.
* @param {Object} plugin - Plugin object from marketplace.json
* @param {string} repoUrl - Source repository URL
* @param {string|null} sourceUrl - Source URL (null for local paths)
* @param {Object} data - Full marketplace.json data
* @returns {Object} Normalized module info
*/
_normalizeCustomModule(plugin, repoUrl, data) {
_normalizeCustomModule(plugin, sourceUrl, data) {
return {
code: plugin.name,
name: plugin.name,
@ -295,8 +629,10 @@ class CustomModuleManager {
description: plugin.description || '',
version: plugin.version || null,
author: plugin.author || data.owner || '',
url: repoUrl,
url: sourceUrl || null,
source: plugin.source || null,
skills: plugin.skills || [],
rawPlugin: plugin,
type: 'custom',
trustTier: 'unverified',
builtIn: false,

View File

@ -135,6 +135,22 @@ class OfficialModules {
const moduleConfigPath = path.join(modulePath, 'module.yaml');
if (!(await fs.pathExists(moduleConfigPath))) {
// Check resolution cache for strategy 5 modules (no module.yaml on disk)
const { CustomModuleManager } = require('./custom-module-manager');
const customMgr = new CustomModuleManager();
const resolved = customMgr.getResolution(defaultName);
if (resolved && resolved.synthesizedModuleYaml) {
return {
id: resolved.code,
path: modulePath,
name: resolved.name,
description: resolved.description,
version: resolved.version || '1.0.0',
source: sourceDescription,
dependencies: [],
defaultSelected: false,
};
}
return null;
}
@ -232,6 +248,14 @@ class OfficialModules {
* @param {Object} options.logger - Logger instance for output
*/
async install(moduleName, bmadDir, fileTrackingCallback = null, options = {}) {
// Check if this module has a plugin resolution (custom marketplace install)
const { CustomModuleManager } = require('./custom-module-manager');
const customMgr = new CustomModuleManager();
const resolved = customMgr.getResolution(moduleName);
if (resolved) {
return this.installFromResolution(resolved, bmadDir, fileTrackingCallback, options);
}
const sourcePath = await this.findModuleSource(moduleName, { silent: options.silent });
const targetPath = path.join(bmadDir, moduleName);
@ -265,6 +289,62 @@ class OfficialModules {
return { success: true, module: moduleName, path: targetPath, versionInfo };
}
/**
* Install a module from a PluginResolver resolution result.
* Copies specific skill directories and places module-help.csv at the target root.
* @param {Object} resolved - ResolvedModule from PluginResolver
* @param {string} bmadDir - Target bmad directory
* @param {Function} fileTrackingCallback - Optional callback to track installed files
* @param {Object} options - Installation options
*/
async installFromResolution(resolved, bmadDir, fileTrackingCallback = null, options = {}) {
const targetPath = path.join(bmadDir, resolved.code);
if (await fs.pathExists(targetPath)) {
await fs.remove(targetPath);
}
await fs.ensureDir(targetPath);
// Copy each skill directory, flattened by leaf name
for (const skillPath of resolved.skillPaths) {
const skillDirName = path.basename(skillPath);
const skillTarget = path.join(targetPath, skillDirName);
await this.copyModuleWithFiltering(skillPath, skillTarget, fileTrackingCallback, options.moduleConfig);
}
// Place module-help.csv at the module root
if (resolved.moduleHelpCsvPath) {
// Strategies 1-4: copy the existing file
const helpTarget = path.join(targetPath, 'module-help.csv');
await fs.copy(resolved.moduleHelpCsvPath, helpTarget, { overwrite: true });
if (fileTrackingCallback) fileTrackingCallback(helpTarget);
} else if (resolved.synthesizedHelpCsv) {
// Strategy 5: write synthesized content
const helpTarget = path.join(targetPath, 'module-help.csv');
await fs.writeFile(helpTarget, resolved.synthesizedHelpCsv, 'utf8');
if (fileTrackingCallback) fileTrackingCallback(helpTarget);
}
// Create directories declared in module.yaml (strategies 1-4 may have these)
if (!options.skipModuleInstaller) {
await this.createModuleDirectories(resolved.code, bmadDir, options);
}
// Update manifest
const { Manifest } = require('../core/manifest');
const manifestObj = new Manifest();
await manifestObj.addModule(bmadDir, resolved.code, {
version: resolved.version || null,
source: 'custom',
npmPackage: null,
repoUrl: resolved.repoUrl || null,
});
return { success: true, module: resolved.code, path: targetPath, versionInfo: { version: resolved.version || '' } };
}
/**
* Update an existing module
* @param {string} moduleName - Name of the module to update

View File

@ -0,0 +1,398 @@
const fs = require('fs-extra');
const path = require('node:path');
const yaml = require('yaml');
/**
* Resolves how to install a plugin from marketplace.json by analyzing
* where module.yaml and module-help.csv live relative to the listed skills.
*
* Five strategies, tried in order:
* 1. Root module files at the common parent of all skills
* 2. A -setup skill with assets/module.yaml + assets/module-help.csv
* 3. Single standalone skill with both files in its assets/
* 4. Multiple standalone skills, each with both files in assets/
* 5. Fallback: synthesize from marketplace.json + SKILL.md frontmatter
*/
class PluginResolver {
/**
* Resolve a plugin to one or more installable module definitions.
* @param {string} repoPath - Absolute path to the cloned repository root
* @param {Object} plugin - Plugin object from marketplace.json
* @param {string} plugin.name - Plugin identifier
* @param {string} [plugin.source] - Relative path from repo root
* @param {string} [plugin.version] - Semantic version
* @param {string} [plugin.description] - Plugin description
* @param {string[]} [plugin.skills] - Relative paths to skill directories
* @returns {Promise<ResolvedModule[]>} Array of resolved module definitions
*/
async resolve(repoPath, plugin) {
const skillRelPaths = plugin.skills || [];
// No skills array: legacy behavior - caller should use existing findModuleSource
if (skillRelPaths.length === 0) {
return [];
}
// Resolve skill paths to absolute, constrain to repo root, filter non-existent
const repoRoot = path.resolve(repoPath);
const skillPaths = [];
for (const rel of skillRelPaths) {
const normalized = rel.replace(/^\.\//, '');
const abs = path.resolve(repoPath, normalized);
// Guard against path traversal (.. segments, absolute paths in marketplace.json)
if (!abs.startsWith(repoRoot + path.sep) && abs !== repoRoot) {
continue;
}
if (await fs.pathExists(abs)) {
skillPaths.push(abs);
}
}
if (skillPaths.length === 0) {
return [];
}
// Try each strategy in order
const result =
(await this._tryRootModuleFiles(repoPath, plugin, skillPaths)) ||
(await this._trySetupSkill(repoPath, plugin, skillPaths)) ||
(await this._trySingleStandalone(repoPath, plugin, skillPaths)) ||
(await this._tryMultipleStandalone(repoPath, plugin, skillPaths)) ||
(await this._synthesizeFallback(repoPath, plugin, skillPaths));
return result;
}
// ─── Strategy 1: Root Module Files ──────────────────────────────────────────
/**
* Check if module.yaml + module-help.csv exist at the common parent of all skills.
*/
async _tryRootModuleFiles(repoPath, plugin, skillPaths) {
const commonParent = this._computeCommonParent(skillPaths);
const moduleYamlPath = path.join(commonParent, 'module.yaml');
const moduleHelpPath = path.join(commonParent, 'module-help.csv');
if (!(await fs.pathExists(moduleYamlPath)) || !(await fs.pathExists(moduleHelpPath))) {
return null;
}
const moduleData = await this._readModuleYaml(moduleYamlPath);
if (!moduleData) return null;
return [
{
code: moduleData.code || plugin.name,
name: moduleData.name || plugin.name,
version: plugin.version || moduleData.module_version || null,
description: moduleData.description || plugin.description || '',
strategy: 1,
pluginName: plugin.name,
moduleYamlPath,
moduleHelpCsvPath: moduleHelpPath,
skillPaths,
synthesizedModuleYaml: null,
synthesizedHelpCsv: null,
},
];
}
// ─── Strategy 2: Setup Skill ────────────────────────────────────────────────
/**
* Search for a skill ending in -setup with assets/module.yaml + assets/module-help.csv.
*/
async _trySetupSkill(repoPath, plugin, skillPaths) {
for (const skillPath of skillPaths) {
const dirName = path.basename(skillPath);
if (!dirName.endsWith('-setup')) continue;
const moduleYamlPath = path.join(skillPath, 'assets', 'module.yaml');
const moduleHelpPath = path.join(skillPath, 'assets', 'module-help.csv');
if (!(await fs.pathExists(moduleYamlPath)) || !(await fs.pathExists(moduleHelpPath))) {
continue;
}
const moduleData = await this._readModuleYaml(moduleYamlPath);
if (!moduleData) continue;
return [
{
code: moduleData.code || plugin.name,
name: moduleData.name || plugin.name,
version: plugin.version || moduleData.module_version || null,
description: moduleData.description || plugin.description || '',
strategy: 2,
pluginName: plugin.name,
moduleYamlPath,
moduleHelpCsvPath: moduleHelpPath,
skillPaths,
synthesizedModuleYaml: null,
synthesizedHelpCsv: null,
},
];
}
return null;
}
// ─── Strategy 3: Single Standalone Skill ────────────────────────────────────
/**
* One skill listed, with assets/module.yaml + assets/module-help.csv.
*/
async _trySingleStandalone(repoPath, plugin, skillPaths) {
if (skillPaths.length !== 1) return null;
const skillPath = skillPaths[0];
const moduleYamlPath = path.join(skillPath, 'assets', 'module.yaml');
const moduleHelpPath = path.join(skillPath, 'assets', 'module-help.csv');
if (!(await fs.pathExists(moduleYamlPath)) || !(await fs.pathExists(moduleHelpPath))) {
return null;
}
const moduleData = await this._readModuleYaml(moduleYamlPath);
if (!moduleData) return null;
return [
{
code: moduleData.code || plugin.name,
name: moduleData.name || plugin.name,
version: plugin.version || moduleData.module_version || null,
description: moduleData.description || plugin.description || '',
strategy: 3,
pluginName: plugin.name,
moduleYamlPath,
moduleHelpCsvPath: moduleHelpPath,
skillPaths,
synthesizedModuleYaml: null,
synthesizedHelpCsv: null,
},
];
}
// ─── Strategy 4: Multiple Standalone Skills ─────────────────────────────────
/**
* Multiple skills, each with assets/module.yaml + assets/module-help.csv.
* Each becomes its own installable module.
*/
async _tryMultipleStandalone(repoPath, plugin, skillPaths) {
if (skillPaths.length < 2) return null;
const resolved = [];
for (const skillPath of skillPaths) {
const moduleYamlPath = path.join(skillPath, 'assets', 'module.yaml');
const moduleHelpPath = path.join(skillPath, 'assets', 'module-help.csv');
if (!(await fs.pathExists(moduleYamlPath)) || !(await fs.pathExists(moduleHelpPath))) {
continue;
}
const moduleData = await this._readModuleYaml(moduleYamlPath);
if (!moduleData) continue;
resolved.push({
code: moduleData.code || path.basename(skillPath),
name: moduleData.name || path.basename(skillPath),
version: plugin.version || moduleData.module_version || null,
description: moduleData.description || '',
strategy: 4,
pluginName: plugin.name,
moduleYamlPath,
moduleHelpCsvPath: moduleHelpPath,
skillPaths: [skillPath],
synthesizedModuleYaml: null,
synthesizedHelpCsv: null,
});
}
// Only use strategy 4 if ALL skills have module files
if (resolved.length === skillPaths.length) {
return resolved;
}
// Partial match: fall through to strategy 5
return null;
}
// ─── Strategy 5: Fallback (Synthesized) ─────────────────────────────────────
/**
* No module files found anywhere. Synthesize from marketplace.json metadata
* and SKILL.md frontmatter.
*/
async _synthesizeFallback(repoPath, plugin, skillPaths) {
const skillInfos = [];
for (const skillPath of skillPaths) {
const frontmatter = await this._parseSkillFrontmatter(skillPath);
skillInfos.push({
dirName: path.basename(skillPath),
name: frontmatter.name || path.basename(skillPath),
description: frontmatter.description || '',
});
}
const moduleName = this._formatDisplayName(plugin.name);
const code = plugin.name;
const synthesizedYaml = {
code,
name: moduleName,
description: plugin.description || '',
module_version: plugin.version || '1.0.0',
default_selected: false,
};
const synthesizedCsv = this._buildSynthesizedHelpCsv(moduleName, skillInfos);
return [
{
code,
name: moduleName,
version: plugin.version || null,
description: plugin.description || '',
strategy: 5,
pluginName: plugin.name,
moduleYamlPath: null,
moduleHelpCsvPath: null,
skillPaths,
synthesizedModuleYaml: synthesizedYaml,
synthesizedHelpCsv: synthesizedCsv,
},
];
}
// ─── Helpers ────────────────────────────────────────────────────────────────
/**
* Compute the deepest common ancestor directory of an array of absolute paths.
* @param {string[]} absPaths - Absolute directory paths
* @returns {string} Common parent directory
*/
_computeCommonParent(absPaths) {
if (absPaths.length === 0) return '/';
if (absPaths.length === 1) return path.dirname(absPaths[0]);
const segments = absPaths.map((p) => p.split(path.sep));
const minLen = Math.min(...segments.map((s) => s.length));
const common = [];
for (let i = 0; i < minLen; i++) {
const segment = segments[0][i];
if (segments.every((s) => s[i] === segment)) {
common.push(segment);
} else {
break;
}
}
return common.join(path.sep) || '/';
}
/**
* Read and parse a module.yaml file.
* @param {string} yamlPath - Absolute path to module.yaml
* @returns {Object|null} Parsed content or null on failure
*/
async _readModuleYaml(yamlPath) {
try {
const content = await fs.readFile(yamlPath, 'utf8');
return yaml.parse(content);
} catch {
return null;
}
}
/**
* Extract name and description from a SKILL.md YAML frontmatter block.
* @param {string} skillDirPath - Absolute path to the skill directory
* @returns {Object} { name, description } or empty strings
*/
async _parseSkillFrontmatter(skillDirPath) {
const skillMdPath = path.join(skillDirPath, 'SKILL.md');
try {
const content = await fs.readFile(skillMdPath, 'utf8');
const match = content.match(/^---\s*\n([\s\S]*?)\n---/);
if (!match) return { name: '', description: '' };
const parsed = yaml.parse(match[1]);
return {
name: parsed.name || '',
description: parsed.description || '',
};
} catch {
return { name: '', description: '' };
}
}
/**
* Build a synthesized module-help.csv from plugin metadata and skill frontmatter.
* Uses the standard 13-column format.
* @param {string} moduleName - Display name for the module column
* @param {Array<{dirName: string, name: string, description: string}>} skillInfos
* @returns {string} CSV content
*/
_buildSynthesizedHelpCsv(moduleName, skillInfos) {
const header = 'module,skill,display-name,menu-code,description,action,args,phase,after,before,required,output-location,outputs';
const rows = [header];
for (const info of skillInfos) {
const displayName = this._formatDisplayName(info.name || info.dirName);
const menuCode = this._generateMenuCode(info.name || info.dirName);
const description = this._escapeCSVField(info.description);
rows.push(`${moduleName},${info.dirName},${displayName},${menuCode},${description},activate,,anytime,,,false,,`);
}
return rows.join('\n') + '\n';
}
/**
* Format a kebab-case or snake_case name into a display name.
* Strips common prefixes like "bmad-" or "bmad-agent-".
* @param {string} name - Raw name
* @returns {string} Formatted display name
*/
_formatDisplayName(name) {
let cleaned = name.replace(/^bmad-agent-/, '').replace(/^bmad-/, '');
return cleaned
.split(/[-_]/)
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ');
}
/**
* Generate a short menu code from a skill name.
* Takes first letter of each significant word, uppercased, max 3 chars.
* @param {string} name - Skill name (kebab-case)
* @returns {string} Menu code (e.g., "CC" for "code-coach")
*/
_generateMenuCode(name) {
const cleaned = name.replace(/^bmad-agent-/, '').replace(/^bmad-/, '');
const words = cleaned.split(/[-_]/).filter((w) => w.length > 0);
return words
.map((w) => w.charAt(0).toUpperCase())
.join('')
.slice(0, 3);
}
/**
* Escape a value for CSV output (wrap in quotes if it contains commas, quotes, or newlines).
* @param {string} value
* @returns {string}
*/
_escapeCSVField(value) {
if (!value) return '';
if (value.includes(',') || value.includes('"') || value.includes('\n')) {
return `"${value.replaceAll('"', '""')}"`;
}
return value;
}
}
module.exports = { PluginResolver };

View File

@ -158,6 +158,9 @@ class UI {
.map((m) => m.trim())
.filter(Boolean);
await prompts.log.info(`Using modules from command-line: ${selectedModules.join(', ')}`);
} else if (options.customSource) {
// Custom source without --modules: start with empty list (core added below)
selectedModules = [];
} else if (options.yes) {
selectedModules = await this.getDefaultModules(installedModuleIds);
await prompts.log.info(
@ -167,6 +170,14 @@ class UI {
selectedModules = await this.selectAllModules(installedModuleIds);
}
// Resolve custom sources from --custom-source flag
if (options.customSource) {
const customCodes = await this._resolveCustomSourcesCli(options.customSource);
for (const code of customCodes) {
if (!selectedModules.includes(code)) selectedModules.push(code);
}
}
// Ensure core is in the modules list
if (!selectedModules.includes('core')) {
selectedModules.unshift('core');
@ -202,6 +213,9 @@ class UI {
.map((m) => m.trim())
.filter(Boolean);
await prompts.log.info(`Using modules from command-line: ${selectedModules.join(', ')}`);
} else if (options.customSource) {
// Custom source without --modules: start with empty list (core added below)
selectedModules = [];
} else if (options.yes) {
// Use default modules when --yes flag is set
selectedModules = await this.getDefaultModules(installedModuleIds);
@ -210,6 +224,14 @@ class UI {
selectedModules = await this.selectAllModules(installedModuleIds);
}
// Resolve custom sources from --custom-source flag
if (options.customSource) {
const customCodes = await this._resolveCustomSourcesCli(options.customSource);
for (const code of customCodes) {
if (!selectedModules.includes(code)) selectedModules.push(code);
}
}
// Ensure core is in the modules list
if (!selectedModules.includes('core')) {
selectedModules.unshift('core');
@ -818,13 +840,13 @@ class UI {
}
/**
* Prompt user to install modules from custom GitHub URLs.
* Prompt user to install modules from custom sources (Git URLs or local paths).
* @param {Set} installedModuleIds - Currently installed module IDs
* @returns {Array} Selected custom module code strings
*/
async _addCustomUrlModules(installedModuleIds = new Set()) {
const addCustom = await prompts.confirm({
message: 'Would you like to install from a custom GitHub URL?',
message: 'Would you like to install from a custom source (Git URL or local path)?',
default: false,
});
if (!addCustom) return [];
@ -835,61 +857,158 @@ class UI {
let addMore = true;
while (addMore) {
const url = await prompts.text({
message: 'GitHub repository URL:',
placeholder: 'https://github.com/owner/repo',
const sourceInput = await prompts.text({
message: 'Git URL or local path:',
placeholder: 'https://github.com/owner/repo or /path/to/module',
validate: (input) => {
if (!input || input.trim() === '') return 'URL is required';
const result = customMgr.validateGitHubUrl(input.trim());
if (!input || input.trim() === '') return 'Source is required';
const result = customMgr.parseSource(input.trim());
return result.isValid ? undefined : result.error;
},
});
const s = await prompts.spinner();
s.start('Fetching module info...');
s.start('Resolving source...');
let sourceResult;
try {
const plugins = await customMgr.discoverModules(url.trim());
s.stop('Module info loaded');
sourceResult = await customMgr.resolveSource(sourceInput.trim(), { skipInstall: true, silent: true });
s.stop(sourceResult.parsed.type === 'local' ? 'Local source resolved' : 'Repository cloned');
} catch (error) {
s.error('Failed to resolve source');
await prompts.log.error(` ${error.message}`);
addMore = await prompts.confirm({ message: 'Try another source?', default: false });
continue;
}
if (sourceResult.parsed.type === 'local') {
await prompts.log.info('LOCAL MODULE: Pointing directly at local source (changes take effect on reinstall).');
} else {
await prompts.log.warn(
'UNVERIFIED MODULE: This module has not been reviewed by the BMad team.\n' + ' Only install modules from sources you trust.',
);
}
// Resolve plugins based on discovery mode vs direct mode
s.start('Analyzing plugin structure...');
const allResolved = [];
const localPath = sourceResult.parsed.type === 'local' ? sourceResult.rootDir : null;
if (sourceResult.mode === 'discovery') {
// Discovery mode: marketplace.json found, list available plugins
let plugins;
try {
plugins = await customMgr.discoverModules(sourceResult.marketplace, sourceResult.sourceUrl);
} catch (discoverError) {
s.error('Failed to discover modules');
await prompts.log.error(` ${discoverError.message}`);
addMore = await prompts.confirm({ message: 'Try another source?', default: false });
continue;
}
const effectiveRepoPath = sourceResult.repoPath || sourceResult.rootDir;
for (const plugin of plugins) {
const versionStr = plugin.version ? ` v${plugin.version}` : '';
await prompts.log.info(` ${plugin.name}${versionStr}\n ${plugin.description}\n Author: ${plugin.author}`);
}
const confirmInstall = await prompts.confirm({
message: `Install ${plugins.length} plugin${plugins.length === 1 ? '' : 's'} from ${url.trim()}?`,
default: false,
});
if (confirmInstall) {
// Pre-clone the repo so it's cached for the install pipeline
s.start('Cloning repository...');
try {
await customMgr.cloneRepo(url.trim());
s.stop('Repository cloned');
} catch (cloneError) {
s.error('Failed to clone repository');
await prompts.log.error(` ${cloneError.message}`);
addMore = await prompts.confirm({ message: 'Try another URL?', default: false });
continue;
}
for (const plugin of plugins) {
selectedModules.push(plugin.code);
const resolved = await customMgr.resolvePlugin(effectiveRepoPath, plugin.rawPlugin, sourceResult.sourceUrl, localPath);
if (resolved.length > 0) {
allResolved.push(...resolved);
} else {
// No skills array or empty - use plugin metadata as-is (legacy)
allResolved.push({
code: plugin.code,
name: plugin.displayName || plugin.name,
version: plugin.version,
description: plugin.description,
strategy: 0,
pluginName: plugin.name,
skillPaths: [],
});
}
} catch (resolveError) {
await prompts.log.warn(` Could not resolve ${plugin.name}: ${resolveError.message}`);
}
}
} catch (error) {
s.error('Failed to load module info');
await prompts.log.error(` ${error.message}`);
} else {
// Direct mode: no marketplace.json, scan directory for skills and resolve
const directPlugin = {
name: sourceResult.parsed.displayName || path.basename(sourceResult.rootDir),
source: '.',
skills: [],
};
// Scan for SKILL.md directories to populate skills array
try {
const entries = await fs.readdir(sourceResult.rootDir, { withFileTypes: true });
for (const entry of entries) {
if (entry.isDirectory()) {
const skillMd = path.join(sourceResult.rootDir, entry.name, 'SKILL.md');
if (await fs.pathExists(skillMd)) {
directPlugin.skills.push(entry.name);
}
}
}
} catch (scanError) {
s.error('Failed to scan directory');
await prompts.log.error(` ${scanError.message}`);
addMore = await prompts.confirm({ message: 'Try another source?', default: false });
continue;
}
if (directPlugin.skills.length > 0) {
try {
const resolved = await customMgr.resolvePlugin(sourceResult.rootDir, directPlugin, sourceResult.sourceUrl, localPath);
allResolved.push(...resolved);
} catch (resolveError) {
await prompts.log.warn(` Could not resolve: ${resolveError.message}`);
}
}
}
s.stop(`Found ${allResolved.length} installable module${allResolved.length === 1 ? '' : 's'}`);
if (allResolved.length === 0) {
await prompts.log.warn('No installable modules found in this source.');
addMore = await prompts.confirm({ message: 'Try another source?', default: false });
continue;
}
// Build multiselect choices
// Already-installed modules are pre-checked (update). New modules are unchecked (opt-in).
// Unchecking an installed module means "skip update" - removal is handled elsewhere.
const choices = allResolved.map((mod) => {
const versionStr = mod.version ? ` v${mod.version}` : '';
const skillCount = mod.skillPaths ? mod.skillPaths.length : 0;
const skillStr = skillCount > 0 ? ` (${skillCount} skill${skillCount === 1 ? '' : 's'})` : '';
const alreadyInstalled = installedModuleIds.has(mod.code);
const hint = alreadyInstalled ? 'update' : undefined;
return {
name: `${mod.name}${versionStr}${skillStr}`,
value: mod.code,
hint,
checked: alreadyInstalled,
};
});
// Show descriptions before the multiselect
for (const mod of allResolved) {
const versionStr = mod.version ? ` v${mod.version}` : '';
await prompts.log.info(` ${mod.name}${versionStr}\n ${mod.description}`);
}
const selected = await prompts.multiselect({
message: 'Select modules to install:',
choices,
required: false,
});
if (selected && selected.length > 0) {
for (const code of selected) {
selectedModules.push(code);
}
}
addMore = await prompts.confirm({
message: 'Add another custom module?',
message: 'Add another custom source?',
default: false,
});
}
@ -901,6 +1020,102 @@ class UI {
return selectedModules;
}
/**
* Resolve custom sources from --custom-source CLI flag (non-interactive).
* Auto-selects all discovered modules from each source.
* @param {string} sourcesArg - Comma-separated Git URLs or local paths
* @returns {Array} Module codes from all resolved sources
*/
async _resolveCustomSourcesCli(sourcesArg) {
const { CustomModuleManager } = require('./modules/custom-module-manager');
const customMgr = new CustomModuleManager();
const allCodes = [];
const sources = sourcesArg
.split(',')
.map((s) => s.trim())
.filter(Boolean);
for (const source of sources) {
const s = await prompts.spinner();
s.start(`Resolving ${source}...`);
let sourceResult;
try {
sourceResult = await customMgr.resolveSource(source, { skipInstall: true, silent: true });
s.stop(sourceResult.parsed.type === 'local' ? 'Local source resolved' : 'Repository cloned');
} catch (error) {
s.error(`Failed to resolve ${source}`);
await prompts.log.error(` ${error.message}`);
continue;
}
const s2 = await prompts.spinner();
s2.start('Analyzing plugin structure...');
const allResolved = [];
const localPath = sourceResult.parsed.type === 'local' ? sourceResult.rootDir : null;
if (sourceResult.mode === 'discovery') {
try {
const plugins = await customMgr.discoverModules(sourceResult.marketplace, sourceResult.sourceUrl);
const effectiveRepoPath = sourceResult.repoPath || sourceResult.rootDir;
for (const plugin of plugins) {
try {
const resolved = await customMgr.resolvePlugin(effectiveRepoPath, plugin.rawPlugin, sourceResult.sourceUrl, localPath);
if (resolved.length > 0) {
allResolved.push(...resolved);
}
} catch {
// Skip unresolvable plugins
}
}
} catch (discoverError) {
s2.error('Failed to discover modules');
await prompts.log.error(` ${discoverError.message}`);
continue;
}
} else {
// Direct mode: scan for SKILL.md directories
const directPlugin = {
name: sourceResult.parsed.displayName || path.basename(sourceResult.rootDir),
source: '.',
skills: [],
};
try {
const entries = await fs.readdir(sourceResult.rootDir, { withFileTypes: true });
for (const entry of entries) {
if (entry.isDirectory()) {
const skillMd = path.join(sourceResult.rootDir, entry.name, 'SKILL.md');
if (await fs.pathExists(skillMd)) {
directPlugin.skills.push(entry.name);
}
}
}
} catch {
// Skip unreadable directories
}
if (directPlugin.skills.length > 0) {
try {
const resolved = await customMgr.resolvePlugin(sourceResult.rootDir, directPlugin, sourceResult.sourceUrl, localPath);
allResolved.push(...resolved);
} catch {
// Skip unresolvable
}
}
}
s2.stop(`Found ${allResolved.length} module${allResolved.length === 1 ? '' : 's'}`);
for (const mod of allResolved) {
allCodes.push(mod.code);
const versionStr = mod.version ? ` v${mod.version}` : '';
await prompts.log.info(` Custom module: ${mod.name}${versionStr}`);
}
}
return allCodes;
}
/**
* Get default modules for non-interactive mode
* @param {Set} installedModuleIds - Already installed module IDs