Merge branch 'main' into feat/opencode-command-pointers
This commit is contained in:
commit
a889a6cc88
|
|
@ -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)
|
- **Node.js** 20+ (the installer requires it)
|
||||||
- **Git** (for cloning external modules)
|
- **Git** (for cloning external modules)
|
||||||
- **An AI tool** such as Claude Code or Cursor — or install without one using `--tools none`
|
- **An AI tool** such as Claude Code or Cursor (run `npx bmad-method install --list-tools` to see all supported tools)
|
||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
|
@ -122,7 +122,8 @@ Under `--yes`, patch and minor upgrades apply automatically. Majors stay frozen
|
||||||
| `--yes`, `-y` | Skip all prompts; accept flag values + defaults |
|
| `--yes`, `-y` | Skip all prompts; accept flag values + defaults |
|
||||||
| `--directory <path>` | Install into this directory (default: current working dir) |
|
| `--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. |
|
| `--modules <a,b,c>` | Exact module set. Core is auto-added. Not a delta — list everything you want kept. |
|
||||||
| `--tools <a,b>` or `--tools none` | IDE/tool selection. `none` skips tool config entirely. |
|
| `--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. |
|
||||||
| `--action <type>` | `install`, `update`, or `quick-update`. Defaults based on existing install state. |
|
| `--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 |
|
| `--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`) |
|
| `--channel <stable\|next>` | Apply to all externals (aliased as `--all-stable` / `--all-next`) |
|
||||||
|
|
@ -165,17 +166,17 @@ npx bmad-method install --yes --modules bmm,bmb --all-next --tools claude-code
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npx bmad-method install --yes --action update \
|
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:**
|
**Mix channels — bmb on next, gds on stable:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npx bmad-method install --yes --action update \
|
npx bmad-method install --yes --action update \
|
||||||
--modules bmm,bmb,cis,gds \
|
--modules bmm,bmb,cis,gds \
|
||||||
--next=bmb \
|
--next=bmb
|
||||||
--tools none
|
|
||||||
```
|
```
|
||||||
|
|
||||||
:::caution[Rate limit on shared IPs]
|
:::caution[Rate limit on shared IPs]
|
||||||
|
|
@ -204,7 +205,7 @@ For cross-machine reproducibility, don't rely on rerunning the same `--modules`
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npx bmad-method install --yes --modules bmb,cis \
|
npx bmad-method install --yes --modules bmb,cis \
|
||||||
--pin bmb=v1.7.0 --pin cis=v0.4.2 --tools none
|
--pin bmb=v1.7.0 --pin cis=v0.4.2 --tools claude-code
|
||||||
```
|
```
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,7 @@ Select **Yes**, then provide a source:
|
||||||
| Input Type | Example |
|
| Input Type | Example |
|
||||||
| --------------------- | ------------------------------------------------- |
|
| --------------------- | ------------------------------------------------- |
|
||||||
| HTTPS URL (any host) | `https://github.com/org/repo` |
|
| HTTPS URL (any host) | `https://github.com/org/repo` |
|
||||||
|
| HTTP URL (any host) | `http://host/org/repo` |
|
||||||
| HTTPS URL with subdir | `https://github.com/org/repo/tree/main/my-module` |
|
| HTTPS URL with subdir | `https://github.com/org/repo/tree/main/my-module` |
|
||||||
| SSH URL | `git@github.com:org/repo.git` |
|
| SSH URL | `git@github.com:org/repo.git` |
|
||||||
| Local path | `/Users/me/projects/my-module` |
|
| Local path | `/Users/me/projects/my-module` |
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,7 @@ Chọn **Yes**, rồi nhập nguồn:
|
||||||
| Loại đầu vào | Ví dụ |
|
| Loại đầu vào | Ví dụ |
|
||||||
| --------------------- | ------------------------------------------------- |
|
| --------------------- | ------------------------------------------------- |
|
||||||
| HTTPS URL trên bất kỳ host nào | `https://github.com/org/repo` |
|
| HTTPS URL trên bất kỳ host nào | `https://github.com/org/repo` |
|
||||||
|
| HTTP URL trên bất kỳ host nào | `http://host/org/repo` |
|
||||||
| HTTPS URL trỏ vào một thư mục con | `https://github.com/org/repo/tree/main/my-module` |
|
| HTTPS URL trỏ vào một thư mục con | `https://github.com/org/repo/tree/main/my-module` |
|
||||||
| SSH URL | `git@github.com:org/repo.git` |
|
| SSH URL | `git@github.com:org/repo.git` |
|
||||||
| Đường dẫn cục bộ | `/Users/me/projects/my-module` |
|
| Đường dẫn cục bộ | `/Users/me/projects/my-module` |
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,7 @@ Would you like to install from a custom source (Git URL or local path)?
|
||||||
| 输入类型 | 示例 |
|
| 输入类型 | 示例 |
|
||||||
| -------- | ---- |
|
| -------- | ---- |
|
||||||
| HTTPS URL(任意主机) | `https://github.com/org/repo` |
|
| HTTPS URL(任意主机) | `https://github.com/org/repo` |
|
||||||
|
| HTTP URL(任意主机) | `http://host/org/repo` |
|
||||||
| 带子目录的 HTTPS URL | `https://github.com/org/repo/tree/main/my-module` |
|
| 带子目录的 HTTPS URL | `https://github.com/org/repo/tree/main/my-module` |
|
||||||
| SSH URL | `git@github.com:org/repo.git` |
|
| SSH URL | `git@github.com:org/repo.git` |
|
||||||
| 本地路径 | `/Users/me/projects/my-module` |
|
| 本地路径 | `/Users/me/projects/my-module` |
|
||||||
|
|
|
||||||
|
|
@ -227,37 +227,39 @@ Prepare the content to append to the document:
|
||||||
|
|
||||||
### Architecture Completeness Checklist
|
### Architecture Completeness Checklist
|
||||||
|
|
||||||
**✅ Requirements Analysis**
|
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.
|
||||||
|
|
||||||
- [x] Project context thoroughly analyzed
|
**Requirements Analysis**
|
||||||
- [x] Scale and complexity assessed
|
|
||||||
- [x] Technical constraints identified
|
|
||||||
- [x] Cross-cutting concerns mapped
|
|
||||||
|
|
||||||
**✅ Architectural Decisions**
|
- [ ] Project context thoroughly analyzed
|
||||||
|
- [ ] Scale and complexity assessed
|
||||||
|
- [ ] Technical constraints identified
|
||||||
|
- [ ] Cross-cutting concerns mapped
|
||||||
|
|
||||||
- [x] Critical decisions documented with versions
|
**Architectural Decisions**
|
||||||
- [x] Technology stack fully specified
|
|
||||||
- [x] Integration patterns defined
|
|
||||||
- [x] Performance considerations addressed
|
|
||||||
|
|
||||||
**✅ Implementation Patterns**
|
- [ ] Critical decisions documented with versions
|
||||||
|
- [ ] Technology stack fully specified
|
||||||
|
- [ ] Integration patterns defined
|
||||||
|
- [ ] Performance considerations addressed
|
||||||
|
|
||||||
- [x] Naming conventions established
|
**Implementation Patterns**
|
||||||
- [x] Structure patterns defined
|
|
||||||
- [x] Communication patterns specified
|
|
||||||
- [x] Process patterns documented
|
|
||||||
|
|
||||||
**✅ Project Structure**
|
- [ ] Naming conventions established
|
||||||
|
- [ ] Structure patterns defined
|
||||||
|
- [ ] Communication patterns specified
|
||||||
|
- [ ] Process patterns documented
|
||||||
|
|
||||||
- [x] Complete directory structure defined
|
**Project Structure**
|
||||||
- [x] Component boundaries established
|
|
||||||
- [x] Integration points mapped
|
- [ ] Complete directory structure defined
|
||||||
- [x] Requirements to structure mapping complete
|
- [ ] Component boundaries established
|
||||||
|
- [ ] Integration points mapped
|
||||||
|
- [ ] Requirements to structure mapping complete
|
||||||
|
|
||||||
### Architecture Readiness Assessment
|
### Architecture Readiness Assessment
|
||||||
|
|
||||||
**Overall Status:** READY FOR IMPLEMENTATION
|
**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)
|
||||||
|
|
||||||
**Confidence Level:** {{high/medium/low}} based on validation results
|
**Confidence Level:** {{high/medium/low}} based on validation results
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,33 +1,33 @@
|
||||||
module,skill,display-name,menu-code,description,action,args,phase,after,before,required,output-location,outputs
|
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,_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-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-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-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-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,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,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,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,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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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,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-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-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-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-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-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-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.
|
|
|
@ -5,15 +5,11 @@ default_selected: true # This module will be selected by default for new install
|
||||||
|
|
||||||
# Variables from Core Config inserted:
|
# Variables from Core Config inserted:
|
||||||
## user_name
|
## user_name
|
||||||
|
## project_name
|
||||||
## communication_language
|
## communication_language
|
||||||
## document_output_language
|
## document_output_language
|
||||||
## output_folder
|
## output_folder
|
||||||
|
|
||||||
project_name:
|
|
||||||
prompt: "What is your project called?"
|
|
||||||
default: "{directory_name}"
|
|
||||||
result: "{value}"
|
|
||||||
|
|
||||||
user_skill_level:
|
user_skill_level:
|
||||||
prompt:
|
prompt:
|
||||||
- "What is your development experience level?"
|
- "What is your development experience level?"
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
module,skill,display-name,menu-code,description,action,args,phase,after,before,required,output-location,outputs
|
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,_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-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-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-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-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-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-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-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-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-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-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-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.
|
|
|
@ -11,6 +11,11 @@ user_name:
|
||||||
default: "BMad"
|
default: "BMad"
|
||||||
result: "{value}"
|
result: "{value}"
|
||||||
|
|
||||||
|
project_name:
|
||||||
|
prompt: "What is your project called?"
|
||||||
|
default: "{directory_name}"
|
||||||
|
result: "{value}"
|
||||||
|
|
||||||
communication_language:
|
communication_language:
|
||||||
prompt: "What language should agents use when chatting with you?"
|
prompt: "What language should agents use when chatting with you?"
|
||||||
scope: user
|
scope: user
|
||||||
|
|
|
||||||
|
|
@ -1933,12 +1933,12 @@ async function runTests() {
|
||||||
const moduleConfigs = {
|
const moduleConfigs = {
|
||||||
core: {
|
core: {
|
||||||
user_name: 'TestUser',
|
user_name: 'TestUser',
|
||||||
|
project_name: 'demo-project',
|
||||||
communication_language: 'Spanish',
|
communication_language: 'Spanish',
|
||||||
document_output_language: 'English',
|
document_output_language: 'English',
|
||||||
output_folder: '_bmad-output',
|
output_folder: '_bmad-output',
|
||||||
},
|
},
|
||||||
bmm: {
|
bmm: {
|
||||||
project_name: 'demo-project',
|
|
||||||
user_skill_level: 'expert',
|
user_skill_level: 'expert',
|
||||||
planning_artifacts: '{project-root}/_bmad-output/planning-artifacts',
|
planning_artifacts: '{project-root}/_bmad-output/planning-artifacts',
|
||||||
implementation_artifacts: '{project-root}/_bmad-output/implementation-artifacts',
|
implementation_artifacts: '{project-root}/_bmad-output/implementation-artifacts',
|
||||||
|
|
@ -1946,7 +1946,10 @@ async function runTests() {
|
||||||
// Spread-from-core pollution: legacy per-module config.yaml merges
|
// Spread-from-core pollution: legacy per-module config.yaml merges
|
||||||
// core values into every module; writeCentralConfig must strip these
|
// core values into every module; writeCentralConfig must strip these
|
||||||
// from [modules.bmm] so core values only live in [core].
|
// 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',
|
user_name: 'TestUser',
|
||||||
|
project_name: 'stale-bmm-copy',
|
||||||
communication_language: 'Spanish',
|
communication_language: 'Spanish',
|
||||||
document_output_language: 'English',
|
document_output_language: 'English',
|
||||||
output_folder: '_bmad-output',
|
output_folder: '_bmad-output',
|
||||||
|
|
@ -1994,6 +1997,7 @@ async function runTests() {
|
||||||
assert(teamContent.includes('[core]'), 'config.toml has [core] section');
|
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('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('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('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');
|
assert(!teamContent.includes('communication_language'), 'communication_language (scope: user) is absent from config.toml');
|
||||||
|
|
||||||
|
|
@ -2008,7 +2012,9 @@ async function runTests() {
|
||||||
assert(bmmTeamMatch !== null, 'config.toml has [modules.bmm] section');
|
assert(bmmTeamMatch !== null, 'config.toml has [modules.bmm] section');
|
||||||
if (bmmTeamMatch) {
|
if (bmmTeamMatch) {
|
||||||
const bmmTeamBlock = bmmTeamMatch[0];
|
const bmmTeamBlock = bmmTeamMatch[0];
|
||||||
assert(bmmTeamBlock.includes('project_name = "demo-project"'), 'bmm team-scope key lands under [modules.bmm]');
|
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('user_name'), 'user_name stripped from [modules.bmm] (core-key pollution)');
|
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('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');
|
assert(!bmmTeamBlock.includes('user_skill_level'), 'user_skill_level (scope: user) absent from [modules.bmm] in config.toml');
|
||||||
|
|
@ -3000,6 +3006,210 @@ async function runTests() {
|
||||||
|
|
||||||
console.log('');
|
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
|
// Summary
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,9 @@ module.exports = {
|
||||||
['--modules <modules>', 'Comma-separated list of module IDs to install (e.g., "bmm,bmb")'],
|
['--modules <modules>', 'Comma-separated list of module IDs to install (e.g., "bmm,bmb")'],
|
||||||
[
|
[
|
||||||
'--tools <tools>',
|
'--tools <tools>',
|
||||||
'Comma-separated list of tool/IDE IDs to configure (e.g., "claude-code,cursor"). Use "none" to skip tool configuration.',
|
'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.',
|
||||||
],
|
],
|
||||||
|
['--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'],
|
['--action <type>', 'Action type for existing installations: install, update, or quick-update'],
|
||||||
['--user-name <name>', 'Name for agents to use (default: system username)'],
|
['--user-name <name>', 'Name for agents to use (default: system username)'],
|
||||||
['--communication-language <lang>', 'Language for agent communication (default: English)'],
|
['--communication-language <lang>', 'Language for agent communication (default: English)'],
|
||||||
|
|
@ -40,6 +41,12 @@ module.exports = {
|
||||||
],
|
],
|
||||||
action: async (options) => {
|
action: async (options) => {
|
||||||
try {
|
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
|
// Set debug flag as environment variable for all components
|
||||||
if (options.debug) {
|
if (options.debug) {
|
||||||
process.env.BMAD_DEBUG_MANIFEST = 'true';
|
process.env.BMAD_DEBUG_MANIFEST = 'true';
|
||||||
|
|
@ -81,7 +88,7 @@ module.exports = {
|
||||||
} else {
|
} else {
|
||||||
await prompts.log.error(`Installation failed: ${error.message}`);
|
await prompts.log.error(`Installation failed: ${error.message}`);
|
||||||
}
|
}
|
||||||
if (error.stack) {
|
if (error.stack && !error.expected) {
|
||||||
await prompts.log.message(error.stack);
|
await prompts.log.message(error.stack);
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
|
|
|
||||||
|
|
@ -923,29 +923,15 @@ class Installer {
|
||||||
/**
|
/**
|
||||||
* Merge all module-help.csv files into a single bmad-help.csv.
|
* Merge all module-help.csv files into a single bmad-help.csv.
|
||||||
* Scans all installed modules for module-help.csv and merges them.
|
* Scans all installed modules for module-help.csv and merges them.
|
||||||
* Enriches agent info from the in-memory agent list produced by ManifestGenerator.
|
* Output preserves the source schema verbatim — see schema below.
|
||||||
* Output is written to _bmad/_config/bmad-help.csv.
|
|
||||||
* @param {string} bmadDir - BMAD installation directory
|
* @param {string} bmadDir - BMAD installation directory
|
||||||
* @param {Array<Object>} agentEntries - Agents collected from module.yaml (code, name, title, icon, module, ...)
|
* @param {Array<Object>} _agentEntries - Unused; retained for call-site compatibility
|
||||||
*/
|
*/
|
||||||
async mergeModuleHelpCatalogs(bmadDir, agentEntries = []) {
|
async mergeModuleHelpCatalogs(bmadDir, _agentEntries = []) {
|
||||||
const allRows = [];
|
const allRows = [];
|
||||||
const headerRow =
|
const headerRow = 'module,skill,display-name,menu-code,description,action,args,phase,after,before,required,output-location,outputs';
|
||||||
'module,phase,name,code,sequence,workflow-file,command,required,agent-name,agent-command,agent-display-name,agent-title,options,description,output-location,outputs';
|
const COLUMN_COUNT = 13;
|
||||||
|
const PHASE_INDEX = 7;
|
||||||
// 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
|
// Get all installed module directories
|
||||||
const entries = await fs.readdir(bmadDir, { withFileTypes: true });
|
const entries = await fs.readdir(bmadDir, { withFileTypes: true });
|
||||||
|
|
@ -984,64 +970,19 @@ class Installer {
|
||||||
|
|
||||||
// Parse the line - handle quoted fields with commas
|
// Parse the line - handle quoted fields with commas
|
||||||
const columns = this.parseCSVLine(line);
|
const columns = this.parseCSVLine(line);
|
||||||
if (columns.length >= 12) {
|
if (columns.length < COLUMN_COUNT - 1) continue;
|
||||||
// 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
|
|
||||||
|
|
||||||
const [
|
// Pad short rows; truncate over-long rows
|
||||||
module,
|
const padded = columns.slice(0, COLUMN_COUNT);
|
||||||
phase,
|
while (padded.length < COLUMN_COUNT) padded.push('');
|
||||||
name,
|
|
||||||
code,
|
|
||||||
sequence,
|
|
||||||
workflowFile,
|
|
||||||
command,
|
|
||||||
required,
|
|
||||||
agentName,
|
|
||||||
options,
|
|
||||||
description,
|
|
||||||
outputLocation,
|
|
||||||
outputs,
|
|
||||||
] = columns;
|
|
||||||
|
|
||||||
// Pass through _meta rows as-is (module metadata, not a skill)
|
// If module column is empty, fill with this module's name
|
||||||
if (phase === '_meta') {
|
// (core stays empty so its rows render as universal tools)
|
||||||
const finalModule = (!module || module.trim() === '') && moduleName !== 'core' ? moduleName : module || '';
|
if ((!padded[0] || padded[0].trim() === '') && moduleName !== 'core') {
|
||||||
const metaRow = [finalModule, '_meta', '', '', '', '', '', 'false', '', '', '', '', '', '', outputLocation || '', ''];
|
padded[0] = moduleName;
|
||||||
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)
|
allRows.push(padded.map((c) => this.escapeCSVField(c)).join(','));
|
||||||
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(','));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
|
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
|
||||||
|
|
@ -1053,44 +994,34 @@ class Installer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort by module, then phase, then sequence
|
// Sort by module, then phase. Stable sort preserves authored order within a phase.
|
||||||
allRows.sort((a, b) => {
|
const decorated = allRows.map((row, index) => ({ row, index, cols: this.parseCSVLine(row) }));
|
||||||
const colsA = this.parseCSVLine(a);
|
decorated.sort((a, b) => {
|
||||||
const colsB = this.parseCSVLine(b);
|
const moduleA = (a.cols[0] || '').toLowerCase();
|
||||||
|
const moduleB = (b.cols[0] || '').toLowerCase();
|
||||||
|
if (moduleA !== moduleB) return moduleA.localeCompare(moduleB);
|
||||||
|
|
||||||
// Module comparison (empty module/universal tools come first)
|
const phaseA = a.cols[PHASE_INDEX] || '';
|
||||||
const moduleA = (colsA[0] || '').toLowerCase();
|
const phaseB = b.cols[PHASE_INDEX] || '';
|
||||||
const moduleB = (colsB[0] || '').toLowerCase();
|
if (phaseA !== phaseB) return phaseA.localeCompare(phaseB);
|
||||||
if (moduleA !== moduleB) {
|
|
||||||
return moduleA.localeCompare(moduleB);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Phase comparison
|
return a.index - b.index;
|
||||||
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
|
// Write merged catalog
|
||||||
const outputDir = path.join(bmadDir, '_config');
|
const outputDir = path.join(bmadDir, '_config');
|
||||||
await fs.ensureDir(outputDir);
|
await fs.ensureDir(outputDir);
|
||||||
const outputPath = path.join(outputDir, 'bmad-help.csv');
|
const outputPath = path.join(outputDir, 'bmad-help.csv');
|
||||||
|
|
||||||
const mergedContent = [headerRow, ...allRows].join('\n');
|
const mergedContent = [headerRow, ...sortedRows].join('\n');
|
||||||
await fs.writeFile(outputPath, mergedContent, 'utf8');
|
await fs.writeFile(outputPath, mergedContent, 'utf8');
|
||||||
|
|
||||||
// Track the installed file
|
// Track the installed file
|
||||||
this.installedFiles.add(outputPath);
|
this.installedFiles.add(outputPath);
|
||||||
|
|
||||||
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
|
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
|
||||||
await prompts.log.message(` Generated bmad-help.csv: ${allRows.length} workflows`);
|
await prompts.log.message(` Generated bmad-help.csv: ${sortedRows.length} workflows`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,50 @@ function clearCache() {
|
||||||
_cachedPlatformCodes = null;
|
_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 = {
|
module.exports = {
|
||||||
loadPlatformCodes,
|
loadPlatformCodes,
|
||||||
clearCache,
|
clearCache,
|
||||||
|
formatPlatformList,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,9 @@ class CustomModuleManager {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse a user-provided source input into a structured descriptor.
|
* Parse a user-provided source input into a structured descriptor.
|
||||||
* Accepts local file paths, HTTPS Git URLs, and SSH Git URLs.
|
* Accepts local file paths, HTTPS Git URLs, HTTP Git URLs, and SSH Git URLs.
|
||||||
* For HTTPS URLs with deep paths (e.g., /tree/main/subdir), extracts the subdir.
|
* For HTTPS/HTTP URLs with deep paths (e.g., /tree/main/subdir), extracts the subdir.
|
||||||
|
* The original protocol (http or https) is preserved in the returned cloneUrl.
|
||||||
*
|
*
|
||||||
* @param {string} input - URL or local file path
|
* @param {string} input - URL or local file path
|
||||||
* @returns {Object} Parsed source descriptor:
|
* @returns {Object} Parsed source descriptor:
|
||||||
|
|
@ -127,11 +128,11 @@ class CustomModuleManager {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTPS URL: https://host/owner/repo[/tree/branch/subdir][.git]
|
// HTTPS/HTTP URL: https://host/owner/repo[/tree/branch/subdir][.git]
|
||||||
const httpsMatch = trimmed.match(/^https?:\/\/([^/]+)\/([^/]+)\/([^/.]+?)(?:\.git)?(\/.*)?$/);
|
const httpsMatch = trimmed.match(/^(https?):\/\/([^/]+)\/([^/]+)\/([^/.]+?)(?:\.git)?(\/.*)?$/);
|
||||||
if (httpsMatch) {
|
if (httpsMatch) {
|
||||||
const [, host, owner, repo, remainder] = httpsMatch;
|
const [, protocol, host, owner, repo, remainder] = httpsMatch;
|
||||||
const cloneUrl = `https://${host}/${owner}/${repo}`;
|
const cloneUrl = `${protocol}://${host}/${owner}/${repo}`;
|
||||||
let subdir = null;
|
let subdir = null;
|
||||||
let urlRef = null; // branch/tag extracted from /tree/<ref>/subdir
|
let urlRef = null; // branch/tag extracted from /tree/<ref>/subdir
|
||||||
|
|
||||||
|
|
@ -311,7 +312,7 @@ class CustomModuleManager {
|
||||||
/**
|
/**
|
||||||
* Clone a custom module repository to cache.
|
* Clone a custom module repository to cache.
|
||||||
* Supports any Git host (GitHub, GitLab, Bitbucket, self-hosted, etc.).
|
* Supports any Git host (GitHub, GitLab, Bitbucket, self-hosted, etc.).
|
||||||
* @param {string} sourceInput - Git URL (HTTPS or SSH)
|
* @param {string} sourceInput - Git URL (HTTPS, HTTP, or SSH)
|
||||||
* @param {Object} [options] - Clone options
|
* @param {Object} [options] - Clone options
|
||||||
* @param {boolean} [options.silent] - Suppress spinner output
|
* @param {boolean} [options.silent] - Suppress spinner output
|
||||||
* @param {boolean} [options.skipInstall] - Skip npm install (for browsing before user confirms)
|
* @param {boolean} [options.skipInstall] - Skip npm install (for browsing before user confirms)
|
||||||
|
|
|
||||||
|
|
@ -903,7 +903,10 @@ class OfficialModules {
|
||||||
try {
|
try {
|
||||||
const content = await fs.readFile(moduleConfigPath, 'utf8');
|
const content = await fs.readFile(moduleConfigPath, 'utf8');
|
||||||
const moduleConfig = yaml.parse(content);
|
const moduleConfig = yaml.parse(content);
|
||||||
if (moduleConfig) {
|
// 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)) {
|
||||||
this._existingConfig[entry.name] = moduleConfig;
|
this._existingConfig[entry.name] = moduleConfig;
|
||||||
foundAny = true;
|
foundAny = true;
|
||||||
}
|
}
|
||||||
|
|
@ -914,9 +917,58 @@ class OfficialModules {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (foundAny) {
|
||||||
|
await this._hoistCoreKeysFromLegacyModuleConfigs();
|
||||||
|
}
|
||||||
|
|
||||||
return foundAny;
|
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.
|
* Pre-scan module schemas to gather metadata for the configuration gateway prompt.
|
||||||
* Returns info about which modules have configurable options.
|
* Returns info about which modules have configurable options.
|
||||||
|
|
|
||||||
|
|
@ -200,12 +200,15 @@ class UI {
|
||||||
actionType = options.action;
|
actionType = options.action;
|
||||||
await prompts.log.info(`Using action from command-line: ${actionType}`);
|
await prompts.log.info(`Using action from command-line: ${actionType}`);
|
||||||
} else if (options.yes) {
|
} else if (options.yes) {
|
||||||
// Default to quick-update if available, otherwise first available choice
|
// 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).
|
||||||
if (choices.length === 0) {
|
if (choices.length === 0) {
|
||||||
throw new Error('No valid actions available for this installation');
|
throw new Error('No valid actions available for this installation');
|
||||||
}
|
}
|
||||||
const hasQuickUpdate = choices.some((c) => c.value === 'quick-update');
|
const hasQuickUpdate = choices.some((c) => c.value === 'quick-update');
|
||||||
actionType = hasQuickUpdate ? 'quick-update' : choices[0].value;
|
const needsFullUpdate = !!options.customSource;
|
||||||
|
actionType = hasQuickUpdate && !needsFullUpdate ? 'quick-update' : (choices.find((c) => c.value === 'update') || choices[0]).value;
|
||||||
await prompts.log.info(`Non-interactive mode (--yes): defaulting to ${actionType}`);
|
await prompts.log.info(`Non-interactive mode (--yes): defaulting to ${actionType}`);
|
||||||
} else {
|
} else {
|
||||||
actionType = await prompts.select({
|
actionType = await prompts.select({
|
||||||
|
|
@ -241,8 +244,11 @@ class UI {
|
||||||
.map((m) => m.trim())
|
.map((m) => m.trim())
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
await prompts.log.info(`Using modules from command-line: ${selectedModules.join(', ')}`);
|
await prompts.log.info(`Using modules from command-line: ${selectedModules.join(', ')}`);
|
||||||
} else if (options.customSource) {
|
} else if (options.customSource && !options.yes) {
|
||||||
// Custom source without --modules: start with empty list (core added below)
|
// 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.
|
||||||
selectedModules = [];
|
selectedModules = [];
|
||||||
} else if (options.yes) {
|
} else if (options.yes) {
|
||||||
selectedModules = await this.getDefaultModules(installedModuleIds);
|
selectedModules = await this.getDefaultModules(installedModuleIds);
|
||||||
|
|
@ -398,6 +404,37 @@ class UI {
|
||||||
* @param {Object} options - Command-line options
|
* @param {Object} options - Command-line options
|
||||||
* @returns {Object} Tool configuration
|
* @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 = {}) {
|
async promptToolSelection(projectDir, options = {}) {
|
||||||
const { ExistingInstall } = require('./core/existing-install');
|
const { ExistingInstall } = require('./core/existing-install');
|
||||||
const { Installer } = require('./core/installer');
|
const { Installer } = require('./core/installer');
|
||||||
|
|
@ -432,15 +469,10 @@ class UI {
|
||||||
const allTools = [...preferredIdes, ...otherIdes];
|
const allTools = [...preferredIdes, ...otherIdes];
|
||||||
|
|
||||||
// Non-interactive: handle --tools and --yes flags before interactive prompt
|
// Non-interactive: handle --tools and --yes flags before interactive prompt
|
||||||
if (options.tools) {
|
// Use !== undefined so an explicit --tools "" falls through to _parseToolsFlag and
|
||||||
if (options.tools.toLowerCase() === 'none') {
|
// gets a specific "passed empty" error instead of being silently ignored.
|
||||||
await prompts.log.info('Skipping tool configuration (--tools none)');
|
if (options.tools !== undefined) {
|
||||||
return { ides: [], skipIde: true };
|
const selectedIdes = this._parseToolsFlag(options.tools, allKnownValues);
|
||||||
}
|
|
||||||
const selectedIdes = options.tools
|
|
||||||
.split(',')
|
|
||||||
.map((t) => t.trim())
|
|
||||||
.filter(Boolean);
|
|
||||||
await prompts.log.info(`Using tools from command-line: ${selectedIdes.join(', ')}`);
|
await prompts.log.info(`Using tools from command-line: ${selectedIdes.join(', ')}`);
|
||||||
await this.displaySelectedTools(selectedIdes, preferredIdes, allTools);
|
await this.displaySelectedTools(selectedIdes, preferredIdes, allTools);
|
||||||
return { ides: selectedIdes, skipIde: false };
|
return { ides: selectedIdes, skipIde: false };
|
||||||
|
|
@ -516,21 +548,13 @@ class UI {
|
||||||
|
|
||||||
let selectedIdes = [];
|
let selectedIdes = [];
|
||||||
|
|
||||||
// Check if tools are provided via command-line
|
// Check if tools are provided via command-line.
|
||||||
if (options.tools) {
|
// Use !== undefined so an explicit --tools "" still hits _parseToolsFlag's empty-value error.
|
||||||
// Check for explicit "none" value to skip tool installation
|
if (options.tools !== undefined) {
|
||||||
if (options.tools.toLowerCase() === 'none') {
|
selectedIdes = this._parseToolsFlag(options.tools, allKnownValues);
|
||||||
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 prompts.log.info(`Using tools from command-line: ${selectedIdes.join(', ')}`);
|
||||||
await this.displaySelectedTools(selectedIdes, preferredIdes, allTools);
|
await this.displaySelectedTools(selectedIdes, preferredIdes, allTools);
|
||||||
return { ides: selectedIdes, skipIde: false };
|
return { ides: selectedIdes, skipIde: false };
|
||||||
}
|
|
||||||
} else if (options.yes) {
|
} else if (options.yes) {
|
||||||
// If --yes flag is set, skip tool prompt and use previously configured tools or empty
|
// If --yes flag is set, skip tool prompt and use previously configured tools or empty
|
||||||
if (configuredIdes.length > 0) {
|
if (configuredIdes.length > 0) {
|
||||||
|
|
@ -538,8 +562,18 @@ class UI {
|
||||||
await this.displaySelectedTools(configuredIdes, preferredIdes, allTools);
|
await this.displaySelectedTools(configuredIdes, preferredIdes, allTools);
|
||||||
return { ides: configuredIdes, skipIde: false };
|
return { ides: configuredIdes, skipIde: false };
|
||||||
} else {
|
} else {
|
||||||
await prompts.log.info('Skipping tool configuration (--yes flag, no previous tools)');
|
const err = new Error(
|
||||||
return { ides: [], skipIde: true };
|
[
|
||||||
|
'--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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -724,6 +758,9 @@ class UI {
|
||||||
const defaultUsername = safeUsername.charAt(0).toUpperCase() + safeUsername.slice(1);
|
const defaultUsername = safeUsername.charAt(0).toUpperCase() + safeUsername.slice(1);
|
||||||
configCollector.collectedConfig.core = {
|
configCollector.collectedConfig.core = {
|
||||||
user_name: defaultUsername,
|
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',
|
communication_language: 'English',
|
||||||
document_output_language: 'English',
|
document_output_language: 'English',
|
||||||
output_folder: '_bmad-output',
|
output_folder: '_bmad-output',
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue