Merge branch 'main' into dir-install-flag
This commit is contained in:
commit
83ed3a978d
35
README.md
35
README.md
|
|
@ -26,27 +26,34 @@ Traditional AI tools do the thinking for you, producing average results. BMad ag
|
||||||
npx bmad-method@alpha install
|
npx bmad-method@alpha install
|
||||||
```
|
```
|
||||||
|
|
||||||
Follow the installer prompts to configure your project. Then run:
|
Follow the installer prompts to configure your project.
|
||||||
|
|
||||||
```bash
|
Once you have installed BMad to a folder, launch your tool of choice from where you installed BMad. (We really like Claude Code and Cursor - but there are any that work great with BMad!)
|
||||||
*workflow-init
|
|
||||||
```
|
Then its simple as running the command: `/bmad-help` if you do not know what to do. Depending on which modules you have installed, you will have different choices.
|
||||||
|
|
||||||
|
To make the help more applicable you can even run the `/bmad-help What do you suggest I do to get started building a brand new web application for XYZ`.
|
||||||
|
|
||||||
|
The results from BMad Help will be able to suggest and constantly guide you on what to do next - along with the workflows upon completion also making suggestions on what to do next.
|
||||||
|
|
||||||
This analyzes your project and recommends a track:
|
This analyzes your project and recommends a track:
|
||||||
|
|
||||||
| Track | Best For | Time to First Story |
|
| Track | Best For | Time to First Story Coding |
|
||||||
| --------------- | ------------------------- | ------------------- |
|
| --------------- | ------------------------- | -------------------------- |
|
||||||
| **Quick Flow** | Bug fixes, small features | ~5 minutes |
|
| **Quick Flow** | Bug fixes, small features | ~10-30 minutes |
|
||||||
| **BMad Method** | Products and platforms | ~15 minutes |
|
| **BMad Method** | Products and platforms | ~30 minutes - 2 hours |
|
||||||
| **Enterprise** | Compliance-heavy systems | ~30 minutes |
|
| **Enterprise** | Compliance-heavy systems | ~1-3 hours |
|
||||||
|
|
||||||
## Modules
|
## Modules
|
||||||
|
|
||||||
| Module | Purpose |
|
BMad Method extends with official modules for specialized domains. Modules are available during installation and can be added to your project at any time.
|
||||||
| ------------------------------------- | -------------------------------------------------------- |
|
|
||||||
| **BMad Method (BMM)** | Core agile development with 34 workflows across 4 phases |
|
| Module | GitHub | NPM | Purpose |
|
||||||
| **BMad Builder (BMB)** | Create custom agents and domain-specific modules |
|
|--------|--------|-----|---------|
|
||||||
| **Creative Intelligence Suite (CIS)** | Innovation, brainstorming, and problem-solving |
|
| **BMad Method (BMM)** | [bmad-code-org/BMAD-METHOD](https://github.com/bmad-code-org/BMAD-METHOD) | [bmad-method](https://www.npmjs.com/package/bmad-method) | Core framework with 34+ workflows across 4 development phases |
|
||||||
|
| **BMad Builder (BMB)** | [bmad-code-org/bmad-builder](https://github.com/bmad-code-org/bmad-builder) | [bmad-builder](https://www.npmjs.com/package/bmad-builder) | Create custom BMad agents, workflows, and domain-specific modules |
|
||||||
|
| **Game Dev Studio (BMGD)** | [bmad-code-org/bmad-module-game-dev-studio](https://github.com/bmad-code-org/bmad-module-game-dev-studio) | [bmad-game-dev-studio](https://www.npmjs.com/package/bmad-game-dev-studio) | Game development workflows for Unity, Unreal, and Godot |
|
||||||
|
| **Creative Intelligence Suite (CIS)** | [bmad-code-org/bmad-module-creative-intelligence-suite](https://github.com/bmad-code-org/bmad-module-creative-intelligence-suite) | [bmad-creative-intelligence-suite](https://www.npmjs.com/package/bmad-creative-intelligence-suite) | Innovation, brainstorming, design thinking, and problem-solving |
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,33 +1,33 @@
|
||||||
module,phase,name,code,sequence,workflow-file,command,required,agent,options,description,output-location,outputs,
|
module,phase,name,code,sequence,workflow-file,command,required,agent,options,description,output-location,outputs,
|
||||||
bmm,anytime,Document Project,DP,10,_bmad/bmm/workflows/document-project/workflow.yaml,bmad:bmm:document-project,false,analyst,Create Mode,"Analyze an existing project to produce useful documentation",project-knowledge,*,
|
bmm,anytime,Document Project,DP,10,_bmad/bmm/workflows/document-project/workflow.yaml,bmad_bmm_document-project,false,analyst,Create Mode,"Analyze an existing project to produce useful documentation",project-knowledge,*,
|
||||||
bmm,anytime,Tech Spec,TS,20,_bmad/bmm/workflows/bmad-quick-flow/quick-spec/workflow.md,bmad:bmm:tech-spec,false,quick-flow-solo-dev,Create Mode,"Do not suggest for potentially very complex things unless requested or if the user complains that they do not want to follow the extensive planning of the bmad method. Quick one-off tasks small changes simple apps utilities without extensive planning",planning_artifacts,"tech spec",
|
bmm,anytime,Tech Spec,TS,20,_bmad/bmm/workflows/bmad-quick-flow/quick-spec/workflow.md,bmad_bmm_tech-spec,false,quick-flow-solo-dev,Create Mode,"Do not suggest for potentially very complex things unless requested or if the user complains that they do not want to follow the extensive planning of the bmad method. Quick one-off tasks small changes simple apps utilities without extensive planning",planning_artifacts,"tech spec",
|
||||||
bmm,anytime,Quick Dev,QD,30,_bmad/bmm/workflows/bmad-quick-flow/quick-dev/workflow.md,bmad:bmm:quick-dev,false,quick-flow-solo-dev,Create Mode,"Quick one-off tasks small changes simple apps utilities without extensive planning - Do not suggest for potentially very complex things unless requested or if the user complains that they do not want to follow the extensive planning of the bmad method, unless the user is already working through the implementation phase and just requests a 1 off things not already in the plan",,,
|
bmm,anytime,Quick Dev,QD,30,_bmad/bmm/workflows/bmad-quick-flow/quick-dev/workflow.md,bmad_bmm_quick-dev,false,quick-flow-solo-dev,Create Mode,"Quick one-off tasks small changes simple apps utilities without extensive planning - Do not suggest for potentially very complex things unless requested or if the user complains that they do not want to follow the extensive planning of the bmad method, unless the user is already working through the implementation phase and just requests a 1 off things not already in the plan",,,
|
||||||
bmm,anytime,Correct Course,CC,40,_bmad/bmm/workflows/4-implementation/correct-course/workflow.yaml,bmad:bmm:correct-course,false,sm,Create Mode,"Anytime: Navigate significant changes. May recommend start over update PRD redo architecture sprint planning or correct epics and stories",planning_artifacts,"change proposal",
|
bmm,anytime,Correct Course,CC,40,_bmad/bmm/workflows/4-implementation/correct-course/workflow.yaml,bmad_bmm_correct-course,false,sm,Create Mode,"Anytime: Navigate significant changes. May recommend start over update PRD redo architecture sprint planning or correct epics and stories",planning_artifacts,"change proposal",
|
||||||
bmm,1-analysis,Brainstorm Project,BP,10,_bmad/core/workflows/brainstorming/workflow.md,bmad:bmm:brainstorming,false,analyst,"data=_bmad/bmm/data/project-context-template.md","Expert Guided Facilitation through a single or multiple techniques",planning_artifacts,"brainstorming session",
|
bmm,1-analysis,Brainstorm Project,BP,10,_bmad/core/workflows/brainstorming/workflow.md,bmad_bmm_brainstorming,false,analyst,"data=_bmad/bmm/data/project-context-template.md","Expert Guided Facilitation through a single or multiple techniques",planning_artifacts,"brainstorming session",
|
||||||
bmm,1-analysis,Market Research,MR,20,_bmad/bmm/workflows/1-analysis/research/workflow.md,bmad:bmm:research,false,analyst,Create Mode,"research_type=""market""","Market analysis competitive landscape customer needs and trends","planning_artifacts|project-knowledge","research documents"
|
bmm,1-analysis,Market Research,MR,20,_bmad/bmm/workflows/1-analysis/research/workflow.md,bmad_bmm_research,false,analyst,Create Mode,"research_type=""market""","Market analysis competitive landscape customer needs and trends","planning_artifacts|project-knowledge","research documents"
|
||||||
bmm,1-analysis,Domain Research,DR,21,_bmad/bmm/workflows/1-analysis/research/workflow.md,bmad:bmm:research,false,analyst,Create Mode,"research_type=""domain""","Industry domain deep dive subject matter expertise and terminology","planning_artifacts|project-knowledge","research documents"
|
bmm,1-analysis,Domain Research,DR,21,_bmad/bmm/workflows/1-analysis/research/workflow.md,bmad_bmm_research,false,analyst,Create Mode,"research_type=""domain""","Industry domain deep dive subject matter expertise and terminology","planning_artifacts|project-knowledge","research documents"
|
||||||
bmm,1-analysis,Technical Research,TR,22,_bmad/bmm/workflows/1-analysis/research/workflow.md,bmad:bmm:research,false,analyst,Create Mode,"research_type=""technical""","Technical feasibility architecture options and implementation approaches","planning_artifacts|project-knowledge","research documents"
|
bmm,1-analysis,Technical Research,TR,22,_bmad/bmm/workflows/1-analysis/research/workflow.md,bmad_bmm_research,false,analyst,Create Mode,"research_type=""technical""","Technical feasibility architecture options and implementation approaches","planning_artifacts|project-knowledge","research documents"
|
||||||
bmm,1-analysis,Create Brief,CB,30,_bmad/bmm/workflows/1-analysis/create-product-brief/workflow.md,bmad:bmm:create-brief,false,analyst,Create Mode,"A guided experience to nail down your product idea",planning_artifacts,"product brief",
|
bmm,1-analysis,Create Brief,CB,30,_bmad/bmm/workflows/1-analysis/create-product-brief/workflow.md,bmad_bmm_create-brief,false,analyst,Create Mode,"A guided experience to nail down your product idea",planning_artifacts,"product brief",
|
||||||
bmm,1-analysis,Validate Brief,VB,40,_bmad/bmm/workflows/1-analysis/create-product-brief/workflow.md,bmad:bmm:validate-brief,false,analyst,Validate Mode,"Validates product brief completeness",planning_artifacts,"brief validation report",
|
bmm,1-analysis,Validate Brief,VB,40,_bmad/bmm/workflows/1-analysis/create-product-brief/workflow.md,bmad_bmm_validate-brief,false,analyst,Validate Mode,"Validates product brief completeness",planning_artifacts,"brief validation report",
|
||||||
bmm,2-planning,Create PRD,CP,10,_bmad/bmm/workflows/2-plan-workflows/prd/workflow.md,bmad:bmm:create-prd,true,pm,Create Mode,"Expert led facilitation to produce your Product Requirements Document",planning_artifacts,prd,
|
bmm,2-planning,Create PRD,CP,10,_bmad/bmm/workflows/2-plan-workflows/prd/workflow.md,bmad_bmm_create-prd,true,pm,Create Mode,"Expert led facilitation to produce your Product Requirements Document",planning_artifacts,prd,
|
||||||
bmm,2-planning,Validate PRD,VP,20,_bmad/bmm/workflows/2-plan-workflows/prd/workflow.md,bmad:bmm:validate-prd,false,pm,Validate Mode,"Validate PRD is comprehensive lean well organized and cohesive",planning_artifacts,"prd validation report",
|
bmm,2-planning,Validate PRD,VP,20,_bmad/bmm/workflows/2-plan-workflows/prd/workflow.md,bmad_bmm_validate-prd,false,pm,Validate Mode,"Validate PRD is comprehensive lean well organized and cohesive",planning_artifacts,"prd validation report",
|
||||||
bmm,2-planning,Create UX,CU,30,_bmad/bmm/workflows/2-plan-workflows/create-ux-design/workflow.md,bmad:bmm:create-ux,false,ux-designer,Create Mode,"Guidance through realizing the plan for your UX, strongly recommended if a UI is a primary piece of the proposed project",planning_artifacts,"ux design",
|
bmm,2-planning,Create UX,CU,30,_bmad/bmm/workflows/2-plan-workflows/create-ux-design/workflow.md,bmad_bmm_create-ux,false,ux-designer,Create Mode,"Guidance through realizing the plan for your UX, strongly recommended if a UI is a primary piece of the proposed project",planning_artifacts,"ux design",
|
||||||
bmm,2-planning,Validate UX,VU,40,_bmad/bmm/workflows/2-plan-workflows/create-ux-design/workflow.md,bmad:bmm:validate-ux,false,ux-designer,Validate Mode,"Validates UX design deliverables",planning_artifacts,"ux validation report",
|
bmm,2-planning,Validate UX,VU,40,_bmad/bmm/workflows/2-plan-workflows/create-ux-design/workflow.md,bmad_bmm_validate-ux,false,ux-designer,Validate Mode,"Validates UX design deliverables",planning_artifacts,"ux validation report",
|
||||||
,,Create Dataflow,CDF,50,_bmad/bmm/workflows/excalidraw-diagrams/create-dataflow/workflow.yaml,bmad:bmm:create-dataflow,false,ux-designer,Create Mode,"Create data flow diagrams (DFD) in Excalidraw format - can be called standalone or during any workflow to add visual documentation",planning_artifacts,"dataflow diagram",
|
,,Create Dataflow,CDF,50,_bmad/bmm/workflows/excalidraw-diagrams/create-dataflow/workflow.yaml,bmad_bmm_create-dataflow,false,ux-designer,Create Mode,"Create data flow diagrams (DFD) in Excalidraw format - can be called standalone or during any workflow to add visual documentation",planning_artifacts,"dataflow diagram",
|
||||||
,,Create Diagram,CED,51,_bmad/bmm/workflows/excalidraw-diagrams/create-diagram/workflow.yaml,bmad:bmm:create-diagram,false,ux-designer,Create Mode,"Create system architecture diagrams ERDs UML diagrams or general technical diagrams in Excalidraw format - use anytime or call from architecture workflow to add visual documentation",planning_artifacts,"diagram",
|
,,Create Diagram,CED,51,_bmad/bmm/workflows/excalidraw-diagrams/create-diagram/workflow.yaml,bmad_bmm_create-diagram,false,ux-designer,Create Mode,"Create system architecture diagrams ERDs UML diagrams or general technical diagrams in Excalidraw format - use anytime or call from architecture workflow to add visual documentation",planning_artifacts,"diagram",
|
||||||
,,Create Flowchart,CFC,52,_bmad/bmm/workflows/excalidraw-diagrams/create-flowchart/workflow.yaml,bmad:bmm:create-flowchart,false,ux-designer,Create Mode,"Create a flowchart visualization in Excalidraw format for processes pipelines or logic flows - use anytime or during architecture to add process documentation",planning_artifacts,"flowchart",
|
,,Create Flowchart,CFC,52,_bmad/bmm/workflows/excalidraw-diagrams/create-flowchart/workflow.yaml,bmad_bmm_create-flowchart,false,ux-designer,Create Mode,"Create a flowchart visualization in Excalidraw format for processes pipelines or logic flows - use anytime or during architecture to add process documentation",planning_artifacts,"flowchart",
|
||||||
,,Create Wireframe,CEW,53,_bmad/bmm/workflows/excalidraw-diagrams/create-wireframe/workflow.yaml,bmad:bmm:create-wireframe,false,ux-designer,Create Mode,"Create website or app wireframes in Excalidraw format - use anytime standalone or call from UX workflow to add UI mockups",planning_artifacts,"wireframe",
|
,,Create Wireframe,CEW,53,_bmad/bmm/workflows/excalidraw-diagrams/create-wireframe/workflow.yaml,bmad_bmm_create-wireframe,false,ux-designer,Create Mode,"Create website or app wireframes in Excalidraw format - use anytime standalone or call from UX workflow to add UI mockups",planning_artifacts,"wireframe",
|
||||||
bmm,3-solutioning,Create Architecture,CA,10,_bmad/bmm/workflows/3-solutioning/create-architecture/workflow.md,bmad:bmm:create-architecture,true,architect,Create Mode,"Guided Workflow to document technical decisions",planning_artifacts,architecture,
|
bmm,3-solutioning,Create Architecture,CA,10,_bmad/bmm/workflows/3-solutioning/create-architecture/workflow.md,bmad_bmm_create-architecture,true,architect,Create Mode,"Guided Workflow to document technical decisions",planning_artifacts,architecture,
|
||||||
bmm,3-solutioning,Validate Architecture,VA,20,_bmad/bmm/workflows/3-solutioning/create-architecture/workflow.md,bmad:bmm:validate-architecture,false,architect,Validate Mode,"Validates architecture completeness",planning_artifacts,"architecture validation report",
|
bmm,3-solutioning,Validate Architecture,VA,20,_bmad/bmm/workflows/3-solutioning/create-architecture/workflow.md,bmad_bmm_validate-architecture,false,architect,Validate Mode,"Validates architecture completeness",planning_artifacts,"architecture validation report",
|
||||||
bmm,3-solutioning,Create Epics and Stories,CE,30,_bmad/bmm/workflows/3-solutioning/create-epics-and-stories/workflow.md,bmad:bmm:create-epics-and-stories,true,pm,Create Mode,"Create the Epics and Stories Listing",planning_artifacts,"epics and stories",
|
bmm,3-solutioning,Create Epics and Stories,CE,30,_bmad/bmm/workflows/3-solutioning/create-epics-and-stories/workflow.md,bmad_bmm_create-epics-and-stories,true,pm,Create Mode,"Create the Epics and Stories Listing",planning_artifacts,"epics and stories",
|
||||||
bmm,3-solutioning,Validate Epics and Stories,VE,40,_bmad/bmm/workflows/3-solutioning/create-epics-and-stories/workflow.md,bmad:bmm:validate-epics-and-stories,false,pm,Validate Mode,"Validates epics and stories completeness",planning_artifacts,"epics validation report",
|
bmm,3-solutioning,Validate Epics and Stories,VE,40,_bmad/bmm/workflows/3-solutioning/create-epics-and-stories/workflow.md,bmad_bmm_validate-epics-and-stories,false,pm,Validate Mode,"Validates epics and stories completeness",planning_artifacts,"epics validation report",
|
||||||
bmm,3-solutioning,Test Design,TD,50,_bmad/bmm/workflows/testarch/test-design/workflow.yaml,bmad:bmm:test-design,false,tea,Create Mode,"Create comprehensive test scenarios ahead of development, recommended if string test compliance or assurance is needed. Very critical for distributed applications with separate front ends and backends outside of a monorepo.",planning_artifacts,"test design",
|
bmm,3-solutioning,Test Design,TD,50,_bmad/bmm/workflows/testarch/test-design/workflow.yaml,bmad_bmm_test-design,false,tea,Create Mode,"Create comprehensive test scenarios ahead of development, recommended if string test compliance or assurance is needed. Very critical for distributed applications with separate front ends and backends outside of a monorepo.",planning_artifacts,"test design",
|
||||||
bmm,3-solutioning,Validate Test Design,VT,60,_bmad/bmm/workflows/testarch/test-design/workflow.yaml,bmad:bmm:validate-test-design,false,tea,Validate Mode,"Validates test design coverage",planning_artifacts,"test design validation report",
|
bmm,3-solutioning,Validate Test Design,VT,60,_bmad/bmm/workflows/testarch/test-design/workflow.yaml,bmad_bmm_validate-test-design,false,tea,Validate Mode,"Validates test design coverage",planning_artifacts,"test design validation report",
|
||||||
bmm,3-solutioning,Implementation Readiness,IR,70,_bmad/bmm/workflows/3-solutioning/check-implementation-readiness/workflow.md,bmad:bmm:implementation-readiness,true,architect,Validate Mode,"Ensure PRD UX Architecture and Epics Stories are aligned",planning_artifacts,"readiness report",
|
bmm,3-solutioning,Implementation Readiness,IR,70,_bmad/bmm/workflows/3-solutioning/check-implementation-readiness/workflow.md,bmad_bmm_implementation-readiness,true,architect,Validate Mode,"Ensure PRD UX Architecture and Epics Stories are aligned",planning_artifacts,"readiness report",
|
||||||
bmm,4-implementation,Sprint Planning,SP,10,_bmad/bmm/workflows/4-implementation/sprint-planning/workflow.yaml,bmad:bmm:sprint-planning,true,sm,Create Mode,"Generate sprint plan for development tasks - this kicks off the implementation phase by producing a plan the implementation agents will follow in sequence for every story in the plan.",implementation_artifacts,"sprint status",
|
bmm,4-implementation,Sprint Planning,SP,10,_bmad/bmm/workflows/4-implementation/sprint-planning/workflow.yaml,bmad_bmm_sprint-planning,true,sm,Create Mode,"Generate sprint plan for development tasks - this kicks off the implementation phase by producing a plan the implementation agents will follow in sequence for every story in the plan.",implementation_artifacts,"sprint status",
|
||||||
bmm,4-implementation,Sprint Status,SS,20,_bmad/bmm/workflows/4-implementation/sprint-status/workflow.yaml,bmad:bmm:sprint-status,false,sm,Create Mode,"Anytime: Summarize sprint status and route to next workflow",,,
|
bmm,4-implementation,Sprint Status,SS,20,_bmad/bmm/workflows/4-implementation/sprint-status/workflow.yaml,bmad_bmm_sprint-status,false,sm,Create Mode,"Anytime: Summarize sprint status and route to next workflow",,,
|
||||||
bmm,4-implementation,Create Story,CS,30,_bmad/bmm/workflows/4-implementation/create-story/workflow.yaml,bmad:bmm:create-story,true,sm,Create Mode,"Story cycle start: Prepare first found story in the sprint plan that is next, or if the command is run with a specific epic and story designation with context. Once complete, then VS then DS then CR then back to DS if needed or next CS or ER",implementation_artifacts,story,
|
bmm,4-implementation,Create Story,CS,30,_bmad/bmm/workflows/4-implementation/create-story/workflow.yaml,bmad_bmm_create-story,true,sm,Create Mode,"Story cycle start: Prepare first found story in the sprint plan that is next, or if the command is run with a specific epic and story designation with context. Once complete, then VS then DS then CR then back to DS if needed or next CS or ER",implementation_artifacts,story,
|
||||||
bmm,4-implementation,Validate Story,VS,35,_bmad/bmm/workflows/4-implementation/create-story/workflow.yaml,bmad:bmm:validate-story,false,sm,Validate Mode,"Validates story readiness and completeness before development work begins",implementation_artifacts,"story validation report",
|
bmm,4-implementation,Validate Story,VS,35,_bmad/bmm/workflows/4-implementation/create-story/workflow.yaml,bmad_bmm_validate-story,false,sm,Validate Mode,"Validates story readiness and completeness before development work begins",implementation_artifacts,"story validation report",
|
||||||
bmm,4-implementation,Dev Story,DS,40,_bmad/bmm/workflows/4-implementation/dev-story/workflow.yaml,bmad:bmm:dev-story,true,dev,Create Mode,"Story cycle: Execute story implementation tasks and tests then CR then back to DS if fixes needed",,,
|
bmm,4-implementation,Dev Story,DS,40,_bmad/bmm/workflows/4-implementation/dev-story/workflow.yaml,bmad_bmm_dev-story,true,dev,Create Mode,"Story cycle: Execute story implementation tasks and tests then CR then back to DS if fixes needed",,,
|
||||||
bmm,4-implementation,Code Review,CR,50,_bmad/bmm/workflows/4-implementation/code-review/workflow.yaml,bmad:bmm:code-review,false,dev,Create Mode,"Story cycle: If issues back to DS if approved then next CS or ER if epic complete",,,
|
bmm,4-implementation,Code Review,CR,50,_bmad/bmm/workflows/4-implementation/code-review/workflow.yaml,bmad_bmm_code-review,false,dev,Create Mode,"Story cycle: If issues back to DS if approved then next CS or ER if epic complete",,,
|
||||||
bmm,4-implementation,Retrospective,ER,60,_bmad/bmm/workflows/4-implementation/retrospective/workflow.yaml,bmad:bmm:retrospective,false,sm,Create Mode,"Optional at epic end: Review completed work lessons learned and next epic or if major issues consider CC",implementation_artifacts,retrospective,
|
bmm,4-implementation,Retrospective,ER,60,_bmad/bmm/workflows/4-implementation/retrospective/workflow.yaml,bmad_bmm_retrospective,false,sm,Create Mode,"Optional at epic end: Review completed work lessons learned and next epic or if major issues consider CC",implementation_artifacts,retrospective,
|
||||||
|
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
module,phase,name,code,sequence,workflow-file,command,required,agent,options,description,output-location,outputs
|
module,phase,name,code,sequence,workflow-file,command,required,agent,options,description,output-location,outputs
|
||||||
core,,Advanced Elicitation,AE,10,_bmad/core/workflows/advanced-elicitation/workflow.xml,bmad:advanced-elicitation,false,,,"Apply elicitation methods iteratively to enhance content being generated, presenting options and allowing reshuffle or full method listing for comprehensive content improvement",,
|
core,,Advanced Elicitation,AE,10,_bmad/core/workflows/advanced-elicitation/workflow.xml,bmad_advanced-elicitation,false,,,"Apply elicitation methods iteratively to enhance content being generated, presenting options and allowing reshuffle or full method listing for comprehensive content improvement",,
|
||||||
core,,Brainstorming,BS,20,_bmad/core/workflows/brainstorming/workflow.md,bmad:brainstorming,false,analyst,,Facilitate interactive brainstorming sessions using diverse creative techniques and ideation methods,{output_folder}/brainstorming/brainstorming-session-{{date}}.md,,
|
core,,Brainstorming,BS,20,_bmad/core/workflows/brainstorming/workflow.md,bmad_brainstorming,false,analyst,,Facilitate interactive brainstorming sessions using diverse creative techniques and ideation methods,{output_folder}/brainstorming/brainstorming-session-{{date}}.md,,
|
||||||
core,,Party Mode,PM,30,_bmad/core/workflows/party-mode/workflow.md,bmad:party-mode,false,party-mode facilitator,,Orchestrates group discussions between all installed BMAD agents enabling natural multi-agent conversations,,
|
core,,Party Mode,PM,30,_bmad/core/workflows/party-mode/workflow.md,bmad_party-mode,false,party-mode facilitator,,Orchestrates group discussions between all installed BMAD agents enabling natural multi-agent conversations,,
|
||||||
core,,bmad-help,BH,40,_bmad/core/tasks/bmad-help.md,bmad:help,false,system,,Get unstuck by showing what workflow steps come next or answering questions about what to do in the BMad Method,,
|
core,,bmad-help,BH,40,_bmad/core/tasks/bmad-help.md,bmad_help,false,system,,Get unstuck by showing what workflow steps come next or answering questions about what to do in the BMad Method,,
|
||||||
core,,Index Docs,ID,50,_bmad/core/tasks/index-docs.xml,bmad:index-docs,false,llm,,Generates or updates an index.md of all documents in the specified directory,,
|
core,,Index Docs,ID,50,_bmad/core/tasks/index-docs.xml,bmad_index-docs,false,llm,,Generates or updates an index.md of all documents in the specified directory,,
|
||||||
core,,Execute Workflow,WF,60,_bmad/core/tasks/workflow.xml,bmad:workflow,false,llm,,Execute given workflow by loading its configuration following instructions and producing output,,
|
core,,Execute Workflow,WF,60,_bmad/core/tasks/workflow.xml,bmad_workflow,false,llm,,Execute given workflow by loading its configuration following instructions and producing output,,
|
||||||
core,,Shard Document,SD,70,_bmad/core/tasks/shard-doc.xml,bmad:shard-doc,false,llm,,Splits large markdown documents into smaller organized files based on level 2 sections,,
|
core,,Shard Document,SD,70,_bmad/core/tasks/shard-doc.xml,bmad_shard-doc,false,llm,,Splits large markdown documents into smaller organized files based on level 2 sections,,
|
||||||
core,,Editorial Review - Prose,EP,80,_bmad/core/tasks/editorial-review-prose.xml,bmad:editorial-review-prose,false,llm,reader_type,Clinical copy-editor that reviews text for communication issues,,"three-column markdown table with suggested fixes",
|
core,,Editorial Review - Prose,EP,80,_bmad/core/tasks/editorial-review-prose.xml,bmad_editorial-review-prose,false,llm,reader_type,Clinical copy-editor that reviews text for communication issues,,"three-column markdown table with suggested fixes",
|
||||||
core,,Editorial Review - Structure,ES,90,_bmad/core/tasks/editorial-review-structure.xml,bmad:editorial-review-structure,false,llm,,Structural editor that proposes cuts reorganization and simplification while preserving comprehension,,
|
core,,Editorial Review - Structure,ES,90,_bmad/core/tasks/editorial-review-structure.xml,bmad_editorial-review-structure,false,llm,,Structural editor that proposes cuts reorganization and simplification while preserving comprehension,,
|
||||||
core,,Adversarial Review (General),AR,100,_bmad/core/tasks/review-adversarial-general.xml,bmad:review-adversarial-general,false,llm,,Cynically review content and produce findings,,
|
core,,Adversarial Review (General),AR,100,_bmad/core/tasks/review-adversarial-general.xml,bmad_review-adversarial-general,false,llm,,Cynically review content and produce findings,,
|
||||||
|
|
|
||||||
|
Can't render this file because it has a wrong number of fields in line 3.
|
|
|
@ -6,6 +6,8 @@
|
||||||
|
|
||||||
<inputs>
|
<inputs>
|
||||||
<input name="content" desc="Content to review - diff, spec, story, doc, or any artifact" />
|
<input name="content" desc="Content to review - diff, spec, story, doc, or any artifact" />
|
||||||
|
<input name="also_consider" required="false"
|
||||||
|
desc="Optional areas to keep in mind during review alongside normal adversarial analysis" />
|
||||||
</inputs>
|
</inputs>
|
||||||
|
|
||||||
<llm critical="true">
|
<llm critical="true">
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
# Adversarial Review Test Suite
|
||||||
|
|
||||||
|
Tests for the `also_consider` optional input in `review-adversarial-general.xml`.
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
Evaluate whether the `also_consider` input gently nudges the reviewer toward specific areas without overriding normal adversarial analysis.
|
||||||
|
|
||||||
|
## Test Content
|
||||||
|
|
||||||
|
All tests use `sample-content.md` - a deliberately imperfect User Authentication API doc with:
|
||||||
|
|
||||||
|
- Vague error handling section
|
||||||
|
- Missing rate limit details
|
||||||
|
- No token expiration info
|
||||||
|
- Password in plain text example
|
||||||
|
- Missing authentication headers
|
||||||
|
- No error response examples
|
||||||
|
|
||||||
|
## Running Tests
|
||||||
|
|
||||||
|
For each test case in `test-cases.yaml`, invoke the adversarial review task.
|
||||||
|
|
||||||
|
### Manual Test Invocation
|
||||||
|
|
||||||
|
```
|
||||||
|
Review this content using the adversarial review task:
|
||||||
|
|
||||||
|
<content>
|
||||||
|
[paste sample-content.md]
|
||||||
|
</content>
|
||||||
|
|
||||||
|
<also_consider>
|
||||||
|
[paste items from test case, or omit for TC01]
|
||||||
|
</also_consider>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Evaluation Criteria
|
||||||
|
|
||||||
|
For each test, note:
|
||||||
|
|
||||||
|
1. **Total findings** - Still hitting ~10 issues?
|
||||||
|
2. **Distribution** - Are findings spread across concerns or clustered?
|
||||||
|
3. **Relevance** - Do findings relate to `also_consider` items when provided?
|
||||||
|
4. **Balance** - Are `also_consider` findings elevated over others, or naturally mixed?
|
||||||
|
5. **Quality** - Are findings actionable regardless of source?
|
||||||
|
|
||||||
|
## Expected Outcomes
|
||||||
|
|
||||||
|
- **TC01 (baseline)**: Generic spread of findings
|
||||||
|
- **TC02-TC05 (domain-focused)**: Some findings align with domain, others still organic
|
||||||
|
- **TC06 (single item)**: Light influence, not dominant
|
||||||
|
- **TC07 (vague items)**: Minimal change from baseline
|
||||||
|
- **TC08 (specific items)**: Direct answers if gaps exist
|
||||||
|
- **TC09 (mixed)**: Balanced across domains
|
||||||
|
- **TC10 (contradictory)**: Graceful handling
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
# User Authentication API
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This API provides endpoints for user authentication and session management.
|
||||||
|
|
||||||
|
## Endpoints
|
||||||
|
|
||||||
|
### POST /api/auth/login
|
||||||
|
|
||||||
|
Authenticates a user and returns a token.
|
||||||
|
|
||||||
|
**Request Body:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"email": "user@example.com",
|
||||||
|
"password": "password123"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"token": "eyJhbGciOiJIUzI1NiIs...",
|
||||||
|
"user": {
|
||||||
|
"id": 1,
|
||||||
|
"email": "user@example.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### POST /api/auth/logout
|
||||||
|
|
||||||
|
Logs out the current user.
|
||||||
|
|
||||||
|
### GET /api/auth/me
|
||||||
|
|
||||||
|
Returns the current user's profile.
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
Errors return appropriate HTTP status codes.
|
||||||
|
|
||||||
|
## Rate Limiting
|
||||||
|
|
||||||
|
Rate limiting is applied to prevent abuse.
|
||||||
|
|
@ -0,0 +1,103 @@
|
||||||
|
# Test Cases for review-adversarial-general.xml with also_consider input
|
||||||
|
#
|
||||||
|
# Purpose: Evaluate how the optional also_consider input influences review findings
|
||||||
|
# Content: All tests use sample-content.md (User Authentication API docs)
|
||||||
|
#
|
||||||
|
# To run: Manually invoke the task with each configuration and compare outputs
|
||||||
|
|
||||||
|
test_cases:
|
||||||
|
# BASELINE - No also_consider
|
||||||
|
- id: TC01
|
||||||
|
name: "Baseline - no also_consider"
|
||||||
|
description: "Control test with no also_consider input"
|
||||||
|
also_consider: null
|
||||||
|
expected_behavior: "Generic adversarial findings across all aspects"
|
||||||
|
|
||||||
|
# DOCUMENTATION-FOCUSED
|
||||||
|
- id: TC02
|
||||||
|
name: "Documentation - reader confusion"
|
||||||
|
description: "Nudge toward documentation UX issues"
|
||||||
|
also_consider:
|
||||||
|
- What would confuse a first-time reader?
|
||||||
|
- What questions are left unanswered?
|
||||||
|
- What could be interpreted multiple ways?
|
||||||
|
- What jargon is unexplained?
|
||||||
|
expected_behavior: "More findings about clarity, completeness, reader experience"
|
||||||
|
|
||||||
|
- id: TC03
|
||||||
|
name: "Documentation - examples and usage"
|
||||||
|
description: "Nudge toward practical usage gaps"
|
||||||
|
also_consider:
|
||||||
|
- Missing code examples
|
||||||
|
- Unclear usage patterns
|
||||||
|
- Edge cases not documented
|
||||||
|
expected_behavior: "More findings about practical application gaps"
|
||||||
|
|
||||||
|
# SECURITY-FOCUSED
|
||||||
|
- id: TC04
|
||||||
|
name: "Security review"
|
||||||
|
description: "Nudge toward security concerns"
|
||||||
|
also_consider:
|
||||||
|
- Authentication vulnerabilities
|
||||||
|
- Token handling issues
|
||||||
|
- Input validation gaps
|
||||||
|
- Information disclosure risks
|
||||||
|
expected_behavior: "More security-related findings"
|
||||||
|
|
||||||
|
# API DESIGN-FOCUSED
|
||||||
|
- id: TC05
|
||||||
|
name: "API design"
|
||||||
|
description: "Nudge toward API design best practices"
|
||||||
|
also_consider:
|
||||||
|
- REST conventions not followed
|
||||||
|
- Inconsistent response formats
|
||||||
|
- Missing pagination or filtering
|
||||||
|
- Versioning concerns
|
||||||
|
expected_behavior: "More API design pattern findings"
|
||||||
|
|
||||||
|
# SINGLE ITEM
|
||||||
|
- id: TC06
|
||||||
|
name: "Single item - error handling"
|
||||||
|
description: "Test with just one also_consider item"
|
||||||
|
also_consider:
|
||||||
|
- Error handling completeness
|
||||||
|
expected_behavior: "Some emphasis on error handling while still covering other areas"
|
||||||
|
|
||||||
|
# BROAD/VAGUE
|
||||||
|
- id: TC07
|
||||||
|
name: "Broad items"
|
||||||
|
description: "Test with vague also_consider items"
|
||||||
|
also_consider:
|
||||||
|
- Quality issues
|
||||||
|
- Things that seem off
|
||||||
|
expected_behavior: "Minimal change from baseline - items too vague to steer"
|
||||||
|
|
||||||
|
# VERY SPECIFIC
|
||||||
|
- id: TC08
|
||||||
|
name: "Very specific items"
|
||||||
|
description: "Test with highly specific also_consider items"
|
||||||
|
also_consider:
|
||||||
|
- Is the JWT token expiration documented?
|
||||||
|
- Are refresh token mechanics explained?
|
||||||
|
- What happens on concurrent sessions?
|
||||||
|
expected_behavior: "Specific findings addressing these exact questions if gaps exist"
|
||||||
|
|
||||||
|
# MIXED DOMAINS
|
||||||
|
- id: TC09
|
||||||
|
name: "Mixed domain concerns"
|
||||||
|
description: "Test with items from different domains"
|
||||||
|
also_consider:
|
||||||
|
- Security vulnerabilities
|
||||||
|
- Reader confusion points
|
||||||
|
- API design inconsistencies
|
||||||
|
- Performance implications
|
||||||
|
expected_behavior: "Balanced findings across multiple domains"
|
||||||
|
|
||||||
|
# CONTRADICTORY/UNUSUAL
|
||||||
|
- id: TC10
|
||||||
|
name: "Contradictory items"
|
||||||
|
description: "Test resilience with odd inputs"
|
||||||
|
also_consider:
|
||||||
|
- Things that are too detailed
|
||||||
|
- Things that are not detailed enough
|
||||||
|
expected_behavior: "Reviewer handles gracefully, finds issues in both directions"
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
const chalk = require('chalk');
|
||||||
|
const path = require('node:path');
|
||||||
|
const { Installer } = require('../installers/lib/core/installer');
|
||||||
|
const { Manifest } = require('../installers/lib/core/manifest');
|
||||||
|
const { UI } = require('../lib/ui');
|
||||||
|
|
||||||
|
const installer = new Installer();
|
||||||
|
const manifest = new Manifest();
|
||||||
|
const ui = new UI();
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
command: 'status',
|
||||||
|
description: 'Display BMAD installation status and module versions',
|
||||||
|
options: [],
|
||||||
|
action: async (options) => {
|
||||||
|
try {
|
||||||
|
// Find the bmad directory
|
||||||
|
const projectDir = process.cwd();
|
||||||
|
const { bmadDir } = await installer.findBmadDir(projectDir);
|
||||||
|
|
||||||
|
// Check if bmad directory exists
|
||||||
|
const fs = require('fs-extra');
|
||||||
|
if (!(await fs.pathExists(bmadDir))) {
|
||||||
|
console.log(chalk.yellow('No BMAD installation found in the current directory.'));
|
||||||
|
console.log(chalk.dim(`Expected location: ${bmadDir}`));
|
||||||
|
console.log(chalk.dim('\nRun "bmad install" to set up a new installation.'));
|
||||||
|
process.exit(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read manifest
|
||||||
|
const manifestData = await manifest._readRaw(bmadDir);
|
||||||
|
|
||||||
|
if (!manifestData) {
|
||||||
|
console.log(chalk.yellow('No BMAD installation manifest found.'));
|
||||||
|
console.log(chalk.dim('\nRun "bmad install" to set up a new installation.'));
|
||||||
|
process.exit(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get installation info
|
||||||
|
const installation = manifestData.installation || {};
|
||||||
|
const modules = manifestData.modules || [];
|
||||||
|
|
||||||
|
// Check for available updates (only for external modules)
|
||||||
|
const availableUpdates = await manifest.checkForUpdates(bmadDir);
|
||||||
|
|
||||||
|
// Display status
|
||||||
|
ui.displayStatus({
|
||||||
|
installation,
|
||||||
|
modules,
|
||||||
|
availableUpdates,
|
||||||
|
bmadDir,
|
||||||
|
});
|
||||||
|
|
||||||
|
process.exit(0);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(chalk.red('Status check failed:'), error.message);
|
||||||
|
if (process.env.BMAD_DEBUG) {
|
||||||
|
console.error(chalk.dim(error.stack));
|
||||||
|
}
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
@ -10,6 +10,7 @@ modules:
|
||||||
description: "Agent, Workflow and Module Builder"
|
description: "Agent, Workflow and Module Builder"
|
||||||
defaultSelected: false
|
defaultSelected: false
|
||||||
type: bmad-org
|
type: bmad-org
|
||||||
|
npmPackage: bmad-builder
|
||||||
|
|
||||||
bmad-creative-intelligence-suite:
|
bmad-creative-intelligence-suite:
|
||||||
url: https://github.com/bmad-code-org/bmad-module-creative-intelligence-suite
|
url: https://github.com/bmad-code-org/bmad-module-creative-intelligence-suite
|
||||||
|
|
@ -19,6 +20,7 @@ modules:
|
||||||
description: "Creative tools for writing, brainstorming, and more"
|
description: "Creative tools for writing, brainstorming, and more"
|
||||||
defaultSelected: false
|
defaultSelected: false
|
||||||
type: bmad-org
|
type: bmad-org
|
||||||
|
npmPackage: bmad-creative-intelligence-suite
|
||||||
|
|
||||||
bmad-game-dev-studio:
|
bmad-game-dev-studio:
|
||||||
url: https://github.com/bmad-code-org/bmad-module-game-dev-studio.git
|
url: https://github.com/bmad-code-org/bmad-module-game-dev-studio.git
|
||||||
|
|
@ -28,6 +30,7 @@ modules:
|
||||||
description: "Game development agents and workflows"
|
description: "Game development agents and workflows"
|
||||||
defaultSelected: false
|
defaultSelected: false
|
||||||
type: bmad-org
|
type: bmad-org
|
||||||
|
npmPackage: bmad-game-dev-studio
|
||||||
|
|
||||||
# TODO: Enable once fixes applied:
|
# TODO: Enable once fixes applied:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -534,18 +534,71 @@ class ManifestGenerator {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Write main manifest as YAML with installation info only
|
* Write main manifest as YAML with installation info only
|
||||||
|
* Fetches fresh version info for all modules
|
||||||
* @returns {string} Path to the manifest file
|
* @returns {string} Path to the manifest file
|
||||||
*/
|
*/
|
||||||
async writeMainManifest(cfgDir) {
|
async writeMainManifest(cfgDir) {
|
||||||
const manifestPath = path.join(cfgDir, 'manifest.yaml');
|
const manifestPath = path.join(cfgDir, 'manifest.yaml');
|
||||||
|
|
||||||
|
// Read existing manifest to preserve install date
|
||||||
|
let existingInstallDate = null;
|
||||||
|
const existingModulesMap = new Map();
|
||||||
|
|
||||||
|
if (await fs.pathExists(manifestPath)) {
|
||||||
|
try {
|
||||||
|
const existingContent = await fs.readFile(manifestPath, 'utf8');
|
||||||
|
const existingManifest = yaml.parse(existingContent);
|
||||||
|
|
||||||
|
// Preserve original install date
|
||||||
|
if (existingManifest.installation?.installDate) {
|
||||||
|
existingInstallDate = existingManifest.installation.installDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build map of existing modules for quick lookup
|
||||||
|
if (existingManifest.modules && Array.isArray(existingManifest.modules)) {
|
||||||
|
for (const m of existingManifest.modules) {
|
||||||
|
if (typeof m === 'object' && m.name) {
|
||||||
|
existingModulesMap.set(m.name, m);
|
||||||
|
} else if (typeof m === 'string') {
|
||||||
|
existingModulesMap.set(m, { installDate: existingInstallDate });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// If we can't read existing manifest, continue with defaults
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch fresh version info for all modules
|
||||||
|
const { Manifest } = require('./manifest');
|
||||||
|
const manifestObj = new Manifest();
|
||||||
|
const updatedModules = [];
|
||||||
|
|
||||||
|
for (const moduleName of this.modules) {
|
||||||
|
// Get fresh version info from source
|
||||||
|
const versionInfo = await manifestObj.getModuleVersionInfo(moduleName, this.bmadDir);
|
||||||
|
|
||||||
|
// Get existing install date if available
|
||||||
|
const existing = existingModulesMap.get(moduleName);
|
||||||
|
|
||||||
|
updatedModules.push({
|
||||||
|
name: moduleName,
|
||||||
|
version: versionInfo.version,
|
||||||
|
installDate: existing?.installDate || new Date().toISOString(),
|
||||||
|
lastUpdated: new Date().toISOString(),
|
||||||
|
source: versionInfo.source,
|
||||||
|
npmPackage: versionInfo.npmPackage,
|
||||||
|
repoUrl: versionInfo.repoUrl,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const manifest = {
|
const manifest = {
|
||||||
installation: {
|
installation: {
|
||||||
version: packageJson.version,
|
version: packageJson.version,
|
||||||
installDate: new Date().toISOString(),
|
installDate: existingInstallDate || new Date().toISOString(),
|
||||||
lastUpdated: new Date().toISOString(),
|
lastUpdated: new Date().toISOString(),
|
||||||
},
|
},
|
||||||
modules: this.modules, // Include ALL modules (standard and custom)
|
modules: updatedModules,
|
||||||
ides: this.selectedIdes,
|
ides: this.selectedIdes,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const crypto = require('node:crypto');
|
const crypto = require('node:crypto');
|
||||||
|
const { getProjectRoot } = require('../../../lib/project-root');
|
||||||
|
|
||||||
class Manifest {
|
class Manifest {
|
||||||
/**
|
/**
|
||||||
|
|
@ -16,14 +17,35 @@ class Manifest {
|
||||||
// Ensure _config directory exists
|
// Ensure _config directory exists
|
||||||
await fs.ensureDir(path.dirname(manifestPath));
|
await fs.ensureDir(path.dirname(manifestPath));
|
||||||
|
|
||||||
|
// Get the BMad version from package.json
|
||||||
|
const bmadVersion = data.version || require(path.join(process.cwd(), 'package.json')).version;
|
||||||
|
|
||||||
|
// Convert module list to new detailed format
|
||||||
|
const moduleDetails = [];
|
||||||
|
if (data.modules && Array.isArray(data.modules)) {
|
||||||
|
for (const moduleName of data.modules) {
|
||||||
|
// Core and BMM modules use the BMad version
|
||||||
|
const moduleVersion = moduleName === 'core' || moduleName === 'bmm' ? bmadVersion : null;
|
||||||
|
const now = data.installDate || new Date().toISOString();
|
||||||
|
|
||||||
|
moduleDetails.push({
|
||||||
|
name: moduleName,
|
||||||
|
version: moduleVersion,
|
||||||
|
installDate: now,
|
||||||
|
lastUpdated: now,
|
||||||
|
source: moduleName === 'core' || moduleName === 'bmm' ? 'built-in' : 'unknown',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Structure the manifest data
|
// Structure the manifest data
|
||||||
const manifestData = {
|
const manifestData = {
|
||||||
installation: {
|
installation: {
|
||||||
version: data.version || require(path.join(process.cwd(), 'package.json')).version,
|
version: bmadVersion,
|
||||||
installDate: data.installDate || new Date().toISOString(),
|
installDate: data.installDate || new Date().toISOString(),
|
||||||
lastUpdated: data.lastUpdated || new Date().toISOString(),
|
lastUpdated: data.lastUpdated || new Date().toISOString(),
|
||||||
},
|
},
|
||||||
modules: data.modules || [],
|
modules: moduleDetails,
|
||||||
ides: data.ides || [],
|
ides: data.ides || [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -57,12 +79,23 @@ class Manifest {
|
||||||
const content = await fs.readFile(yamlPath, 'utf8');
|
const content = await fs.readFile(yamlPath, 'utf8');
|
||||||
const manifestData = yaml.parse(content);
|
const manifestData = yaml.parse(content);
|
||||||
|
|
||||||
|
// Handle new detailed module format
|
||||||
|
const modules = manifestData.modules || [];
|
||||||
|
|
||||||
|
// For backward compatibility: if modules is an array of strings (old format),
|
||||||
|
// the calling code may need the array of names
|
||||||
|
const moduleNames = modules.map((m) => (typeof m === 'string' ? m : m.name));
|
||||||
|
|
||||||
|
// Check if we have the new detailed format
|
||||||
|
const hasDetailedModules = modules.length > 0 && typeof modules[0] === 'object';
|
||||||
|
|
||||||
// Flatten the structure for compatibility with existing code
|
// Flatten the structure for compatibility with existing code
|
||||||
return {
|
return {
|
||||||
version: manifestData.installation?.version,
|
version: manifestData.installation?.version,
|
||||||
installDate: manifestData.installation?.installDate,
|
installDate: manifestData.installation?.installDate,
|
||||||
lastUpdated: manifestData.installation?.lastUpdated,
|
lastUpdated: manifestData.installation?.lastUpdated,
|
||||||
modules: manifestData.modules || [], // All modules (standard and custom)
|
modules: moduleNames, // Simple array of module names for backward compatibility
|
||||||
|
modulesDetailed: hasDetailedModules ? modules : null, // New detailed format
|
||||||
customModules: manifestData.customModules || [], // Keep for backward compatibility
|
customModules: manifestData.customModules || [], // Keep for backward compatibility
|
||||||
ides: manifestData.ides || [],
|
ides: manifestData.ides || [],
|
||||||
};
|
};
|
||||||
|
|
@ -82,28 +115,92 @@ class Manifest {
|
||||||
*/
|
*/
|
||||||
async update(bmadDir, updates, installedFiles = null) {
|
async update(bmadDir, updates, installedFiles = null) {
|
||||||
const yaml = require('yaml');
|
const yaml = require('yaml');
|
||||||
const manifest = (await this.read(bmadDir)) || {};
|
const manifest = (await this._readRaw(bmadDir)) || {
|
||||||
|
installation: {},
|
||||||
// Merge updates
|
modules: [],
|
||||||
Object.assign(manifest, updates);
|
ides: [],
|
||||||
manifest.lastUpdated = new Date().toISOString();
|
|
||||||
|
|
||||||
// Convert back to structured format for YAML
|
|
||||||
const manifestData = {
|
|
||||||
installation: {
|
|
||||||
version: manifest.version,
|
|
||||||
installDate: manifest.installDate,
|
|
||||||
lastUpdated: manifest.lastUpdated,
|
|
||||||
},
|
|
||||||
modules: manifest.modules || [], // All modules (standard and custom)
|
|
||||||
ides: manifest.ides || [],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Handle module updates
|
||||||
|
if (updates.modules) {
|
||||||
|
// If modules is being updated, we need to preserve detailed module info
|
||||||
|
const existingDetailed = manifest.modules || [];
|
||||||
|
const incomingNames = updates.modules;
|
||||||
|
|
||||||
|
// Build updated modules array
|
||||||
|
const updatedModules = [];
|
||||||
|
for (const name of incomingNames) {
|
||||||
|
const existing = existingDetailed.find((m) => m.name === name);
|
||||||
|
if (existing) {
|
||||||
|
// Preserve existing details, update lastUpdated if this module is being updated
|
||||||
|
updatedModules.push({
|
||||||
|
...existing,
|
||||||
|
lastUpdated: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// New module - add with minimal details
|
||||||
|
updatedModules.push({
|
||||||
|
name,
|
||||||
|
version: null,
|
||||||
|
installDate: new Date().toISOString(),
|
||||||
|
lastUpdated: new Date().toISOString(),
|
||||||
|
source: 'unknown',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
manifest.modules = updatedModules;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge other updates
|
||||||
|
if (updates.version) {
|
||||||
|
manifest.installation.version = updates.version;
|
||||||
|
}
|
||||||
|
if (updates.installDate) {
|
||||||
|
manifest.installation.installDate = updates.installDate;
|
||||||
|
}
|
||||||
|
manifest.installation.lastUpdated = new Date().toISOString();
|
||||||
|
|
||||||
|
if (updates.ides) {
|
||||||
|
manifest.ides = updates.ides;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle per-module version updates
|
||||||
|
if (updates.moduleVersions) {
|
||||||
|
for (const [moduleName, versionInfo] of Object.entries(updates.moduleVersions)) {
|
||||||
|
const moduleIndex = manifest.modules.findIndex((m) => m.name === moduleName);
|
||||||
|
if (moduleIndex !== -1) {
|
||||||
|
manifest.modules[moduleIndex] = {
|
||||||
|
...manifest.modules[moduleIndex],
|
||||||
|
...versionInfo,
|
||||||
|
lastUpdated: new Date().toISOString(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle adding a new module with version info
|
||||||
|
if (updates.addModule) {
|
||||||
|
const { name, version, source, npmPackage, repoUrl } = updates.addModule;
|
||||||
|
const existing = manifest.modules.find((m) => m.name === name);
|
||||||
|
if (!existing) {
|
||||||
|
manifest.modules.push({
|
||||||
|
name,
|
||||||
|
version: version || null,
|
||||||
|
installDate: new Date().toISOString(),
|
||||||
|
lastUpdated: new Date().toISOString(),
|
||||||
|
source: source || 'external',
|
||||||
|
npmPackage: npmPackage || null,
|
||||||
|
repoUrl: repoUrl || null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const manifestPath = path.join(bmadDir, '_config', 'manifest.yaml');
|
const manifestPath = path.join(bmadDir, '_config', 'manifest.yaml');
|
||||||
await fs.ensureDir(path.dirname(manifestPath));
|
await fs.ensureDir(path.dirname(manifestPath));
|
||||||
|
|
||||||
// Clean the manifest data to remove any non-serializable values
|
// Clean the manifest data to remove any non-serializable values
|
||||||
const cleanManifestData = structuredClone(manifestData);
|
const cleanManifestData = structuredClone(manifest);
|
||||||
|
|
||||||
const yamlContent = yaml.stringify(cleanManifestData, {
|
const yamlContent = yaml.stringify(cleanManifestData, {
|
||||||
indent: 2,
|
indent: 2,
|
||||||
|
|
@ -115,16 +212,61 @@ class Manifest {
|
||||||
const content = yamlContent.endsWith('\n') ? yamlContent : yamlContent + '\n';
|
const content = yamlContent.endsWith('\n') ? yamlContent : yamlContent + '\n';
|
||||||
await fs.writeFile(manifestPath, content, 'utf8');
|
await fs.writeFile(manifestPath, content, 'utf8');
|
||||||
|
|
||||||
return manifest;
|
// Return the flattened format for compatibility
|
||||||
|
return this._flattenManifest(manifest);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a module to the manifest
|
* Read raw manifest data without flattening
|
||||||
|
* @param {string} bmadDir - Path to bmad directory
|
||||||
|
* @returns {Object|null} Raw manifest data or null if not found
|
||||||
|
*/
|
||||||
|
async _readRaw(bmadDir) {
|
||||||
|
const yamlPath = path.join(bmadDir, '_config', 'manifest.yaml');
|
||||||
|
const yaml = require('yaml');
|
||||||
|
|
||||||
|
if (await fs.pathExists(yamlPath)) {
|
||||||
|
try {
|
||||||
|
const content = await fs.readFile(yamlPath, 'utf8');
|
||||||
|
return yaml.parse(content);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to read YAML manifest:', error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flatten manifest for backward compatibility
|
||||||
|
* @param {Object} manifest - Raw manifest data
|
||||||
|
* @returns {Object} Flattened manifest
|
||||||
|
*/
|
||||||
|
_flattenManifest(manifest) {
|
||||||
|
const modules = manifest.modules || [];
|
||||||
|
const moduleNames = modules.map((m) => (typeof m === 'string' ? m : m.name));
|
||||||
|
const hasDetailedModules = modules.length > 0 && typeof modules[0] === 'object';
|
||||||
|
|
||||||
|
return {
|
||||||
|
version: manifest.installation?.version,
|
||||||
|
installDate: manifest.installation?.installDate,
|
||||||
|
lastUpdated: manifest.installation?.lastUpdated,
|
||||||
|
modules: moduleNames,
|
||||||
|
modulesDetailed: hasDetailedModules ? modules : null,
|
||||||
|
customModules: manifest.customModules || [],
|
||||||
|
ides: manifest.ides || [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a module to the manifest with optional version info
|
||||||
|
* If module already exists, update its version info
|
||||||
* @param {string} bmadDir - Path to bmad directory
|
* @param {string} bmadDir - Path to bmad directory
|
||||||
* @param {string} moduleName - Module name to add
|
* @param {string} moduleName - Module name to add
|
||||||
|
* @param {Object} options - Optional version info
|
||||||
*/
|
*/
|
||||||
async addModule(bmadDir, moduleName) {
|
async addModule(bmadDir, moduleName, options = {}) {
|
||||||
const manifest = await this.read(bmadDir);
|
const manifest = await this._readRaw(bmadDir);
|
||||||
if (!manifest) {
|
if (!manifest) {
|
||||||
throw new Error('No manifest found');
|
throw new Error('No manifest found');
|
||||||
}
|
}
|
||||||
|
|
@ -133,10 +275,33 @@ class Manifest {
|
||||||
manifest.modules = [];
|
manifest.modules = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!manifest.modules.includes(moduleName)) {
|
const existingIndex = manifest.modules.findIndex((m) => m.name === moduleName);
|
||||||
manifest.modules.push(moduleName);
|
|
||||||
await this.update(bmadDir, { modules: manifest.modules });
|
if (existingIndex === -1) {
|
||||||
|
// Module doesn't exist, add it
|
||||||
|
manifest.modules.push({
|
||||||
|
name: moduleName,
|
||||||
|
version: options.version || null,
|
||||||
|
installDate: new Date().toISOString(),
|
||||||
|
lastUpdated: new Date().toISOString(),
|
||||||
|
source: options.source || 'unknown',
|
||||||
|
npmPackage: options.npmPackage || null,
|
||||||
|
repoUrl: options.repoUrl || null,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Module exists, update its version info
|
||||||
|
const existing = manifest.modules[existingIndex];
|
||||||
|
manifest.modules[existingIndex] = {
|
||||||
|
...existing,
|
||||||
|
version: options.version === undefined ? existing.version : options.version,
|
||||||
|
source: options.source || existing.source,
|
||||||
|
npmPackage: options.npmPackage === undefined ? existing.npmPackage : options.npmPackage,
|
||||||
|
repoUrl: options.repoUrl === undefined ? existing.repoUrl : options.repoUrl,
|
||||||
|
lastUpdated: new Date().toISOString(),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await this._writeRaw(bmadDir, manifest);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -145,18 +310,93 @@ class Manifest {
|
||||||
* @param {string} moduleName - Module name to remove
|
* @param {string} moduleName - Module name to remove
|
||||||
*/
|
*/
|
||||||
async removeModule(bmadDir, moduleName) {
|
async removeModule(bmadDir, moduleName) {
|
||||||
const manifest = await this.read(bmadDir);
|
const manifest = await this._readRaw(bmadDir);
|
||||||
if (!manifest || !manifest.modules) {
|
if (!manifest || !manifest.modules) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const index = manifest.modules.indexOf(moduleName);
|
const index = manifest.modules.findIndex((m) => m.name === moduleName);
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
manifest.modules.splice(index, 1);
|
manifest.modules.splice(index, 1);
|
||||||
await this.update(bmadDir, { modules: manifest.modules });
|
await this._writeRaw(bmadDir, manifest);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update a single module's version info
|
||||||
|
* @param {string} bmadDir - Path to bmad directory
|
||||||
|
* @param {string} moduleName - Module name
|
||||||
|
* @param {Object} versionInfo - Version info to update
|
||||||
|
*/
|
||||||
|
async updateModuleVersion(bmadDir, moduleName, versionInfo) {
|
||||||
|
const manifest = await this._readRaw(bmadDir);
|
||||||
|
if (!manifest || !manifest.modules) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const index = manifest.modules.findIndex((m) => m.name === moduleName);
|
||||||
|
if (index !== -1) {
|
||||||
|
manifest.modules[index] = {
|
||||||
|
...manifest.modules[index],
|
||||||
|
...versionInfo,
|
||||||
|
lastUpdated: new Date().toISOString(),
|
||||||
|
};
|
||||||
|
await this._writeRaw(bmadDir, manifest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get version info for a specific module
|
||||||
|
* @param {string} bmadDir - Path to bmad directory
|
||||||
|
* @param {string} moduleName - Module name
|
||||||
|
* @returns {Object|null} Module version info or null
|
||||||
|
*/
|
||||||
|
async getModuleVersion(bmadDir, moduleName) {
|
||||||
|
const manifest = await this._readRaw(bmadDir);
|
||||||
|
if (!manifest || !manifest.modules) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return manifest.modules.find((m) => m.name === moduleName) || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all modules with their version info
|
||||||
|
* @param {string} bmadDir - Path to bmad directory
|
||||||
|
* @returns {Array} Array of module info objects
|
||||||
|
*/
|
||||||
|
async getAllModuleVersions(bmadDir) {
|
||||||
|
const manifest = await this._readRaw(bmadDir);
|
||||||
|
if (!manifest || !manifest.modules) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return manifest.modules;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write raw manifest data to file
|
||||||
|
* @param {string} bmadDir - Path to bmad directory
|
||||||
|
* @param {Object} manifestData - Raw manifest data to write
|
||||||
|
*/
|
||||||
|
async _writeRaw(bmadDir, manifestData) {
|
||||||
|
const yaml = require('yaml');
|
||||||
|
const manifestPath = path.join(bmadDir, '_config', 'manifest.yaml');
|
||||||
|
|
||||||
|
await fs.ensureDir(path.dirname(manifestPath));
|
||||||
|
|
||||||
|
const cleanManifestData = structuredClone(manifestData);
|
||||||
|
|
||||||
|
const yamlContent = yaml.stringify(cleanManifestData, {
|
||||||
|
indent: 2,
|
||||||
|
lineWidth: 0,
|
||||||
|
sortKeys: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const content = yamlContent.endsWith('\n') ? yamlContent : yamlContent + '\n';
|
||||||
|
await fs.writeFile(manifestPath, content, 'utf8');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add an IDE configuration to the manifest
|
* Add an IDE configuration to the manifest
|
||||||
* @param {string} bmadDir - Path to bmad directory
|
* @param {string} bmadDir - Path to bmad directory
|
||||||
|
|
@ -585,6 +825,212 @@ class Manifest {
|
||||||
await this.update(bmadDir, { customModules: manifest.customModules });
|
await this.update(bmadDir, { customModules: manifest.customModules });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get module version info from source
|
||||||
|
* @param {string} moduleName - Module name/code
|
||||||
|
* @param {string} bmadDir - Path to bmad directory
|
||||||
|
* @param {string} moduleSourcePath - Optional source path for custom modules
|
||||||
|
* @returns {Object} Version info object with version, source, npmPackage, repoUrl
|
||||||
|
*/
|
||||||
|
async getModuleVersionInfo(moduleName, bmadDir, moduleSourcePath = null) {
|
||||||
|
const os = require('node:os');
|
||||||
|
|
||||||
|
// Built-in modules use BMad version (only core and bmm are in BMAD-METHOD repo)
|
||||||
|
if (['core', 'bmm'].includes(moduleName)) {
|
||||||
|
const bmadVersion = require(path.join(getProjectRoot(), 'package.json')).version;
|
||||||
|
return {
|
||||||
|
version: bmadVersion,
|
||||||
|
source: 'built-in',
|
||||||
|
npmPackage: null,
|
||||||
|
repoUrl: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if this is an external official module
|
||||||
|
const { ExternalModuleManager } = require('../modules/external-manager');
|
||||||
|
const extMgr = new ExternalModuleManager();
|
||||||
|
const moduleInfo = await extMgr.getModuleByCode(moduleName);
|
||||||
|
|
||||||
|
if (moduleInfo) {
|
||||||
|
// External module - try to get version from npm registry first, then fall back to cache
|
||||||
|
let version = null;
|
||||||
|
|
||||||
|
if (moduleInfo.npmPackage) {
|
||||||
|
// Fetch version from npm registry
|
||||||
|
try {
|
||||||
|
version = await this.fetchNpmVersion(moduleInfo.npmPackage);
|
||||||
|
} catch {
|
||||||
|
// npm fetch failed, try cache as fallback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If npm didn't work, try reading from cached repo's package.json
|
||||||
|
if (!version) {
|
||||||
|
const cacheDir = path.join(os.homedir(), '.bmad', 'cache', 'external-modules', moduleName);
|
||||||
|
const packageJsonPath = path.join(cacheDir, 'package.json');
|
||||||
|
|
||||||
|
if (await fs.pathExists(packageJsonPath)) {
|
||||||
|
try {
|
||||||
|
const pkg = require(packageJsonPath);
|
||||||
|
version = pkg.version;
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Failed to read package.json for ${moduleName}: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
version: version,
|
||||||
|
source: 'external',
|
||||||
|
npmPackage: moduleInfo.npmPackage || null,
|
||||||
|
repoUrl: moduleInfo.url || null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom module - check cache directory
|
||||||
|
const cacheDir = path.join(bmadDir, '_config', 'custom', moduleName);
|
||||||
|
const moduleYamlPath = path.join(cacheDir, 'module.yaml');
|
||||||
|
|
||||||
|
if (await fs.pathExists(moduleYamlPath)) {
|
||||||
|
try {
|
||||||
|
const yamlContent = await fs.readFile(moduleYamlPath, 'utf8');
|
||||||
|
const moduleConfig = yaml.parse(yamlContent);
|
||||||
|
return {
|
||||||
|
version: moduleConfig.version || null,
|
||||||
|
source: 'custom',
|
||||||
|
npmPackage: moduleConfig.npmPackage || null,
|
||||||
|
repoUrl: moduleConfig.repoUrl || null,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Failed to read module.yaml for ${moduleName}: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unknown module
|
||||||
|
return {
|
||||||
|
version: null,
|
||||||
|
source: 'unknown',
|
||||||
|
npmPackage: null,
|
||||||
|
repoUrl: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch latest version from npm for a package
|
||||||
|
* @param {string} packageName - npm package name
|
||||||
|
* @returns {string|null} Latest version or null
|
||||||
|
*/
|
||||||
|
async fetchNpmVersion(packageName) {
|
||||||
|
try {
|
||||||
|
const https = require('node:https');
|
||||||
|
const { execSync } = require('node:child_process');
|
||||||
|
|
||||||
|
// Try using npm view first (more reliable)
|
||||||
|
try {
|
||||||
|
const result = execSync(`npm view ${packageName} version`, {
|
||||||
|
encoding: 'utf8',
|
||||||
|
stdio: 'pipe',
|
||||||
|
timeout: 10_000,
|
||||||
|
});
|
||||||
|
return result.trim();
|
||||||
|
} catch {
|
||||||
|
// Fallback to npm registry API
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
https
|
||||||
|
.get(`https://registry.npmjs.org/${packageName}`, (res) => {
|
||||||
|
let data = '';
|
||||||
|
res.on('data', (chunk) => (data += chunk));
|
||||||
|
res.on('end', () => {
|
||||||
|
try {
|
||||||
|
const pkg = JSON.parse(data);
|
||||||
|
resolve(pkg['dist-tags']?.latest || pkg.version || null);
|
||||||
|
} catch {
|
||||||
|
resolve(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.on('error', () => resolve(null));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check for available updates for installed modules
|
||||||
|
* @param {string} bmadDir - Path to bmad directory
|
||||||
|
* @returns {Array} Array of update info objects
|
||||||
|
*/
|
||||||
|
async checkForUpdates(bmadDir) {
|
||||||
|
const modules = await this.getAllModuleVersions(bmadDir);
|
||||||
|
const updates = [];
|
||||||
|
|
||||||
|
for (const module of modules) {
|
||||||
|
if (!module.npmPackage) {
|
||||||
|
continue; // Skip modules without npm package (built-in)
|
||||||
|
}
|
||||||
|
|
||||||
|
const latestVersion = await this.fetchNpmVersion(module.npmPackage);
|
||||||
|
if (!latestVersion) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (module.version !== latestVersion) {
|
||||||
|
updates.push({
|
||||||
|
name: module.name,
|
||||||
|
installedVersion: module.version,
|
||||||
|
latestVersion: latestVersion,
|
||||||
|
npmPackage: module.npmPackage,
|
||||||
|
updateAvailable: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return updates;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compare two semantic versions
|
||||||
|
* @param {string} v1 - First version
|
||||||
|
* @param {string} v2 - Second version
|
||||||
|
* @returns {number} -1 if v1 < v2, 0 if v1 == v2, 1 if v1 > v2
|
||||||
|
*/
|
||||||
|
compareVersions(v1, v2) {
|
||||||
|
if (!v1 || !v2) return 0;
|
||||||
|
|
||||||
|
const normalize = (v) => {
|
||||||
|
// Remove leading 'v' if present
|
||||||
|
v = v.replace(/^v/, '');
|
||||||
|
// Handle prerelease tags
|
||||||
|
const parts = v.split('-');
|
||||||
|
const main = parts[0].split('.');
|
||||||
|
const prerelease = parts[1];
|
||||||
|
return { main, prerelease };
|
||||||
|
};
|
||||||
|
|
||||||
|
const n1 = normalize(v1);
|
||||||
|
const n2 = normalize(v2);
|
||||||
|
|
||||||
|
// Compare main version parts
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
const num1 = parseInt(n1.main[i] || '0', 10);
|
||||||
|
const num2 = parseInt(n2.main[i] || '0', 10);
|
||||||
|
if (num1 !== num2) {
|
||||||
|
return num1 < num2 ? -1 : 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If main versions are equal, compare prerelease
|
||||||
|
if (n1.prerelease && n2.prerelease) {
|
||||||
|
return n1.prerelease < n2.prerelease ? -1 : n1.prerelease > n2.prerelease ? 1 : 0;
|
||||||
|
}
|
||||||
|
if (n1.prerelease) return -1; // Prerelease is older than stable
|
||||||
|
if (n2.prerelease) return 1; // Stable is newer than prerelease
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { Manifest };
|
module.exports = { Manifest };
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
Standardize IDE installers to use **flat file naming** and centralize duplicated code in shared utilities.
|
Standardize IDE installers to use **flat file naming** with **underscores** (Windows-compatible) and centralize duplicated code in shared utilities.
|
||||||
|
|
||||||
**Key Rule: Only folder-based IDEs convert to colon format. IDEs already using dashes keep using dashes.**
|
**Key Rule: All IDEs use underscore format for Windows compatibility (colons don't work on Windows).**
|
||||||
|
|
||||||
## Current State Analysis
|
## Current State Analysis
|
||||||
|
|
||||||
|
|
@ -15,10 +15,10 @@ Standardize IDE installers to use **flat file naming** and centralize duplicated
|
||||||
| **claude-code** | Hierarchical | `.claude/commands/bmad/{module}/agents/{name}.md` |
|
| **claude-code** | Hierarchical | `.claude/commands/bmad/{module}/agents/{name}.md` |
|
||||||
| **cursor** | Hierarchical | `.cursor/commands/bmad/{module}/agents/{name}.md` |
|
| **cursor** | Hierarchical | `.cursor/commands/bmad/{module}/agents/{name}.md` |
|
||||||
| **crush** | Hierarchical | `.crush/commands/bmad/{module}/agents/{name}.md` |
|
| **crush** | Hierarchical | `.crush/commands/bmad/{module}/agents/{name}.md` |
|
||||||
| **antigravity** | Flattened (dashes) | `.agent/workflows/bmad-module-agents-name.md` |
|
| **antigravity** | Flattened (underscores) | `.agent/workflows/bmad_module_agents_name.md` |
|
||||||
| **codex** | Flattened (dashes) | `~/.codex/prompts/bmad-module-agents-name.md` |
|
| **codex** | Flattened (underscores) | `~/.codex/prompts/bmad_module_agents_name.md` |
|
||||||
| **cline** | Flattened (dashes) | `.clinerules/workflows/bmad-module-type-name.md` |
|
| **cline** | Flattened (underscores) | `.clinerules/workflows/bmad_module_type_name.md` |
|
||||||
| **roo** | Flattened (dashes) | `.roo/commands/bmad-{module}-agent-{name}.md` |
|
| **roo** | Flattened (underscores) | `.roo/commands/bmad_module_agent_name.md` |
|
||||||
| **auggie** | Hybrid | `.augment/commands/bmad/agents/{module}-{name}.md` |
|
| **auggie** | Hybrid | `.augment/commands/bmad/agents/{module}-{name}.md` |
|
||||||
| **iflow** | Hybrid | `.iflow/commands/bmad/agents/{module}-{name}.md` |
|
| **iflow** | Hybrid | `.iflow/commands/bmad/agents/{module}-{name}.md` |
|
||||||
| **trae** | Different (rules) | `.trae/rules/bmad-agent-{module}-{name}.md` |
|
| **trae** | Different (rules) | `.trae/rules/bmad-agent-{module}-{name}.md` |
|
||||||
|
|
@ -40,35 +40,24 @@ All currently create artifacts with **nested relative paths** like `{module}/age
|
||||||
|
|
||||||
## Target Standardization
|
## Target Standardization
|
||||||
|
|
||||||
### For Folder-Based IDEs (convert to colon format)
|
### For All IDEs (underscore format - Windows-compatible)
|
||||||
|
|
||||||
**IDEs affected:** claude-code, cursor, crush
|
**IDEs affected:** claude-code, cursor, crush, antigravity, codex, cline, roo
|
||||||
|
|
||||||
```
|
```
|
||||||
Format: bmad:{module}:{type}:{name}.md
|
Format: bmad_{module}_{type}_{name}.md
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
- Agent: bmad:bmm:agents:pm.md
|
- Agent: bmad_bmm_agents_pm.md
|
||||||
- Agent: bmad:core:agents:dev.md
|
- Agent: bmad_core_agents_dev.md
|
||||||
- Workflow: bmad:bmm:workflows:correct-course.md
|
- Workflow: bmad_bmm_workflows_correct-course.md
|
||||||
- Task: bmad:bmm:tasks:bmad-help.md
|
- Task: bmad_bmm_tasks_bmad-help.md
|
||||||
- Tool: bmad:core:tools:code-review.md
|
- Tool: bmad_core_tools_code-review.md
|
||||||
- Custom: bmad:custom:agents:fred-commit-poet.md
|
- Custom: bmad_custom_agents_fred-commit-poet.md
|
||||||
```
|
```
|
||||||
|
|
||||||
### For Already-Flat IDEs (keep using dashes)
|
**Note:** Type segments (agents, workflows, tasks, tools) are filtered out from names:
|
||||||
|
- `bmm/agents/pm.md` → `bmad_bmm_pm.md` (not `bmad_bmm_agents_pm.md`)
|
||||||
**IDEs affected:** antigravity, codex, cline, roo
|
|
||||||
|
|
||||||
```
|
|
||||||
Format: bmad-{module}-{type}-{name}.md
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
- Agent: bmad-bmm-agents-pm.md
|
|
||||||
- Workflow: bmad-bmm-workflows-correct-course.md
|
|
||||||
- Task: bmad-bmm-tasks-bmad-help.md
|
|
||||||
- Custom: bmad-custom-agents-fred-commit-poet.md
|
|
||||||
```
|
|
||||||
|
|
||||||
### For Hybrid IDEs (keep as-is)
|
### For Hybrid IDEs (keep as-is)
|
||||||
|
|
||||||
|
|
@ -88,57 +77,50 @@ These use `{module}-{name}.md` format within subdirectories - keep as-is.
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
/**
|
/**
|
||||||
* Convert hierarchical path to flat colon-separated name (for folder-based IDEs)
|
* Convert hierarchical path to flat underscore-separated name (Windows-compatible)
|
||||||
* @param {string} module - Module name (e.g., 'bmm', 'core')
|
* @param {string} module - Module name (e.g., 'bmm', 'core')
|
||||||
* @param {string} type - Artifact type ('agents', 'workflows', 'tasks', 'tools')
|
* @param {string} type - Artifact type ('agents', 'workflows', 'tasks', 'tools') - filtered out
|
||||||
* @param {string} name - Artifact name (e.g., 'pm', 'correct-course')
|
* @param {string} name - Artifact name (e.g., 'pm', 'correct-course')
|
||||||
* @returns {string} Flat filename like 'bmad:bmm:agents:pm.md'
|
* @returns {string} Flat filename like 'bmad_bmm_pm.md'
|
||||||
*/
|
*/
|
||||||
function toColonName(module, type, name) {
|
function toUnderscoreName(module, type, name) {
|
||||||
return `bmad:${module}:${type}:${name}.md`;
|
return `bmad_${module}_${name}.md`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert relative path to flat colon-separated name (for folder-based IDEs)
|
* Convert relative path to flat underscore-separated name (Windows-compatible)
|
||||||
* @param {string} relativePath - Path like 'bmm/agents/pm.md'
|
* @param {string} relativePath - Path like 'bmm/agents/pm.md'
|
||||||
* @returns {string} Flat filename like 'bmad:bmm:agents:pm.md'
|
* @returns {string} Flat filename like 'bmad_bmm_pm.md'
|
||||||
*/
|
*/
|
||||||
function toColonPath(relativePath) {
|
function toUnderscorePath(relativePath) {
|
||||||
const withoutExt = relativePath.replace('.md', '');
|
const withoutExt = relativePath.replace('.md', '');
|
||||||
const parts = withoutExt.split(/[\/\\]/);
|
const parts = withoutExt.split(/[\/\\]/);
|
||||||
return `bmad:${parts.join(':')}.md`;
|
// Filter out type segments (agents, workflows, tasks, tools)
|
||||||
|
const filtered = parts.filter((p) => !TYPE_SEGMENTS.includes(p));
|
||||||
|
return `bmad_${filtered.join('_')}.md`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert hierarchical path to flat dash-separated name (for flat IDEs)
|
* Create custom agent underscore name
|
||||||
* @param {string} relativePath - Path like 'bmm/agents/pm.md'
|
|
||||||
* @returns {string} Flat filename like 'bmad-bmm-agents-pm.md'
|
|
||||||
*/
|
|
||||||
function toDashPath(relativePath) {
|
|
||||||
const withoutExt = relativePath.replace('.md', '');
|
|
||||||
const parts = withoutExt.split(/[\/\\]/);
|
|
||||||
return `bmad-${parts.join('-')}.md`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create custom agent colon name
|
|
||||||
* @param {string} agentName - Custom agent name
|
* @param {string} agentName - Custom agent name
|
||||||
* @returns {string} Flat filename like 'bmad:custom:agents:fred-commit-poet.md'
|
* @returns {string} Flat filename like 'bmad_custom_fred-commit-poet.md'
|
||||||
*/
|
*/
|
||||||
function customAgentColonName(agentName) {
|
function customAgentUnderscoreName(agentName) {
|
||||||
return `bmad:custom:agents:${agentName}.md`;
|
return `bmad_custom_${agentName}.md`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Backward compatibility aliases
|
||||||
* Create custom agent dash name
|
const toColonName = toUnderscoreName;
|
||||||
* @param {string} agentName - Custom agent name
|
const toColonPath = toUnderscorePath;
|
||||||
* @returns {string} Flat filename like 'bmad-custom-agents-fred-commit-poet.md'
|
const toDashPath = toUnderscorePath;
|
||||||
*/
|
const customAgentColonName = customAgentUnderscoreName;
|
||||||
function customAgentDashName(agentName) {
|
const customAgentDashName = customAgentUnderscoreName;
|
||||||
return `bmad-custom-agents-${agentName}.md`;
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
toUnderscoreName,
|
||||||
|
toUnderscorePath,
|
||||||
|
customAgentUnderscoreName,
|
||||||
|
// Backward compatibility
|
||||||
toColonName,
|
toColonName,
|
||||||
toColonPath,
|
toColonPath,
|
||||||
toDashPath,
|
toDashPath,
|
||||||
|
|
@ -157,34 +139,26 @@ module.exports = {
|
||||||
**Changes:**
|
**Changes:**
|
||||||
1. Import path utilities
|
1. Import path utilities
|
||||||
2. Change `relativePath` to use flat format
|
2. Change `relativePath` to use flat format
|
||||||
3. Add method `writeColonArtifacts()` for folder-based IDEs
|
3. Add method `writeColonArtifacts()` for folder-based IDEs (uses underscore)
|
||||||
4. Add method `writeDashArtifacts()` for flat IDEs
|
4. Add method `writeDashArtifacts()` for flat IDEs (uses underscore)
|
||||||
|
|
||||||
### Phase 3: Update Folder-Based IDEs
|
### Phase 3: Update All IDEs
|
||||||
|
|
||||||
**Files to modify:**
|
**Files to modify:**
|
||||||
- `claude-code.js`
|
- `claude-code.js`
|
||||||
- `cursor.js`
|
- `cursor.js`
|
||||||
- `crush.js`
|
- `crush.js`
|
||||||
|
|
||||||
**Changes:**
|
|
||||||
1. Import `toColonPath`, `customAgentColonName` from path-utils
|
|
||||||
2. Change from hierarchical to flat colon naming
|
|
||||||
3. Update cleanup to handle flat structure
|
|
||||||
|
|
||||||
### Phase 4: Update Flat IDEs
|
|
||||||
|
|
||||||
**Files to modify:**
|
|
||||||
- `antigravity.js`
|
- `antigravity.js`
|
||||||
- `codex.js`
|
- `codex.js`
|
||||||
- `cline.js`
|
- `cline.js`
|
||||||
- `roo.js`
|
- `roo.js`
|
||||||
|
|
||||||
**Changes:**
|
**Changes:**
|
||||||
1. Import `toDashPath`, `customAgentDashName` from path-utils
|
1. Import utilities from path-utils
|
||||||
2. Replace local `flattenFilename()` with shared `toDashPath()`
|
2. Change from hierarchical to flat underscore naming
|
||||||
|
3. Update cleanup to handle flat structure (`startsWith('bmad')`)
|
||||||
|
|
||||||
### Phase 5: Update Base Class
|
### Phase 4: Update Base Class
|
||||||
|
|
||||||
**File:** `_base-ide.js`
|
**File:** `_base-ide.js`
|
||||||
|
|
||||||
|
|
@ -195,24 +169,23 @@ module.exports = {
|
||||||
## Migration Checklist
|
## Migration Checklist
|
||||||
|
|
||||||
### New Files
|
### New Files
|
||||||
- [ ] Create `shared/path-utils.js`
|
- [x] Create `shared/path-utils.js`
|
||||||
|
|
||||||
### Folder-Based IDEs (convert to colon format)
|
### All IDEs (convert to underscore format)
|
||||||
- [ ] Update `shared/agent-command-generator.js` - add `writeColonArtifacts()`
|
- [x] Update `shared/agent-command-generator.js` - update for underscore
|
||||||
- [ ] Update `shared/task-tool-command-generator.js` - add `writeColonArtifacts()`
|
- [x] Update `shared/task-tool-command-generator.js` - update for underscore
|
||||||
- [ ] Update `shared/workflow-command-generator.js` - add `writeColonArtifacts()`
|
- [x] Update `shared/workflow-command-generator.js` - update for underscore
|
||||||
- [ ] Update `claude-code.js` - convert to colon format
|
- [x] Update `claude-code.js` - convert to underscore format
|
||||||
- [ ] Update `cursor.js` - convert to colon format
|
- [x] Update `cursor.js` - convert to underscore format
|
||||||
- [ ] Update `crush.js` - convert to colon format
|
- [x] Update `crush.js` - convert to underscore format
|
||||||
|
- [ ] Update `antigravity.js` - use underscore format
|
||||||
|
- [ ] Update `codex.js` - use underscore format
|
||||||
|
- [ ] Update `cline.js` - use underscore format
|
||||||
|
- [ ] Update `roo.js` - use underscore format
|
||||||
|
|
||||||
### Flat IDEs (standardize dash format)
|
### CSV Command Files
|
||||||
- [ ] Update `shared/agent-command-generator.js` - add `writeDashArtifacts()`
|
- [x] Update `src/core/module-help.csv` - change colons to underscores
|
||||||
- [ ] Update `shared/task-tool-command-generator.js` - add `writeDashArtifacts()`
|
- [x] Update `src/bmm/module-help.csv` - change colons to underscores
|
||||||
- [ ] Update `shared/workflow-command-generator.js` - add `writeDashArtifacts()`
|
|
||||||
- [ ] Update `antigravity.js` - use shared `toDashPath()`
|
|
||||||
- [ ] Update `codex.js` - use shared `toDashPath()`
|
|
||||||
- [ ] Update `cline.js` - use shared `toDashPath()`
|
|
||||||
- [ ] Update `roo.js` - use shared `toDashPath()`
|
|
||||||
|
|
||||||
### Base Class
|
### Base Class
|
||||||
- [ ] Update `_base-ide.js` - add deprecation notice
|
- [ ] Update `_base-ide.js` - add deprecation notice
|
||||||
|
|
@ -228,7 +201,8 @@ module.exports = {
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
1. **Keep segments**: agents, workflows, tasks, tools all become part of the flat name
|
1. **Filter type segments**: agents, workflows, tasks, tools are filtered out from flat names
|
||||||
2. **Colon vs Dash**: Colons for folder-based IDEs converting to flat, dashes for already-flat IDEs
|
2. **Underscore format**: Universal underscore format for Windows compatibility
|
||||||
3. **Custom agents**: Follow the same pattern as regular agents
|
3. **Custom agents**: Follow the same pattern as regular agents
|
||||||
4. **Backward compatibility**: Cleanup will remove old folder structure
|
4. **Backward compatibility**: Old function names kept as aliases
|
||||||
|
5. **Cleanup**: Will remove old `bmad:` format files on next install
|
||||||
|
|
|
||||||
|
|
@ -127,8 +127,8 @@ class AntigravitySetup extends BaseIdeSetup {
|
||||||
const { artifacts: agentArtifacts, counts: agentCounts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []);
|
const { artifacts: agentArtifacts, counts: agentCounts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []);
|
||||||
|
|
||||||
// Write agent launcher files with FLATTENED naming using shared utility
|
// Write agent launcher files with FLATTENED naming using shared utility
|
||||||
// Antigravity ignores directory structure, so we flatten to: bmad-module-name.md
|
// Antigravity ignores directory structure, so we flatten to: bmad_module_name.md
|
||||||
// This creates slash commands like /bmad-bmm-dev instead of /dev
|
// This creates slash commands like /bmad_bmm_dev instead of /dev
|
||||||
const agentCount = await agentGen.writeDashArtifacts(bmadWorkflowsDir, agentArtifacts);
|
const agentCount = await agentGen.writeDashArtifacts(bmadWorkflowsDir, agentArtifacts);
|
||||||
|
|
||||||
// Process Antigravity specific injections for installed modules
|
// Process Antigravity specific injections for installed modules
|
||||||
|
|
@ -167,7 +167,7 @@ class AntigravitySetup extends BaseIdeSetup {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
console.log(chalk.dim(` - Workflows directory: ${path.relative(projectDir, bmadWorkflowsDir)}`));
|
console.log(chalk.dim(` - Workflows directory: ${path.relative(projectDir, bmadWorkflowsDir)}`));
|
||||||
console.log(chalk.yellow(`\n Note: Antigravity uses flattened slash commands (e.g., /bmad-module-agents-name)`));
|
console.log(chalk.yellow(`\n Note: Antigravity uses flattened slash commands (e.g., /bmad_module_agents_name)`));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
|
|
@ -455,7 +455,7 @@ usage: |
|
||||||
|
|
||||||
⚠️ **IMPORTANT**: Run @${agentPath} to load the complete agent before using this launcher!`;
|
⚠️ **IMPORTANT**: Run @${agentPath} to load the complete agent before using this launcher!`;
|
||||||
|
|
||||||
// Use dash format: bmad-custom-agents-fred-commit-poet.md
|
// Use underscore format: bmad_custom_fred-commit-poet.md
|
||||||
const fileName = customAgentDashName(agentName);
|
const fileName = customAgentDashName(agentName);
|
||||||
const launcherPath = path.join(bmadWorkflowsDir, fileName);
|
const launcherPath = path.join(bmadWorkflowsDir, fileName);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -92,12 +92,12 @@ class ClaudeCodeSetup extends BaseIdeSetup {
|
||||||
async cleanup(projectDir) {
|
async cleanup(projectDir) {
|
||||||
const commandsDir = path.join(projectDir, this.configDir, this.commandsDir);
|
const commandsDir = path.join(projectDir, this.configDir, this.commandsDir);
|
||||||
|
|
||||||
// Remove any bmad:* files from the commands directory
|
// Remove any bmad* files from the commands directory (cleans up old bmad: and bmad- formats)
|
||||||
if (await fs.pathExists(commandsDir)) {
|
if (await fs.pathExists(commandsDir)) {
|
||||||
const entries = await fs.readdir(commandsDir);
|
const entries = await fs.readdir(commandsDir);
|
||||||
let removedCount = 0;
|
let removedCount = 0;
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
if (entry.startsWith('bmad:')) {
|
if (entry.startsWith('bmad')) {
|
||||||
await fs.remove(path.join(commandsDir, entry));
|
await fs.remove(path.join(commandsDir, entry));
|
||||||
removedCount++;
|
removedCount++;
|
||||||
}
|
}
|
||||||
|
|
@ -151,16 +151,16 @@ class ClaudeCodeSetup extends BaseIdeSetup {
|
||||||
const commandsDir = path.join(claudeDir, this.commandsDir);
|
const commandsDir = path.join(claudeDir, this.commandsDir);
|
||||||
await this.ensureDir(commandsDir);
|
await this.ensureDir(commandsDir);
|
||||||
|
|
||||||
// Use colon format: files written directly to commands dir (no bmad subfolder)
|
// Use underscore format: files written directly to commands dir (no bmad subfolder)
|
||||||
// Creates: .claude/commands/bmad:bmm:pm.md
|
// Creates: .claude/commands/bmad_bmm_pm.md
|
||||||
|
|
||||||
// Generate agent launchers using AgentCommandGenerator
|
// Generate agent launchers using AgentCommandGenerator
|
||||||
// This creates small launcher files that reference the actual agents in _bmad/
|
// This creates small launcher files that reference the actual agents in _bmad/
|
||||||
const agentGen = new AgentCommandGenerator(this.bmadFolderName);
|
const agentGen = new AgentCommandGenerator(this.bmadFolderName);
|
||||||
const { artifacts: agentArtifacts, counts: agentCounts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []);
|
const { artifacts: agentArtifacts, counts: agentCounts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []);
|
||||||
|
|
||||||
// Write agent launcher files using flat colon naming
|
// Write agent launcher files using flat underscore naming
|
||||||
// Creates files like: bmad:bmm:pm.md
|
// Creates files like: bmad_bmm_pm.md
|
||||||
const agentCount = await agentGen.writeColonArtifacts(commandsDir, agentArtifacts);
|
const agentCount = await agentGen.writeColonArtifacts(commandsDir, agentArtifacts);
|
||||||
|
|
||||||
// Process Claude Code specific injections for installed modules
|
// Process Claude Code specific injections for installed modules
|
||||||
|
|
@ -182,8 +182,8 @@ class ClaudeCodeSetup extends BaseIdeSetup {
|
||||||
const workflowGen = new WorkflowCommandGenerator(this.bmadFolderName);
|
const workflowGen = new WorkflowCommandGenerator(this.bmadFolderName);
|
||||||
const { artifacts: workflowArtifacts } = await workflowGen.collectWorkflowArtifacts(bmadDir);
|
const { artifacts: workflowArtifacts } = await workflowGen.collectWorkflowArtifacts(bmadDir);
|
||||||
|
|
||||||
// Write workflow-command artifacts using flat colon naming
|
// Write workflow-command artifacts using flat underscore naming
|
||||||
// Creates files like: bmad:bmm:correct-course.md
|
// Creates files like: bmad_bmm_correct-course.md
|
||||||
const workflowCommandCount = await workflowGen.writeColonArtifacts(commandsDir, workflowArtifacts);
|
const workflowCommandCount = await workflowGen.writeColonArtifacts(commandsDir, workflowArtifacts);
|
||||||
|
|
||||||
// Generate task and tool commands from manifests (if they exist)
|
// Generate task and tool commands from manifests (if they exist)
|
||||||
|
|
@ -490,7 +490,7 @@ You must fully embody this agent's persona and follow all activation instruction
|
||||||
</agent-activation>
|
</agent-activation>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// Use colon format: bmad:custom:agents:fred-commit-poet.md
|
// Use underscore format: bmad_custom_fred-commit-poet.md
|
||||||
// Written directly to commands dir (no bmad subfolder)
|
// Written directly to commands dir (no bmad subfolder)
|
||||||
const launcherName = customAgentColonName(agentName);
|
const launcherName = customAgentColonName(agentName);
|
||||||
const launcherPath = path.join(commandsDir, launcherName);
|
const launcherPath = path.join(commandsDir, launcherName);
|
||||||
|
|
|
||||||
|
|
@ -57,8 +57,8 @@ class ClineSetup extends BaseIdeSetup {
|
||||||
console.log(chalk.cyan(' BMAD workflows are available as slash commands in Cline'));
|
console.log(chalk.cyan(' BMAD workflows are available as slash commands in Cline'));
|
||||||
console.log(chalk.dim(' Usage:'));
|
console.log(chalk.dim(' Usage:'));
|
||||||
console.log(chalk.dim(' - Type / to see available commands'));
|
console.log(chalk.dim(' - Type / to see available commands'));
|
||||||
console.log(chalk.dim(' - All BMAD items start with "bmad-"'));
|
console.log(chalk.dim(' - All BMAD items start with "bmad_"'));
|
||||||
console.log(chalk.dim(' - Example: /bmad-bmm-pm'));
|
console.log(chalk.dim(' - Example: /bmad_bmm_pm'));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
|
|
@ -81,7 +81,7 @@ class ClineSetup extends BaseIdeSetup {
|
||||||
}
|
}
|
||||||
|
|
||||||
const entries = await fs.readdir(workflowsDir);
|
const entries = await fs.readdir(workflowsDir);
|
||||||
return entries.some((entry) => entry.startsWith('bmad-'));
|
return entries.some((entry) => entry.startsWith('bmad'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -146,7 +146,7 @@ class ClineSetup extends BaseIdeSetup {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flatten file path to bmad-module-type-name.md format
|
* Flatten file path to bmad_module_type_name.md format
|
||||||
* Uses shared toDashPath utility
|
* Uses shared toDashPath utility
|
||||||
*/
|
*/
|
||||||
flattenFilename(relativePath) {
|
flattenFilename(relativePath) {
|
||||||
|
|
@ -180,7 +180,7 @@ class ClineSetup extends BaseIdeSetup {
|
||||||
const entries = await fs.readdir(destDir);
|
const entries = await fs.readdir(destDir);
|
||||||
|
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
if (!entry.startsWith('bmad-')) {
|
if (!entry.startsWith('bmad')) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -246,7 +246,7 @@ The agent will follow the persona and instructions from the main agent file.
|
||||||
|
|
||||||
*Generated by BMAD Method*`;
|
*Generated by BMAD Method*`;
|
||||||
|
|
||||||
// Use dash format: bmad-custom-agents-fred-commit-poet.md
|
// Use underscore format: bmad_custom_fred-commit-poet.md
|
||||||
const fileName = customAgentDashName(agentName);
|
const fileName = customAgentDashName(agentName);
|
||||||
const launcherPath = path.join(workflowsDir, fileName);
|
const launcherPath = path.join(workflowsDir, fileName);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,7 @@ class CodexSetup extends BaseIdeSetup {
|
||||||
await fs.ensureDir(destDir);
|
await fs.ensureDir(destDir);
|
||||||
await this.clearOldBmadFiles(destDir);
|
await this.clearOldBmadFiles(destDir);
|
||||||
|
|
||||||
// Collect artifacts and write using DASH format
|
// Collect artifacts and write using underscore format
|
||||||
const agentGen = new AgentCommandGenerator(this.bmadFolderName);
|
const agentGen = new AgentCommandGenerator(this.bmadFolderName);
|
||||||
const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []);
|
const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []);
|
||||||
const agentCount = await agentGen.writeDashArtifacts(destDir, agentArtifacts);
|
const agentCount = await agentGen.writeDashArtifacts(destDir, agentArtifacts);
|
||||||
|
|
@ -115,7 +115,7 @@ class CodexSetup extends BaseIdeSetup {
|
||||||
const { artifacts: workflowArtifacts } = await workflowGenerator.collectWorkflowArtifacts(bmadDir);
|
const { artifacts: workflowArtifacts } = await workflowGenerator.collectWorkflowArtifacts(bmadDir);
|
||||||
const workflowCount = await workflowGenerator.writeDashArtifacts(destDir, workflowArtifacts);
|
const workflowCount = await workflowGenerator.writeDashArtifacts(destDir, workflowArtifacts);
|
||||||
|
|
||||||
// Also write tasks using dash format
|
// Also write tasks using underscore format
|
||||||
const ttGen = new TaskToolCommandGenerator();
|
const ttGen = new TaskToolCommandGenerator();
|
||||||
const tasksWritten = await ttGen.writeDashArtifacts(destDir, taskArtifacts);
|
const tasksWritten = await ttGen.writeDashArtifacts(destDir, taskArtifacts);
|
||||||
|
|
||||||
|
|
@ -155,7 +155,7 @@ class CodexSetup extends BaseIdeSetup {
|
||||||
// Check global location
|
// Check global location
|
||||||
if (await fs.pathExists(globalDir)) {
|
if (await fs.pathExists(globalDir)) {
|
||||||
const entries = await fs.readdir(globalDir);
|
const entries = await fs.readdir(globalDir);
|
||||||
if (entries.some((entry) => entry.startsWith('bmad-'))) {
|
if (entries.some((entry) => entry.startsWith('bmad'))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -163,7 +163,7 @@ class CodexSetup extends BaseIdeSetup {
|
||||||
// Check project-specific location
|
// Check project-specific location
|
||||||
if (await fs.pathExists(projectSpecificDir)) {
|
if (await fs.pathExists(projectSpecificDir)) {
|
||||||
const entries = await fs.readdir(projectSpecificDir);
|
const entries = await fs.readdir(projectSpecificDir);
|
||||||
if (entries.some((entry) => entry.startsWith('bmad-'))) {
|
if (entries.some((entry) => entry.startsWith('bmad'))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -256,7 +256,7 @@ class CodexSetup extends BaseIdeSetup {
|
||||||
const entries = await fs.readdir(destDir);
|
const entries = await fs.readdir(destDir);
|
||||||
|
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
if (!entry.startsWith('bmad-')) {
|
if (!entry.startsWith('bmad')) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -292,7 +292,7 @@ class CodexSetup extends BaseIdeSetup {
|
||||||
chalk.dim(" To use with other projects, you'd need to copy the _bmad dir"),
|
chalk.dim(" To use with other projects, you'd need to copy the _bmad dir"),
|
||||||
'',
|
'',
|
||||||
chalk.green(' ✓ You can now use /commands in Codex CLI'),
|
chalk.green(' ✓ You can now use /commands in Codex CLI'),
|
||||||
chalk.dim(' Example: /bmad-bmm-pm'),
|
chalk.dim(' Example: /bmad_bmm_pm'),
|
||||||
chalk.dim(' Type / to see all available commands'),
|
chalk.dim(' Type / to see all available commands'),
|
||||||
'',
|
'',
|
||||||
chalk.bold.cyan('═'.repeat(70)),
|
chalk.bold.cyan('═'.repeat(70)),
|
||||||
|
|
@ -397,7 +397,7 @@ You must fully embody this agent's persona and follow all activation instruction
|
||||||
</agent-activation>
|
</agent-activation>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// Use dash format: bmad-custom-agents-fred-commit-poet.md
|
// Use underscore format: bmad_custom_fred-commit-poet.md
|
||||||
const fileName = customAgentDashName(agentName);
|
const fileName = customAgentDashName(agentName);
|
||||||
const launcherPath = path.join(destDir, fileName);
|
const launcherPath = path.join(destDir, fileName);
|
||||||
await fs.writeFile(launcherPath, launcherContent, 'utf8');
|
await fs.writeFile(launcherPath, launcherContent, 'utf8');
|
||||||
|
|
|
||||||
|
|
@ -35,26 +35,26 @@ class CrushSetup extends BaseIdeSetup {
|
||||||
const commandsDir = path.join(crushDir, this.commandsDir);
|
const commandsDir = path.join(crushDir, this.commandsDir);
|
||||||
await this.ensureDir(commandsDir);
|
await this.ensureDir(commandsDir);
|
||||||
|
|
||||||
// Use colon format: files written directly to commands dir (no bmad subfolder)
|
// Use underscore format: files written directly to commands dir (no bmad subfolder)
|
||||||
// Creates: .crush/commands/bmad:bmm:pm.md
|
// Creates: .crush/commands/bmad_bmm_pm.md
|
||||||
|
|
||||||
// Generate agent launchers
|
// Generate agent launchers
|
||||||
const agentGen = new AgentCommandGenerator(this.bmadFolderName);
|
const agentGen = new AgentCommandGenerator(this.bmadFolderName);
|
||||||
const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []);
|
const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []);
|
||||||
|
|
||||||
// Write agent launcher files using flat colon naming
|
// Write agent launcher files using flat underscore naming
|
||||||
// Creates files like: bmad:bmm:pm.md
|
// Creates files like: bmad_bmm_pm.md
|
||||||
const agentCount = await agentGen.writeColonArtifacts(commandsDir, agentArtifacts);
|
const agentCount = await agentGen.writeColonArtifacts(commandsDir, agentArtifacts);
|
||||||
|
|
||||||
// Get ALL workflows using the new workflow command generator
|
// Get ALL workflows using the new workflow command generator
|
||||||
const workflowGenerator = new WorkflowCommandGenerator(this.bmadFolderName);
|
const workflowGenerator = new WorkflowCommandGenerator(this.bmadFolderName);
|
||||||
const { artifacts: workflowArtifacts } = await workflowGenerator.collectWorkflowArtifacts(bmadDir);
|
const { artifacts: workflowArtifacts } = await workflowGenerator.collectWorkflowArtifacts(bmadDir);
|
||||||
|
|
||||||
// Write workflow-command artifacts using flat colon naming
|
// Write workflow-command artifacts using flat underscore naming
|
||||||
// Creates files like: bmad:bmm:correct-course.md
|
// Creates files like: bmad_bmm_correct-course.md
|
||||||
const workflowCount = await workflowGenerator.writeColonArtifacts(commandsDir, workflowArtifacts);
|
const workflowCount = await workflowGenerator.writeColonArtifacts(commandsDir, workflowArtifacts);
|
||||||
|
|
||||||
// Generate task and tool commands using flat colon naming
|
// Generate task and tool commands using flat underscore naming
|
||||||
const taskToolGen = new TaskToolCommandGenerator();
|
const taskToolGen = new TaskToolCommandGenerator();
|
||||||
const taskToolResult = await taskToolGen.generateColonTaskToolCommands(projectDir, bmadDir, commandsDir);
|
const taskToolResult = await taskToolGen.generateColonTaskToolCommands(projectDir, bmadDir, commandsDir);
|
||||||
|
|
||||||
|
|
@ -81,11 +81,11 @@ class CrushSetup extends BaseIdeSetup {
|
||||||
async cleanup(projectDir) {
|
async cleanup(projectDir) {
|
||||||
const commandsDir = path.join(projectDir, this.configDir, this.commandsDir);
|
const commandsDir = path.join(projectDir, this.configDir, this.commandsDir);
|
||||||
|
|
||||||
// Remove any bmad:* files from the commands directory
|
// Remove any bmad* files from the commands directory (cleans up old bmad: and bmad- formats)
|
||||||
if (await fs.pathExists(commandsDir)) {
|
if (await fs.pathExists(commandsDir)) {
|
||||||
const entries = await fs.readdir(commandsDir);
|
const entries = await fs.readdir(commandsDir);
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
if (entry.startsWith('bmad:')) {
|
if (entry.startsWith('bmad')) {
|
||||||
await fs.remove(path.join(commandsDir, entry));
|
await fs.remove(path.join(commandsDir, entry));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -129,7 +129,7 @@ The agent will follow the persona and instructions from the main agent file.
|
||||||
|
|
||||||
*Generated by BMAD Method*`;
|
*Generated by BMAD Method*`;
|
||||||
|
|
||||||
// Use colon format: bmad:custom:agents:fred-commit-poet.md
|
// Use underscore format: bmad_custom_fred-commit-poet.md
|
||||||
// Written directly to commands dir (no bmad subfolder)
|
// Written directly to commands dir (no bmad subfolder)
|
||||||
const launcherName = customAgentColonName(agentName);
|
const launcherName = customAgentColonName(agentName);
|
||||||
const launcherPath = path.join(commandsDir, launcherName);
|
const launcherPath = path.join(commandsDir, launcherName);
|
||||||
|
|
|
||||||
|
|
@ -25,11 +25,11 @@ class CursorSetup extends BaseIdeSetup {
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const commandsDir = path.join(projectDir, this.configDir, this.commandsDir);
|
const commandsDir = path.join(projectDir, this.configDir, this.commandsDir);
|
||||||
|
|
||||||
// Remove any bmad:* files from the commands directory
|
// Remove any bmad* files from the commands directory (cleans up old bmad: and bmad- formats)
|
||||||
if (await fs.pathExists(commandsDir)) {
|
if (await fs.pathExists(commandsDir)) {
|
||||||
const entries = await fs.readdir(commandsDir);
|
const entries = await fs.readdir(commandsDir);
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
if (entry.startsWith('bmad:')) {
|
if (entry.startsWith('bmad')) {
|
||||||
await fs.remove(path.join(commandsDir, entry));
|
await fs.remove(path.join(commandsDir, entry));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -59,24 +59,24 @@ class CursorSetup extends BaseIdeSetup {
|
||||||
const commandsDir = path.join(cursorDir, this.commandsDir);
|
const commandsDir = path.join(cursorDir, this.commandsDir);
|
||||||
await this.ensureDir(commandsDir);
|
await this.ensureDir(commandsDir);
|
||||||
|
|
||||||
// Use colon format: files written directly to commands dir (no bmad subfolder)
|
// Use underscore format: files written directly to commands dir (no bmad subfolder)
|
||||||
// Creates: .cursor/commands/bmad:bmm:pm.md
|
// Creates: .cursor/commands/bmad_bmm_pm.md
|
||||||
|
|
||||||
// Generate agent launchers using AgentCommandGenerator
|
// Generate agent launchers using AgentCommandGenerator
|
||||||
// This creates small launcher files that reference the actual agents in _bmad/
|
// This creates small launcher files that reference the actual agents in _bmad/
|
||||||
const agentGen = new AgentCommandGenerator(this.bmadFolderName);
|
const agentGen = new AgentCommandGenerator(this.bmadFolderName);
|
||||||
const { artifacts: agentArtifacts, counts: agentCounts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []);
|
const { artifacts: agentArtifacts, counts: agentCounts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []);
|
||||||
|
|
||||||
// Write agent launcher files using flat colon naming
|
// Write agent launcher files using flat underscore naming
|
||||||
// Creates files like: bmad:bmm:pm.md
|
// Creates files like: bmad_bmm_pm.md
|
||||||
const agentCount = await agentGen.writeColonArtifacts(commandsDir, agentArtifacts);
|
const agentCount = await agentGen.writeColonArtifacts(commandsDir, agentArtifacts);
|
||||||
|
|
||||||
// Generate workflow commands from manifest (if it exists)
|
// Generate workflow commands from manifest (if it exists)
|
||||||
const workflowGen = new WorkflowCommandGenerator(this.bmadFolderName);
|
const workflowGen = new WorkflowCommandGenerator(this.bmadFolderName);
|
||||||
const { artifacts: workflowArtifacts } = await workflowGen.collectWorkflowArtifacts(bmadDir);
|
const { artifacts: workflowArtifacts } = await workflowGen.collectWorkflowArtifacts(bmadDir);
|
||||||
|
|
||||||
// Write workflow-command artifacts using flat colon naming
|
// Write workflow-command artifacts using flat underscore naming
|
||||||
// Creates files like: bmad:bmm:correct-course.md
|
// Creates files like: bmad_bmm_correct-course.md
|
||||||
const workflowCommandCount = await workflowGen.writeColonArtifacts(commandsDir, workflowArtifacts);
|
const workflowCommandCount = await workflowGen.writeColonArtifacts(commandsDir, workflowArtifacts);
|
||||||
|
|
||||||
// Generate task and tool commands from manifests (if they exist)
|
// Generate task and tool commands from manifests (if they exist)
|
||||||
|
|
@ -144,7 +144,7 @@ description: '${agentName} agent'
|
||||||
${launcherContent}
|
${launcherContent}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// Use colon format: bmad:custom:agents:fred-commit-poet.md
|
// Use underscore format: bmad_custom_fred-commit-poet.md
|
||||||
// Written directly to commands dir (no bmad subfolder)
|
// Written directly to commands dir (no bmad subfolder)
|
||||||
const launcherName = customAgentColonName(agentName);
|
const launcherName = customAgentColonName(agentName);
|
||||||
const launcherPath = path.join(commandsDir, launcherName);
|
const launcherPath = path.join(commandsDir, launcherName);
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,7 @@ class GeminiSetup extends BaseIdeSetup {
|
||||||
await this.writeFile(tomlPath, tomlContent);
|
await this.writeFile(tomlPath, tomlContent);
|
||||||
agentCount++;
|
agentCount++;
|
||||||
|
|
||||||
console.log(chalk.green(` ✓ Added agent: /bmad:agents:${artifact.module}:${artifact.name}`));
|
console.log(chalk.green(` ✓ Added agent: /bmad_agents_${artifact.module}_${artifact.name}`));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Install tasks as TOML files with bmad- prefix (flat structure)
|
// Install tasks as TOML files with bmad- prefix (flat structure)
|
||||||
|
|
@ -100,7 +100,7 @@ class GeminiSetup extends BaseIdeSetup {
|
||||||
await this.writeFile(tomlPath, tomlContent);
|
await this.writeFile(tomlPath, tomlContent);
|
||||||
taskCount++;
|
taskCount++;
|
||||||
|
|
||||||
console.log(chalk.green(` ✓ Added task: /bmad:tasks:${task.module}:${task.name}`));
|
console.log(chalk.green(` ✓ Added task: /bmad_tasks_${task.module}_${task.name}`));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Install workflows as TOML files with bmad- prefix (flat structure)
|
// Install workflows as TOML files with bmad- prefix (flat structure)
|
||||||
|
|
@ -116,7 +116,7 @@ class GeminiSetup extends BaseIdeSetup {
|
||||||
await this.writeFile(tomlPath, tomlContent);
|
await this.writeFile(tomlPath, tomlContent);
|
||||||
workflowCount++;
|
workflowCount++;
|
||||||
|
|
||||||
console.log(chalk.green(` ✓ Added workflow: /bmad:workflows:${artifact.module}:${workflowName}`));
|
console.log(chalk.green(` ✓ Added workflow: /bmad_workflows_${artifact.module}_${workflowName}`));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -125,9 +125,9 @@ class GeminiSetup extends BaseIdeSetup {
|
||||||
console.log(chalk.dim(` - ${taskCount} tasks configured`));
|
console.log(chalk.dim(` - ${taskCount} tasks configured`));
|
||||||
console.log(chalk.dim(` - ${workflowCount} workflows configured`));
|
console.log(chalk.dim(` - ${workflowCount} workflows configured`));
|
||||||
console.log(chalk.dim(` - Commands directory: ${path.relative(projectDir, commandsDir)}`));
|
console.log(chalk.dim(` - Commands directory: ${path.relative(projectDir, commandsDir)}`));
|
||||||
console.log(chalk.dim(` - Agent activation: /bmad:agents:{agent-name}`));
|
console.log(chalk.dim(` - Agent activation: /bmad_agents_{agent-name}`));
|
||||||
console.log(chalk.dim(` - Task activation: /bmad:tasks:{task-name}`));
|
console.log(chalk.dim(` - Task activation: /bmad_tasks_{task-name}`));
|
||||||
console.log(chalk.dim(` - Workflow activation: /bmad:workflows:{workflow-name}`));
|
console.log(chalk.dim(` - Workflow activation: /bmad_workflows_{workflow-name}`));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
|
|
@ -233,12 +233,12 @@ ${contentWithoutFrontmatter}
|
||||||
const commandsDir = path.join(projectDir, this.configDir, this.commandsDir);
|
const commandsDir = path.join(projectDir, this.configDir, this.commandsDir);
|
||||||
|
|
||||||
if (await fs.pathExists(commandsDir)) {
|
if (await fs.pathExists(commandsDir)) {
|
||||||
// Only remove files that start with bmad- prefix
|
// Remove any bmad* files (cleans up old bmad- and bmad: formats)
|
||||||
const files = await fs.readdir(commandsDir);
|
const files = await fs.readdir(commandsDir);
|
||||||
let removed = 0;
|
let removed = 0;
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
if (file.startsWith('bmad-') && file.endsWith('.toml')) {
|
if (file.startsWith('bmad') && file.endsWith('.toml')) {
|
||||||
await fs.remove(path.join(commandsDir, file));
|
await fs.remove(path.join(commandsDir, file));
|
||||||
removed++;
|
removed++;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -275,7 +275,7 @@ ${cleanContent}
|
||||||
let removed = 0;
|
let removed = 0;
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
if (file.startsWith('bmad-') && file.endsWith('.chatmode.md')) {
|
if (file.startsWith('bmad') && file.endsWith('.chatmode.md')) {
|
||||||
await fs.remove(path.join(chatmodesDir, file));
|
await fs.remove(path.join(chatmodesDir, file));
|
||||||
removed++;
|
removed++;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ class KiroCliSetup extends BaseIdeSetup {
|
||||||
// Remove existing BMad agents
|
// Remove existing BMad agents
|
||||||
const files = await fs.readdir(bmadAgentsDir);
|
const files = await fs.readdir(bmadAgentsDir);
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
if (file.startsWith('bmad-') || file.includes('bmad')) {
|
if (file.startsWith('bmad')) {
|
||||||
await fs.remove(path.join(bmadAgentsDir, file));
|
await fs.remove(path.join(bmadAgentsDir, file));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -185,7 +185,7 @@ class OpenCodeSetup extends BaseIdeSetup {
|
||||||
if (await fs.pathExists(agentsDir)) {
|
if (await fs.pathExists(agentsDir)) {
|
||||||
const files = await fs.readdir(agentsDir);
|
const files = await fs.readdir(agentsDir);
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
if (file.startsWith('bmad-') && file.endsWith('.md')) {
|
if (file.startsWith('bmad') && file.endsWith('.md')) {
|
||||||
await fs.remove(path.join(agentsDir, file));
|
await fs.remove(path.join(agentsDir, file));
|
||||||
removed++;
|
removed++;
|
||||||
}
|
}
|
||||||
|
|
@ -196,7 +196,7 @@ class OpenCodeSetup extends BaseIdeSetup {
|
||||||
if (await fs.pathExists(commandsDir)) {
|
if (await fs.pathExists(commandsDir)) {
|
||||||
const files = await fs.readdir(commandsDir);
|
const files = await fs.readdir(commandsDir);
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
if (file.startsWith('bmad-') && file.endsWith('.md')) {
|
if (file.startsWith('bmad') && file.endsWith('.md')) {
|
||||||
await fs.remove(path.join(commandsDir, file));
|
await fs.remove(path.join(commandsDir, file));
|
||||||
removed++;
|
removed++;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ class QwenSetup extends BaseIdeSetup {
|
||||||
await this.writeFile(targetPath, tomlContent);
|
await this.writeFile(targetPath, tomlContent);
|
||||||
|
|
||||||
agentCount++;
|
agentCount++;
|
||||||
console.log(chalk.green(` ✓ Added agent: /bmad:${artifact.module}:agents:${artifact.name}`));
|
console.log(chalk.green(` ✓ Added agent: /bmad_${artifact.module}_agents_${artifact.name}`));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create TOML files for each task
|
// Create TOML files for each task
|
||||||
|
|
@ -90,7 +90,7 @@ class QwenSetup extends BaseIdeSetup {
|
||||||
await this.writeFile(targetPath, content);
|
await this.writeFile(targetPath, content);
|
||||||
|
|
||||||
taskCount++;
|
taskCount++;
|
||||||
console.log(chalk.green(` ✓ Added task: /bmad:${task.module}:tasks:${task.name}`));
|
console.log(chalk.green(` ✓ Added task: /bmad_${task.module}_tasks_${task.name}`));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create TOML files for each tool
|
// Create TOML files for each tool
|
||||||
|
|
@ -106,7 +106,7 @@ class QwenSetup extends BaseIdeSetup {
|
||||||
await this.writeFile(targetPath, content);
|
await this.writeFile(targetPath, content);
|
||||||
|
|
||||||
toolCount++;
|
toolCount++;
|
||||||
console.log(chalk.green(` ✓ Added tool: /bmad:${tool.module}:tools:${tool.name}`));
|
console.log(chalk.green(` ✓ Added tool: /bmad_${tool.module}_tools_${tool.name}`));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create TOML files for each workflow
|
// Create TOML files for each workflow
|
||||||
|
|
@ -122,7 +122,7 @@ class QwenSetup extends BaseIdeSetup {
|
||||||
await this.writeFile(targetPath, content);
|
await this.writeFile(targetPath, content);
|
||||||
|
|
||||||
workflowCount++;
|
workflowCount++;
|
||||||
console.log(chalk.green(` ✓ Added workflow: /bmad:${workflow.module}:workflows:${workflow.name}`));
|
console.log(chalk.green(` ✓ Added workflow: /bmad_${workflow.module}_workflows_${workflow.name}`));
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(chalk.green(`✓ ${this.name} configured:`));
|
console.log(chalk.green(`✓ ${this.name} configured:`));
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ class RooSetup extends BaseIdeSetup {
|
||||||
let skippedCount = 0;
|
let skippedCount = 0;
|
||||||
|
|
||||||
for (const artifact of agentArtifacts) {
|
for (const artifact of agentArtifacts) {
|
||||||
// Use shared toDashPath to get consistent naming: bmad-bmm-name.md
|
// Use shared toDashPath to get consistent naming: bmad_bmm_name.md
|
||||||
const commandName = toDashPath(artifact.relativePath).replace('.md', '');
|
const commandName = toDashPath(artifact.relativePath).replace('.md', '');
|
||||||
const commandPath = path.join(rooCommandsDir, `${commandName}.md`);
|
const commandPath = path.join(rooCommandsDir, `${commandName}.md`);
|
||||||
|
|
||||||
|
|
@ -169,7 +169,7 @@ class RooSetup extends BaseIdeSetup {
|
||||||
let removedCount = 0;
|
let removedCount = 0;
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
if (file.startsWith('bmad-') && file.endsWith('.md')) {
|
if (file.startsWith('bmad') && file.endsWith('.md')) {
|
||||||
await fs.remove(path.join(rooCommandsDir, file));
|
await fs.remove(path.join(rooCommandsDir, file));
|
||||||
removedCount++;
|
removedCount++;
|
||||||
}
|
}
|
||||||
|
|
@ -192,7 +192,7 @@ class RooSetup extends BaseIdeSetup {
|
||||||
let removedCount = 0;
|
let removedCount = 0;
|
||||||
|
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
if (/^\s*- slug: bmad-/.test(line)) {
|
if (/^\s*- slug: bmad/.test(line)) {
|
||||||
skipMode = true;
|
skipMode = true;
|
||||||
removedCount++;
|
removedCount++;
|
||||||
} else if (skipMode && /^\s*- slug: /.test(line)) {
|
} else if (skipMode && /^\s*- slug: /.test(line)) {
|
||||||
|
|
@ -224,7 +224,7 @@ class RooSetup extends BaseIdeSetup {
|
||||||
const rooCommandsDir = path.join(projectDir, this.configDir, this.commandsDir);
|
const rooCommandsDir = path.join(projectDir, this.configDir, this.commandsDir);
|
||||||
await this.ensureDir(rooCommandsDir);
|
await this.ensureDir(rooCommandsDir);
|
||||||
|
|
||||||
// Use dash format: bmad-custom-agents-fred-commit-poet.md
|
// Use underscore format: bmad_custom_fred-commit-poet.md
|
||||||
const commandName = customAgentDashName(agentName).replace('.md', '');
|
const commandName = customAgentDashName(agentName).replace('.md', '');
|
||||||
const commandPath = path.join(rooCommandsDir, `${commandName}.md`);
|
const commandPath = path.join(rooCommandsDir, `${commandName}.md`);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ class RovoDevSetup extends BaseIdeSetup {
|
||||||
const subagentsDir = path.join(rovoDevDir, this.subagentsDir);
|
const subagentsDir = path.join(rovoDevDir, this.subagentsDir);
|
||||||
if (await fs.pathExists(subagentsDir)) {
|
if (await fs.pathExists(subagentsDir)) {
|
||||||
const entries = await fs.readdir(subagentsDir);
|
const entries = await fs.readdir(subagentsDir);
|
||||||
const bmadFiles = entries.filter((file) => file.startsWith('bmad-') && file.endsWith('.md'));
|
const bmadFiles = entries.filter((file) => file.startsWith('bmad') && file.endsWith('.md'));
|
||||||
|
|
||||||
for (const file of bmadFiles) {
|
for (const file of bmadFiles) {
|
||||||
await fs.remove(path.join(subagentsDir, file));
|
await fs.remove(path.join(subagentsDir, file));
|
||||||
|
|
@ -48,7 +48,7 @@ class RovoDevSetup extends BaseIdeSetup {
|
||||||
const workflowsDir = path.join(rovoDevDir, this.workflowsDir);
|
const workflowsDir = path.join(rovoDevDir, this.workflowsDir);
|
||||||
if (await fs.pathExists(workflowsDir)) {
|
if (await fs.pathExists(workflowsDir)) {
|
||||||
const entries = await fs.readdir(workflowsDir);
|
const entries = await fs.readdir(workflowsDir);
|
||||||
const bmadFiles = entries.filter((file) => file.startsWith('bmad-') && file.endsWith('.md'));
|
const bmadFiles = entries.filter((file) => file.startsWith('bmad') && file.endsWith('.md'));
|
||||||
|
|
||||||
for (const file of bmadFiles) {
|
for (const file of bmadFiles) {
|
||||||
await fs.remove(path.join(workflowsDir, file));
|
await fs.remove(path.join(workflowsDir, file));
|
||||||
|
|
@ -59,7 +59,7 @@ class RovoDevSetup extends BaseIdeSetup {
|
||||||
const referencesDir = path.join(rovoDevDir, this.referencesDir);
|
const referencesDir = path.join(rovoDevDir, this.referencesDir);
|
||||||
if (await fs.pathExists(referencesDir)) {
|
if (await fs.pathExists(referencesDir)) {
|
||||||
const entries = await fs.readdir(referencesDir);
|
const entries = await fs.readdir(referencesDir);
|
||||||
const bmadFiles = entries.filter((file) => file.startsWith('bmad-') && file.endsWith('.md'));
|
const bmadFiles = entries.filter((file) => file.startsWith('bmad') && file.endsWith('.md'));
|
||||||
|
|
||||||
for (const file of bmadFiles) {
|
for (const file of bmadFiles) {
|
||||||
await fs.remove(path.join(referencesDir, file));
|
await fs.remove(path.join(referencesDir, file));
|
||||||
|
|
@ -249,7 +249,7 @@ class RovoDevSetup extends BaseIdeSetup {
|
||||||
if (await fs.pathExists(subagentsDir)) {
|
if (await fs.pathExists(subagentsDir)) {
|
||||||
try {
|
try {
|
||||||
const entries = await fs.readdir(subagentsDir);
|
const entries = await fs.readdir(subagentsDir);
|
||||||
if (entries.some((entry) => entry.startsWith('bmad-') && entry.endsWith('.md'))) {
|
if (entries.some((entry) => entry.startsWith('bmad') && entry.endsWith('.md'))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
|
|
@ -262,7 +262,7 @@ class RovoDevSetup extends BaseIdeSetup {
|
||||||
if (await fs.pathExists(workflowsDir)) {
|
if (await fs.pathExists(workflowsDir)) {
|
||||||
try {
|
try {
|
||||||
const entries = await fs.readdir(workflowsDir);
|
const entries = await fs.readdir(workflowsDir);
|
||||||
if (entries.some((entry) => entry.startsWith('bmad-') && entry.endsWith('.md'))) {
|
if (entries.some((entry) => entry.startsWith('bmad') && entry.endsWith('.md'))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
|
|
@ -275,7 +275,7 @@ class RovoDevSetup extends BaseIdeSetup {
|
||||||
if (await fs.pathExists(referencesDir)) {
|
if (await fs.pathExists(referencesDir)) {
|
||||||
try {
|
try {
|
||||||
const entries = await fs.readdir(referencesDir);
|
const entries = await fs.readdir(referencesDir);
|
||||||
if (entries.some((entry) => entry.startsWith('bmad-') && entry.endsWith('.md'))) {
|
if (entries.some((entry) => entry.startsWith('bmad') && entry.endsWith('.md'))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
|
|
|
||||||
|
|
@ -94,8 +94,8 @@ class AgentCommandGenerator {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Write agent launcher artifacts using COLON format (for folder-based IDEs)
|
* Write agent launcher artifacts using underscore format (Windows-compatible)
|
||||||
* Creates flat files like: bmad:bmm:pm.md
|
* Creates flat files like: bmad_bmm_pm.md
|
||||||
*
|
*
|
||||||
* @param {string} baseCommandsDir - Base commands directory for the IDE
|
* @param {string} baseCommandsDir - Base commands directory for the IDE
|
||||||
* @param {Array} artifacts - Agent launcher artifacts
|
* @param {Array} artifacts - Agent launcher artifacts
|
||||||
|
|
@ -106,7 +106,7 @@ class AgentCommandGenerator {
|
||||||
|
|
||||||
for (const artifact of artifacts) {
|
for (const artifact of artifacts) {
|
||||||
if (artifact.type === 'agent-launcher') {
|
if (artifact.type === 'agent-launcher') {
|
||||||
// Convert relativePath to colon format: bmm/agents/pm.md → bmad:bmm:pm.md
|
// Convert relativePath to underscore format: bmm/agents/pm.md → bmad_bmm_pm.md
|
||||||
const flatName = toColonPath(artifact.relativePath);
|
const flatName = toColonPath(artifact.relativePath);
|
||||||
const launcherPath = path.join(baseCommandsDir, flatName);
|
const launcherPath = path.join(baseCommandsDir, flatName);
|
||||||
await fs.ensureDir(path.dirname(launcherPath));
|
await fs.ensureDir(path.dirname(launcherPath));
|
||||||
|
|
@ -119,8 +119,8 @@ class AgentCommandGenerator {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Write agent launcher artifacts using DASH format (for flat IDEs)
|
* Write agent launcher artifacts using underscore format (Windows-compatible)
|
||||||
* Creates flat files like: bmad-bmm-pm.md
|
* Creates flat files like: bmad_bmm_pm.md
|
||||||
*
|
*
|
||||||
* @param {string} baseCommandsDir - Base commands directory for the IDE
|
* @param {string} baseCommandsDir - Base commands directory for the IDE
|
||||||
* @param {Array} artifacts - Agent launcher artifacts
|
* @param {Array} artifacts - Agent launcher artifacts
|
||||||
|
|
@ -131,7 +131,7 @@ class AgentCommandGenerator {
|
||||||
|
|
||||||
for (const artifact of artifacts) {
|
for (const artifact of artifacts) {
|
||||||
if (artifact.type === 'agent-launcher') {
|
if (artifact.type === 'agent-launcher') {
|
||||||
// Convert relativePath to dash format: bmm/agents/pm.md → bmad-bmm-pm.md
|
// Convert relativePath to underscore format: bmm/agents/pm.md → bmad_bmm_pm.md
|
||||||
const flatName = toDashPath(artifact.relativePath);
|
const flatName = toDashPath(artifact.relativePath);
|
||||||
const launcherPath = path.join(baseCommandsDir, flatName);
|
const launcherPath = path.join(baseCommandsDir, flatName);
|
||||||
await fs.ensureDir(path.dirname(launcherPath));
|
await fs.ensureDir(path.dirname(launcherPath));
|
||||||
|
|
@ -144,18 +144,18 @@ class AgentCommandGenerator {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the custom agent name in colon format
|
* Get the custom agent name in underscore format (Windows-compatible)
|
||||||
* @param {string} agentName - Custom agent name
|
* @param {string} agentName - Custom agent name
|
||||||
* @returns {string} Colon-formatted filename
|
* @returns {string} Underscore-formatted filename
|
||||||
*/
|
*/
|
||||||
getCustomAgentColonName(agentName) {
|
getCustomAgentColonName(agentName) {
|
||||||
return customAgentColonName(agentName);
|
return customAgentColonName(agentName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the custom agent name in dash format
|
* Get the custom agent name in underscore format (Windows-compatible)
|
||||||
* @param {string} agentName - Custom agent name
|
* @param {string} agentName - Custom agent name
|
||||||
* @returns {string} Dash-formatted filename
|
* @returns {string} Underscore-formatted filename
|
||||||
*/
|
*/
|
||||||
getCustomAgentDashName(agentName) {
|
getCustomAgentDashName(agentName) {
|
||||||
return customAgentDashName(agentName);
|
return customAgentDashName(agentName);
|
||||||
|
|
|
||||||
|
|
@ -2,109 +2,72 @@
|
||||||
* Path transformation utilities for IDE installer standardization
|
* Path transformation utilities for IDE installer standardization
|
||||||
*
|
*
|
||||||
* Provides utilities to convert hierarchical paths to flat naming conventions.
|
* Provides utilities to convert hierarchical paths to flat naming conventions.
|
||||||
* - Colon format (bmad:module:name.md) for folder-based IDEs converting to flat
|
* - Underscore format (bmad_module_name.md) - Windows-compatible universal format
|
||||||
* - Dash format (bmad-module-name.md) for already-flat IDEs
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Type segments to filter out from paths
|
// Type segments to filter out from paths
|
||||||
const TYPE_SEGMENTS = ['agents', 'workflows', 'tasks', 'tools'];
|
const TYPE_SEGMENTS = ['agents', 'workflows', 'tasks', 'tools'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert hierarchical path to flat colon-separated name (for folder-based IDEs)
|
* Convert hierarchical path to flat underscore-separated name
|
||||||
* Converts: 'bmm/agents/pm.md' → 'bmad:bmm:pm.md'
|
* Converts: 'bmm/agents/pm.md' → 'bmad_bmm_pm.md'
|
||||||
* Converts: 'bmm/workflows/correct-course.md' → 'bmad:bmm:correct-course.md'
|
* Converts: 'bmm/workflows/correct-course.md' → 'bmad_bmm_correct-course.md'
|
||||||
*
|
*
|
||||||
* @param {string} module - Module name (e.g., 'bmm', 'core')
|
* @param {string} module - Module name (e.g., 'bmm', 'core')
|
||||||
* @param {string} type - Artifact type ('agents', 'workflows', 'tasks', 'tools') - filtered out
|
* @param {string} type - Artifact type ('agents', 'workflows', 'tasks', 'tools') - filtered out
|
||||||
* @param {string} name - Artifact name (e.g., 'pm', 'correct-course')
|
* @param {string} name - Artifact name (e.g., 'pm', 'correct-course')
|
||||||
* @returns {string} Flat filename like 'bmad:bmm:pm.md'
|
* @returns {string} Flat filename like 'bmad_bmm_pm.md'
|
||||||
*/
|
*/
|
||||||
function toColonName(module, type, name) {
|
function toUnderscoreName(module, type, name) {
|
||||||
return `bmad:${module}:${name}.md`;
|
return `bmad_${module}_${name}.md`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert relative path to flat colon-separated name (for folder-based IDEs)
|
* Convert relative path to flat underscore-separated name
|
||||||
* Converts: 'bmm/agents/pm.md' → 'bmad:bmm:pm.md'
|
* Converts: 'bmm/agents/pm.md' → 'bmad_bmm_pm.md'
|
||||||
* Converts: 'bmm/workflows/correct-course.md' → 'bmad:bmm:correct-course.md'
|
* Converts: 'bmm/workflows/correct-course.md' → 'bmad_bmm_correct-course.md'
|
||||||
*
|
*
|
||||||
* @param {string} relativePath - Path like 'bmm/agents/pm.md'
|
* @param {string} relativePath - Path like 'bmm/agents/pm.md'
|
||||||
* @returns {string} Flat filename like 'bmad:bmm:pm.md'
|
* @returns {string} Flat filename like 'bmad_bmm_pm.md'
|
||||||
*/
|
*/
|
||||||
function toColonPath(relativePath) {
|
function toUnderscorePath(relativePath) {
|
||||||
const withoutExt = relativePath.replace('.md', '');
|
const withoutExt = relativePath.replace('.md', '');
|
||||||
const parts = withoutExt.split(/[/\\]/);
|
const parts = withoutExt.split(/[/\\]/);
|
||||||
// Filter out type segments (agents, workflows, tasks, tools)
|
// Filter out type segments (agents, workflows, tasks, tools)
|
||||||
const filtered = parts.filter((p) => !TYPE_SEGMENTS.includes(p));
|
const filtered = parts.filter((p) => !TYPE_SEGMENTS.includes(p));
|
||||||
return `bmad:${filtered.join(':')}.md`;
|
return `bmad_${filtered.join('_')}.md`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert hierarchical path to flat dash-separated name (for flat IDEs)
|
* Create custom agent underscore name
|
||||||
* Converts: 'bmm/agents/pm.md' → 'bmad-bmm-pm.md'
|
* Creates: 'bmad_custom_fred-commit-poet.md'
|
||||||
* Converts: 'bmm/workflows/correct-course.md' → 'bmad-bmm-correct-course.md'
|
|
||||||
*
|
|
||||||
* @param {string} relativePath - Path like 'bmm/agents/pm.md'
|
|
||||||
* @returns {string} Flat filename like 'bmad-bmm-pm.md'
|
|
||||||
*/
|
|
||||||
function toDashPath(relativePath) {
|
|
||||||
const withoutExt = relativePath.replace('.md', '');
|
|
||||||
const parts = withoutExt.split(/[/\\]/);
|
|
||||||
// Filter out type segments (agents, workflows, tasks, tools)
|
|
||||||
const filtered = parts.filter((p) => !TYPE_SEGMENTS.includes(p));
|
|
||||||
return `bmad-${filtered.join('-')}.md`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create custom agent colon name (for folder-based IDEs)
|
|
||||||
* Creates: 'bmad:custom:fred-commit-poet.md'
|
|
||||||
*
|
*
|
||||||
* @param {string} agentName - Custom agent name
|
* @param {string} agentName - Custom agent name
|
||||||
* @returns {string} Flat filename like 'bmad:custom:fred-commit-poet.md'
|
* @returns {string} Flat filename like 'bmad_custom_fred-commit-poet.md'
|
||||||
*/
|
*/
|
||||||
function customAgentColonName(agentName) {
|
function customAgentUnderscoreName(agentName) {
|
||||||
return `bmad:custom:${agentName}.md`;
|
return `bmad_custom_${agentName}.md`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create custom agent dash name (for flat IDEs)
|
* Check if a filename uses underscore format
|
||||||
* Creates: 'bmad-custom-fred-commit-poet.md'
|
|
||||||
*
|
|
||||||
* @param {string} agentName - Custom agent name
|
|
||||||
* @returns {string} Flat filename like 'bmad-custom-fred-commit-poet.md'
|
|
||||||
*/
|
|
||||||
function customAgentDashName(agentName) {
|
|
||||||
return `bmad-custom-${agentName}.md`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if a filename uses colon format
|
|
||||||
* @param {string} filename - Filename to check
|
* @param {string} filename - Filename to check
|
||||||
* @returns {boolean} True if filename uses colon format
|
* @returns {boolean} True if filename uses underscore format
|
||||||
*/
|
*/
|
||||||
function isColonFormat(filename) {
|
function isUnderscoreFormat(filename) {
|
||||||
return filename.includes('bmad:') && filename.includes(':');
|
return filename.startsWith('bmad_') && filename.includes('_');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a filename uses dash format
|
* Extract parts from an underscore-formatted filename
|
||||||
* @param {string} filename - Filename to check
|
* Parses: 'bmad_bmm_pm.md' → { prefix: 'bmad', module: 'bmm', name: 'pm' }
|
||||||
* @returns {boolean} True if filename uses dash format
|
|
||||||
*/
|
|
||||||
function isDashFormat(filename) {
|
|
||||||
return filename.startsWith('bmad-') && !filename.includes(':');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extract parts from a colon-formatted filename
|
|
||||||
* Parses: 'bmad:bmm:pm.md' → { prefix: 'bmad', module: 'bmm', name: 'pm' }
|
|
||||||
*
|
*
|
||||||
* @param {string} filename - Colon-formatted filename
|
* @param {string} filename - Underscore-formatted filename
|
||||||
* @returns {Object|null} Parsed parts or null if invalid format
|
* @returns {Object|null} Parsed parts or null if invalid format
|
||||||
*/
|
*/
|
||||||
function parseColonName(filename) {
|
function parseUnderscoreName(filename) {
|
||||||
const withoutExt = filename.replace('.md', '');
|
const withoutExt = filename.replace('.md', '');
|
||||||
const parts = withoutExt.split(':');
|
const parts = withoutExt.split('_');
|
||||||
|
|
||||||
if (parts.length < 3 || parts[0] !== 'bmad') {
|
if (parts.length < 3 || parts[0] !== 'bmad') {
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -113,33 +76,28 @@ function parseColonName(filename) {
|
||||||
return {
|
return {
|
||||||
prefix: parts[0],
|
prefix: parts[0],
|
||||||
module: parts[1],
|
module: parts[1],
|
||||||
name: parts.slice(2).join(':'), // Handle names that might contain colons
|
name: parts.slice(2).join('_'), // Handle names that might contain underscores
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Backward compatibility aliases (deprecated)
|
||||||
* Extract parts from a dash-formatted filename
|
const toColonName = toUnderscoreName;
|
||||||
* Parses: 'bmad-bmm-pm.md' → { prefix: 'bmad', module: 'bmm', name: 'pm' }
|
const toColonPath = toUnderscorePath;
|
||||||
*
|
const toDashPath = toUnderscorePath;
|
||||||
* @param {string} filename - Dash-formatted filename
|
const customAgentColonName = customAgentUnderscoreName;
|
||||||
* @returns {Object|null} Parsed parts or null if invalid format
|
const customAgentDashName = customAgentUnderscoreName;
|
||||||
*/
|
const isColonFormat = isUnderscoreFormat;
|
||||||
function parseDashName(filename) {
|
const isDashFormat = isUnderscoreFormat;
|
||||||
const withoutExt = filename.replace('.md', '');
|
const parseColonName = parseUnderscoreName;
|
||||||
const parts = withoutExt.split('-');
|
const parseDashName = parseUnderscoreName;
|
||||||
|
|
||||||
if (parts.length < 3 || parts[0] !== 'bmad') {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
prefix: parts[0],
|
|
||||||
module: parts[1],
|
|
||||||
name: parts.slice(2).join('-'), // Handle names that might contain dashes
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
toUnderscoreName,
|
||||||
|
toUnderscorePath,
|
||||||
|
customAgentUnderscoreName,
|
||||||
|
isUnderscoreFormat,
|
||||||
|
parseUnderscoreName,
|
||||||
|
// Backward compatibility aliases
|
||||||
toColonName,
|
toColonName,
|
||||||
toColonPath,
|
toColonPath,
|
||||||
toDashPath,
|
toDashPath,
|
||||||
|
|
|
||||||
|
|
@ -117,8 +117,8 @@ Follow all instructions in the ${type} file exactly as written.
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate task and tool commands using COLON format (for folder-based IDEs)
|
* Generate task and tool commands using underscore format (Windows-compatible)
|
||||||
* Creates flat files like: bmad:bmm:bmad-help.md
|
* Creates flat files like: bmad_bmm_bmad-help.md
|
||||||
*
|
*
|
||||||
* @param {string} projectDir - Project directory
|
* @param {string} projectDir - Project directory
|
||||||
* @param {string} bmadDir - BMAD installation directory
|
* @param {string} bmadDir - BMAD installation directory
|
||||||
|
|
@ -138,7 +138,7 @@ Follow all instructions in the ${type} file exactly as written.
|
||||||
// Generate command files for tasks
|
// Generate command files for tasks
|
||||||
for (const task of standaloneTasks) {
|
for (const task of standaloneTasks) {
|
||||||
const commandContent = this.generateCommandContent(task, 'task');
|
const commandContent = this.generateCommandContent(task, 'task');
|
||||||
// Use colon format: bmad:bmm:name.md
|
// Use underscore format: bmad_bmm_name.md
|
||||||
const flatName = toColonName(task.module, 'tasks', task.name);
|
const flatName = toColonName(task.module, 'tasks', task.name);
|
||||||
const commandPath = path.join(baseCommandsDir, flatName);
|
const commandPath = path.join(baseCommandsDir, flatName);
|
||||||
await fs.ensureDir(path.dirname(commandPath));
|
await fs.ensureDir(path.dirname(commandPath));
|
||||||
|
|
@ -149,7 +149,7 @@ Follow all instructions in the ${type} file exactly as written.
|
||||||
// Generate command files for tools
|
// Generate command files for tools
|
||||||
for (const tool of standaloneTools) {
|
for (const tool of standaloneTools) {
|
||||||
const commandContent = this.generateCommandContent(tool, 'tool');
|
const commandContent = this.generateCommandContent(tool, 'tool');
|
||||||
// Use colon format: bmad:bmm:name.md
|
// Use underscore format: bmad_bmm_name.md
|
||||||
const flatName = toColonName(tool.module, 'tools', tool.name);
|
const flatName = toColonName(tool.module, 'tools', tool.name);
|
||||||
const commandPath = path.join(baseCommandsDir, flatName);
|
const commandPath = path.join(baseCommandsDir, flatName);
|
||||||
await fs.ensureDir(path.dirname(commandPath));
|
await fs.ensureDir(path.dirname(commandPath));
|
||||||
|
|
@ -165,8 +165,8 @@ Follow all instructions in the ${type} file exactly as written.
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate task and tool commands using DASH format (for flat IDEs)
|
* Generate task and tool commands using underscore format (Windows-compatible)
|
||||||
* Creates flat files like: bmad-bmm-bmad-help.md
|
* Creates flat files like: bmad_bmm_bmad-help.md
|
||||||
*
|
*
|
||||||
* @param {string} projectDir - Project directory
|
* @param {string} projectDir - Project directory
|
||||||
* @param {string} bmadDir - BMAD installation directory
|
* @param {string} bmadDir - BMAD installation directory
|
||||||
|
|
@ -186,7 +186,7 @@ Follow all instructions in the ${type} file exactly as written.
|
||||||
// Generate command files for tasks
|
// Generate command files for tasks
|
||||||
for (const task of standaloneTasks) {
|
for (const task of standaloneTasks) {
|
||||||
const commandContent = this.generateCommandContent(task, 'task');
|
const commandContent = this.generateCommandContent(task, 'task');
|
||||||
// Use dash format: bmad-bmm-name.md
|
// Use underscore format: bmad_bmm_name.md
|
||||||
const flatName = toDashPath(`${task.module}/tasks/${task.name}.md`);
|
const flatName = toDashPath(`${task.module}/tasks/${task.name}.md`);
|
||||||
const commandPath = path.join(baseCommandsDir, flatName);
|
const commandPath = path.join(baseCommandsDir, flatName);
|
||||||
await fs.ensureDir(path.dirname(commandPath));
|
await fs.ensureDir(path.dirname(commandPath));
|
||||||
|
|
@ -197,7 +197,7 @@ Follow all instructions in the ${type} file exactly as written.
|
||||||
// Generate command files for tools
|
// Generate command files for tools
|
||||||
for (const tool of standaloneTools) {
|
for (const tool of standaloneTools) {
|
||||||
const commandContent = this.generateCommandContent(tool, 'tool');
|
const commandContent = this.generateCommandContent(tool, 'tool');
|
||||||
// Use dash format: bmad-bmm-name.md
|
// Use underscore format: bmad_bmm_name.md
|
||||||
const flatName = toDashPath(`${tool.module}/tools/${tool.name}.md`);
|
const flatName = toDashPath(`${tool.module}/tools/${tool.name}.md`);
|
||||||
const commandPath = path.join(baseCommandsDir, flatName);
|
const commandPath = path.join(baseCommandsDir, flatName);
|
||||||
await fs.ensureDir(path.dirname(commandPath));
|
await fs.ensureDir(path.dirname(commandPath));
|
||||||
|
|
@ -213,8 +213,8 @@ Follow all instructions in the ${type} file exactly as written.
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Write task/tool artifacts using COLON format (for folder-based IDEs)
|
* Write task/tool artifacts using underscore format (Windows-compatible)
|
||||||
* Creates flat files like: bmad:bmm:bmad-help.md
|
* Creates flat files like: bmad_bmm_bmad-help.md
|
||||||
*
|
*
|
||||||
* @param {string} baseCommandsDir - Base commands directory for the IDE
|
* @param {string} baseCommandsDir - Base commands directory for the IDE
|
||||||
* @param {Array} artifacts - Task/tool artifacts with relativePath
|
* @param {Array} artifacts - Task/tool artifacts with relativePath
|
||||||
|
|
@ -226,7 +226,7 @@ Follow all instructions in the ${type} file exactly as written.
|
||||||
for (const artifact of artifacts) {
|
for (const artifact of artifacts) {
|
||||||
if (artifact.type === 'task' || artifact.type === 'tool') {
|
if (artifact.type === 'task' || artifact.type === 'tool') {
|
||||||
const commandContent = this.generateCommandContent(artifact, artifact.type);
|
const commandContent = this.generateCommandContent(artifact, artifact.type);
|
||||||
// Use colon format: bmad:module:name.md
|
// Use underscore format: bmad_module_name.md
|
||||||
const flatName = toColonPath(artifact.relativePath);
|
const flatName = toColonPath(artifact.relativePath);
|
||||||
const commandPath = path.join(baseCommandsDir, flatName);
|
const commandPath = path.join(baseCommandsDir, flatName);
|
||||||
await fs.ensureDir(path.dirname(commandPath));
|
await fs.ensureDir(path.dirname(commandPath));
|
||||||
|
|
@ -239,8 +239,8 @@ Follow all instructions in the ${type} file exactly as written.
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Write task/tool artifacts using DASH format (for flat IDEs)
|
* Write task/tool artifacts using underscore format (Windows-compatible)
|
||||||
* Creates flat files like: bmad-bmm-bmad-help.md
|
* Creates flat files like: bmad_bmm_bmad-help.md
|
||||||
*
|
*
|
||||||
* @param {string} baseCommandsDir - Base commands directory for the IDE
|
* @param {string} baseCommandsDir - Base commands directory for the IDE
|
||||||
* @param {Array} artifacts - Task/tool artifacts with relativePath
|
* @param {Array} artifacts - Task/tool artifacts with relativePath
|
||||||
|
|
@ -252,7 +252,7 @@ Follow all instructions in the ${type} file exactly as written.
|
||||||
for (const artifact of artifacts) {
|
for (const artifact of artifacts) {
|
||||||
if (artifact.type === 'task' || artifact.type === 'tool') {
|
if (artifact.type === 'task' || artifact.type === 'tool') {
|
||||||
const commandContent = this.generateCommandContent(artifact, artifact.type);
|
const commandContent = this.generateCommandContent(artifact, artifact.type);
|
||||||
// Use dash format: bmad-module-name.md
|
// Use underscore format: bmad_module_name.md
|
||||||
const flatName = toDashPath(artifact.relativePath);
|
const flatName = toDashPath(artifact.relativePath);
|
||||||
const commandPath = path.join(baseCommandsDir, flatName);
|
const commandPath = path.join(baseCommandsDir, flatName);
|
||||||
await fs.ensureDir(path.dirname(commandPath));
|
await fs.ensureDir(path.dirname(commandPath));
|
||||||
|
|
|
||||||
|
|
@ -240,8 +240,8 @@ When running any workflow:
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Write workflow command artifacts using COLON format (for folder-based IDEs)
|
* Write workflow command artifacts using underscore format (Windows-compatible)
|
||||||
* Creates flat files like: bmad:bmm:correct-course.md
|
* Creates flat files like: bmad_bmm_correct-course.md
|
||||||
*
|
*
|
||||||
* @param {string} baseCommandsDir - Base commands directory for the IDE
|
* @param {string} baseCommandsDir - Base commands directory for the IDE
|
||||||
* @param {Array} artifacts - Workflow artifacts
|
* @param {Array} artifacts - Workflow artifacts
|
||||||
|
|
@ -252,7 +252,7 @@ When running any workflow:
|
||||||
|
|
||||||
for (const artifact of artifacts) {
|
for (const artifact of artifacts) {
|
||||||
if (artifact.type === 'workflow-command') {
|
if (artifact.type === 'workflow-command') {
|
||||||
// Convert relativePath to colon format: bmm/workflows/correct-course.md → bmad:bmm:correct-course.md
|
// Convert relativePath to underscore format: bmm/workflows/correct-course.md → bmad_bmm_correct-course.md
|
||||||
const flatName = toColonPath(artifact.relativePath);
|
const flatName = toColonPath(artifact.relativePath);
|
||||||
const commandPath = path.join(baseCommandsDir, flatName);
|
const commandPath = path.join(baseCommandsDir, flatName);
|
||||||
await fs.ensureDir(path.dirname(commandPath));
|
await fs.ensureDir(path.dirname(commandPath));
|
||||||
|
|
@ -265,8 +265,8 @@ When running any workflow:
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Write workflow command artifacts using DASH format (for flat IDEs)
|
* Write workflow command artifacts using underscore format (Windows-compatible)
|
||||||
* Creates flat files like: bmad-bmm-correct-course.md
|
* Creates flat files like: bmad_bmm_correct-course.md
|
||||||
*
|
*
|
||||||
* @param {string} baseCommandsDir - Base commands directory for the IDE
|
* @param {string} baseCommandsDir - Base commands directory for the IDE
|
||||||
* @param {Array} artifacts - Workflow artifacts
|
* @param {Array} artifacts - Workflow artifacts
|
||||||
|
|
@ -277,7 +277,7 @@ When running any workflow:
|
||||||
|
|
||||||
for (const artifact of artifacts) {
|
for (const artifact of artifacts) {
|
||||||
if (artifact.type === 'workflow-command') {
|
if (artifact.type === 'workflow-command') {
|
||||||
// Convert relativePath to dash format: bmm/workflows/correct-course.md → bmad-bmm-correct-course.md
|
// Convert relativePath to underscore format: bmm/workflows/correct-course.md → bmad_bmm_correct-course.md
|
||||||
const flatName = toDashPath(artifact.relativePath);
|
const flatName = toDashPath(artifact.relativePath);
|
||||||
const commandPath = path.join(baseCommandsDir, flatName);
|
const commandPath = path.join(baseCommandsDir, flatName);
|
||||||
await fs.ensureDir(path.dirname(commandPath));
|
await fs.ensureDir(path.dirname(commandPath));
|
||||||
|
|
|
||||||
|
|
@ -246,12 +246,12 @@ Part of the BMAD ${workflow.module.toUpperCase()} module.
|
||||||
const rulesPath = path.join(projectDir, this.configDir, this.rulesDir);
|
const rulesPath = path.join(projectDir, this.configDir, this.rulesDir);
|
||||||
|
|
||||||
if (await fs.pathExists(rulesPath)) {
|
if (await fs.pathExists(rulesPath)) {
|
||||||
// Only remove files that start with bmad- prefix
|
// Remove any bmad* files (cleans up old bmad- and bmad: formats)
|
||||||
const files = await fs.readdir(rulesPath);
|
const files = await fs.readdir(rulesPath);
|
||||||
let removed = 0;
|
let removed = 0;
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
if (file.startsWith('bmad-') && file.endsWith('.md')) {
|
if (file.startsWith('bmad') && file.endsWith('.md')) {
|
||||||
await fs.remove(path.join(rulesPath, file));
|
await fs.remove(path.join(rulesPath, file));
|
||||||
removed++;
|
removed++;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@ class ExternalModuleManager {
|
||||||
description: moduleConfig.description || '',
|
description: moduleConfig.description || '',
|
||||||
defaultSelected: moduleConfig.defaultSelected === true,
|
defaultSelected: moduleConfig.defaultSelected === true,
|
||||||
type: moduleConfig.type || 'community', // bmad-org or community
|
type: moduleConfig.type || 'community', // bmad-org or community
|
||||||
|
npmPackage: moduleConfig.npmPackage || null, // Include npm package name
|
||||||
isExternal: true,
|
isExternal: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -95,6 +96,7 @@ class ExternalModuleManager {
|
||||||
description: moduleConfig.description || '',
|
description: moduleConfig.description || '',
|
||||||
defaultSelected: moduleConfig.defaultSelected === true,
|
defaultSelected: moduleConfig.defaultSelected === true,
|
||||||
type: moduleConfig.type || 'community', // bmad-org or community
|
type: moduleConfig.type || 'community', // bmad-org or community
|
||||||
|
npmPackage: moduleConfig.npmPackage || null, // Include npm package name
|
||||||
isExternal: true,
|
isExternal: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -371,9 +371,9 @@ class ModuleManager {
|
||||||
const fetchSpinner = ora(`Fetching ${moduleInfo.name}...`).start();
|
const fetchSpinner = ora(`Fetching ${moduleInfo.name}...`).start();
|
||||||
try {
|
try {
|
||||||
const currentRef = execSync('git rev-parse HEAD', { cwd: moduleCacheDir, stdio: 'pipe' }).toString().trim();
|
const currentRef = execSync('git rev-parse HEAD', { cwd: moduleCacheDir, stdio: 'pipe' }).toString().trim();
|
||||||
execSync('git fetch --depth 1', { cwd: moduleCacheDir, stdio: 'pipe' });
|
// Fetch and reset to remote - works better with shallow clones than pull
|
||||||
execSync('git checkout -f', { cwd: moduleCacheDir, stdio: 'pipe' });
|
execSync('git fetch origin --depth 1', { cwd: moduleCacheDir, stdio: 'pipe' });
|
||||||
execSync('git pull --ff-only', { cwd: moduleCacheDir, stdio: 'pipe' });
|
execSync('git reset --hard origin/HEAD', { cwd: moduleCacheDir, stdio: 'pipe' });
|
||||||
const newRef = execSync('git rev-parse HEAD', { cwd: moduleCacheDir, stdio: 'pipe' }).toString().trim();
|
const newRef = execSync('git rev-parse HEAD', { cwd: moduleCacheDir, stdio: 'pipe' }).toString().trim();
|
||||||
|
|
||||||
fetchSpinner.succeed(`Fetched ${moduleInfo.name}`);
|
fetchSpinner.succeed(`Fetched ${moduleInfo.name}`);
|
||||||
|
|
@ -555,10 +555,23 @@ class ModuleManager {
|
||||||
await this.runModuleInstaller(moduleName, bmadDir, options);
|
await this.runModuleInstaller(moduleName, bmadDir, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Capture version info for manifest
|
||||||
|
const { Manifest } = require('../core/manifest');
|
||||||
|
const manifestObj = new Manifest();
|
||||||
|
const versionInfo = await manifestObj.getModuleVersionInfo(moduleName, bmadDir, sourcePath);
|
||||||
|
|
||||||
|
await manifestObj.addModule(bmadDir, moduleName, {
|
||||||
|
version: versionInfo.version,
|
||||||
|
source: versionInfo.source,
|
||||||
|
npmPackage: versionInfo.npmPackage,
|
||||||
|
repoUrl: versionInfo.repoUrl,
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
module: moduleName,
|
module: moduleName,
|
||||||
path: targetPath,
|
path: targetPath,
|
||||||
|
versionInfo,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1601,6 +1601,131 @@ class UI {
|
||||||
|
|
||||||
return proceed === 'proceed';
|
return proceed === 'proceed';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display module versions with update availability
|
||||||
|
* @param {Array} modules - Array of module info objects with version info
|
||||||
|
* @param {Array} availableUpdates - Array of available updates
|
||||||
|
*/
|
||||||
|
displayModuleVersions(modules, availableUpdates = []) {
|
||||||
|
console.log('');
|
||||||
|
console.log(chalk.cyan.bold('📦 Module Versions'));
|
||||||
|
console.log(chalk.gray('─'.repeat(80)));
|
||||||
|
|
||||||
|
// Group modules by source
|
||||||
|
const builtIn = modules.filter((m) => m.source === 'built-in');
|
||||||
|
const external = modules.filter((m) => m.source === 'external');
|
||||||
|
const custom = modules.filter((m) => m.source === 'custom');
|
||||||
|
const unknown = modules.filter((m) => m.source === 'unknown');
|
||||||
|
|
||||||
|
const displayGroup = (group, title) => {
|
||||||
|
if (group.length === 0) return;
|
||||||
|
|
||||||
|
console.log(chalk.yellow(`\n${title}`));
|
||||||
|
for (const module of group) {
|
||||||
|
const updateInfo = availableUpdates.find((u) => u.name === module.name);
|
||||||
|
const versionDisplay = module.version || chalk.gray('unknown');
|
||||||
|
|
||||||
|
if (updateInfo) {
|
||||||
|
console.log(
|
||||||
|
` ${chalk.cyan(module.name.padEnd(20))} ${versionDisplay} → ${chalk.green(updateInfo.latestVersion)} ${chalk.green('↑')}`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.log(` ${chalk.cyan(module.name.padEnd(20))} ${versionDisplay} ${chalk.gray('✓')}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
displayGroup(builtIn, 'Built-in Modules');
|
||||||
|
displayGroup(external, 'External Modules (Official)');
|
||||||
|
displayGroup(custom, 'Custom Modules');
|
||||||
|
displayGroup(unknown, 'Other Modules');
|
||||||
|
|
||||||
|
console.log('');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prompt user to select which modules to update
|
||||||
|
* @param {Array} availableUpdates - Array of available updates
|
||||||
|
* @returns {Array} Selected module names to update
|
||||||
|
*/
|
||||||
|
async promptUpdateSelection(availableUpdates) {
|
||||||
|
if (availableUpdates.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('');
|
||||||
|
console.log(chalk.cyan.bold('🔄 Available Updates'));
|
||||||
|
console.log(chalk.gray('─'.repeat(80)));
|
||||||
|
|
||||||
|
const choices = availableUpdates.map((update) => ({
|
||||||
|
name: `${update.name} ${chalk.dim(`(v${update.installedVersion} → v${update.latestVersion})`)}`,
|
||||||
|
value: update.name,
|
||||||
|
checked: true, // Default to selecting all updates
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Add "Update All" and "Cancel" options
|
||||||
|
const action = await prompts.select({
|
||||||
|
message: 'How would you like to proceed?',
|
||||||
|
choices: [
|
||||||
|
{ name: 'Update all available modules', value: 'all' },
|
||||||
|
{ name: 'Select specific modules to update', value: 'select' },
|
||||||
|
{ name: 'Skip updates for now', value: 'skip' },
|
||||||
|
],
|
||||||
|
default: 'all',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (action === 'all') {
|
||||||
|
return availableUpdates.map((u) => u.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action === 'skip') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow specific selection
|
||||||
|
const selected = await prompts.multiselect({
|
||||||
|
message: `Select modules to update ${chalk.dim('(↑/↓ navigates, SPACE toggles, ENTER to confirm)')}:`,
|
||||||
|
choices: choices,
|
||||||
|
required: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
return selected || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display status of all installed modules
|
||||||
|
* @param {Object} statusData - Status data with modules, installation info, and available updates
|
||||||
|
*/
|
||||||
|
displayStatus(statusData) {
|
||||||
|
const { installation, modules, availableUpdates, bmadDir } = statusData;
|
||||||
|
|
||||||
|
console.log('');
|
||||||
|
console.log(chalk.cyan.bold('📋 BMAD Status'));
|
||||||
|
console.log(chalk.gray('─'.repeat(80)));
|
||||||
|
|
||||||
|
// Installation info
|
||||||
|
console.log(chalk.yellow('\nInstallation'));
|
||||||
|
console.log(` ${chalk.gray('Version:'.padEnd(20))} ${installation.version || chalk.gray('unknown')}`);
|
||||||
|
console.log(` ${chalk.gray('Location:'.padEnd(20))} ${bmadDir}`);
|
||||||
|
console.log(` ${chalk.gray('Installed:'.padEnd(20))} ${new Date(installation.installDate).toLocaleDateString()}`);
|
||||||
|
console.log(
|
||||||
|
` ${chalk.gray('Last Updated:'.padEnd(20))} ${installation.lastUpdated ? new Date(installation.lastUpdated).toLocaleDateString() : chalk.gray('unknown')}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Module versions
|
||||||
|
this.displayModuleVersions(modules, availableUpdates);
|
||||||
|
|
||||||
|
// Update summary
|
||||||
|
if (availableUpdates.length > 0) {
|
||||||
|
console.log(chalk.yellow.bold(`\n⚠️ ${availableUpdates.length} update(s) available`));
|
||||||
|
console.log(chalk.dim(` Run 'bmad install' and select "Quick Update" to update`));
|
||||||
|
} else {
|
||||||
|
console.log(chalk.green.bold('\n✓ All modules are up to date'));
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { UI };
|
module.exports = { UI };
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue