Compare commits

...

14 Commits

Author SHA1 Message Date
Jacob du Toit 4d4344f9ce
Merge 61bba10cb3 into 0eae7c4352 2026-05-18 01:15:49 -04:00
github-actions[bot] 0eae7c4352 chore(release): v6.7.0 [skip ci] 2026-05-17 23:14:04 +00:00
Brian 74cf467d57
v6.7.0: bundle module registry, retire marketplace, refresh display names (#2388)
* feat(installer): bundle module registry, retire marketplace, refresh display names

Prepares v6.7.0 for release:

- Moves bundled module list from tools/installer/modules/registry-fallback.yaml
  to bmad-modules.yaml at repo root; renames to reflect single-source-of-truth role.
- Retires the remote marketplace registry fetch in ExternalModuleManager; the
  installer now reads the bundled YAML only.
- Adds WDS (Whiteport Design Studio) entry alongside BMM, BMB, BMA, CIS, GDS, TEA.
- Refreshes display names and descriptions on every bundled module; TEA
  repositioned after BMM in the picker.
- Adds plugin_name override field on registry entries so modules whose
  marketplace.json declares a plugin under a different name than the installer
  code (e.g. WDS uses bmad-wds) match without falling back to the single-plugin
  heuristic.
- Removes the community modules picker from the interactive installer; previously
  installed community modules are preserved on update and can still be installed
  via --custom-source.
- Renames the custom-source confirm prompt for clarity.

CHANGELOG.md updated with the full v6.7.0 entry.

* feat(installer): fully retire community catalog plumbing

Removes the last marketplace network connections from the installer.
The v6.7.0 first pass retired the official-registry fetch but left
CommunityModuleManager + RegistryClient in place, which still
fetched community-index.yaml and categories.yaml on every install
to support the channel-gate and update flows.

This commit:

- Deletes tools/installer/modules/community-manager.js and
  registry-client.js entirely.
- Strips CommunityModuleManager calls from ui.js (channel gate +
  update channels), core/manifest.js (getModuleVersionInfo),
  core/installer.js (resolution + installed-modules listing), and
  modules/official-modules.js (findModuleSource fallback +
  pre-install plugin resolution + post-install manifest entry).
- Simplifies installFromResolution: community branch removed; all
  non-external installs are now treated as custom-source.
- Removes corresponding test suites (CommunityModuleManager unit
  tests and the entire RegistryClient suite).
- Updates CHANGELOG with the migration note.

After this commit, grep confirms zero references to the bmad-plugins-
marketplace registry from the installer. The only remaining 'marketplace'
references are about per-repo .claude-plugin/marketplace.json files,
which the installer reads from cloned custom-source repos.
2026-05-17 17:47:25 -05:00
Brian 61bba10cb3
Merge branch 'main' into feat/expand-advanced-elicitation-methods 2026-04-27 21:06:12 -05: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
16 changed files with 182 additions and 1562 deletions

View File

@ -1,5 +1,49 @@
# Changelog
## v6.7.0 - 2026-05-17
### ✨ Headline
**PRD and Product Brief rebuilt as lean, outcome-driven facilitators called bmad-prd and bmad-brief.** Both flagship planning skills now ship three first-class intents (Create / Update / Validate), support express and guided modes, drive elicitation rather than LLM-suggested filler, and adapt output to your needs. New PRD validation pipeline replaces the adversarial reviewer with a quality-rubric synthesis pass that emits both HTML and markdown reports. New **bmad-investigate** skill brings forensic, evidence-graded case files for bug triage, incident RCA, and unfamiliar-code exploration.
A new .decision-log pattern is implemented in this release that will track through workflows all decisions made from the start, allowing for easier continuation or later modifications, where memory of what was decided and why will be remembered.
The existing create, edit and validate prd skills still exist but internally will route to the single prd skill with the proper intent. These shims will be removed with the 7.0.0 release when similar updates are completed across all of v6.
The shape of the toml customizations is still the same, so if you make them for create already, it will still work. There are new fields supported also that can improve your experience with the new bmad-prd skill.
### 💥 Breaking Changes
* **Community modules picker removed from the interactive installer.** Previously installed community modules are preserved on update. Install community modules headlessly with `--custom-source <git-url-or-path>`, or wait for the forthcoming dedicated community installer.
* **Remote marketplace registry fully retired.** The installer makes zero network calls to `bmad-code-org/bmad-plugins-marketplace`. Both the official-registry fetch (`registry/official.yaml`) and the community-catalog fetch (`registry/community-index.yaml`, `categories.yaml`) are gone. `CommunityModuleManager` and `RegistryClient` are deleted. The bundled `bmad-modules.yaml` at the repo root is the single source of truth for which official modules appear in the picker. Per-module version bumps continue to happen in each module's own repo. **Migration note:** users with previously installed community modules will see them preserved in their manifest, but updates must be handled via `--custom-source <url>` going forward (a dedicated community installer is planned separately).
### 🎁 Features
* **WDS (Whiteport Design Studio) now bundled in the official module picker.** Selectable alongside BMM, BMB, BMA, CIS, GDS, and TEA without needing `--custom-source`.
* **Refreshed display names and hints across all bundled modules.** Shorter, clearer names; hints now describe what each module provides. TEA repositioned to sit directly after BMM in the picker.
* **Registry entries can declare a `plugin_name` override.** When a module's `.claude-plugin/marketplace.json` declares the plugin under a name different from the module's installer code (e.g., WDS uses `bmad-wds`), set `plugin_name: <name>` on the registry entry to match the marketplace plugin without falling back to the single-plugin heuristic.
* **bmad-prd overhaul** — Three intents (Create / Update / Validate); new Discovery shape (Brain dump → Stakes calibration → Working mode → mode-scoped work); capability-first or user-first modes; Essential Spine template plus Adapt-In Menu with authorized section invention for compliance, integration, hardware, SLAs, monetization, data governance; subagent web research default-on; rebuilt validation via PRD Quality Rubric → synthesis pass → HTML + markdown reports; cross-skill parity with `bmad-product-brief` (variable names, `.decision-log.md`, `persistent_facts` auto-loads `project-context.md`); headless mode with per-intent inputs and `partial` status (#2385, #2378)
* **bmad-product-brief refactor** — Streamlined from a five-stage scripted workflow to a single outcome-driven SKILL.md with Create / Update / Validate intents; inline discovery, elicitation, and review (no more scripted agent fan-outs); new `assets/brief-template.md` with adapt-aggressively guidance; finalize chain through `bmad-distillator` and `bmad-help`; JSON headless responses (#2370, #2371)
* **New bmad-investigate skill** — Forensic case investigation with evidence-graded findings (Confirmed / Deduced / Hypothesized), delegation discipline for large codebases, resume-on-collision logic; supports both defect-chasing and area-exploration modes (#2345 and follow-ups)
* **Interactive directory prompt in installer** — `@clack/core` AutocompletePrompt for install-path selection: Tab-cycles existing child dirs, accepts not-yet-created paths, validates raw input (#2387)
* **OpenCode and GitHub Copilot pointer files** — Generic `installCommandPointers()` mechanism driven by per-platform YAML. OpenCode gets `.opencode/commands/<id>.md` for every skill; Copilot gets `.github/agents/<id>.agent.md` for persona agents only (plus `bmad-tea` allowlist), keeping the Custom Agents picker uncluttered. Works for external modules automatically via `skill-manifest.csv` (#2324)
* **BMad Automator (`bma`) registered** — Bundled registry fallback gains source-root external-module support, enabling `--modules bma` (#2345)
### 🐛 Fixes
* **Clear installer error on missing module definition** — `findExternalModuleSource()` throws an actionable error naming the module, missing path, and channel, with a suggested `--next=<code>` recovery path, replacing a silent ENOENT in `getFileList` (#2377)
* **bmad-product-brief Update/Validate discipline** — Headless Update now requires decision-log entry + addendum before modifying `brief.md`; distillate regeneration is mandatory; Validate always returns `"offer_to_update": true`; eval expectations tightened (#2371)
* **Module help catalog directional clarity** — Renamed `after`/`before` columns (and JSON manifest keys) to `preceded-by`/`followed-by` to eliminate ambiguity that was causing dependency-direction flips; `required` retains hard-gate semantics (#2360)
* **bmad-help removed from Copilot Custom Agents picker** — Not a true agent; every persona already advertises it on activation (#2359)
* **bmad-investigate robustness** — Collapsed multi-line description, unwrapped case-file template, tightened PRD discovery glob (review follow-ups)
* **Dependency security audit** — Lockfile-only fixes closed 12 of 14 open Dependabot alerts (`vite`, `postcss`, `h3`, `yaml`, `brace-expansion`, `picomatch`, `astro`, others). Two `astro <6.1.10` alerts and one `markdown-it` (via `markdownlint-cli2`) deferred pending major bumps (#2382)
### 📚 Docs
* New `docs/explanation/forensic-investigation.md` (EN + FR) explaining the bmad-investigate workflow and evidence-grading discipline; workflow maps updated in both languages
* Installer prerequisite docs updated across README, install/upgrade/non-interactive/tutorial guides and FR / CS / ZH-CN / VI-VN translations to advertise Node.js 20.12+ (#2387)
## v6.6.0 - 2026-04-28
### 💥 Breaking Changes

View File

@ -1,18 +1,31 @@
# Fallback module registry — used only when the BMad Marketplace repo
# (bmad-code-org/bmad-plugins-marketplace) is unreachable.
# The remote registry/official.yaml is the source of truth.
# Official module registry — the single source of truth for which modules
# the BMad installer offers and how they are displayed.
#
# Order here determines display order in the installer picker (after the
# built-in core and bmm entries, which are loaded from local module.yaml).
#
# default_channel (optional) — the install channel when the user does not
# override with --channel/--pin/--next. Valid values: stable | next.
# Omit to inherit the installer's hardcoded default (stable).
modules:
bmad-method-test-architecture-enterprise:
url: https://github.com/bmad-code-org/bmad-method-test-architecture-enterprise
module-definition: src/module.yaml
code: tea
name: "BMad Test Architect"
description: "Quality strategy, test automation, and release gates for enterprise teams"
defaultSelected: false
type: bmad-org
npmPackage: bmad-method-test-architecture-enterprise
default_channel: stable
bmad-builder:
url: https://github.com/bmad-code-org/bmad-builder
module-definition: skills/module.yaml
code: bmb
name: "BMad Builder"
description: "Agent and Builder"
description: "Build AI agents, workflows, and modules from a conversation"
defaultSelected: false
type: bmad-org
npmPackage: bmad-builder
@ -21,9 +34,9 @@ modules:
bmad-automator:
url: https://github.com/bmad-code-org/bmad-automator
module-definition: skills/module.yaml
code: baut
name: "BMad Automator"
description: "Story automation skills"
code: automator
name: "BMad Automator Epic Builder Experimental"
description: "EXPERIMENTAL: only supports claude and codex currently"
defaultSelected: false
type: experimental
npmPackage: bmad-story-automator
@ -34,7 +47,7 @@ modules:
module-definition: src/module.yaml
code: cis
name: "BMad Creative Intelligence Suite"
description: "Creative tools for writing, brainstorming, and more"
description: "Brainstorming, ideation, storytelling, design thinking, and problem-solving"
defaultSelected: false
type: bmad-org
npmPackage: bmad-creative-intelligence-suite
@ -45,19 +58,20 @@ modules:
module-definition: src/module.yaml
code: gds
name: "BMad Game Dev Studio"
description: "Game development agents and workflows"
description: "Game design and development for Unity, Unreal, Godot, and Phaser."
defaultSelected: false
type: bmad-org
npmPackage: bmad-game-dev-studio
default_channel: stable
bmad-method-test-architecture-enterprise:
url: https://github.com/bmad-code-org/bmad-method-test-architecture-enterprise
bmad-method-wds-expansion:
url: https://github.com/bmad-code-org/bmad-method-wds-expansion
module-definition: src/module.yaml
code: tea
name: "Test Architect"
description: "Master Test Architect for quality strategy, test automation, and release gates"
code: wds
plugin_name: bmad-wds # WDS marketplace.json declares the plugin under this name
name: "Whiteport Design Studio"
description: "Strategic UX and Design first planning methodology"
defaultSelected: false
type: bmad-org
npmPackage: bmad-method-test-architecture-enterprise
npmPackage: bmad-wds
default_channel: stable

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "bmad-method",
"version": "6.6.0",
"version": "6.7.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "bmad-method",
"version": "6.6.0",
"version": "6.7.0",
"license": "MIT",
"dependencies": {
"@clack/core": "^1.3.1",

View File

@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "bmad-method",
"version": "6.6.0",
"version": "6.7.0",
"description": "Breakthrough Method of Agile AI-driven Development",
"keywords": [
"agile",

View File

@ -1,6 +1,6 @@
code: bmm
name: "BMad Method Agile-AI Driven-Development"
description: "AI-driven agile development framework"
name: "BMad Method"
description: "Full-lifecycle AI agile development: analysis, planning, architecture, implementation"
default_selected: true # This module will be selected by default for new installations
# Variables from Core Config inserted:

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

@ -1,6 +1,6 @@
code: core
name: "BMad Core Module"
description: "Core configuration and shared resources"
description: "Shared utilities across modules"
header: "BMad Core Configuration"
subheader: "Configure the core settings for your BMad installation.\nThese settings will be used across all installed bmad skills, workflows, and agents."

View File

@ -1666,9 +1666,9 @@ async function runTests() {
console.log('');
// ============================================================
// Test Suite 33: Community & Custom Module Managers
// Test Suite 33: Custom Module Managers
// ============================================================
console.log(`${colors.yellow}Test Suite 33: Community & Custom Module Managers${colors.reset}\n`);
console.log(`${colors.yellow}Test Suite 33: Custom Module Managers${colors.reset}\n`);
// --- CustomModuleManager._normalizeCustomModule ---
{
@ -1690,288 +1690,6 @@ async function runTests() {
assert(result2.author === 'Fallback Owner', 'normalizeCustomModule falls back to data.owner');
}
// --- CommunityModuleManager._normalizeCommunityModule ---
{
const { CommunityModuleManager } = require('../tools/installer/modules/community-manager');
const mgr = new CommunityModuleManager();
const mod = {
name: 'test-mod',
display_name: 'Test Module',
code: 'tm',
description: 'desc',
repository: 'https://github.com/o/r',
module_definition: 'src/module.yaml',
category: 'software-development',
subcategory: 'dev-tools',
trust_tier: 'bmad-certified',
version: '2.0.0',
approved_sha: 'abc123',
promoted: true,
promoted_rank: 1,
keywords: ['test', 'module'],
};
const result = mgr._normalizeCommunityModule(mod);
assert(result.code === 'tm', 'normalizeCommunityModule sets code');
assert(result.displayName === 'Test Module', 'normalizeCommunityModule sets displayName from display_name');
assert(result.type === 'community', 'normalizeCommunityModule sets type to community');
assert(result.category === 'software-development', 'normalizeCommunityModule preserves category');
assert(result.trustTier === 'bmad-certified', 'normalizeCommunityModule maps trust_tier');
assert(result.approvedSha === 'abc123', 'normalizeCommunityModule maps approved_sha');
assert(result.promoted === true, 'normalizeCommunityModule maps promoted');
assert(result.promotedRank === 1, 'normalizeCommunityModule maps promoted_rank');
assert(result.builtIn === false, 'normalizeCommunityModule sets builtIn false');
}
// --- CommunityModuleManager.searchByKeyword (with injected cache) ---
{
const { CommunityModuleManager } = require('../tools/installer/modules/community-manager');
const mgr = new CommunityModuleManager();
// Inject cached index to avoid network call
mgr._cachedIndex = {
modules: [
{ name: 'mod-a', display_name: 'Alpha', code: 'a', description: 'testing tools', category: 'dev', keywords: ['test'] },
{ name: 'mod-b', display_name: 'Beta', code: 'b', description: 'design suite', category: 'design', keywords: ['ux'] },
{ name: 'mod-c', display_name: 'Gamma', code: 'c', description: 'game engine', category: 'game', keywords: ['unity'] },
],
};
const r1 = await mgr.searchByKeyword('test');
assert(r1.length === 1 && r1[0].code === 'a', 'searchByKeyword matches keyword');
const r2 = await mgr.searchByKeyword('design');
assert(r2.length === 1 && r2[0].code === 'b', 'searchByKeyword matches description');
const r3 = await mgr.searchByKeyword('alpha');
assert(r3.length === 1 && r3[0].code === 'a', 'searchByKeyword matches display name');
const r4 = await mgr.searchByKeyword('xyz');
assert(r4.length === 0, 'searchByKeyword returns empty for no match');
const r5 = await mgr.searchByKeyword('UNITY');
assert(r5.length === 1 && r5[0].code === 'c', 'searchByKeyword is case-insensitive');
}
// --- CommunityModuleManager.listFeatured (with injected cache) ---
{
const { CommunityModuleManager } = require('../tools/installer/modules/community-manager');
const mgr = new CommunityModuleManager();
mgr._cachedIndex = {
modules: [
{ name: 'a', code: 'a', promoted: true, promoted_rank: 3 },
{ name: 'b', code: 'b', promoted: false },
{ name: 'c', code: 'c', promoted: true, promoted_rank: 1 },
],
};
const featured = await mgr.listFeatured();
assert(featured.length === 2, 'listFeatured returns only promoted modules');
assert(featured[0].code === 'c' && featured[1].code === 'a', 'listFeatured sorts by promoted_rank ascending');
}
// --- CommunityModuleManager.getCategoryList (with injected cache) ---
{
const { CommunityModuleManager } = require('../tools/installer/modules/community-manager');
const mgr = new CommunityModuleManager();
mgr._cachedIndex = {
modules: [
{ name: 'a', code: 'a', category: 'software-development' },
{ name: 'b', code: 'b', category: 'design-and-creative' },
{ name: 'c', code: 'c', category: 'software-development' },
],
};
mgr._cachedCategories = {
categories: {
'software-development': { name: 'Software Development' },
'design-and-creative': { name: 'Design & Creative' },
},
};
const cats = await mgr.getCategoryList();
assert(cats.length === 2, 'getCategoryList returns categories with modules');
const swDev = cats.find((c) => c.slug === 'software-development');
assert(swDev && swDev.moduleCount === 2, 'getCategoryList counts modules per category');
assert(cats[0].name === 'Design & Creative', 'getCategoryList sorts alphabetically');
}
// --- CommunityModuleManager SHA pinning normalization ---
{
const { CommunityModuleManager } = require('../tools/installer/modules/community-manager');
const mgr = new CommunityModuleManager();
// Module with SHA set
const withSha = mgr._normalizeCommunityModule({
name: 'pinned-mod',
code: 'pm',
approved_sha: 'abc123def456',
approved_tag: 'v1.0.0',
});
assert(withSha.approvedSha === 'abc123def456', 'SHA is preserved when set');
assert(withSha.approvedTag === 'v1.0.0', 'Tag is preserved as metadata');
// Module with null SHA (trusted contributor)
const noSha = mgr._normalizeCommunityModule({
name: 'trusted-mod',
code: 'tm',
approved_sha: null,
});
assert(noSha.approvedSha === null, 'Null SHA means no pinning (trusted contributor)');
}
// --- CommunityModuleManager.listByCategory (with injected cache) ---
{
const { CommunityModuleManager } = require('../tools/installer/modules/community-manager');
const mgr = new CommunityModuleManager();
mgr._cachedIndex = {
modules: [
{ name: 'a', code: 'a', category: 'design-and-creative' },
{ name: 'b', code: 'b', category: 'software-development' },
{ name: 'c', code: 'c', category: 'design-and-creative' },
{ name: 'd', code: 'd', category: 'game-development' },
],
};
const design = await mgr.listByCategory('design-and-creative');
assert(design.length === 2, 'listByCategory filters to matching category');
assert(
design.every((m) => m.category === 'design-and-creative'),
'listByCategory returns only matching modules',
);
const empty = await mgr.listByCategory('nonexistent');
assert(empty.length === 0, 'listByCategory returns empty for unknown category');
}
// --- CommunityModuleManager.getModuleByCode (with injected cache) ---
{
const { CommunityModuleManager } = require('../tools/installer/modules/community-manager');
const mgr = new CommunityModuleManager();
mgr._cachedIndex = {
modules: [
{ name: 'test-mod', code: 'tm', display_name: 'Test Module' },
{ name: 'other-mod', code: 'om', display_name: 'Other Module' },
],
};
const found = await mgr.getModuleByCode('tm');
assert(found !== null && found.code === 'tm', 'getModuleByCode finds existing module');
const notFound = await mgr.getModuleByCode('xyz');
assert(notFound === null, 'getModuleByCode returns null for unknown code');
}
console.log('');
// ============================================================
// Test Suite 34: RegistryClient GitHub API Cascade
// ============================================================
console.log(`${colors.yellow}Test Suite 34: RegistryClient GitHub API Cascade${colors.reset}\n`);
{
const { RegistryClient } = require('../tools/installer/modules/registry-client');
// Build a RegistryClient with stubbed fetch paths so we can assert on cascade behavior
// without making real network calls.
function createStubbedClient({ apiResult, rawResult }) {
const client = new RegistryClient();
const calls = [];
// Stub _fetchWithHeaders (GitHub API path)
client._fetchWithHeaders = async (url) => {
calls.push(`api:${url}`);
if (apiResult instanceof Error) throw apiResult;
return apiResult;
};
// Stub fetch (raw CDN path) — only intercept raw.githubusercontent.com calls
const originalFetch = client.fetch.bind(client);
client.fetch = async (url, timeout) => {
if (url.includes('raw.githubusercontent.com')) {
calls.push(`raw:${url}`);
if (rawResult instanceof Error) throw rawResult;
return rawResult;
}
return originalFetch(url, timeout);
};
return { client, calls };
}
// --- API success skips raw CDN ---
{
const { client, calls } = createStubbedClient({ apiResult: 'api-content', rawResult: 'raw-content' });
const result = await client.fetchGitHubFile('owner', 'repo', 'path/file.txt', 'main');
assert(result === 'api-content', 'RegistryClient API success returns API content');
assert(calls.length === 1, 'RegistryClient API success makes exactly one call');
assert(calls[0].startsWith('api:'), 'RegistryClient API success calls API endpoint');
}
// --- API failure falls back to raw CDN ---
{
const { client, calls } = createStubbedClient({ apiResult: new Error('HTTP 403'), rawResult: 'raw-content' });
const result = await client.fetchGitHubFile('owner', 'repo', 'path/file.txt', 'main');
assert(result === 'raw-content', 'RegistryClient API failure returns raw CDN content');
assert(calls.length === 2, 'RegistryClient API failure makes two calls');
assert(calls[0].startsWith('api:'), 'RegistryClient first call is to API');
assert(calls[1].startsWith('raw:'), 'RegistryClient second call is to raw CDN');
}
// --- Both endpoints failing throws ---
{
const { client } = createStubbedClient({ apiResult: new Error('HTTP 403'), rawResult: new Error('HTTP 404') });
let threw = false;
try {
await client.fetchGitHubFile('owner', 'repo', 'path/file.txt', 'main');
} catch {
threw = true;
}
assert(threw, 'RegistryClient both endpoints failing throws an error');
}
// --- API URL construction ---
{
const { client, calls } = createStubbedClient({ apiResult: 'content', rawResult: 'content' });
await client.fetchGitHubFile('bmad-code-org', 'bmad-plugins-marketplace', 'registry/official.yaml', 'main');
const apiCall = calls[0];
assert(
apiCall.includes('api.github.com/repos/bmad-code-org/bmad-plugins-marketplace/contents/registry/official.yaml'),
'RegistryClient API URL contains correct path',
);
assert(apiCall.includes('ref=main'), 'RegistryClient API URL contains ref parameter');
}
// --- Raw CDN URL construction ---
{
const { client, calls } = createStubbedClient({ apiResult: new Error('fail'), rawResult: 'content' });
await client.fetchGitHubFile('bmad-code-org', 'bmad-plugins-marketplace', 'registry/official.yaml', 'main');
const rawCall = calls[1];
assert(
rawCall.includes('raw.githubusercontent.com/bmad-code-org/bmad-plugins-marketplace/main/registry/official.yaml'),
'RegistryClient raw CDN URL contains correct path',
);
}
// --- fetchGitHubYaml parses YAML ---
{
const yamlContent = 'modules:\n - name: test\n description: A test module\n';
const { client } = createStubbedClient({ apiResult: yamlContent, rawResult: yamlContent });
const result = await client.fetchGitHubYaml('owner', 'repo', 'file.yaml', 'main');
assert(Array.isArray(result.modules), 'fetchGitHubYaml parses YAML correctly');
assert(result.modules[0].name === 'test', 'fetchGitHubYaml preserves YAML values');
}
}
console.log('');
// ============================================================

View File

@ -640,13 +640,7 @@ class Installer {
const moduleInfo = sourcePath ? await officialModules.getModuleInfo(sourcePath, moduleName, '') : null;
const displayName = moduleInfo?.name || moduleName;
const externalResolution = officialModules.externalModuleManager.getResolution(moduleName);
let communityResolution = null;
if (!externalResolution) {
const { CommunityModuleManager } = require('../modules/community-manager');
communityResolution = new CommunityModuleManager().getResolution(moduleName);
}
const resolution = externalResolution || communityResolution;
const resolution = officialModules.externalModuleManager.getResolution(moduleName);
const cachedResolution = CustomModuleManager._resolutionCache.get(moduleName);
const versionInfo = await resolveModuleVersion(moduleName, {
moduleSourcePath: sourcePath,
@ -1178,21 +1172,6 @@ class Installer {
}
}
// Add installed community modules to available modules
const { CommunityModuleManager } = require('../modules/community-manager');
const communityMgr = new CommunityModuleManager();
const communityModules = await communityMgr.listAll();
for (const communityModule of communityModules) {
if (installedModules.includes(communityModule.code) && !availableModules.some((m) => m.id === communityModule.code)) {
availableModules.push({
id: communityModule.code,
name: communityModule.displayName,
isExternal: true,
fromCommunity: true,
});
}
}
// Add installed custom modules to available modules
const { CustomModuleManager } = require('../modules/custom-module-manager');
const customMgr = new CustomModuleManager();

View File

@ -310,28 +310,6 @@ class Manifest {
};
}
// Check if this is a community module
const { CommunityModuleManager } = require('../modules/community-manager');
const communityMgr = new CommunityModuleManager();
const communityInfo = await communityMgr.getModuleByCode(moduleName);
if (communityInfo) {
const communityResolution = communityMgr.getResolution(moduleName);
const versionInfo = await resolveModuleVersion(moduleName, {
moduleSourcePath,
fallbackVersion: communityInfo.version,
});
return {
version: communityResolution?.version || versionInfo.version || communityInfo.version,
source: 'community',
npmPackage: communityInfo.npmPackage || null,
repoUrl: communityInfo.url || null,
channel: communityResolution?.channel || null,
sha: communityResolution?.sha || null,
registryApprovedTag: communityResolution?.registryApprovedTag || null,
registryApprovedSha: communityResolution?.registryApprovedSha || null,
};
}
// 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();

View File

@ -7,7 +7,7 @@
* We build the plan from:
* 1. CLI flags (--channel / --all-* / --next=CODE / --pin CODE=TAG)
* 2. Interactive answers (the "all stable?" gate + per-module picker)
* 3. Registry defaults (default_channel from registry-fallback.yaml / official.yaml)
* 3. Registry defaults (default_channel from bmad-modules.yaml)
* 4. Hardcoded fallback 'stable'
*
* Precedence: --pin > --next=CODE > --channel (global) > registry default > 'stable'.

View File

@ -1,704 +0,0 @@
const fs = require('../fs-native');
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');
const { decideChannelForModule } = require('./channel-plan');
const { parseGitHubRepo, tagExists } = require('./channel-resolver');
const MARKETPLACE_OWNER = 'bmad-code-org';
const MARKETPLACE_REPO = 'bmad-plugins-marketplace';
const MARKETPLACE_REF = 'main';
/**
* Manages community modules from the BMad marketplace registry.
* Fetches community-index.yaml and categories.yaml from GitHub.
* Returns empty results when the registry is unreachable.
* Community modules are pinned to approved SHA when set; uses HEAD otherwise.
*/
function quoteShellRef(ref) {
if (typeof ref !== 'string' || !/^[\w.\-+/]+$/.test(ref)) {
throw new Error(`Unsafe ref name: ${JSON.stringify(ref)}`);
}
return `"${ref}"`;
}
class CommunityModuleManager {
// moduleCode → { channel, version, sha, registryApprovedTag, registryApprovedSha, repoUrl, bypassedCurator }
// Shared across all instances; the manifest writer often uses a fresh instance.
static _resolutions = new Map();
// moduleCode → ResolvedModule (from PluginResolver) when the cloned repo ships
// a `.claude-plugin/marketplace.json`. Lets community installs reuse the same
// skill-level install pipeline as custom-source installs (installFromResolution).
static _pluginResolutions = new Map();
constructor() {
this._client = new RegistryClient();
this._cachedIndex = null;
this._cachedCategories = null;
}
/** Get the most recent channel resolution for a community module. */
getResolution(moduleCode) {
return CommunityModuleManager._resolutions.get(moduleCode) || null;
}
/** Get the marketplace.json-derived plugin resolution for a community module, if any. */
getPluginResolution(moduleCode) {
return CommunityModuleManager._pluginResolutions.get(moduleCode) || null;
}
// ─── Data Loading ──────────────────────────────────────────────────────────
/**
* Load the community module index from the marketplace repo.
* Returns empty when the registry is unreachable.
* @returns {Object} Parsed YAML with modules array
*/
async loadCommunityIndex() {
if (this._cachedIndex) return this._cachedIndex;
try {
const config = await this._client.fetchGitHubYaml(
MARKETPLACE_OWNER,
MARKETPLACE_REPO,
'registry/community-index.yaml',
MARKETPLACE_REF,
);
if (config?.modules?.length) {
this._cachedIndex = config;
return config;
}
} catch {
// Registry unreachable - no community modules available
}
return { modules: [] };
}
/**
* Load categories from the marketplace repo.
* Returns empty when the registry is unreachable.
* @returns {Object} Parsed categories.yaml content
*/
async loadCategories() {
if (this._cachedCategories) return this._cachedCategories;
try {
const config = await this._client.fetchGitHubYaml(MARKETPLACE_OWNER, MARKETPLACE_REPO, 'categories.yaml', MARKETPLACE_REF);
if (config?.categories) {
this._cachedCategories = config;
return config;
}
} catch {
// Registry unreachable - no categories available
}
return { categories: {} };
}
// ─── Listing & Filtering ──────────────────────────────────────────────────
/**
* Get all community modules, normalized.
* @returns {Array<Object>} Normalized community modules
*/
async listAll() {
const index = await this.loadCommunityIndex();
return (index.modules || []).map((mod) => this._normalizeCommunityModule(mod));
}
/**
* Get community modules filtered to a category.
* @param {string} categorySlug - Category slug (e.g., 'design-and-creative')
* @returns {Array<Object>} Filtered modules
*/
async listByCategory(categorySlug) {
const all = await this.listAll();
return all.filter((mod) => mod.category === categorySlug);
}
/**
* Get promoted/featured community modules, sorted by rank.
* @returns {Array<Object>} Featured modules
*/
async listFeatured() {
const all = await this.listAll();
return all.filter((mod) => mod.promoted === true).sort((a, b) => (a.promotedRank || 999) - (b.promotedRank || 999));
}
/**
* Search community modules by keyword.
* Matches against name, display name, description, and keywords array.
* @param {string} query - Search query
* @returns {Array<Object>} Matching modules
*/
async searchByKeyword(query) {
const all = await this.listAll();
const q = query.toLowerCase();
return all.filter((mod) => {
const searchable = [mod.name, mod.displayName, mod.description, ...(mod.keywords || [])].join(' ').toLowerCase();
return searchable.includes(q);
});
}
/**
* Get categories with module counts for UI display.
* Only returns categories that have at least one community module.
* @returns {Array<Object>} Array of { slug, name, moduleCount }
*/
async getCategoryList() {
const all = await this.listAll();
const categoriesData = await this.loadCategories();
const categories = categoriesData.categories || {};
// Count modules per category
const counts = {};
for (const mod of all) {
counts[mod.category] = (counts[mod.category] || 0) + 1;
}
// Build list with display names from categories.yaml
const result = [];
for (const [slug, count] of Object.entries(counts)) {
const catInfo = categories[slug];
result.push({
slug,
name: catInfo?.name || slug,
moduleCount: count,
});
}
// Sort alphabetically by name
result.sort((a, b) => a.name.localeCompare(b.name));
return result;
}
// ─── Module Lookup ────────────────────────────────────────────────────────
/**
* Get a community module by its code.
* @param {string} code - Module code (e.g., 'wds')
* @returns {Object|null} Normalized module or null
*/
async getModuleByCode(code) {
const all = await this.listAll();
return all.find((m) => m.code === code) || null;
}
// ─── Clone with Tag Pinning ───────────────────────────────────────────────
/**
* Get the cache directory for community modules.
* @returns {string} Path to the community modules cache directory
*/
getCacheDir() {
return path.join(os.homedir(), '.bmad', 'cache', 'community-modules');
}
/**
* Clone a community module repository, pinned to its approved tag.
* @param {string} moduleCode - Module code
* @param {Object} [options] - Clone options
* @param {boolean} [options.silent] - Suppress spinner output
* @returns {string} Path to the cloned repository
*/
async cloneModule(moduleCode, options = {}) {
const moduleInfo = await this.getModuleByCode(moduleCode);
if (!moduleInfo) {
throw new Error(`Community module '${moduleCode}' not found in the registry`);
}
const cacheDir = this.getCacheDir();
const moduleCacheDir = path.join(cacheDir, moduleCode);
const silent = options.silent || false;
await fs.ensureDir(cacheDir);
const createSpinner = async () => {
if (silent) {
return { start() {}, stop() {}, error() {}, message() {} };
}
return await prompts.spinner();
};
// ─── Resolve channel plan ──────────────────────────────────────────────
// Default community behavior (stable channel) honors the curator's
// approved SHA. --next=CODE and --pin CODE=TAG override the curator; we
// warn the user before bypassing the approved version.
const planEntry = decideChannelForModule({
code: moduleCode,
channelOptions: options.channelOptions,
registryDefault: 'stable',
});
const approvedSha = moduleInfo.approvedSha;
const approvedTag = moduleInfo.approvedTag;
let bypassedCurator = false;
if (planEntry.channel !== 'stable') {
bypassedCurator = true;
if (!silent) {
const approvedLabel = approvedTag || approvedSha || 'curator-approved version';
await prompts.log.warn(
`WARNING: Installing '${moduleCode}' from ${
planEntry.channel === 'pinned' ? `tag ${planEntry.pin}` : 'main HEAD'
} bypasses the curator-approved ${approvedLabel}. Proceed only if you trust this source.`,
);
if (!options.channelOptions?.acceptBypass) {
const proceed = await prompts.confirm({
message: `Continue installing '${moduleCode}' with curator bypass?`,
default: false,
});
if (!proceed) {
throw new Error(`Install of community module '${moduleCode}' cancelled by user.`);
}
}
}
}
let needsDependencyInstall = false;
let wasNewClone = false;
if (await fs.pathExists(moduleCacheDir)) {
// Already cloned — refresh to the correct ref for the resolved channel.
// A pinned install must not reset to origin/HEAD (it would silently drift
// to main on every re-install). Stable + approvedSha is handled below
// by the curator-SHA checkout logic.
const fetchSpinner = await createSpinner();
fetchSpinner.start(`Checking ${moduleInfo.displayName}...`);
try {
const currentRef = execSync('git rev-parse HEAD', { cwd: moduleCacheDir, stdio: 'pipe' }).toString().trim();
execSync('git fetch origin --depth 1', {
cwd: moduleCacheDir,
stdio: ['ignore', 'pipe', 'pipe'],
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
});
if (planEntry.channel === 'pinned') {
// Fetch the pin tag specifically and check it out.
execSync(`git fetch --depth 1 origin ${quoteShellRef(planEntry.pin)} --no-tags`, {
cwd: moduleCacheDir,
stdio: ['ignore', 'pipe', 'pipe'],
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
});
execSync('git checkout --quiet FETCH_HEAD', {
cwd: moduleCacheDir,
stdio: ['ignore', 'pipe', 'pipe'],
});
} else {
// stable (approvedSha path re-checks out below) and next: track main.
execSync('git reset --hard origin/HEAD', {
cwd: moduleCacheDir,
stdio: ['ignore', 'pipe', 'pipe'],
});
}
const newRef = execSync('git rev-parse HEAD', { cwd: moduleCacheDir, stdio: 'pipe' }).toString().trim();
if (currentRef !== newRef) needsDependencyInstall = true;
fetchSpinner.stop(`Verified ${moduleInfo.displayName}`);
} catch {
fetchSpinner.error(`Fetch failed, re-downloading ${moduleInfo.displayName}`);
await fs.remove(moduleCacheDir);
wasNewClone = true;
}
} else {
wasNewClone = true;
}
if (wasNewClone) {
const fetchSpinner = await createSpinner();
fetchSpinner.start(`Fetching ${moduleInfo.displayName}...`);
try {
if (planEntry.channel === 'pinned') {
execSync(`git clone --depth 1 --branch ${quoteShellRef(planEntry.pin)} "${moduleInfo.url}" "${moduleCacheDir}"`, {
stdio: ['ignore', 'pipe', 'pipe'],
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
});
} else {
execSync(`git clone --depth 1 "${moduleInfo.url}" "${moduleCacheDir}"`, {
stdio: ['ignore', 'pipe', 'pipe'],
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
});
}
fetchSpinner.stop(`Fetched ${moduleInfo.displayName}`);
needsDependencyInstall = true;
} catch (error) {
fetchSpinner.error(`Failed to fetch ${moduleInfo.displayName}`);
throw new Error(`Failed to clone community module '${moduleCode}': ${error.message}`);
}
}
// ─── Check out the resolved ref per channel ──────────────────────────
if (planEntry.channel === 'stable' && approvedSha) {
// Default path: pin to the curator-approved SHA. Refuse install if the SHA
// is unreachable (tag may have been deleted or rewritten) — security requirement.
const headSha = execSync('git rev-parse HEAD', { cwd: moduleCacheDir, stdio: 'pipe' }).toString().trim();
if (headSha !== approvedSha) {
try {
execSync(`git fetch --depth 1 origin ${quoteShellRef(approvedSha)}`, {
cwd: moduleCacheDir,
stdio: ['ignore', 'pipe', 'pipe'],
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
});
execSync(`git checkout ${quoteShellRef(approvedSha)}`, {
cwd: moduleCacheDir,
stdio: ['ignore', 'pipe', 'pipe'],
});
needsDependencyInstall = true;
} catch {
await fs.remove(moduleCacheDir);
throw new Error(
`Community module '${moduleCode}' could not be pinned to its approved commit (${approvedSha}). ` +
`Installation refused for security. The module registry entry may need updating, ` +
`or use --next=${moduleCode} / --pin ${moduleCode}=<tag> to explicitly bypass.`,
);
}
}
} else if (planEntry.channel === 'stable' && !approvedSha) {
// Registry data gap: tag or SHA missing. Warn but proceed at HEAD (pre-existing behavior).
if (!silent) {
await prompts.log.warn(`Community module '${moduleCode}' has no curator-approved SHA in the registry; installing from main HEAD.`);
}
} else if (planEntry.channel === 'pinned') {
// We cloned the tag directly above (via --branch), but ensure HEAD matches.
// No additional checkout needed.
}
// else: 'next' channel — already at origin/HEAD from the fetch/reset above.
// Record the resolution so the manifest writer can pick up channel/version/sha.
const installedSha = execSync('git rev-parse HEAD', { cwd: moduleCacheDir, stdio: 'pipe' }).toString().trim();
const recordedVersion =
planEntry.channel === 'pinned' ? planEntry.pin : planEntry.channel === 'next' ? 'main' : approvedTag || installedSha.slice(0, 7);
CommunityModuleManager._resolutions.set(moduleCode, {
channel: planEntry.channel,
version: recordedVersion,
sha: installedSha,
registryApprovedTag: approvedTag || null,
registryApprovedSha: approvedSha || null,
repoUrl: moduleInfo.url,
bypassedCurator,
planSource: planEntry.source,
});
// If the repo ships a marketplace.json, route through PluginResolver so the
// skill-level install pipeline (installFromResolution) handles the copy.
// Repos without marketplace.json fall through to the legacy findModuleSource
// path unchanged.
await this._tryResolveMarketplacePlugin(moduleCacheDir, moduleInfo, {
channel: planEntry.channel,
version: recordedVersion,
sha: installedSha,
approvedTag,
approvedSha,
});
// Install dependencies if needed
const packageJsonPath = path.join(moduleCacheDir, 'package.json');
if ((needsDependencyInstall || wasNewClone) && (await fs.pathExists(packageJsonPath))) {
const installSpinner = await createSpinner();
installSpinner.start(`Installing dependencies for ${moduleInfo.displayName}...`);
try {
execSync('npm install --omit=dev --no-audit --no-fund --no-progress --legacy-peer-deps', {
cwd: moduleCacheDir,
stdio: ['ignore', 'pipe', 'pipe'],
timeout: 120_000,
});
installSpinner.stop(`Installed dependencies for ${moduleInfo.displayName}`);
} catch (error) {
installSpinner.error(`Failed to install dependencies for ${moduleInfo.displayName}`);
if (!silent) await prompts.log.warn(` ${error.message}`);
}
}
return moduleCacheDir;
}
// ─── Marketplace.json Resolution ──────────────────────────────────────────
/**
* Detect `.claude-plugin/marketplace.json` in a cloned community repo and
* route through PluginResolver. When successful, caches the resolution so
* OfficialModulesManager.install() can route the copy through
* installFromResolution() — the same path used by custom-source installs.
*
* Silent no-op when marketplace.json is absent or the resolver returns no
* matches; the legacy findModuleSource path then handles the install.
*
* @param {string} repoPath - Absolute path to the cloned repo
* @param {Object} moduleInfo - Normalized community module info
* @param {Object} resolution - Resolution metadata from cloneModule
* @param {string} resolution.channel - Channel ('stable' | 'next' | 'pinned')
* @param {string} resolution.version - Recorded version string
* @param {string} resolution.sha - Resolved git SHA
* @param {string|null} resolution.approvedTag - Registry approved tag
* @param {string|null} resolution.approvedSha - Registry approved SHA
*/
async _tryResolveMarketplacePlugin(repoPath, moduleInfo, resolution) {
const marketplacePath = path.join(repoPath, '.claude-plugin', 'marketplace.json');
if (!(await fs.pathExists(marketplacePath))) return;
let marketplaceData;
try {
marketplaceData = JSON.parse(await fs.readFile(marketplacePath, 'utf8'));
} catch {
// Malformed marketplace.json — fall through to legacy path.
return;
}
const plugins = Array.isArray(marketplaceData?.plugins) ? marketplaceData.plugins : [];
if (plugins.length === 0) return;
const selection = this._selectPluginForModule(plugins, moduleInfo);
if (!selection) {
await this._safeWarn(
`Community module '${moduleInfo.code}' ships marketplace.json but no plugin entry matches the registry code. ` +
`Falling back to legacy install path.`,
);
return;
}
if (selection.source === 'single-fallback') {
// Single-entry marketplace.json whose plugin name doesn't match the registry
// code or the module_definition hint. Most likely correct, but worth surfacing
// in case marketplace.json is misconfigured and we'd install the wrong plugin.
await this._safeWarn(
`Community module '${moduleInfo.code}' picked the only plugin in marketplace.json ('${selection.plugin?.name}') ` +
`because no name or module_definition match was found. Verify marketplace.json if the install looks wrong.`,
);
}
const { PluginResolver } = require('./plugin-resolver');
const resolver = new PluginResolver();
let resolved;
try {
resolved = await resolver.resolve(repoPath, selection.plugin);
} catch (error) {
// PluginResolver threw (malformed plugin entry, missing files, etc.).
// Honor the silent-fallthrough contract — warn and let the legacy
// findModuleSource path handle the install.
await this._safeWarn(
`PluginResolver failed for community module '${moduleInfo.code}': ${error.message}. ` + `Falling back to legacy install path.`,
);
return;
}
if (!resolved || resolved.length === 0) return;
// The registry registers a single code per module. If the resolver returns
// multiple modules (Strategy 4: multiple standalone skills), accept only
// the entry whose code matches the registry. Other entries are ignored —
// they belong to plugins not registered in the community catalog.
const matched = resolved.find((mod) => mod.code === moduleInfo.code) || (resolved.length === 1 ? resolved[0] : null);
if (!matched) return;
// Shallow-clone before stamping provenance — the resolver may cache or reuse
// its return objects, and we don't want install-specific fields leaking back.
const stamped = {
...matched,
code: moduleInfo.code,
repoUrl: moduleInfo.url,
cloneRef: resolution.channel === 'pinned' ? resolution.version : resolution.approvedTag || null,
cloneSha: resolution.sha,
communitySource: true,
communityChannel: resolution.channel,
communityVersion: resolution.version,
registryApprovedTag: resolution.approvedTag,
registryApprovedSha: resolution.approvedSha,
};
CommunityModuleManager._pluginResolutions.set(moduleInfo.code, stamped);
}
/**
* Lazy fallback: resolve marketplace.json straight from the on-disk cache
* when `_pluginResolutions` is empty (e.g. callers that reach `install()`
* without `cloneModule` having populated the cache earlier in this process).
*
* Reuses an existing channel resolution if present; otherwise synthesizes a
* minimal stable-channel stub from the registry entry + the cached repo's
* current HEAD. Returns the cached plugin resolution if one is produced,
* otherwise null (caller falls back to the legacy path).
*
* @param {string} moduleCode
* @returns {Promise<Object|null>}
*/
async resolveFromCache(moduleCode) {
const existing = this.getPluginResolution(moduleCode);
if (existing) return existing;
const cacheRepoDir = path.join(this.getCacheDir(), moduleCode);
const marketplacePath = path.join(cacheRepoDir, '.claude-plugin', 'marketplace.json');
if (!(await fs.pathExists(marketplacePath))) return null;
let moduleInfo;
try {
moduleInfo = await this.getModuleByCode(moduleCode);
} catch {
return null;
}
if (!moduleInfo) return null;
let channelResolution = this.getResolution(moduleCode);
if (!channelResolution) {
let sha = '';
try {
sha = execSync('git rev-parse HEAD', { cwd: cacheRepoDir, stdio: 'pipe' }).toString().trim();
} catch {
// Not a git repo or unreadable — give up and let the legacy path run.
return null;
}
channelResolution = {
channel: 'stable',
version: moduleInfo.approvedTag || sha.slice(0, 7),
sha,
registryApprovedTag: moduleInfo.approvedTag || null,
registryApprovedSha: moduleInfo.approvedSha || null,
};
}
await this._tryResolveMarketplacePlugin(cacheRepoDir, moduleInfo, {
channel: channelResolution.channel,
version: channelResolution.version,
sha: channelResolution.sha,
approvedTag: channelResolution.registryApprovedTag,
approvedSha: channelResolution.registryApprovedSha,
});
return this.getPluginResolution(moduleCode);
}
/**
* Best-effort warning emitter. `prompts.log.warn` may be undefined in some
* harnesses and may return a rejected promise — swallow both cases so a
* fallthrough warning can never crash the install.
*/
async _safeWarn(message) {
try {
const result = prompts.log?.warn?.(message);
if (result && typeof result.then === 'function') await result;
} catch {
/* ignore */
}
}
/**
* Pick which plugin entry from marketplace.json represents this community module.
* Precedence:
* 1. Exact match on `plugin.name === moduleInfo.code`
* 2. Trailing directory of `module_definition` matches `plugin.name`
* 3. Single plugin in marketplace.json — accepted with a warning so a
* mismatched-but-uniquely-named plugin doesn't install silently.
* Otherwise null (caller falls back to legacy path).
*
* @returns {{plugin: Object, source: 'name'|'hint'|'single-fallback'}|null}
*/
_selectPluginForModule(plugins, moduleInfo) {
const byCode = plugins.find((p) => p && p.name === moduleInfo.code);
if (byCode) return { plugin: byCode, source: 'name' };
if (moduleInfo.moduleDefinition) {
// module_definition like "src/skills/suno-setup/assets/module.yaml" →
// hint segment "suno-setup". Match that against plugin names.
const segments = moduleInfo.moduleDefinition.split('/').filter(Boolean);
const setupIdx = segments.findIndex((s) => s.endsWith('-setup'));
if (setupIdx !== -1) {
const hint = segments[setupIdx];
const byHint = plugins.find((p) => p && p.name === hint);
if (byHint) return { plugin: byHint, source: 'hint' };
}
}
if (plugins.length === 1) return { plugin: plugins[0], source: 'single-fallback' };
return null;
}
// ─── Source Finding ───────────────────────────────────────────────────────
/**
* Find the source path for a community module (clone + locate module.yaml).
* @param {string} moduleCode - Module code
* @param {Object} [options] - Options passed to cloneModule
* @returns {string|null} Path to the module source or null
*/
async findModuleSource(moduleCode, options = {}) {
const moduleInfo = await this.getModuleByCode(moduleCode);
if (!moduleInfo) return null;
const cloneDir = await this.cloneModule(moduleCode, options);
// Check configured module_definition path first
if (moduleInfo.moduleDefinition) {
const configuredPath = path.join(cloneDir, moduleInfo.moduleDefinition);
if (await fs.pathExists(configuredPath)) {
return path.dirname(configuredPath);
}
}
// Fallback: search skills/ and src/ directories
for (const dir of ['skills', 'src']) {
const rootCandidate = path.join(cloneDir, dir, 'module.yaml');
if (await fs.pathExists(rootCandidate)) {
return path.dirname(rootCandidate);
}
const dirPath = path.join(cloneDir, dir);
if (await fs.pathExists(dirPath)) {
const entries = await fs.readdir(dirPath, { withFileTypes: true });
for (const entry of entries) {
if (entry.isDirectory()) {
const subCandidate = path.join(dirPath, entry.name, 'module.yaml');
if (await fs.pathExists(subCandidate)) {
return path.dirname(subCandidate);
}
}
}
}
}
// Check repo root
const rootCandidate = path.join(cloneDir, 'module.yaml');
if (await fs.pathExists(rootCandidate)) {
return path.dirname(rootCandidate);
}
return moduleInfo.moduleDefinition ? path.dirname(path.join(cloneDir, moduleInfo.moduleDefinition)) : null;
}
// ─── Normalization ────────────────────────────────────────────────────────
/**
* Normalize a community module entry to a consistent shape.
* @param {Object} mod - Raw module from community-index.yaml
* @returns {Object} Normalized module info
*/
_normalizeCommunityModule(mod) {
return {
key: mod.name,
code: mod.code,
name: mod.display_name || mod.name,
displayName: mod.display_name || mod.name,
description: mod.description || '',
url: mod.repository || mod.url,
moduleDefinition: mod.module_definition || mod['module-definition'],
npmPackage: mod.npm_package || mod.npmPackage || null,
author: mod.author || '',
license: mod.license || '',
type: 'community',
category: mod.category || '',
subcategory: mod.subcategory || '',
keywords: mod.keywords || [],
version: mod.version || null,
approvedTag: mod.approved_tag || null,
approvedSha: mod.approved_sha || null,
approvedDate: mod.approved_date || null,
reviewer: mod.reviewer || null,
trustTier: mod.trust_tier || 'unverified',
promoted: mod.promoted === true,
promotedRank: mod.promoted_rank || null,
defaultSelected: false,
builtIn: false,
isExternal: true,
};
}
}
module.exports = { CommunityModuleManager };

View File

@ -4,9 +4,9 @@ const path = require('node:path');
const { execSync } = require('node:child_process');
const yaml = require('yaml');
const prompts = require('../prompts');
const { RegistryClient } = require('./registry-client');
const { resolveChannel, tagExists, parseGitHubRepo } = require('./channel-resolver');
const { decideChannelForModule } = require('./channel-plan');
const { getProjectRoot } = require('../project-root');
const VALID_CHANNELS = new Set(['stable', 'next', 'pinned']);
@ -46,15 +46,12 @@ async function writeChannelMarker(markerPath, data) {
}
}
const MARKETPLACE_OWNER = 'bmad-code-org';
const MARKETPLACE_REPO = 'bmad-plugins-marketplace';
const MARKETPLACE_REF = 'main';
const FALLBACK_CONFIG_PATH = path.join(__dirname, 'registry-fallback.yaml');
const REGISTRY_CONFIG_PATH = path.join(getProjectRoot(), 'bmad-modules.yaml');
/**
* Manages official modules from the remote BMad marketplace registry.
* Fetches registry/official.yaml from GitHub; falls back to the bundled
* external-official-modules.yaml when the network is unavailable.
* Manages official modules from the bundled registry file. The remote
* marketplace fetch has been retired; this repo is the single source of truth
* for which official modules exist and how they are displayed.
*
* @class ExternalModuleManager
*/
@ -65,9 +62,7 @@ class ExternalModuleManager {
// ExternalModuleManager) sees resolutions made during install.
static _resolutions = new Map();
constructor() {
this._client = new RegistryClient();
}
constructor() {}
/**
* Get the most recent channel resolution for a module (if any).
@ -79,8 +74,7 @@ class ExternalModuleManager {
}
/**
* Load the official modules registry from GitHub, falling back to the
* bundled YAML file if the fetch fails.
* Load the official modules registry from the bundled YAML file.
* @returns {Object} Parsed YAML content with modules array
*/
async loadExternalModulesConfig() {
@ -88,23 +82,10 @@ class ExternalModuleManager {
return this.cachedModules;
}
// Try remote registry first
try {
const config = await this._client.fetchGitHubYaml(MARKETPLACE_OWNER, MARKETPLACE_REPO, 'registry/official.yaml', MARKETPLACE_REF);
if (config?.modules?.length) {
this.cachedModules = config;
return config;
}
} catch {
// Fall through to local fallback
}
// Fallback to bundled file
try {
const content = await fs.readFile(FALLBACK_CONFIG_PATH, 'utf8');
const content = await fs.readFile(REGISTRY_CONFIG_PATH, 'utf8');
const config = yaml.parse(content);
this.cachedModules = config;
await prompts.log.warn('Could not reach BMad registry; using bundled module list.');
return config;
} catch (error) {
await prompts.log.warn(`Failed to load modules config: ${error.message}`);
@ -130,6 +111,7 @@ class ExternalModuleManager {
defaultSelected: mod.default_selected === true || mod.defaultSelected === true,
type: mod.type || 'bmad-org',
npmPackage: mod.npm_package || mod.npmPackage || null,
pluginName: mod.plugin_name || mod.pluginName || null,
defaultChannel: normalizeChannelName(mod.default_channel || mod.defaultChannel) || 'stable',
builtIn: mod.built_in === true,
isExternal: mod.built_in !== true,

View File

@ -231,14 +231,6 @@ class OfficialModules {
return externalSource;
}
// Check community modules (pass channelOptions for --next/--pin overrides)
const { CommunityModuleManager } = require('./community-manager');
const communityMgr = new CommunityModuleManager();
const communitySource = await communityMgr.findModuleSource(moduleCode, options);
if (communitySource) {
return communitySource;
}
// Check custom modules (from user-provided URLs, already cloned to cache)
const { CustomModuleManager } = require('./custom-module-manager');
const customMgr = new CustomModuleManager();
@ -269,21 +261,6 @@ class OfficialModules {
return this.installFromResolution(resolved, bmadDir, fileTrackingCallback, options);
}
// Community modules whose cloned repo ships marketplace.json get the same
// skill-level install treatment as custom-source installs. If the in-process
// cache wasn't populated (e.g. caller skipped the pre-clone phase), fall
// back to resolving directly from `~/.bmad/cache/community-modules/<name>/`
// so we don't silently regress to the legacy half-install path.
const { CommunityModuleManager } = require('./community-manager');
const communityMgr = new CommunityModuleManager();
let communityResolved = communityMgr.getPluginResolution(moduleName);
if (!communityResolved) {
communityResolved = await communityMgr.resolveFromCache(moduleName);
}
if (communityResolved) {
return this.installFromResolution(communityResolved, bmadDir, fileTrackingCallback, options);
}
const sourcePath = await this.findModuleSource(moduleName, {
silent: options.silent,
channelOptions: options.channelOptions,
@ -310,14 +287,9 @@ class OfficialModules {
const manifestObj = new Manifest();
const versionInfo = await manifestObj.getModuleVersionInfo(moduleName, bmadDir, sourcePath);
// Pick up channel resolution recorded by whichever manager did the clone.
const externalResolution = this.externalModuleManager.getResolution(moduleName);
let communityResolution = null;
if (!externalResolution) {
const { CommunityModuleManager } = require('./community-manager');
communityResolution = new CommunityModuleManager().getResolution(moduleName);
}
const resolution = externalResolution || communityResolution;
// Pick up channel resolution recorded by the external manager (the only
// manager that does pre-clone resolution now that community is retired).
const resolution = this.externalModuleManager.getResolution(moduleName);
await manifestObj.addModule(bmadDir, moduleName, {
version: resolution?.version || versionInfo.version,
@ -326,8 +298,6 @@ class OfficialModules {
repoUrl: versionInfo.repoUrl,
channel: resolution?.channel,
sha: resolution?.sha,
registryApprovedTag: communityResolution?.registryApprovedTag,
registryApprovedSha: communityResolution?.registryApprovedSha,
});
return { success: true, module: moduleName, path: targetPath, versionInfo };
@ -375,27 +345,19 @@ class OfficialModules {
await this.createModuleDirectories(resolved.code, bmadDir, options);
}
// Update manifest. For community installs we honor the channel resolved by
// CommunityModuleManager (stable/next/pinned) and propagate the registry's
// approved tag/sha. For custom-source installs we derive channel from the
// Update manifest. For custom-source installs we derive channel from the
// cloneRef (present → pinned, absent → next; local paths have no channel).
const { Manifest } = require('../core/manifest');
const manifestObj = new Manifest();
const hasGitClone = !!resolved.repoUrl;
const isCommunity = resolved.communitySource === true;
const manifestEntry = {
version: resolved.communityVersion || resolved.cloneRef || (hasGitClone ? 'main' : resolved.version || null),
source: isCommunity ? 'community' : 'custom',
version: resolved.cloneRef || (hasGitClone ? 'main' : resolved.version || null),
source: 'custom',
npmPackage: null,
repoUrl: resolved.repoUrl || null,
};
if (isCommunity) {
if (resolved.communityChannel) manifestEntry.channel = resolved.communityChannel;
if (resolved.cloneSha) manifestEntry.sha = resolved.cloneSha;
if (resolved.registryApprovedTag) manifestEntry.registryApprovedTag = resolved.registryApprovedTag;
if (resolved.registryApprovedSha) manifestEntry.registryApprovedSha = resolved.registryApprovedSha;
} else if (hasGitClone) {
if (hasGitClone) {
manifestEntry.channel = resolved.cloneRef ? 'pinned' : 'next';
if (resolved.cloneSha) manifestEntry.sha = resolved.cloneSha;
if (resolved.rawInput) manifestEntry.rawSource = resolved.rawInput;
@ -408,11 +370,10 @@ class OfficialModules {
module: resolved.code,
path: targetPath,
// Mirror the manifestEntry.version precedence above so downstream summary
// lines show the same string we just wrote to disk (community installs
// use the registry-approved tag via `communityVersion`; custom git-backed
// lines show the same string we just wrote to disk (custom git-backed
// installs show the cloned ref or 'main').
versionInfo: {
version: resolved.communityVersion || resolved.cloneRef || (hasGitClone ? 'main' : resolved.version || ''),
version: resolved.cloneRef || (hasGitClone ? 'main' : resolved.version || ''),
},
};
}

View File

@ -1,187 +0,0 @@
const https = require('node:https');
const yaml = require('yaml');
/**
* Build a rich Error from a non-2xx response. Includes the URL, the GitHub
* JSON error message (or a truncated body snippet), rate-limit reset time,
* and Retry-After — anything present that would help a user recover.
*/
function buildHttpError(url, res, body) {
const parts = [`HTTP ${res.statusCode} ${url}`];
if (body) {
try {
const parsed = JSON.parse(body);
if (parsed.message) parts.push(parsed.message);
if (parsed.documentation_url) parts.push(`(see ${parsed.documentation_url})`);
} catch {
const snippet = body.slice(0, 200).trim();
if (snippet) parts.push(snippet);
}
}
const remaining = res.headers['x-ratelimit-remaining'];
const reset = res.headers['x-ratelimit-reset'];
if (remaining === '0' && reset) {
parts.push(`rate limit exhausted; resets at ${new Date(Number(reset) * 1000).toISOString()}`);
}
const retryAfter = res.headers['retry-after'];
if (retryAfter) parts.push(`retry after ${retryAfter}`);
return new Error(parts.join(' — '));
}
/**
* Shared HTTP client for fetching registry data from GitHub.
* Used by ExternalModuleManager, CommunityModuleManager, and CustomModuleManager.
*/
class RegistryClient {
constructor(options = {}) {
this.timeout = options.timeout || 10_000;
}
/**
* Fetch a URL and return the response body as a string.
* Follows up to 3 redirects (GitHub sometimes 301s).
* @param {string} url - URL to fetch
* @param {number} [timeout] - Timeout in ms (overrides default)
* @param {number} [maxRedirects=3] - Maximum redirects to follow
* @returns {Promise<string>} Response body
*/
fetch(url, timeout, maxRedirects = 3) {
const timeoutMs = timeout || this.timeout;
return new Promise((resolve, reject) => {
const req = https
.get(url, { timeout: timeoutMs }, (res) => {
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
if (maxRedirects <= 0) {
return reject(new Error('Too many redirects'));
}
return this.fetch(res.headers.location, timeoutMs, maxRedirects - 1).then(resolve, reject);
}
let data = '';
res.on('data', (chunk) => (data += chunk));
res.on('end', () => {
if (res.statusCode !== 200) {
return reject(buildHttpError(url, res, data));
}
resolve(data);
});
})
.on('error', reject)
.on('timeout', () => {
req.destroy();
reject(new Error('Request timed out'));
});
});
}
/**
* Fetch a URL and parse the response as YAML.
* @param {string} url - URL to fetch
* @param {number} [timeout] - Timeout in ms
* @returns {Promise<Object>} Parsed YAML content
*/
async fetchYaml(url, timeout) {
const content = await this.fetch(url, timeout);
return yaml.parse(content);
}
/**
* Fetch a file from a GitHub repo using the Contents API first,
* falling back to raw.githubusercontent.com if the API fails.
*
* The API endpoint (`api.github.com`) is tried first because corporate
* proxies commonly block `raw.githubusercontent.com` while allowing
* `api.github.com` under the "Software Development" category.
*
* @param {string} owner - Repository owner (e.g., 'bmad-code-org')
* @param {string} repo - Repository name (e.g., 'bmad-plugins-marketplace')
* @param {string} filePath - Path within the repo (e.g., 'registry/official.yaml')
* @param {string} ref - Git ref (branch, tag, or SHA; e.g., 'main')
* @param {number} [timeout] - Timeout in ms (overrides default)
* @returns {Promise<string>} Raw file content
*/
async fetchGitHubFile(owner, repo, filePath, ref, timeout) {
const apiUrl = `https://api.github.com/repos/${owner}/${repo}/contents/${filePath}?ref=${ref}`;
const rawUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${ref}/${filePath}`;
// Try GitHub Contents API first (with raw content accept header)
try {
return await this._fetchWithHeaders(apiUrl, { Accept: 'application/vnd.github.raw+json' }, timeout);
} catch (apiError) {
// API failed — fall back to raw CDN
try {
return await this.fetch(rawUrl, timeout);
} catch (cdnError) {
throw new AggregateError([apiError, cdnError], `Both GitHub API and raw CDN failed for ${filePath}`);
}
}
}
/**
* Fetch a file from GitHub and parse as YAML.
* @param {string} owner - Repository owner
* @param {string} repo - Repository name
* @param {string} filePath - Path within the repo
* @param {string} ref - Git ref
* @param {number} [timeout] - Timeout in ms
* @returns {Promise<Object>} Parsed YAML content
*/
async fetchGitHubYaml(owner, repo, filePath, ref, timeout) {
const content = await this.fetchGitHubFile(owner, repo, filePath, ref, timeout);
return yaml.parse(content);
}
/**
* Fetch a URL with custom headers. Used for GitHub API requests.
* Follows up to 3 redirects.
* @param {string} url - URL to fetch
* @param {Object} headers - Request headers
* @param {number} [timeout] - Timeout in ms
* @param {number} [maxRedirects=3] - Maximum redirects to follow
* @returns {Promise<string>} Response body
* @private
*/
_fetchWithHeaders(url, headers, timeout, maxRedirects = 3) {
const timeoutMs = timeout || this.timeout;
const parsed = new URL(url);
const options = {
hostname: parsed.hostname,
path: parsed.pathname + parsed.search,
timeout: timeoutMs,
headers: {
'User-Agent': 'bmad-installer',
...headers,
},
};
return new Promise((resolve, reject) => {
const req = https
.get(options, (res) => {
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
if (maxRedirects <= 0) {
return reject(new Error('Too many redirects'));
}
return this._fetchWithHeaders(res.headers.location, headers, timeoutMs, maxRedirects - 1).then(resolve, reject);
}
let data = '';
res.on('data', (chunk) => (data += chunk));
res.on('end', () => {
if (res.statusCode !== 200) {
return reject(buildHttpError(url, res, data));
}
resolve(data);
});
})
.on('error', reject)
.on('timeout', () => {
req.destroy();
reject(new Error('Request timed out'));
});
});
}
}
module.exports = { RegistryClient };

View File

@ -818,32 +818,18 @@ class UI {
// Phase 1: Official modules
const officialSelected = await this._selectOfficialModules(installedModuleIds, installedModuleVersions, channelOptions);
// Determine which installed modules are NOT official (community or custom).
// These must be preserved even if the user declines to browse community/custom.
const officialCodes = new Set(officialSelected);
// Identify installed modules that aren't official (previously installed
// community modules or custom-source modules). Preserve them on update;
// they can be managed via --custom-source, uninstall, or a dedicated installer.
const externalManager = new ExternalModuleManager();
const registryModules = await externalManager.listAvailable();
const officialRegistryCodes = new Set(['core', 'bmm', ...registryModules.map((m) => m.code)]);
const installedNonOfficial = [...installedModuleIds].filter((id) => !officialRegistryCodes.has(id));
// Phase 2: Community modules (category drill-down)
// Returns { codes, didBrowse } so we know if the user entered the flow
const communityResult = await this._browseCommunityModules(installedModuleIds);
// Phase 3: Custom URL modules
// Phase 2: Custom URL modules
const customSelected = await this._addCustomUrlModules(installedModuleIds);
// Merge all selections
const allSelected = new Set([...officialSelected, ...communityResult.codes, ...customSelected]);
// Auto-include installed non-official modules that the user didn't get
// a chance to manage (they declined to browse). If they did browse,
// trust their selections - they could have deselected intentionally.
if (!communityResult.didBrowse) {
for (const code of installedNonOfficial) {
allSelected.add(code);
}
}
const allSelected = new Set([...officialSelected, ...customSelected, ...installedNonOfficial]);
return [...allSelected];
}
@ -954,166 +940,6 @@ class UI {
return result;
}
/**
* Browse and select community modules using category drill-down.
* Featured/promoted modules appear at the top.
* @param {Set} installedModuleIds - Currently installed module IDs
* @returns {Object} { codes: string[], didBrowse: boolean }
*/
async _browseCommunityModules(installedModuleIds = new Set()) {
const browseCommunity = await prompts.confirm({
message: 'Would you like to browse community modules?',
default: false,
});
if (!browseCommunity) return { codes: [], didBrowse: false };
const { CommunityModuleManager } = require('./modules/community-manager');
const communityMgr = new CommunityModuleManager();
const s = await prompts.spinner();
s.start('Loading community module catalog...');
let categories, featured, allCommunity;
try {
[categories, featured, allCommunity] = await Promise.all([
communityMgr.getCategoryList(),
communityMgr.listFeatured(),
communityMgr.listAll(),
]);
s.stop(`Community catalog loaded (${allCommunity.length} modules)`);
} catch (error) {
s.error('Failed to load community catalog');
await prompts.log.warn(` ${error.message}`);
return { codes: [], didBrowse: false };
}
if (allCommunity.length === 0) {
await prompts.log.info('No community modules are currently available.');
return { codes: [], didBrowse: false };
}
const selectedCodes = new Set();
let browsing = true;
while (browsing) {
const categoryChoices = [];
// Featured section at top
if (featured.length > 0) {
categoryChoices.push({
value: '__featured__',
label: `\u2605 Featured (${featured.length} module${featured.length === 1 ? '' : 's'})`,
});
}
// Categories with module counts
for (const cat of categories) {
categoryChoices.push({
value: cat.slug,
label: `${cat.name} (${cat.moduleCount} module${cat.moduleCount === 1 ? '' : 's'})`,
});
}
// Special actions at bottom
categoryChoices.push(
{ value: '__all__', label: '\u25CE View all community modules' },
{ value: '__search__', label: '\u25CE Search by keyword' },
{ value: '__done__', label: '\u2713 Done browsing' },
);
const selectedCount = selectedCodes.size;
const categoryChoice = await prompts.select({
message: `Browse community modules${selectedCount > 0 ? ` (${selectedCount} selected)` : ''}:`,
choices: categoryChoices,
});
if (categoryChoice === '__done__') {
browsing = false;
continue;
}
let modulesToShow;
switch (categoryChoice) {
case '__featured__': {
modulesToShow = featured;
break;
}
case '__all__': {
modulesToShow = allCommunity;
break;
}
case '__search__': {
const query = await prompts.text({
message: 'Search community modules:',
placeholder: 'e.g., design, testing, game',
});
if (!query || query.trim() === '') continue;
modulesToShow = await communityMgr.searchByKeyword(query.trim());
if (modulesToShow.length === 0) {
await prompts.log.warn('No matching modules found.');
continue;
}
break;
}
default: {
modulesToShow = await communityMgr.listByCategory(categoryChoice);
}
}
// Build options for autocompleteMultiselect
const trustBadge = (tier) => {
if (tier === 'bmad-certified') return '\u2713';
if (tier === 'community-reviewed') return '\u25CB';
return '\u26A0';
};
const options = modulesToShow.map((mod) => {
const versionStr = mod.version ? ` (v${mod.version})` : '';
const badge = trustBadge(mod.trustTier);
return {
label: `${mod.displayName}${versionStr} [${badge}]`,
value: mod.code,
hint: mod.description,
};
});
// Pre-check modules that are already selected or installed
const initialValues = modulesToShow.filter((m) => selectedCodes.has(m.code) || installedModuleIds.has(m.code)).map((m) => m.code);
const selected = await prompts.autocompleteMultiselect({
message: 'Select community modules:',
options,
initialValues: initialValues.length > 0 ? initialValues : undefined,
required: false,
maxItems: Math.min(options.length, 10),
});
// Update accumulated selections: sync with what user selected in this view
const shownCodes = new Set(modulesToShow.map((m) => m.code));
for (const code of shownCodes) {
if (selected && selected.includes(code)) {
selectedCodes.add(code);
} else {
selectedCodes.delete(code);
}
}
}
if (selectedCodes.size > 0) {
const moduleLines = [];
for (const code of selectedCodes) {
const mod = await communityMgr.getModuleByCode(code);
moduleLines.push(` \u2022 ${mod?.displayName || code}`);
}
await prompts.log.message('Selected community modules:\n' + moduleLines.join('\n'));
}
return { codes: [...selectedCodes], didBrowse: true };
}
/**
* Prompt user to install modules from custom sources (Git URLs or local paths).
* @param {Set} installedModuleIds - Currently installed module IDs
@ -1121,7 +947,7 @@ class UI {
*/
async _addCustomUrlModules(installedModuleIds = new Set()) {
const addCustom = await prompts.confirm({
message: 'Would you like to install from a custom source (Git URL or local path)?',
message: 'Do you want to install custom or community modules (Git URL or local path)?',
default: false,
});
if (!addCustom) return [];
@ -1885,19 +1711,14 @@ class UI {
const haveFlagIntent = channelOptions.global || channelOptions.nextSet.size > 0 || channelOptions.pins.size > 0;
if (haveFlagIntent) return;
// Figure out which selected modules actually get a channel (externals +
// community modules). Bundled core/bmm and custom modules skip the picker.
// Figure out which selected modules actually get a channel (externals only).
// Bundled core/bmm and custom modules skip the picker.
const externalManager = new ExternalModuleManager();
const externals = await externalManager.listAvailable();
const externalByCode = new Map(externals.map((m) => [m.code, m]));
const { CommunityModuleManager } = require('./modules/community-manager');
const communityMgr = new CommunityModuleManager();
const community = await communityMgr.listAll();
const communityByCode = new Map(community.map((m) => [m.code, m]));
const channelSelectable = selectedModules.filter((code) => {
const info = externalByCode.get(code) || communityByCode.get(code);
const info = externalByCode.get(code);
return info && !info.builtIn;
});
if (channelSelectable.length === 0) return;
@ -1912,7 +1733,7 @@ class UI {
const { fetchStableTags, parseGitHubRepo } = require('./modules/channel-resolver');
for (const code of channelSelectable) {
const info = externalByCode.get(code) || communityByCode.get(code);
const info = externalByCode.get(code);
const repoUrl = info.url;
// Try to pre-resolve the top stable tag so we can surface it in the picker.
@ -1987,11 +1808,6 @@ class UI {
const externals = await externalManager.listAvailable();
const externalByCode = new Map(externals.map((m) => [m.code, m]));
const { CommunityModuleManager } = require('./modules/community-manager');
const communityMgr = new CommunityModuleManager();
const community = await communityMgr.listAll();
const communityByCode = new Map(community.map((m) => [m.code, m]));
const { fetchStableTags, classifyUpgrade, releaseNotesUrl } = require('./modules/channel-resolver');
const { parseGitHubRepo } = require('./modules/channel-resolver');
@ -2003,7 +1819,7 @@ class UI {
const existingWithChannel = selectedModules.filter((code) => {
const prev = existingByName.get(code);
if (!prev) return false;
const info = externalByCode.get(code) || communityByCode.get(code);
const info = externalByCode.get(code);
return info && !info.builtIn;
});
if (existingWithChannel.length > 0) {
@ -2018,7 +1834,7 @@ class UI {
const prev = existingByName.get(code);
if (!prev) continue;
const info = externalByCode.get(code) || communityByCode.get(code);
const info = externalByCode.get(code);
if (!info) continue;
// Bundled modules (core/bmm) ship with the installer binary itself —
// their version is stapled to the CLI version, not a git tag. Skip