Compare commits

..

6 Commits

Author SHA1 Message Date
Brian 23aa389ffc
Merge branch 'main' into feat/global-user-config 2026-04-27 20:26:15 -05:00
Jerome Revillard d907776b1d fix(skills): update config resolution in SKILL.md files migrated from workflow.md
The 5 skills whose workflow.md was absorbed into SKILL.md by PR #2308
still had the old config.yaml loading instruction. Updated them to use
resolve_config.py like all other skills.
2026-04-27 18:25:23 +02:00
Jerome Revillard f35424aa21 fix(skills): use resolve_config.py instead of reading config.yaml directly
Skills were reading _bmad/bmm|core/config.yaml directly, bypassing the
TOML merge mechanism. Now they call resolve_config.py first, with a
fallback to read the merge logic and apply it manually.
2026-04-27 18:25:08 +02:00
Jerome Revillard 97d546d3dc fix: update argparse descriptions to match actual layer count 2026-04-27 18:24:44 +02:00
Jerome Revillard 343ba22b2d fix(config): correct global layer priority — overrides installer defaults
The global user layer was lowest priority, meaning installer-generated
defaults in _bmad/config.user.toml always shadowed it. Reorder so
global user preferences override installer defaults but are still
overridden by hand-authored project customizations.

New order for resolve_config.py:
  base_team → base_user → global_user → custom_team → custom_user

New order for resolve_customization.py:
  defaults → global_user → team → user
2026-04-27 18:24:44 +02:00
Jerome Revillard 3a8bc08b18 feat(config): add per-user global config layer at ~/.bmad/config/
Users working across multiple worktrees or repos no longer need to
re-enter personal settings (user_name, communication_language,
user_skill_level) in every project. A global user layer at
~/.bmad/config/config.user.toml is merged as the lowest-priority
fallback in both resolve_config.py (5-layer) and
resolve_customization.py (4-layer). Project-level overrides always
win. Missing global dir is fully backward-compatible.

Closes #2338
2026-04-27 18:24:44 +02:00
13 changed files with 199 additions and 483 deletions

View File

@ -18,7 +18,7 @@ Use `npx bmad-method install` to set up BMad in your project. One command handle
- **Node.js** 20+ (the installer requires it)
- **Git** (for cloning external modules)
- **An AI tool** such as Claude Code or Cursor (run `npx bmad-method install --list-tools` to see all supported tools)
- **An AI tool** such as Claude Code or Cursor — or install without one using `--tools none`
:::
@ -122,8 +122,7 @@ Under `--yes`, patch and minor upgrades apply automatically. Majors stay frozen
| `--yes`, `-y` | Skip all prompts; accept flag values + defaults |
| `--directory <path>` | Install into this directory (default: current working dir) |
| `--modules <a,b,c>` | Exact module set. Core is auto-added. Not a delta — list everything you want kept. |
| `--tools <a,b>` | IDE/tool selection. Required for fresh `--yes` installs. Run `--list-tools` for valid IDs. |
| `--list-tools` | Print all supported tool/IDE IDs (with target directories) and exit. |
| `--tools <a,b>` or `--tools none` | IDE/tool selection. `none` skips tool config entirely. |
| `--action <type>` | `install`, `update`, or `quick-update`. Defaults based on existing install state. |
| `--custom-source <urls>` | Install custom modules from Git URLs or local paths |
| `--channel <stable\|next>` | Apply to all externals (aliased as `--all-stable` / `--all-next`) |
@ -166,17 +165,17 @@ npx bmad-method install --yes --modules bmm,bmb --all-next --tools claude-code
```bash
npx bmad-method install --yes --action update \
--modules bmm,bmb,gds
--modules bmm,bmb,gds \
--tools none
```
`--tools` is omitted intentionally — `--action update` reuses the tools configured during the first install.
**Mix channels — bmb on next, gds on stable:**
```bash
npx bmad-method install --yes --action update \
--modules bmm,bmb,cis,gds \
--next=bmb
--next=bmb \
--tools none
```
:::caution[Rate limit on shared IPs]
@ -205,7 +204,7 @@ For cross-machine reproducibility, don't rely on rerunning the same `--modules`
```bash
npx bmad-method install --yes --modules bmb,cis \
--pin bmb=v1.7.0 --pin cis=v0.4.2 --tools claude-code
--pin bmb=v1.7.0 --pin cis=v0.4.2 --tools none
```
## Troubleshooting

View File

@ -227,39 +227,37 @@ Prepare the content to append to the document:
### Architecture Completeness Checklist
Mark each item `[x]` only if validation confirms it; leave `[ ]` if it is missing, partial, or unverified. Any unchecked item must be reflected in the Gap Analysis above and in the Overall Status below.
**✅ Requirements Analysis**
**Requirements Analysis**
- [x] Project context thoroughly analyzed
- [x] Scale and complexity assessed
- [x] Technical constraints identified
- [x] Cross-cutting concerns mapped
- [ ] Project context thoroughly analyzed
- [ ] Scale and complexity assessed
- [ ] Technical constraints identified
- [ ] Cross-cutting concerns mapped
**✅ Architectural Decisions**
**Architectural Decisions**
- [x] Critical decisions documented with versions
- [x] Technology stack fully specified
- [x] Integration patterns defined
- [x] Performance considerations addressed
- [ ] Critical decisions documented with versions
- [ ] Technology stack fully specified
- [ ] Integration patterns defined
- [ ] Performance considerations addressed
**✅ Implementation Patterns**
**Implementation Patterns**
- [x] Naming conventions established
- [x] Structure patterns defined
- [x] Communication patterns specified
- [x] Process patterns documented
- [ ] Naming conventions established
- [ ] Structure patterns defined
- [ ] Communication patterns specified
- [ ] Process patterns documented
**✅ Project Structure**
**Project Structure**
- [ ] Complete directory structure defined
- [ ] Component boundaries established
- [ ] Integration points mapped
- [ ] Requirements to structure mapping complete
- [x] Complete directory structure defined
- [x] Component boundaries established
- [x] Integration points mapped
- [x] Requirements to structure mapping complete
### Architecture Readiness Assessment
**Overall Status:** {{READY FOR IMPLEMENTATION | READY WITH MINOR GAPS | NOT READY}} (choose READY FOR IMPLEMENTATION only when all 16 checklist items are `[x]` and no Critical Gaps remain; choose NOT READY when any Critical Gap is open or any Requirements Analysis or Architectural Decisions item is unchecked; otherwise READY WITH MINOR GAPS)
**Overall Status:** READY FOR IMPLEMENTATION
**Confidence Level:** {{high/medium/low}} based on validation results

View File

@ -1,33 +1,33 @@
module,skill,display-name,menu-code,description,action,args,phase,after,before,required,output-location,outputs
BMad Method,_meta,,,,,,,,,false,https://docs.bmad-method.org/llms.txt,
BMad Method,bmad-document-project,Document Project,DP,Analyze an existing project to produce useful documentation.,,,anytime,,,false,project-knowledge,*
BMad Method,bmad-generate-project-context,Generate Project Context,GPC,Scan existing codebase to generate a lean LLM-optimized project-context.md. Essential for brownfield projects.,,,anytime,,,false,output_folder,project context
BMad Method,bmad-quick-dev,Quick Dev,QQ,Unified intent-in code-out workflow: clarify plan implement review and present.,,,anytime,,,false,implementation_artifacts,spec and project implementation
BMad Method,bmad-correct-course,Correct Course,CC,Navigate significant changes. May recommend start over update PRD redo architecture sprint planning or correct epics and stories.,,,anytime,,,false,planning_artifacts,change proposal
BMad Method,bmad-document-project,Document Project,DP,Analyze an existing project to produce useful documentation.,,anytime,,,false,project-knowledge,*
BMad Method,bmad-generate-project-context,Generate Project Context,GPC,Scan existing codebase to generate a lean LLM-optimized project-context.md. Essential for brownfield projects.,,anytime,,,false,output_folder,project context
BMad Method,bmad-quick-dev,Quick Dev,QQ,Unified intent-in code-out workflow: clarify plan implement review and present.,,anytime,,,false,implementation_artifacts,spec and project implementation
BMad Method,bmad-correct-course,Correct Course,CC,Navigate significant changes. May recommend start over update PRD redo architecture sprint planning or correct epics and stories.,,anytime,,,false,planning_artifacts,change proposal
BMad Method,bmad-agent-tech-writer,Write Document,WD,"Describe in detail what you want, and the agent will follow documentation best practices. Multi-turn conversation with subprocess for research/review.",write,,anytime,,,false,project-knowledge,document
BMad Method,bmad-agent-tech-writer,Update Standards,US,Update agent memory documentation-standards.md with your specific preferences if you discover missing document conventions.,update-standards,,anytime,,,false,_bmad/_memory/tech-writer-sidecar,standards
BMad Method,bmad-agent-tech-writer,Mermaid Generate,MG,Create a Mermaid diagram based on user description. Will suggest diagram types if not specified.,mermaid,,anytime,,,false,planning_artifacts,mermaid diagram
BMad Method,bmad-agent-tech-writer,Validate Document,VD,Review the specified document against documentation standards and best practices. Returns specific actionable improvement suggestions organized by priority.,validate,[path],anytime,,,false,planning_artifacts,validation report
BMad Method,bmad-agent-tech-writer,Explain Concept,EC,Create clear technical explanations with examples and diagrams for complex concepts.,explain,[topic],anytime,,,false,project_knowledge,explanation
BMad Method,bmad-brainstorming,Brainstorm Project,BP,Expert guided facilitation through a single or multiple techniques.,,,1-analysis,,,false,planning_artifacts,brainstorming session
BMad Method,bmad-market-research,Market Research,MR,Market analysis competitive landscape customer needs and trends.,,,1-analysis,,,false,planning_artifacts|project-knowledge,research documents
BMad Method,bmad-domain-research,Domain Research,DR,Industry domain deep dive subject matter expertise and terminology.,,,1-analysis,,,false,planning_artifacts|project_knowledge,research documents
BMad Method,bmad-technical-research,Technical Research,TR,Technical feasibility architecture options and implementation approaches.,,,1-analysis,,,false,planning_artifacts|project_knowledge,research documents
BMad Method,bmad-brainstorming,Brainstorm Project,BP,Expert guided facilitation through a single or multiple techniques.,,1-analysis,,,false,planning_artifacts,brainstorming session
BMad Method,bmad-market-research,Market Research,MR,"Market analysis competitive landscape customer needs and trends.",,1-analysis,,,false,"planning_artifacts|project-knowledge",research documents
BMad Method,bmad-domain-research,Domain Research,DR,Industry domain deep dive subject matter expertise and terminology.,,1-analysis,,,false,"planning_artifacts|project_knowledge",research documents
BMad Method,bmad-technical-research,Technical Research,TR,Technical feasibility architecture options and implementation approaches.,,1-analysis,,,false,"planning_artifacts|project_knowledge",research documents
BMad Method,bmad-product-brief,Create Brief,CB,An expert guided experience to nail down your product idea in a brief. a gentler approach than PRFAQ when you are already sure of your concept and nothing will sway you.,,-A,1-analysis,,,false,planning_artifacts,product brief
BMad Method,bmad-prfaq,PRFAQ Challenge,WB,Working Backwards guided experience to forge and stress-test your product concept to ensure you have a great product that users will love and need through the PRFAQ gauntlet to determine feasibility and alignment with user needs. alternative to product brief.,,-H,1-analysis,,,false,planning_artifacts,prfaq document
BMad Method,bmad-create-prd,Create PRD,CP,Expert led facilitation to produce your Product Requirements Document.,,,2-planning,,,true,planning_artifacts,prd
BMad Method,bmad-create-prd,Create PRD,CP,Expert led facilitation to produce your Product Requirements Document.,,2-planning,,,true,planning_artifacts,prd
BMad Method,bmad-validate-prd,Validate PRD,VP,,,[path],2-planning,bmad-create-prd,,false,planning_artifacts,prd validation report
BMad Method,bmad-edit-prd,Edit PRD,EP,,,[path],2-planning,bmad-validate-prd,,false,planning_artifacts,updated prd
BMad Method,bmad-create-ux-design,Create UX,CU,"Guidance through realizing the plan for your UX, strongly recommended if a UI is a primary piece of the proposed project.",,,2-planning,bmad-create-prd,,false,planning_artifacts,ux design
BMad Method,bmad-create-architecture,Create Architecture,CA,Guided workflow to document technical decisions.,,,3-solutioning,,,true,planning_artifacts,architecture
BMad Method,bmad-create-epics-and-stories,Create Epics and Stories,CE,,,,3-solutioning,bmad-create-architecture,,true,planning_artifacts,epics and stories
BMad Method,bmad-check-implementation-readiness,Check Implementation Readiness,IR,Ensure PRD UX Architecture and Epics Stories are aligned.,,,3-solutioning,bmad-create-epics-and-stories,,true,planning_artifacts,readiness report
BMad Method,bmad-sprint-planning,Sprint Planning,SP,Kicks off implementation by producing a plan the implementation agents will follow in sequence for every story.,,,4-implementation,,,true,implementation_artifacts,sprint status
BMad Method,bmad-sprint-status,Sprint Status,SS,Anytime: Summarize sprint status and route to next workflow.,,,4-implementation,bmad-sprint-planning,,false,,
BMad Method,bmad-create-story,Create Story,CS,Story cycle start: Prepare first found story in the sprint plan that is next or a specific epic/story designation.,create,,4-implementation,bmad-sprint-planning,bmad-create-story:validate,true,implementation_artifacts,story
BMad Method,bmad-create-ux-design,Create UX,CU,"Guidance through realizing the plan for your UX, strongly recommended if a UI is a primary piece of the proposed project.",,2-planning,bmad-create-prd,,false,planning_artifacts,ux design
BMad Method,bmad-create-architecture,Create Architecture,CA,Guided workflow to document technical decisions.,,3-solutioning,,,true,planning_artifacts,architecture
BMad Method,bmad-create-epics-and-stories,Create Epics and Stories,CE,,,3-solutioning,bmad-create-architecture,,true,planning_artifacts,epics and stories
BMad Method,bmad-check-implementation-readiness,Check Implementation Readiness,IR,Ensure PRD UX Architecture and Epics Stories are aligned.,,3-solutioning,bmad-create-epics-and-stories,,true,planning_artifacts,readiness report
BMad Method,bmad-sprint-planning,Sprint Planning,SP,Kicks off implementation by producing a plan the implementation agents will follow in sequence for every story.,,4-implementation,,,true,implementation_artifacts,sprint status
BMad Method,bmad-sprint-status,Sprint Status,SS,Anytime: Summarize sprint status and route to next workflow.,,4-implementation,bmad-sprint-planning,,false,,
BMad Method,bmad-create-story,Create Story,CS,"Story cycle start: Prepare first found story in the sprint plan that is next or a specific epic/story designation.",create,,4-implementation,bmad-sprint-planning,bmad-create-story:validate,true,implementation_artifacts,story
BMad Method,bmad-create-story,Validate Story,VS,Validates story readiness and completeness before development work begins.,validate,,4-implementation,bmad-create-story:create,bmad-dev-story,false,implementation_artifacts,story validation report
BMad Method,bmad-dev-story,Dev Story,DS,Story cycle: Execute story implementation tasks and tests then CR then back to DS if fixes needed.,,,4-implementation,bmad-create-story:validate,,true,,
BMad Method,bmad-code-review,Code Review,CR,Story cycle: If issues back to DS if approved then next CS or ER if epic complete.,,,4-implementation,bmad-dev-story,,false,,
BMad Method,bmad-checkpoint-preview,Checkpoint,CK,Guided walkthrough of a change from purpose and context into details. Use for human review of commits branches or PRs.,,,4-implementation,,,false,,
BMad Method,bmad-qa-generate-e2e-tests,QA Automation Test,QA,Generate automated API and E2E tests for implemented code. NOT for code review or story validation — use CR for that.,,,4-implementation,bmad-dev-story,,false,implementation_artifacts,test suite
BMad Method,bmad-retrospective,Retrospective,ER,Optional at epic end: Review completed work lessons learned and next epic or if major issues consider CC.,,,4-implementation,bmad-code-review,,false,implementation_artifacts,retrospective
BMad Method,bmad-dev-story,Dev Story,DS,Story cycle: Execute story implementation tasks and tests then CR then back to DS if fixes needed.,,4-implementation,bmad-create-story:validate,,true,,
BMad Method,bmad-code-review,Code Review,CR,Story cycle: If issues back to DS if approved then next CS or ER if epic complete.,,4-implementation,bmad-dev-story,,false,,
BMad Method,bmad-checkpoint-preview,Checkpoint,CK,Guided walkthrough of a change from purpose and context into details. Use for human review of commits branches or PRs.,,4-implementation,,,false,,
BMad Method,bmad-qa-generate-e2e-tests,QA Automation Test,QA,Generate automated API and E2E tests for implemented code. NOT for code review or story validation — use CR for that.,,4-implementation,bmad-dev-story,,false,implementation_artifacts,test suite
BMad Method,bmad-retrospective,Retrospective,ER,Optional at epic end: Review completed work lessons learned and next epic or if major issues consider CC.,,4-implementation,bmad-code-review,,false,implementation_artifacts,retrospective

Can't render this file because it has a wrong number of fields in line 3.

View File

@ -5,11 +5,15 @@ default_selected: true # This module will be selected by default for new install
# Variables from Core Config inserted:
## user_name
## project_name
## communication_language
## document_output_language
## output_folder
project_name:
prompt: "What is your project called?"
default: "{directory_name}"
result: "{value}"
user_skill_level:
prompt:
- "What is your development experience level?"

View File

@ -1,13 +1,13 @@
module,skill,display-name,menu-code,description,action,args,phase,after,before,required,output-location,outputs
Core,_meta,,,,,,,,,false,https://docs.bmad-method.org/llms.txt,
Core,bmad-brainstorming,Brainstorming,BSP,Use early in ideation or when stuck generating ideas.,,,anytime,,,false,{output_folder}/brainstorming,brainstorming session
Core,bmad-party-mode,Party Mode,PM,Orchestrate multi-agent discussions when you need multiple perspectives or want agents to collaborate.,,,anytime,,,false,,
Core,bmad-help,BMad Help,BH,,,,anytime,,,false,,
Core,bmad-index-docs,Index Docs,ID,Use when LLM needs to understand available docs without loading everything.,,,anytime,,,false,,
Core,bmad-shard-doc,Shard Document,SD,Use when doc becomes too large (>500 lines) to manage effectively.,,[path],anytime,,,false,,
Core,bmad-editorial-review-prose,Editorial Review - Prose,EP,Use after drafting to polish written content.,,[path],anytime,,,false,report located with target document,three-column markdown table with suggested fixes
Core,bmad-editorial-review-structure,Editorial Review - Structure,ES,Use when doc produced from multiple subprocesses or needs structural improvement.,,[path],anytime,,,false,report located with target document,
Core,bmad-review-adversarial-general,Adversarial Review,AR,"Use for quality assurance or before finalizing deliverables. Code Review in other modules runs this automatically, but also useful for document reviews.",,[path],anytime,,,false,,
Core,bmad-review-edge-case-hunter,Edge Case Hunter Review,ECH,Use alongside adversarial review for orthogonal coverage — method-driven not attitude-driven.,,[path],anytime,,,false,,
Core,bmad-distillator,Distillator,DG,Use when you need token-efficient distillates that preserve all information for downstream LLM consumption.,,[path],anytime,,,false,adjacent to source document or specified output_path,distillate markdown file(s)
Core,bmad-customize,BMad Customize,BC,"Use when you want to change how an agent or workflow behaves — add persistent facts, swap templates, insert activation hooks, or customize menus. Scans what's customizable, picks the right scope (agent vs workflow), writes the override to _bmad/custom/, and verifies the merge. No TOML hand-authoring required.",,,anytime,,,false,{project-root}/_bmad/custom,TOML override files
Core,bmad-brainstorming,Brainstorming,BSP,Use early in ideation or when stuck generating ideas.,,anytime,,,false,{output_folder}/brainstorming,brainstorming session
Core,bmad-party-mode,Party Mode,PM,Orchestrate multi-agent discussions when you need multiple perspectives or want agents to collaborate.,,anytime,,,false,,
Core,bmad-help,BMad Help,BH,,,anytime,,,false,,
Core,bmad-index-docs,Index Docs,ID,Use when LLM needs to understand available docs without loading everything.,,anytime,,,false,,
Core,bmad-shard-doc,Shard Document,SD,Use when doc becomes too large (>500 lines) to manage effectively.,[path],anytime,,,false,,
Core,bmad-editorial-review-prose,Editorial Review - Prose,EP,Use after drafting to polish written content.,[path],anytime,,,false,report located with target document,three-column markdown table with suggested fixes
Core,bmad-editorial-review-structure,Editorial Review - Structure,ES,Use when doc produced from multiple subprocesses or needs structural improvement.,[path],anytime,,,false,report located with target document,
Core,bmad-review-adversarial-general,Adversarial Review,AR,"Use for quality assurance or before finalizing deliverables. Code Review in other modules runs this automatically, but also useful for document reviews.",[path],anytime,,,false,,
Core,bmad-review-edge-case-hunter,Edge Case Hunter Review,ECH,Use alongside adversarial review for orthogonal coverage — method-driven not attitude-driven.,[path],anytime,,,false,,
Core,bmad-distillator,Distillator,DG,Use when you need token-efficient distillates that preserve all information for downstream LLM consumption.,[path],anytime,,,false,adjacent to source document or specified output_path,distillate markdown file(s)
Core,bmad-customize,BMad Customize,BC,"Use when you want to change how an agent or workflow behaves — add persistent facts, swap templates, insert activation hooks, or customize menus. Scans what's customizable, picks the right scope (agent vs workflow), writes the override to _bmad/custom/, and verifies the merge. No TOML hand-authoring required.",,anytime,,,false,{project-root}/_bmad/custom,TOML override files

Can't render this file because it has a wrong number of fields in line 3.

View File

@ -11,11 +11,6 @@ user_name:
default: "BMad"
result: "{value}"
project_name:
prompt: "What is your project called?"
default: "{directory_name}"
result: "{value}"
communication_language:
prompt: "What language should agents use when chatting with you?"
scope: user

View File

@ -1813,12 +1813,12 @@ async function runTests() {
const moduleConfigs = {
core: {
user_name: 'TestUser',
project_name: 'demo-project',
communication_language: 'Spanish',
document_output_language: 'English',
output_folder: '_bmad-output',
},
bmm: {
project_name: 'demo-project',
user_skill_level: 'expert',
planning_artifacts: '{project-root}/_bmad-output/planning-artifacts',
implementation_artifacts: '{project-root}/_bmad-output/implementation-artifacts',
@ -1826,10 +1826,7 @@ async function runTests() {
// Spread-from-core pollution: legacy per-module config.yaml merges
// core values into every module; writeCentralConfig must strip these
// from [modules.bmm] so core values only live in [core].
// project_name is now a core key (#2279), so it joins user_name etc.
// as a spread-from-core key that must be stripped.
user_name: 'TestUser',
project_name: 'stale-bmm-copy',
communication_language: 'Spanish',
document_output_language: 'English',
output_folder: '_bmad-output',
@ -1877,7 +1874,6 @@ async function runTests() {
assert(teamContent.includes('[core]'), 'config.toml has [core] section');
assert(teamContent.includes('document_output_language = "English"'), 'Team-scope core key lands in config.toml');
assert(teamContent.includes('output_folder = "_bmad-output"'), 'Team-scope output_folder lands in config.toml');
assert(teamContent.includes('project_name = "demo-project"'), 'project_name lands in [core] (core key as of #2279)');
assert(!teamContent.includes('user_name'), 'user_name (scope: user) is absent from config.toml');
assert(!teamContent.includes('communication_language'), 'communication_language (scope: user) is absent from config.toml');
@ -1892,9 +1888,7 @@ async function runTests() {
assert(bmmTeamMatch !== null, 'config.toml has [modules.bmm] section');
if (bmmTeamMatch) {
const bmmTeamBlock = bmmTeamMatch[0];
assert(bmmTeamBlock.includes('planning_artifacts'), 'bmm-owned team-scope key (planning_artifacts) lands under [modules.bmm]');
assert(!bmmTeamBlock.includes('project_name'), 'project_name stripped from [modules.bmm] (now a core key, #2279)');
assert(!bmmTeamBlock.includes('stale-bmm-copy'), 'stale bmm-copy of project_name not leaked into config.toml');
assert(bmmTeamBlock.includes('project_name = "demo-project"'), 'bmm team-scope key lands under [modules.bmm]');
assert(!bmmTeamBlock.includes('user_name'), 'user_name stripped from [modules.bmm] (core-key pollution)');
assert(!bmmTeamBlock.includes('communication_language'), 'communication_language stripped from [modules.bmm]');
assert(!bmmTeamBlock.includes('user_skill_level'), 'user_skill_level (scope: user) absent from [modules.bmm] in config.toml');
@ -2779,210 +2773,6 @@ async function runTests() {
console.log('');
// ============================================================
// Test Suite 42: --tools flag parsing & validation (#2326)
// ============================================================
console.log(`${colors.yellow}Test Suite 42: --tools flag parsing & validation${colors.reset}\n`);
try {
const { UI } = require('../tools/installer/ui');
const ui = new UI();
const known = new Set(['claude-code', 'cursor', 'windsurf']);
assert(
JSON.stringify(ui._parseToolsFlag('claude-code', known)) === JSON.stringify(['claude-code']),
'parseToolsFlag returns single ID',
);
assert(
JSON.stringify(ui._parseToolsFlag('claude-code,cursor', known)) === JSON.stringify(['claude-code', 'cursor']),
'parseToolsFlag returns multiple IDs',
);
assert(
JSON.stringify(ui._parseToolsFlag(' claude-code , cursor ', known)) === JSON.stringify(['claude-code', 'cursor']),
'parseToolsFlag trims whitespace',
);
let emptyErr;
try {
ui._parseToolsFlag('', known);
} catch (error) {
emptyErr = error;
}
assert(
emptyErr && emptyErr.expected === true && /empty/i.test(emptyErr.message),
'parseToolsFlag rejects empty string with expected=true',
);
let commasOnlyErr;
try {
ui._parseToolsFlag(' , , ', known);
} catch (error) {
commasOnlyErr = error;
}
assert(commasOnlyErr && commasOnlyErr.expected === true, 'parseToolsFlag rejects whitespace/comma-only input');
let noneErr;
try {
ui._parseToolsFlag('none', known);
} catch (error) {
noneErr = error;
}
assert(noneErr && noneErr.expected === true && /Unknown tool ID/.test(noneErr.message), 'parseToolsFlag rejects "none" as unknown ID');
let typoErr;
try {
ui._parseToolsFlag('claude-code,claude-cdoe', known);
} catch (error) {
typoErr = error;
}
const typoHeader = typoErr ? typoErr.message.split('\n')[0] : '';
assert(
typoErr && typoErr.expected === true && /claude-cdoe/.test(typoHeader) && !/claude-code/.test(typoHeader),
'parseToolsFlag reports only the unknown ID in error header (valid ones not listed as unknown)',
);
// --list-tools and --tools validation must agree on what counts as a valid ID.
const { formatPlatformList } = require('../tools/installer/ide/platform-codes');
const { IdeManager } = require('../tools/installer/ide/manager');
const ideManager42 = new IdeManager();
await ideManager42.ensureInitialized();
const validIds = new Set(ideManager42.getAvailableIdes().map((i) => i.value));
const listed = await formatPlatformList();
// Each entry line starts with ' *' (preferred) or ' ' (other), followed by the ID, then padding.
const entryLines = listed.split('\n').filter((l) => /^( \*| {2})[a-z]/.test(l));
const listedIds = entryLines.map((l) => l.trim().replace(/^\*/, '').split(/\s+/)[0]);
const missingFromList = [...validIds].filter((id) => !listedIds.includes(id));
const extraInList = listedIds.filter((id) => !validIds.has(id));
assert(
missingFromList.length === 0 && extraInList.length === 0,
'--list-tools output matches the IDs that --tools accepts',
`Missing from list: ${missingFromList.join(',') || '(none)'}; Extra in list: ${extraInList.join(',') || '(none)'}`,
);
} catch (error) {
console.log(`${colors.red}Test Suite 42 setup failed: ${error.message}${colors.reset}`);
console.log(error.stack);
failed++;
}
console.log('');
// ============================================================
// Test Suite 43: project_name promoted to core + hoist migration (#2279)
// ============================================================
console.log(`${colors.yellow}Test Suite 43: project_name in core + hoist migration${colors.reset}\n`);
try {
const yamlLib = require('yaml');
const coreSchemaPath = path.join(__dirname, '..', 'src', 'core-skills', 'module.yaml');
const bmmSchemaPath = path.join(__dirname, '..', 'src', 'bmm-skills', 'module.yaml');
const coreSchema = yamlLib.parse(await fs.readFile(coreSchemaPath, 'utf8'));
const bmmSchema = yamlLib.parse(await fs.readFile(bmmSchemaPath, 'utf8'));
assert(
coreSchema.project_name && coreSchema.project_name.prompt && coreSchema.project_name.default === '{directory_name}',
'core/module.yaml declares project_name with {directory_name} default',
);
assert(coreSchema.project_name.scope === undefined, 'project_name has no user scope (project-scoped, not user-scoped)');
assert(bmmSchema.project_name === undefined, 'bmm/module.yaml no longer declares project_name (now inherited from core)');
// Set up a mock existing install: bmm directory has project_name (legacy),
// core has user_name but not project_name. After hoist, project_name should
// move to core, leaving bmm with only its own keys.
const fixtureRoot43 = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-fixture-43-'));
const bmadDir43 = path.join(fixtureRoot43, '_bmad');
await fs.ensureDir(path.join(bmadDir43, '_config'));
await fs.writeFile(path.join(bmadDir43, '_config', 'manifest.yaml'), 'modules: []\n', 'utf8');
await fs.ensureDir(path.join(bmadDir43, 'core'));
await fs.ensureDir(path.join(bmadDir43, 'bmm'));
await fs.writeFile(path.join(bmadDir43, 'core', 'config.yaml'), 'user_name: alice\n', 'utf8');
await fs.writeFile(
path.join(bmadDir43, 'bmm', 'config.yaml'),
'project_name: legacy-from-bmm\nuser_skill_level: intermediate\n',
'utf8',
);
const officialModules43 = new OfficialModules();
await officialModules43.loadExistingConfig(fixtureRoot43);
assert(
officialModules43.existingConfig.core?.project_name === 'legacy-from-bmm',
'loadExistingConfig hoists bmm.project_name to core on existing-install upgrade',
);
assert(
!('project_name' in (officialModules43.existingConfig.bmm || {})),
'loadExistingConfig removes project_name from bmm after hoisting',
);
assert(
officialModules43.existingConfig.bmm?.user_skill_level === 'intermediate',
'loadExistingConfig leaves non-core bmm keys (user_skill_level) untouched',
);
assert(officialModules43.existingConfig.core?.user_name === 'alice', 'loadExistingConfig preserves pre-existing core values');
// Precedence: if core already has the key, hoist must NOT overwrite it.
const fixtureRoot43b = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-fixture-43b-'));
const bmadDir43b = path.join(fixtureRoot43b, '_bmad');
await fs.ensureDir(path.join(bmadDir43b, '_config'));
await fs.writeFile(path.join(bmadDir43b, '_config', 'manifest.yaml'), 'modules: []\n', 'utf8');
await fs.ensureDir(path.join(bmadDir43b, 'core'));
await fs.ensureDir(path.join(bmadDir43b, 'bmm'));
await fs.writeFile(path.join(bmadDir43b, 'core', 'config.yaml'), 'project_name: from-core\n', 'utf8');
await fs.writeFile(path.join(bmadDir43b, 'bmm', 'config.yaml'), 'project_name: stale-from-bmm\n', 'utf8');
const officialModules43b = new OfficialModules();
await officialModules43b.loadExistingConfig(fixtureRoot43b);
assert(officialModules43b.existingConfig.core?.project_name === 'from-core', 'hoist does not overwrite an existing core value');
assert(
!('project_name' in (officialModules43b.existingConfig.bmm || {})),
'hoist still strips the duplicate from bmm so writeCentralConfig partition stays clean',
);
// Malformed config.yaml (parses to a scalar) must not crash loadExistingConfig
// or the hoist pass — they should treat it as "no config for that module"
// and continue. Regression for augment review on PR #2348.
const fixtureRoot43c = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-fixture-43c-'));
const bmadDir43c = path.join(fixtureRoot43c, '_bmad');
await fs.ensureDir(path.join(bmadDir43c, '_config'));
await fs.writeFile(path.join(bmadDir43c, '_config', 'manifest.yaml'), 'modules: []\n', 'utf8');
await fs.ensureDir(path.join(bmadDir43c, 'core'));
await fs.ensureDir(path.join(bmadDir43c, 'bmm'));
// Scalar YAML — yaml.parse returns the literal 42 (truthy non-object).
// Pre-fix this crashed _hoistCoreKeysFromLegacyModuleConfigs with
// "Cannot use 'in' operator to search for 'project_name' in 42".
await fs.writeFile(path.join(bmadDir43c, 'core', 'config.yaml'), '42\n', 'utf8');
await fs.writeFile(path.join(bmadDir43c, 'bmm', 'config.yaml'), 'project_name: rescued\n', 'utf8');
const officialModules43c = new OfficialModules();
let crashErr;
try {
await officialModules43c.loadExistingConfig(fixtureRoot43c);
} catch (error) {
crashErr = error;
}
assert(!crashErr, 'loadExistingConfig does not crash on a scalar core/config.yaml', crashErr?.stack);
assert(
officialModules43c.existingConfig.core?.project_name === 'rescued',
'scalar core gets replaced with {} and bmm.project_name still hoists in',
);
await fs.remove(fixtureRoot43).catch(() => {});
await fs.remove(fixtureRoot43b).catch(() => {});
await fs.remove(fixtureRoot43c).catch(() => {});
} catch (error) {
console.log(`${colors.red}Test Suite 43 setup failed: ${error.message}${colors.reset}`);
console.log(error.stack);
failed++;
}
console.log('');
// ============================================================
// Summary
// ============================================================

View File

@ -15,9 +15,8 @@ module.exports = {
['--modules <modules>', 'Comma-separated list of module IDs to install (e.g., "bmm,bmb")'],
[
'--tools <tools>',
'Comma-separated list of tool/IDE IDs to configure (e.g., "claude-code,cursor"). Required for fresh non-interactive (--yes) installs. Run with --list-tools to see all valid IDs.',
'Comma-separated list of tool/IDE IDs to configure (e.g., "claude-code,cursor"). Use "none" to skip tool configuration.',
],
['--list-tools', 'Print all supported tool/IDE IDs (with target directories) and exit.'],
['--action <type>', 'Action type for existing installations: install, update, or quick-update'],
['--user-name <name>', 'Name for agents to use (default: system username)'],
['--communication-language <lang>', 'Language for agent communication (default: English)'],
@ -41,12 +40,6 @@ module.exports = {
],
action: async (options) => {
try {
if (options.listTools) {
const { formatPlatformList } = require('../ide/platform-codes');
process.stdout.write((await formatPlatformList()) + '\n');
process.exit(0);
}
// Set debug flag as environment variable for all components
if (options.debug) {
process.env.BMAD_DEBUG_MANIFEST = 'true';
@ -88,7 +81,7 @@ module.exports = {
} else {
await prompts.log.error(`Installation failed: ${error.message}`);
}
if (error.stack && !error.expected) {
if (error.stack) {
await prompts.log.message(error.stack);
}
} catch {

View File

@ -923,15 +923,29 @@ class Installer {
/**
* Merge all module-help.csv files into a single bmad-help.csv.
* Scans all installed modules for module-help.csv and merges them.
* Output preserves the source schema verbatim see schema below.
* Enriches agent info from the in-memory agent list produced by ManifestGenerator.
* Output is written to _bmad/_config/bmad-help.csv.
* @param {string} bmadDir - BMAD installation directory
* @param {Array<Object>} _agentEntries - Unused; retained for call-site compatibility
* @param {Array<Object>} agentEntries - Agents collected from module.yaml (code, name, title, icon, module, ...)
*/
async mergeModuleHelpCatalogs(bmadDir, _agentEntries = []) {
async mergeModuleHelpCatalogs(bmadDir, agentEntries = []) {
const allRows = [];
const headerRow = 'module,skill,display-name,menu-code,description,action,args,phase,after,before,required,output-location,outputs';
const COLUMN_COUNT = 13;
const PHASE_INDEX = 7;
const headerRow =
'module,phase,name,code,sequence,workflow-file,command,required,agent-name,agent-command,agent-display-name,agent-title,options,description,output-location,outputs';
// Build agent lookup from the in-memory list (agent code → command + display fields).
const agentInfo = new Map();
for (const agent of agentEntries) {
if (!agent || !agent.code) continue;
const agentCommand = agent.module ? `bmad:${agent.module}:agent:${agent.code}` : `bmad:agent:${agent.code}`;
const displayName = agent.name || agent.code;
const titleCombined = agent.icon && agent.title ? `${agent.icon} ${agent.title}` : agent.title || agent.code;
agentInfo.set(agent.code, {
command: agentCommand,
displayName,
title: titleCombined,
});
}
// Get all installed module directories
const entries = await fs.readdir(bmadDir, { withFileTypes: true });
@ -970,19 +984,64 @@ class Installer {
// Parse the line - handle quoted fields with commas
const columns = this.parseCSVLine(line);
if (columns.length < COLUMN_COUNT - 1) continue;
if (columns.length >= 12) {
// Map old schema to new schema
// Old: module,phase,name,code,sequence,workflow-file,command,required,agent,options,description,output-location,outputs
// New: module,phase,name,code,sequence,workflow-file,command,required,agent-name,agent-command,agent-display-name,agent-title,options,description,output-location,outputs
// Pad short rows; truncate over-long rows
const padded = columns.slice(0, COLUMN_COUNT);
while (padded.length < COLUMN_COUNT) padded.push('');
const [
module,
phase,
name,
code,
sequence,
workflowFile,
command,
required,
agentName,
options,
description,
outputLocation,
outputs,
] = columns;
// If module column is empty, fill with this module's name
// (core stays empty so its rows render as universal tools)
if ((!padded[0] || padded[0].trim() === '') && moduleName !== 'core') {
padded[0] = moduleName;
// Pass through _meta rows as-is (module metadata, not a skill)
if (phase === '_meta') {
const finalModule = (!module || module.trim() === '') && moduleName !== 'core' ? moduleName : module || '';
const metaRow = [finalModule, '_meta', '', '', '', '', '', 'false', '', '', '', '', '', '', outputLocation || '', ''];
allRows.push(metaRow.map((c) => this.escapeCSVField(c)).join(','));
continue;
}
// If module column is empty, set it to this module's name (except for core which stays empty for universal tools)
const finalModule = (!module || module.trim() === '') && moduleName !== 'core' ? moduleName : module || '';
// Lookup agent info
const cleanAgentName = agentName ? agentName.trim() : '';
const agentData = agentInfo.get(cleanAgentName) || { command: '', displayName: '', title: '' };
// Build new row with agent info
const newRow = [
finalModule,
phase || '',
name || '',
code || '',
sequence || '',
workflowFile || '',
command || '',
required || 'false',
cleanAgentName,
agentData.command,
agentData.displayName,
agentData.title,
options || '',
description || '',
outputLocation || '',
outputs || '',
];
allRows.push(newRow.map((c) => this.escapeCSVField(c)).join(','));
}
allRows.push(padded.map((c) => this.escapeCSVField(c)).join(','));
}
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
@ -994,34 +1053,44 @@ class Installer {
}
}
// Sort by module, then phase. Stable sort preserves authored order within a phase.
const decorated = allRows.map((row, index) => ({ row, index, cols: this.parseCSVLine(row) }));
decorated.sort((a, b) => {
const moduleA = (a.cols[0] || '').toLowerCase();
const moduleB = (b.cols[0] || '').toLowerCase();
if (moduleA !== moduleB) return moduleA.localeCompare(moduleB);
// Sort by module, then phase, then sequence
allRows.sort((a, b) => {
const colsA = this.parseCSVLine(a);
const colsB = this.parseCSVLine(b);
const phaseA = a.cols[PHASE_INDEX] || '';
const phaseB = b.cols[PHASE_INDEX] || '';
if (phaseA !== phaseB) return phaseA.localeCompare(phaseB);
// Module comparison (empty module/universal tools come first)
const moduleA = (colsA[0] || '').toLowerCase();
const moduleB = (colsB[0] || '').toLowerCase();
if (moduleA !== moduleB) {
return moduleA.localeCompare(moduleB);
}
return a.index - b.index;
// Phase comparison
const phaseA = colsA[1] || '';
const phaseB = colsB[1] || '';
if (phaseA !== phaseB) {
return phaseA.localeCompare(phaseB);
}
// Sequence comparison
const seqA = parseInt(colsA[4] || '0', 10);
const seqB = parseInt(colsB[4] || '0', 10);
return seqA - seqB;
});
const sortedRows = decorated.map((d) => d.row);
// Write merged catalog
const outputDir = path.join(bmadDir, '_config');
await fs.ensureDir(outputDir);
const outputPath = path.join(outputDir, 'bmad-help.csv');
const mergedContent = [headerRow, ...sortedRows].join('\n');
const mergedContent = [headerRow, ...allRows].join('\n');
await fs.writeFile(outputPath, mergedContent, 'utf8');
// Track the installed file
this.installedFiles.add(outputPath);
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
await prompts.log.message(` Generated bmad-help.csv: ${sortedRows.length} workflows`);
await prompts.log.message(` Generated bmad-help.csv: ${allRows.length} workflows`);
}
}

View File

@ -31,50 +31,7 @@ function clearCache() {
_cachedPlatformCodes = null;
}
/**
* Format the installable platform list for human-readable output (used by --list-tools).
* Sourced from IdeManager so this view matches what --tools accepts at install time
* (suspended platforms excluded).
* @returns {Promise<string>} Formatted multi-line string with id, name, target_dir, preferred flag.
*/
async function formatPlatformList() {
const { IdeManager } = require('./manager');
const ideManager = new IdeManager();
await ideManager.ensureInitialized();
const entries = ideManager.getAvailableIdes().map((ide) => {
const handler = ideManager.handlers.get(ide.value);
return {
id: ide.value,
name: ide.name,
targetDir: handler?.installerConfig?.target_dir || '',
preferred: ide.preferred,
};
});
const idWidth = Math.max(...entries.map((e) => e.id.length), 'ID'.length);
const nameWidth = Math.max(...entries.map((e) => e.name.length), 'Name'.length);
const pad = (s, w) => s + ' '.repeat(Math.max(0, w - s.length));
const lines = [
`Supported tool IDs (pass via --tools <id>[,<id>...]):`,
'',
` ${pad('ID', idWidth)} ${pad('Name', nameWidth)} Target dir`,
` ${pad('-'.repeat(idWidth), idWidth)} ${pad('-'.repeat(nameWidth), nameWidth)} ${'-'.repeat(10)}`,
];
for (const e of entries) {
const star = e.preferred ? ' *' : ' ';
lines.push(`${star}${pad(e.id, idWidth)} ${pad(e.name, nameWidth)} ${e.targetDir}`);
}
lines.push('', '* = recommended / preferred', '', 'Example: bmad-method install --modules bmm --tools claude-code');
return lines.join('\n');
}
module.exports = {
loadPlatformCodes,
clearCache,
formatPlatformList,
};

View File

@ -336,7 +336,7 @@ class CustomModuleManager {
const createSpinner = async () => {
if (silent) {
return { start() {}, stop() {}, error() {} };
return { start() { }, stop() { }, error() { } };
}
return await prompts.spinner();
};

View File

@ -903,10 +903,7 @@ class OfficialModules {
try {
const content = await fs.readFile(moduleConfigPath, 'utf8');
const moduleConfig = yaml.parse(content);
// Only keep plain object parses. A corrupt config.yaml that parses
// to a scalar or array would crash later code that does `key in cfg`
// / `Object.keys(cfg)`; treat it the same as a parse error.
if (moduleConfig && typeof moduleConfig === 'object' && !Array.isArray(moduleConfig)) {
if (moduleConfig) {
this._existingConfig[entry.name] = moduleConfig;
foundAny = true;
}
@ -917,58 +914,9 @@ class OfficialModules {
}
}
if (foundAny) {
await this._hoistCoreKeysFromLegacyModuleConfigs();
}
return foundAny;
}
/**
* Migrate prior answers when a key has moved from a non-core module to core
* (e.g. project_name moving from bmm to core in #2279). Without this, the
* partition logic in writeCentralConfig drops the value from the bmm bucket
* (because it's now a core key) without re-homing it under [core], so the
* user's prior answer silently disappears on the next install/quick-update.
*/
async _hoistCoreKeysFromLegacyModuleConfigs() {
const coreSchemaPath = path.join(getSourcePath(), 'core-skills', 'module.yaml');
if (!(await fs.pathExists(coreSchemaPath))) return;
let coreSchema;
try {
coreSchema = yaml.parse(await fs.readFile(coreSchemaPath, 'utf8'));
} catch {
return;
}
if (!coreSchema || typeof coreSchema !== 'object') return;
const coreKeys = new Set(
Object.entries(coreSchema)
.filter(([, v]) => v && typeof v === 'object' && 'prompt' in v)
.map(([k]) => k),
);
if (coreKeys.size === 0) return;
// Belt-and-suspenders: loadExistingConfig already filters non-object parses,
// but anyone calling _hoistCoreKeysFromLegacyModuleConfigs in isolation (or
// future code paths populating _existingConfig directly) shouldn't be able
// to crash this with a scalar / array.
const existingCore = this._existingConfig.core;
this._existingConfig.core = existingCore && typeof existingCore === 'object' && !Array.isArray(existingCore) ? existingCore : {};
for (const [moduleName, cfg] of Object.entries(this._existingConfig)) {
if (moduleName === 'core' || !cfg || typeof cfg !== 'object' || Array.isArray(cfg)) continue;
for (const key of Object.keys(cfg)) {
if (!coreKeys.has(key)) continue;
if (!(key in this._existingConfig.core)) {
this._existingConfig.core[key] = cfg[key];
}
delete cfg[key];
}
}
}
/**
* Pre-scan module schemas to gather metadata for the configuration gateway prompt.
* Returns info about which modules have configurable options.

View File

@ -200,15 +200,12 @@ class UI {
actionType = options.action;
await prompts.log.info(`Using action from command-line: ${actionType}`);
} else if (options.yes) {
// Default to quick-update if available, unless flags that require the
// full update path are present (e.g. --custom-source which re-clones
// modules at a new version — quick-update skips that entirely).
// Default to quick-update if available, otherwise first available choice
if (choices.length === 0) {
throw new Error('No valid actions available for this installation');
}
const hasQuickUpdate = choices.some((c) => c.value === 'quick-update');
const needsFullUpdate = !!options.customSource;
actionType = hasQuickUpdate && !needsFullUpdate ? 'quick-update' : (choices.find((c) => c.value === 'update') || choices[0]).value;
actionType = hasQuickUpdate ? 'quick-update' : choices[0].value;
await prompts.log.info(`Non-interactive mode (--yes): defaulting to ${actionType}`);
} else {
actionType = await prompts.select({
@ -244,11 +241,8 @@ class UI {
.map((m) => m.trim())
.filter(Boolean);
await prompts.log.info(`Using modules from command-line: ${selectedModules.join(', ')}`);
} else if (options.customSource && !options.yes) {
// Custom source without --modules or --yes: start with empty list
// (only custom source modules + core will be installed).
// When --yes is also set, fall through to the --yes branch so all
// installed modules are included alongside the custom source modules.
} else if (options.customSource) {
// Custom source without --modules: start with empty list (core added below)
selectedModules = [];
} else if (options.yes) {
selectedModules = await this.getDefaultModules(installedModuleIds);
@ -404,37 +398,6 @@ class UI {
* @param {Object} options - Command-line options
* @returns {Object} Tool configuration
*/
_parseToolsFlag(toolsArg, allKnownValues) {
const selectedIdes = toolsArg
.split(',')
.map((t) => t.trim())
.filter(Boolean);
if (selectedIdes.length === 0) {
const err = new Error(
'--tools was passed empty. Provide at least one tool ID (e.g. --tools claude-code) or run with --list-tools to see valid IDs.',
);
err.expected = true;
throw err;
}
const unknown = selectedIdes.filter((id) => !allKnownValues.has(id));
if (unknown.length > 0) {
const err = new Error(
[
`Unknown tool ID${unknown.length === 1 ? '' : 's'}: ${unknown.join(', ')}`,
'',
'Run with --list-tools to see all valid IDs.',
'Common: claude-code, cursor, copilot, windsurf, cline',
].join('\n'),
);
err.expected = true;
throw err;
}
return selectedIdes;
}
async promptToolSelection(projectDir, options = {}) {
const { ExistingInstall } = require('./core/existing-install');
const { Installer } = require('./core/installer');
@ -469,10 +432,15 @@ class UI {
const allTools = [...preferredIdes, ...otherIdes];
// Non-interactive: handle --tools and --yes flags before interactive prompt
// Use !== undefined so an explicit --tools "" falls through to _parseToolsFlag and
// gets a specific "passed empty" error instead of being silently ignored.
if (options.tools !== undefined) {
const selectedIdes = this._parseToolsFlag(options.tools, allKnownValues);
if (options.tools) {
if (options.tools.toLowerCase() === 'none') {
await prompts.log.info('Skipping tool configuration (--tools none)');
return { ides: [], skipIde: true };
}
const selectedIdes = options.tools
.split(',')
.map((t) => t.trim())
.filter(Boolean);
await prompts.log.info(`Using tools from command-line: ${selectedIdes.join(', ')}`);
await this.displaySelectedTools(selectedIdes, preferredIdes, allTools);
return { ides: selectedIdes, skipIde: false };
@ -548,13 +516,21 @@ class UI {
let selectedIdes = [];
// Check if tools are provided via command-line.
// Use !== undefined so an explicit --tools "" still hits _parseToolsFlag's empty-value error.
if (options.tools !== undefined) {
selectedIdes = this._parseToolsFlag(options.tools, allKnownValues);
await prompts.log.info(`Using tools from command-line: ${selectedIdes.join(', ')}`);
await this.displaySelectedTools(selectedIdes, preferredIdes, allTools);
return { ides: selectedIdes, skipIde: false };
// Check if tools are provided via command-line
if (options.tools) {
// Check for explicit "none" value to skip tool installation
if (options.tools.toLowerCase() === 'none') {
await prompts.log.info('Skipping tool configuration (--tools none)');
return { ides: [], skipIde: true };
} else {
selectedIdes = options.tools
.split(',')
.map((t) => t.trim())
.filter(Boolean);
await prompts.log.info(`Using tools from command-line: ${selectedIdes.join(', ')}`);
await this.displaySelectedTools(selectedIdes, preferredIdes, allTools);
return { ides: selectedIdes, skipIde: false };
}
} else if (options.yes) {
// If --yes flag is set, skip tool prompt and use previously configured tools or empty
if (configuredIdes.length > 0) {
@ -562,18 +538,8 @@ class UI {
await this.displaySelectedTools(configuredIdes, preferredIdes, allTools);
return { ides: configuredIdes, skipIde: false };
} else {
const err = new Error(
[
'--tools is required for non-interactive install (--yes / -y) when no tools are previously configured.',
'',
'Common: claude-code, cursor, copilot, windsurf, cline',
'See all supported tools: bmad-method install --list-tools',
'',
'Example: bmad-method install --modules bmm --tools claude-code -y',
].join('\n'),
);
err.expected = true;
throw err;
await prompts.log.info('Skipping tool configuration (--yes flag, no previous tools)');
return { ides: [], skipIde: true };
}
}
@ -758,9 +724,6 @@ class UI {
const defaultUsername = safeUsername.charAt(0).toUpperCase() + safeUsername.slice(1);
configCollector.collectedConfig.core = {
user_name: defaultUsername,
// {directory_name} default per src/core-skills/module.yaml — matches what the
// interactive flow resolves via buildQuestion()'s {directory_name} placeholder.
project_name: path.basename(directory),
communication_language: 'English',
document_output_language: 'English',
output_folder: '_bmad-output',