bmad-method 6.0.0-alpha.9 → 6.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.augment/code_review_guidelines.yaml +271 -0
- package/.claude/skills/bmad-os-changelog-social/SKILL.md +178 -0
- package/.claude/skills/bmad-os-changelog-social/examples/discord-example.md +53 -0
- package/.claude/skills/bmad-os-changelog-social/examples/linkedin-example.md +49 -0
- package/.claude/skills/bmad-os-changelog-social/examples/twitter-example.md +55 -0
- package/.claude/skills/bmad-os-diataxis-style-fix/SKILL.md +7 -0
- package/.claude/skills/bmad-os-diataxis-style-fix/prompts/instructions.md +229 -0
- package/.claude/skills/bmad-os-draft-changelog/SKILL.md +7 -0
- package/.claude/skills/bmad-os-draft-changelog/prompts/instructions.md +82 -0
- package/.claude/skills/bmad-os-gh-triage/README.md +14 -0
- package/.claude/skills/bmad-os-gh-triage/SKILL.md +12 -0
- package/.claude/skills/bmad-os-gh-triage/prompts/agent-prompt.md +60 -0
- package/.claude/skills/bmad-os-gh-triage/prompts/instructions.md +74 -0
- package/.claude/skills/bmad-os-release-module/README.md +24 -0
- package/.claude/skills/bmad-os-release-module/SKILL.md +7 -0
- package/.claude/skills/bmad-os-release-module/prompts/instructions.md +53 -0
- package/.coderabbit.yaml +85 -0
- package/.github/CODE_OF_CONDUCT.md +128 -0
- package/.github/ISSUE_TEMPLATE/bug-report.yaml +124 -0
- package/.github/ISSUE_TEMPLATE/config.yaml +5 -2
- package/.github/ISSUE_TEMPLATE/documentation.yaml +55 -0
- package/.github/ISSUE_TEMPLATE/feature-request.md +22 -0
- package/.github/ISSUE_TEMPLATE/issue.md +32 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +13 -0
- package/.github/scripts/discord-helpers.sh +34 -0
- package/.github/workflows/coderabbit-review.yaml +22 -0
- package/.github/workflows/discord.yaml +82 -8
- package/.github/workflows/docs.yaml +64 -0
- package/.github/workflows/quality.yaml +40 -2
- package/.husky/pre-commit +13 -0
- package/.markdownlint-cli2.yaml +41 -0
- package/.prettierignore +7 -0
- package/.vscode/settings.json +4 -3
- package/CHANGELOG.md +1074 -674
- package/CNAME +1 -0
- package/CONTRIBUTING.md +97 -189
- package/CONTRIBUTORS.md +32 -0
- package/LICENSE +7 -3
- package/README.md +51 -396
- package/SECURITY.md +85 -0
- package/TRADEMARK.md +55 -0
- package/Wordmark.png +0 -0
- package/banner-bmad-method.png +0 -0
- package/docs/404.md +9 -0
- package/docs/_STYLE_GUIDE.md +370 -0
- package/docs/explanation/advanced-elicitation.md +49 -0
- package/docs/explanation/adversarial-review.md +59 -0
- package/docs/explanation/brainstorming.md +33 -0
- package/docs/explanation/established-projects-faq.md +50 -0
- package/docs/explanation/party-mode.md +59 -0
- package/docs/explanation/preventing-agent-conflicts.md +112 -0
- package/docs/explanation/project-context.md +157 -0
- package/docs/explanation/quick-flow.md +73 -0
- package/docs/explanation/why-solutioning-matters.md +77 -0
- package/docs/how-to/customize-bmad.md +172 -0
- package/docs/how-to/established-projects.md +105 -0
- package/docs/how-to/get-answers-about-bmad.md +103 -0
- package/docs/how-to/install-bmad.md +88 -0
- package/docs/how-to/non-interactive-installation.md +171 -0
- package/docs/how-to/project-context.md +136 -0
- package/docs/how-to/quick-fixes.md +123 -0
- package/docs/how-to/shard-large-documents.md +78 -0
- package/docs/how-to/upgrade-to-v6.md +97 -0
- package/docs/index.md +33 -206
- package/docs/reference/agents.md +28 -0
- package/docs/reference/commands.md +131 -0
- package/docs/reference/modules.md +76 -0
- package/docs/reference/testing.md +106 -0
- package/docs/reference/workflow-map.md +122 -0
- package/docs/tutorials/getting-started.md +219 -0
- package/eslint.config.mjs +26 -16
- package/package.json +35 -28
- package/src/bmm/agents/analyst.agent.yaml +43 -0
- package/src/bmm/agents/architect.agent.yaml +29 -0
- package/src/bmm/agents/dev.agent.yaml +38 -0
- package/src/bmm/agents/pm.agent.yaml +44 -0
- package/src/bmm/agents/qa.agent.yaml +58 -0
- package/src/bmm/agents/quick-flow-solo-dev.agent.yaml +32 -0
- package/src/bmm/agents/sm.agent.yaml +37 -0
- package/src/bmm/agents/tech-writer/tech-writer-sidecar/documentation-standards.md +224 -0
- package/src/bmm/agents/tech-writer/tech-writer.agent.yaml +46 -0
- package/src/bmm/agents/ux-designer.agent.yaml +27 -0
- package/src/bmm/data/project-context-template.md +26 -0
- package/src/bmm/module-help.csv +31 -0
- package/src/bmm/module.yaml +50 -0
- package/src/bmm/teams/default-party.csv +20 -0
- package/src/bmm/workflows/1-analysis/create-product-brief/product-brief.template.md +10 -0
- package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-01-init.md +177 -0
- package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-01b-continue.md +161 -0
- package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-02-vision.md +199 -0
- package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-03-users.md +202 -0
- package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-04-metrics.md +205 -0
- package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-05-scope.md +219 -0
- package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-06-complete.md +162 -0
- package/src/bmm/workflows/1-analysis/create-product-brief/workflow.md +57 -0
- package/src/bmm/workflows/1-analysis/research/domain-steps/step-01-init.md +137 -0
- package/src/bmm/workflows/1-analysis/research/domain-steps/step-02-domain-analysis.md +229 -0
- package/src/bmm/workflows/1-analysis/research/domain-steps/step-03-competitive-landscape.md +238 -0
- package/src/bmm/workflows/1-analysis/research/domain-steps/step-04-regulatory-focus.md +206 -0
- package/src/bmm/workflows/1-analysis/research/domain-steps/step-05-technical-trends.md +234 -0
- package/src/bmm/workflows/1-analysis/research/domain-steps/step-06-research-synthesis.md +443 -0
- package/src/bmm/workflows/1-analysis/research/market-steps/step-01-init.md +182 -0
- package/src/bmm/workflows/1-analysis/research/market-steps/step-02-customer-behavior.md +237 -0
- package/src/bmm/workflows/1-analysis/research/market-steps/step-03-customer-pain-points.md +249 -0
- package/src/bmm/workflows/1-analysis/research/market-steps/step-04-customer-decisions.md +259 -0
- package/src/bmm/workflows/1-analysis/research/market-steps/step-05-competitive-analysis.md +177 -0
- package/src/bmm/workflows/1-analysis/research/market-steps/step-06-research-completion.md +475 -0
- package/src/bmm/workflows/1-analysis/research/research.template.md +29 -0
- package/src/bmm/workflows/1-analysis/research/technical-steps/step-01-init.md +137 -0
- package/src/bmm/workflows/1-analysis/research/technical-steps/step-02-technical-overview.md +239 -0
- package/src/bmm/workflows/1-analysis/research/technical-steps/step-03-integration-patterns.md +248 -0
- package/src/bmm/workflows/1-analysis/research/technical-steps/step-04-architectural-patterns.md +202 -0
- package/src/bmm/workflows/1-analysis/research/technical-steps/step-05-implementation-research.md +233 -0
- package/src/bmm/workflows/1-analysis/research/technical-steps/step-06-research-synthesis.md +486 -0
- package/src/bmm/workflows/1-analysis/research/workflow-domain-research.md +54 -0
- package/src/bmm/workflows/1-analysis/research/workflow-market-research.md +54 -0
- package/src/bmm/workflows/1-analysis/research/workflow-technical-research.md +54 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/data/domain-complexity.csv +15 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/data/prd-purpose.md +197 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/data/project-types.csv +11 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-01-init.md +191 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-01b-continue.md +153 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-02-discovery.md +224 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-02b-vision.md +154 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-02c-executive-summary.md +170 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-03-success.md +226 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-04-journeys.md +213 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-05-domain.md +207 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-06-innovation.md +226 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-07-project-type.md +237 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-08-scoping.md +228 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-09-functional.md +231 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-10-nonfunctional.md +242 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-11-polish.md +217 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-12-complete.md +124 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-e/step-e-01-discovery.md +247 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-e/step-e-01b-legacy-conversion.md +208 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-e/step-e-02-review.md +249 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-e/step-e-03-edit.md +253 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-e/step-e-04-complete.md +168 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-01-discovery.md +226 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-02-format-detection.md +191 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-02b-parity-check.md +209 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-03-density-validation.md +174 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-04-brief-coverage-validation.md +214 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-05-measurability-validation.md +228 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-06-traceability-validation.md +217 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-07-implementation-leakage-validation.md +205 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-08-domain-compliance-validation.md +243 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-09-project-type-validation.md +263 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-10-smart-validation.md +209 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-11-holistic-quality-validation.md +264 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-12-completeness-validation.md +242 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-13-report-complete.md +231 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/templates/prd-template.md +10 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/workflow-create-prd.md +63 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/workflow-edit-prd.md +65 -0
- package/src/bmm/workflows/2-plan-workflows/create-prd/workflow-validate-prd.md +63 -0
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-01-init.md +135 -0
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-01b-continue.md +127 -0
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-02-discovery.md +190 -0
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-03-core-experience.md +216 -0
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-04-emotional-response.md +219 -0
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-05-inspiration.md +234 -0
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-06-design-system.md +252 -0
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-07-defining-experience.md +254 -0
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-08-visual-foundation.md +224 -0
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-09-design-directions.md +224 -0
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-10-user-journeys.md +241 -0
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-11-component-strategy.md +248 -0
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-12-ux-patterns.md +237 -0
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-13-responsive-accessibility.md +264 -0
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-14-complete.md +171 -0
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/ux-design-template.md +13 -0
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/workflow.md +42 -0
- package/src/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-01-document-discovery.md +184 -0
- package/src/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-02-prd-analysis.md +172 -0
- package/src/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-03-epic-coverage-validation.md +173 -0
- package/src/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-04-ux-alignment.md +133 -0
- package/src/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-05-epic-quality-review.md +245 -0
- package/src/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-06-final-assessment.md +129 -0
- package/src/bmm/workflows/3-solutioning/check-implementation-readiness/templates/readiness-report-template.md +4 -0
- package/src/bmm/workflows/3-solutioning/check-implementation-readiness/workflow.md +54 -0
- package/src/bmm/workflows/3-solutioning/create-architecture/architecture-decision-template.md +12 -0
- package/src/bmm/workflows/3-solutioning/create-architecture/data/domain-complexity.csv +13 -0
- package/src/bmm/workflows/3-solutioning/create-architecture/data/project-types.csv +7 -0
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-01-init.md +153 -0
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-01b-continue.md +164 -0
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-02-context.md +224 -0
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-03-starter.md +331 -0
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-04-decisions.md +318 -0
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-05-patterns.md +359 -0
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-06-structure.md +379 -0
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-07-validation.md +359 -0
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-08-complete.md +76 -0
- package/src/bmm/workflows/3-solutioning/create-architecture/workflow.md +49 -0
- package/src/bmm/workflows/3-solutioning/create-epics-and-stories/steps/step-01-validate-prerequisites.md +259 -0
- package/src/bmm/workflows/3-solutioning/create-epics-and-stories/steps/step-02-design-epics.md +233 -0
- package/src/bmm/workflows/3-solutioning/create-epics-and-stories/steps/step-03-create-stories.md +272 -0
- package/src/bmm/workflows/3-solutioning/create-epics-and-stories/steps/step-04-final-validation.md +149 -0
- package/src/bmm/workflows/3-solutioning/create-epics-and-stories/templates/epics-template.md +57 -0
- package/src/bmm/workflows/3-solutioning/create-epics-and-stories/workflow.md +58 -0
- package/src/bmm/workflows/4-implementation/code-review/checklist.md +23 -0
- package/src/bmm/workflows/4-implementation/code-review/instructions.xml +227 -0
- package/src/bmm/workflows/4-implementation/code-review/workflow.yaml +44 -0
- package/src/bmm/workflows/4-implementation/correct-course/checklist.md +288 -0
- package/src/bmm/workflows/4-implementation/correct-course/instructions.md +207 -0
- package/src/bmm/workflows/4-implementation/correct-course/workflow.yaml +54 -0
- package/src/bmm/workflows/4-implementation/create-story/checklist.md +358 -0
- package/src/bmm/workflows/4-implementation/create-story/instructions.xml +346 -0
- package/src/bmm/workflows/4-implementation/create-story/template.md +49 -0
- package/src/bmm/workflows/4-implementation/create-story/workflow.yaml +53 -0
- package/src/bmm/workflows/4-implementation/dev-story/checklist.md +80 -0
- package/src/bmm/workflows/4-implementation/dev-story/instructions.xml +410 -0
- package/src/bmm/workflows/4-implementation/dev-story/workflow.yaml +21 -0
- package/src/bmm/workflows/4-implementation/retrospective/instructions.md +1444 -0
- package/src/bmm/workflows/4-implementation/retrospective/workflow.yaml +53 -0
- package/src/bmm/workflows/4-implementation/sprint-planning/instructions.md +226 -0
- package/src/bmm/workflows/4-implementation/sprint-planning/sprint-status-template.yaml +55 -0
- package/src/bmm/workflows/4-implementation/sprint-planning/workflow.yaml +47 -0
- package/src/bmm/workflows/4-implementation/sprint-status/instructions.md +230 -0
- package/src/bmm/workflows/4-implementation/sprint-status/workflow.yaml +25 -0
- package/src/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-01-mode-detection.md +174 -0
- package/src/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-02-context-gathering.md +118 -0
- package/src/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-03-execute.md +111 -0
- package/src/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-04-self-check.md +111 -0
- package/src/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-05-adversarial-review.md +104 -0
- package/src/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-06-resolve-findings.md +146 -0
- package/src/bmm/workflows/bmad-quick-flow/quick-dev/workflow.md +50 -0
- package/src/bmm/workflows/bmad-quick-flow/quick-spec/steps/step-01-understand.md +191 -0
- package/src/bmm/workflows/bmad-quick-flow/quick-spec/steps/step-02-investigate.md +144 -0
- package/src/bmm/workflows/bmad-quick-flow/quick-spec/steps/step-03-generate.md +127 -0
- package/src/bmm/workflows/bmad-quick-flow/quick-spec/steps/step-04-review.md +200 -0
- package/src/bmm/workflows/bmad-quick-flow/quick-spec/tech-spec-template.md +74 -0
- package/src/bmm/workflows/bmad-quick-flow/quick-spec/workflow.md +79 -0
- package/src/bmm/workflows/document-project/checklist.md +245 -0
- package/src/bmm/workflows/document-project/instructions.md +130 -0
- package/src/bmm/workflows/document-project/templates/project-scan-report-schema.json +160 -0
- package/src/bmm/workflows/document-project/workflow.yaml +22 -0
- package/src/bmm/workflows/document-project/workflows/deep-dive-instructions.md +298 -0
- package/src/bmm/workflows/document-project/workflows/deep-dive.yaml +31 -0
- package/src/bmm/workflows/document-project/workflows/full-scan-instructions.md +1106 -0
- package/src/bmm/workflows/document-project/workflows/full-scan.yaml +31 -0
- package/src/bmm/workflows/generate-project-context/project-context-template.md +21 -0
- package/src/bmm/workflows/generate-project-context/steps/step-01-discover.md +184 -0
- package/src/bmm/workflows/generate-project-context/steps/step-02-generate.md +318 -0
- package/src/bmm/workflows/generate-project-context/steps/step-03-complete.md +278 -0
- package/src/bmm/workflows/generate-project-context/workflow.md +49 -0
- package/src/bmm/workflows/qa/automate/checklist.md +33 -0
- package/src/bmm/workflows/qa/automate/instructions.md +110 -0
- package/src/bmm/workflows/qa/automate/workflow.yaml +44 -0
- package/src/core/agents/bmad-master.agent.yaml +12 -21
- package/src/core/module-help.csv +9 -0
- package/src/core/module.yaml +25 -0
- package/src/core/tasks/editorial-review-prose.xml +102 -0
- package/src/core/tasks/editorial-review-structure.xml +209 -0
- package/src/core/tasks/help.md +85 -0
- package/src/core/tasks/index-docs.xml +2 -2
- package/src/core/tasks/review-adversarial-general.xml +48 -0
- package/src/core/tasks/shard-doc.xml +108 -0
- package/src/core/tasks/workflow.xml +39 -74
- package/src/core/workflows/advanced-elicitation/methods.csv +51 -0
- package/src/core/workflows/advanced-elicitation/workflow.xml +117 -0
- package/src/core/workflows/brainstorming/brain-methods.csv +62 -36
- package/src/core/workflows/brainstorming/steps/step-01-session-setup.md +197 -0
- package/src/core/workflows/brainstorming/steps/step-01b-continue.md +122 -0
- package/src/core/workflows/brainstorming/steps/step-02a-user-selected.md +225 -0
- package/src/core/workflows/brainstorming/steps/step-02b-ai-recommended.md +237 -0
- package/src/core/workflows/brainstorming/steps/step-02c-random-selection.md +209 -0
- package/src/core/workflows/brainstorming/steps/step-02d-progressive-flow.md +264 -0
- package/src/core/workflows/brainstorming/steps/step-03-technique-execution.md +399 -0
- package/src/core/workflows/brainstorming/steps/step-04-idea-organization.md +303 -0
- package/src/core/workflows/brainstorming/template.md +13 -104
- package/src/core/workflows/brainstorming/workflow.md +58 -0
- package/src/core/workflows/party-mode/steps/step-01-agent-loading.md +138 -0
- package/src/core/workflows/party-mode/steps/step-02-discussion-orchestration.md +187 -0
- package/src/core/workflows/party-mode/steps/step-03-graceful-exit.md +168 -0
- package/src/core/workflows/party-mode/workflow.md +194 -0
- package/src/utility/agent-components/activation-rules.txt +6 -0
- package/src/utility/agent-components/activation-steps.txt +14 -0
- package/src/utility/agent-components/agent-command-header.md +1 -0
- package/src/utility/agent-components/agent.customize.template.yaml +41 -0
- package/src/utility/agent-components/handler-action.txt +4 -0
- package/src/utility/agent-components/handler-exec.txt +6 -0
- package/src/utility/agent-components/handler-multi.txt +14 -0
- package/src/utility/agent-components/handler-tmpl.txt +5 -0
- package/src/utility/agent-components/handler-validate-workflow.txt +7 -0
- package/src/utility/agent-components/handler-workflow.txt +10 -0
- package/src/utility/agent-components/menu-handlers.txt +6 -0
- package/test/README.md +4 -4
- package/test/adversarial-review-tests/README.md +56 -0
- package/test/adversarial-review-tests/sample-content.md +46 -0
- package/test/adversarial-review-tests/test-cases.yaml +103 -0
- package/test/fixtures/agent-schema/invalid/critical-actions/actions-as-string.agent.yaml +1 -0
- package/test/fixtures/agent-schema/invalid/critical-actions/empty-string-in-actions.agent.yaml +1 -0
- package/test/fixtures/agent-schema/invalid/menu/empty-menu.agent.yaml +1 -0
- package/test/fixtures/agent-schema/invalid/menu/missing-menu.agent.yaml +1 -0
- package/test/fixtures/agent-schema/invalid/menu-commands/empty-command-target.agent.yaml +1 -0
- package/test/fixtures/agent-schema/invalid/menu-commands/no-command-target.agent.yaml +1 -0
- package/test/fixtures/agent-schema/invalid/menu-triggers/camel-case.agent.yaml +1 -0
- package/test/fixtures/agent-schema/invalid/menu-triggers/compound-invalid-format.agent.yaml +25 -0
- package/test/fixtures/agent-schema/invalid/menu-triggers/compound-mismatched-kebab.agent.yaml +25 -0
- package/test/fixtures/agent-schema/invalid/menu-triggers/duplicate-triggers.agent.yaml +1 -0
- package/test/fixtures/agent-schema/invalid/menu-triggers/empty-trigger.agent.yaml +1 -0
- package/test/fixtures/agent-schema/invalid/menu-triggers/leading-asterisk.agent.yaml +1 -0
- package/test/fixtures/agent-schema/invalid/menu-triggers/snake-case.agent.yaml +1 -0
- package/test/fixtures/agent-schema/invalid/menu-triggers/trigger-with-spaces.agent.yaml +1 -0
- package/test/fixtures/agent-schema/invalid/metadata/empty-module-string.agent.yaml +1 -1
- package/test/fixtures/agent-schema/invalid/metadata/extra-metadata-fields.agent.yaml +1 -0
- package/test/fixtures/agent-schema/invalid/persona/empty-principles-array.agent.yaml +1 -0
- package/test/fixtures/agent-schema/invalid/persona/empty-string-in-principles.agent.yaml +1 -0
- package/test/fixtures/agent-schema/invalid/persona/extra-persona-fields.agent.yaml +1 -0
- package/test/fixtures/agent-schema/invalid/persona/missing-role.agent.yaml +1 -0
- package/test/fixtures/agent-schema/invalid/prompts/empty-content.agent.yaml +1 -0
- package/test/fixtures/agent-schema/invalid/prompts/extra-prompt-fields.agent.yaml +1 -0
- package/test/fixtures/agent-schema/invalid/prompts/missing-content.agent.yaml +1 -0
- package/test/fixtures/agent-schema/invalid/prompts/missing-id.agent.yaml +1 -0
- package/test/fixtures/agent-schema/invalid/top-level/extra-top-level-keys.agent.yaml +1 -0
- package/test/fixtures/agent-schema/valid/critical-actions/empty-critical-actions.agent.yaml +1 -0
- package/test/fixtures/agent-schema/valid/critical-actions/no-critical-actions.agent.yaml +1 -0
- package/test/fixtures/agent-schema/valid/critical-actions/valid-critical-actions.agent.yaml +1 -0
- package/test/fixtures/agent-schema/valid/menu/multiple-menu-items.agent.yaml +1 -0
- package/test/fixtures/agent-schema/valid/menu/single-menu-item.agent.yaml +1 -0
- package/test/fixtures/agent-schema/valid/menu-commands/all-command-types.agent.yaml +2 -3
- package/test/fixtures/agent-schema/valid/menu-commands/multiple-commands.agent.yaml +1 -0
- package/test/fixtures/agent-schema/valid/menu-triggers/compound-triggers.agent.yaml +31 -0
- package/test/fixtures/agent-schema/valid/menu-triggers/kebab-case-triggers.agent.yaml +2 -1
- package/test/fixtures/agent-schema/valid/metadata/core-agent-with-module.agent.yaml +24 -0
- package/test/fixtures/agent-schema/valid/metadata/empty-module-name-in-path.agent.yaml +1 -0
- package/test/fixtures/agent-schema/valid/metadata/malformed-path-treated-as-core.agent.yaml +2 -1
- package/test/fixtures/agent-schema/valid/metadata/module-agent-correct.agent.yaml +2 -1
- package/test/fixtures/agent-schema/valid/metadata/module-agent-missing-module.agent.yaml +23 -0
- package/test/fixtures/agent-schema/valid/metadata/wrong-module-value.agent.yaml +24 -0
- package/test/fixtures/agent-schema/valid/persona/complete-persona.agent.yaml +1 -0
- package/test/fixtures/agent-schema/valid/prompts/empty-prompts.agent.yaml +1 -0
- package/test/fixtures/agent-schema/valid/prompts/no-prompts.agent.yaml +1 -0
- package/test/fixtures/agent-schema/valid/prompts/valid-prompts-minimal.agent.yaml +1 -0
- package/test/fixtures/agent-schema/valid/prompts/valid-prompts-with-description.agent.yaml +1 -0
- package/test/fixtures/agent-schema/valid/top-level/minimal-core-agent.agent.yaml +1 -0
- package/test/fixtures/file-refs-csv/invalid/all-empty-workflow.csv +3 -0
- package/test/fixtures/file-refs-csv/invalid/empty-data.csv +1 -0
- package/test/fixtures/file-refs-csv/invalid/no-workflow-column.csv +3 -0
- package/test/fixtures/file-refs-csv/invalid/unresolvable-vars.csv +3 -0
- package/test/fixtures/file-refs-csv/valid/bmm-style.csv +3 -0
- package/test/fixtures/file-refs-csv/valid/core-style.csv +3 -0
- package/test/fixtures/file-refs-csv/valid/minimal.csv +2 -0
- package/test/test-agent-schema.js +4 -4
- package/test/test-file-refs-csv.js +133 -0
- package/test/test-installation-components.js +10 -12
- package/test/test-rehype-plugins.mjs +1050 -0
- package/test/unit-test-schema.js +2 -2
- package/tools/build-docs.mjs +463 -0
- package/tools/cli/README.md +41 -589
- package/tools/cli/bmad-cli.js +67 -1
- package/tools/cli/commands/install.js +42 -34
- package/tools/cli/commands/status.js +44 -26
- package/tools/cli/commands/uninstall.js +146 -23
- package/tools/cli/external-official-modules.yaml +53 -0
- package/tools/cli/installers/install-messages.yaml +39 -0
- package/tools/cli/installers/lib/core/config-collector.js +589 -95
- package/tools/cli/installers/lib/core/custom-module-cache.js +260 -0
- package/tools/cli/installers/lib/core/dependency-resolver.js +57 -39
- package/tools/cli/installers/lib/core/detector.js +19 -125
- package/tools/cli/installers/lib/core/ide-config-manager.js +12 -9
- package/tools/cli/installers/lib/core/installer.js +2075 -1161
- package/tools/cli/installers/lib/core/manifest-generator.js +494 -103
- package/tools/cli/installers/lib/core/manifest.js +544 -46
- package/tools/cli/installers/lib/custom/handler.js +358 -0
- package/tools/cli/installers/lib/ide/_base-ide.js +83 -44
- package/tools/cli/installers/lib/ide/_config-driven.js +560 -0
- package/tools/cli/installers/lib/ide/codex.js +277 -54
- package/tools/cli/installers/lib/ide/github-copilot.js +607 -209
- package/tools/cli/installers/lib/ide/kilo.js +169 -75
- package/tools/cli/installers/lib/ide/manager.js +179 -48
- package/tools/cli/installers/lib/ide/platform-codes.js +100 -0
- package/tools/cli/installers/lib/ide/platform-codes.yaml +227 -0
- package/tools/cli/installers/lib/ide/shared/agent-command-generator.js +95 -5
- package/tools/cli/installers/lib/ide/shared/bmad-artifacts.js +51 -20
- package/tools/cli/installers/lib/ide/shared/module-injections.js +6 -3
- package/tools/cli/installers/lib/ide/shared/path-utils.js +299 -0
- package/tools/cli/installers/lib/ide/shared/task-tool-command-generator.js +262 -14
- package/tools/cli/installers/lib/ide/shared/workflow-command-generator.js +108 -27
- package/tools/cli/installers/lib/ide/templates/agent-command-template.md +2 -1
- package/tools/cli/installers/lib/ide/templates/combined/antigravity.md +8 -0
- package/tools/cli/installers/lib/ide/templates/combined/default-agent.md +16 -0
- package/tools/cli/installers/lib/ide/templates/combined/default-task.md +10 -0
- package/tools/cli/installers/lib/ide/templates/combined/default-tool.md +10 -0
- package/tools/cli/installers/lib/ide/templates/combined/default-workflow-yaml.md +15 -0
- package/tools/cli/installers/lib/ide/templates/combined/default-workflow.md +7 -0
- package/tools/cli/installers/lib/ide/templates/combined/gemini-agent.toml +14 -0
- package/tools/cli/installers/lib/ide/templates/combined/gemini-task.toml +11 -0
- package/tools/cli/installers/lib/ide/templates/combined/gemini-tool.toml +11 -0
- package/tools/cli/installers/lib/ide/templates/combined/gemini-workflow-yaml.toml +16 -0
- package/tools/cli/installers/lib/ide/templates/combined/gemini-workflow.toml +14 -0
- package/tools/cli/installers/lib/ide/templates/combined/kiro-agent.md +16 -0
- package/tools/cli/installers/lib/ide/templates/combined/kiro-task.md +9 -0
- package/tools/cli/installers/lib/ide/templates/combined/kiro-tool.md +9 -0
- package/tools/cli/installers/lib/ide/templates/combined/kiro-workflow-yaml.md +15 -0
- package/tools/cli/installers/lib/ide/templates/combined/kiro-workflow.md +7 -0
- package/tools/cli/installers/lib/ide/templates/combined/opencode-agent.md +15 -0
- package/tools/cli/installers/lib/ide/templates/combined/opencode-task.md +14 -0
- package/tools/cli/installers/lib/ide/templates/combined/opencode-tool.md +14 -0
- package/tools/cli/installers/lib/ide/templates/combined/opencode-workflow-yaml.md +17 -0
- package/tools/cli/installers/lib/ide/templates/combined/opencode-workflow.md +17 -0
- package/tools/cli/installers/lib/ide/templates/combined/rovodev.md +9 -0
- package/tools/cli/installers/lib/ide/templates/combined/trae.md +9 -0
- package/tools/cli/installers/lib/ide/templates/combined/windsurf-workflow.md +10 -0
- package/tools/cli/installers/lib/ide/templates/workflow-command-template.md +2 -1
- package/tools/cli/installers/lib/ide/templates/workflow-commander.md +6 -0
- package/tools/cli/installers/lib/message-loader.js +83 -0
- package/tools/cli/installers/lib/modules/external-manager.js +136 -0
- package/tools/cli/installers/lib/modules/manager.js +931 -207
- package/tools/cli/lib/activation-builder.js +13 -16
- package/tools/cli/lib/agent/compiler.js +525 -0
- package/tools/cli/lib/agent/installer.js +680 -0
- package/tools/cli/lib/agent/template-engine.js +152 -0
- package/tools/cli/lib/agent-analyzer.js +48 -20
- package/tools/cli/lib/agent-party-generator.js +5 -17
- package/tools/cli/lib/cli-utils.js +76 -104
- package/tools/cli/lib/config.js +4 -3
- package/tools/cli/lib/platform-codes.js +2 -2
- package/tools/cli/lib/project-root.js +6 -0
- package/tools/cli/lib/prompts.js +809 -0
- package/tools/cli/lib/ui.js +1600 -272
- package/tools/cli/lib/xml-handler.js +4 -55
- package/tools/cli/lib/yaml-format.js +4 -6
- package/tools/cli/lib/yaml-xml-builder.js +183 -61
- package/tools/docs/_prompt-external-modules-page.md +59 -0
- package/tools/docs/fix-refs.md +91 -0
- package/tools/fix-doc-links.js +285 -0
- package/tools/lib/xml-utils.js +13 -0
- package/tools/maintainer/review-pr-README.md +55 -0
- package/tools/maintainer/review-pr.md +242 -0
- package/tools/migrate-custom-module-paths.js +124 -0
- package/tools/platform-codes.yaml +24 -0
- package/tools/schema/agent.js +306 -53
- package/tools/validate-agent-schema.js +3 -3
- package/tools/validate-doc-links.js +393 -0
- package/tools/validate-file-refs.js +554 -0
- package/tools/validate-svg-changes.sh +356 -0
- package/website/README.md +75 -0
- package/website/astro.config.mjs +135 -0
- package/website/public/favicon.ico +0 -0
- package/website/public/img/bmad-dark.png +0 -0
- package/website/public/img/bmad-light.png +0 -0
- package/website/public/workflow-map-diagram.html +361 -0
- package/website/src/components/Banner.astro +62 -0
- package/website/src/components/Header.astro +96 -0
- package/website/src/components/MobileMenuFooter.astro +33 -0
- package/website/src/content/config.ts +6 -0
- package/website/src/lib/site-url.mjs +25 -0
- package/website/src/pages/404.astro +11 -0
- package/website/src/pages/robots.txt.ts +48 -0
- package/website/src/rehype-base-paths.js +112 -0
- package/website/src/rehype-markdown-links.js +111 -0
- package/website/src/styles/custom.css +509 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -32
- package/.github/ISSUE_TEMPLATE/idea_submission.md +0 -109
- package/.github/workflows/bundle-latest.yaml +0 -277
- package/.github/workflows/manual-release.yaml +0 -212
- package/docs/BUNDLE_DISTRIBUTION_SETUP.md +0 -95
- package/docs/agent-customization-guide.md +0 -208
- package/docs/document-sharding-guide.md +0 -447
- package/docs/ide-info/auggie.md +0 -31
- package/docs/ide-info/claude-code.md +0 -25
- package/docs/ide-info/cline.md +0 -31
- package/docs/ide-info/codex.md +0 -21
- package/docs/ide-info/crush.md +0 -30
- package/docs/ide-info/cursor.md +0 -25
- package/docs/ide-info/gemini.md +0 -25
- package/docs/ide-info/github-copilot.md +0 -26
- package/docs/ide-info/iflow.md +0 -33
- package/docs/ide-info/kilo.md +0 -24
- package/docs/ide-info/opencode.md +0 -24
- package/docs/ide-info/qwen.md +0 -25
- package/docs/ide-info/roo.md +0 -27
- package/docs/ide-info/trae.md +0 -25
- package/docs/ide-info/windsurf.md +0 -22
- package/docs/installers-bundlers/ide-injections.md +0 -186
- package/docs/installers-bundlers/installers-modules-platforms-reference.md +0 -388
- package/docs/installers-bundlers/web-bundler-usage.md +0 -54
- package/docs/v4-to-v6-upgrade.md +0 -227
- package/docs/web-bundles-gemini-gpt-guide.md +0 -473
- package/src/core/_module-installer/install-config.yaml +0 -35
- package/src/core/_module-installer/installer.js +0 -60
- package/src/core/agents/bmad-web-orchestrator.agent.xml +0 -113
- package/src/core/tasks/adv-elicit-methods.csv +0 -39
- package/src/core/tasks/advanced-elicitation.xml +0 -106
- package/src/core/tasks/validate-workflow.xml +0 -89
- package/src/core/tools/shard-doc.xml +0 -109
- package/src/core/workflows/brainstorming/README.md +0 -261
- package/src/core/workflows/brainstorming/instructions.md +0 -315
- package/src/core/workflows/brainstorming/workflow.yaml +0 -43
- package/src/core/workflows/party-mode/instructions.md +0 -183
- package/src/core/workflows/party-mode/workflow.yaml +0 -27
- package/src/modules/bmb/README.md +0 -194
- package/src/modules/bmb/_module-installer/install-config.yaml +0 -31
- package/src/modules/bmb/agents/bmad-builder.agent.yaml +0 -59
- package/src/modules/bmb/workflows/audit-workflow/checklist.md +0 -142
- package/src/modules/bmb/workflows/audit-workflow/instructions.md +0 -341
- package/src/modules/bmb/workflows/audit-workflow/template.md +0 -118
- package/src/modules/bmb/workflows/audit-workflow/workflow.yaml +0 -25
- package/src/modules/bmb/workflows/convert-legacy/README.md +0 -262
- package/src/modules/bmb/workflows/convert-legacy/checklist.md +0 -205
- package/src/modules/bmb/workflows/convert-legacy/instructions.md +0 -377
- package/src/modules/bmb/workflows/convert-legacy/workflow.yaml +0 -34
- package/src/modules/bmb/workflows/create-agent/README.md +0 -203
- package/src/modules/bmb/workflows/create-agent/agent-architecture.md +0 -415
- package/src/modules/bmb/workflows/create-agent/agent-command-patterns.md +0 -759
- package/src/modules/bmb/workflows/create-agent/agent-types.md +0 -292
- package/src/modules/bmb/workflows/create-agent/brainstorm-context.md +0 -174
- package/src/modules/bmb/workflows/create-agent/checklist.md +0 -62
- package/src/modules/bmb/workflows/create-agent/communication-styles.md +0 -202
- package/src/modules/bmb/workflows/create-agent/instructions.md +0 -456
- package/src/modules/bmb/workflows/create-agent/workflow.yaml +0 -49
- package/src/modules/bmb/workflows/create-module/README.md +0 -229
- package/src/modules/bmb/workflows/create-module/brainstorm-context.md +0 -137
- package/src/modules/bmb/workflows/create-module/checklist.md +0 -235
- package/src/modules/bmb/workflows/create-module/installer-templates/install-config.yaml +0 -92
- package/src/modules/bmb/workflows/create-module/installer-templates/installer.js +0 -231
- package/src/modules/bmb/workflows/create-module/instructions.md +0 -576
- package/src/modules/bmb/workflows/create-module/module-structure.md +0 -400
- package/src/modules/bmb/workflows/create-module/workflow.yaml +0 -44
- package/src/modules/bmb/workflows/create-workflow/README.md +0 -277
- package/src/modules/bmb/workflows/create-workflow/brainstorm-context.md +0 -197
- package/src/modules/bmb/workflows/create-workflow/checklist.md +0 -94
- package/src/modules/bmb/workflows/create-workflow/instructions.md +0 -724
- package/src/modules/bmb/workflows/create-workflow/workflow-creation-guide.md +0 -1306
- package/src/modules/bmb/workflows/create-workflow/workflow-template/checklist.md +0 -24
- package/src/modules/bmb/workflows/create-workflow/workflow-template/instructions.md +0 -13
- package/src/modules/bmb/workflows/create-workflow/workflow-template/template.md +0 -9
- package/src/modules/bmb/workflows/create-workflow/workflow-template/workflow.yaml +0 -65
- package/src/modules/bmb/workflows/create-workflow/workflow.yaml +0 -42
- package/src/modules/bmb/workflows/edit-agent/README.md +0 -112
- package/src/modules/bmb/workflows/edit-agent/checklist.md +0 -112
- package/src/modules/bmb/workflows/edit-agent/instructions.md +0 -290
- package/src/modules/bmb/workflows/edit-agent/workflow.yaml +0 -35
- package/src/modules/bmb/workflows/edit-module/README.md +0 -187
- package/src/modules/bmb/workflows/edit-module/checklist.md +0 -165
- package/src/modules/bmb/workflows/edit-module/instructions.md +0 -339
- package/src/modules/bmb/workflows/edit-module/workflow.yaml +0 -36
- package/src/modules/bmb/workflows/edit-workflow/README.md +0 -119
- package/src/modules/bmb/workflows/edit-workflow/checklist.md +0 -70
- package/src/modules/bmb/workflows/edit-workflow/instructions.md +0 -342
- package/src/modules/bmb/workflows/edit-workflow/workflow.yaml +0 -29
- package/src/modules/bmb/workflows/module-brief/README.md +0 -264
- package/src/modules/bmb/workflows/module-brief/checklist.md +0 -116
- package/src/modules/bmb/workflows/module-brief/instructions.md +0 -267
- package/src/modules/bmb/workflows/module-brief/template.md +0 -275
- package/src/modules/bmb/workflows/module-brief/workflow.yaml +0 -31
- package/src/modules/bmb/workflows/redoc/README.md +0 -87
- package/src/modules/bmb/workflows/redoc/checklist.md +0 -99
- package/src/modules/bmb/workflows/redoc/instructions.md +0 -265
- package/src/modules/bmb/workflows/redoc/workflow.yaml +0 -34
- package/src/modules/bmgd/README.md +0 -208
- package/src/modules/bmgd/_module-installer/install-config.yaml +0 -54
- package/src/modules/bmgd/agents/game-architect.agent.yaml +0 -33
- package/src/modules/bmgd/agents/game-designer.agent.yaml +0 -40
- package/src/modules/bmgd/agents/game-dev.agent.yaml +0 -40
- package/src/modules/bmgd/agents/game-scrum-master.agent.yaml +0 -75
- package/src/modules/bmgd/teams/default-party.csv +0 -10
- package/src/modules/bmgd/teams/team-gamedev.yaml +0 -18
- package/src/modules/bmgd/workflows/1-preproduction/brainstorm-game/game-brain-methods.csv +0 -26
- package/src/modules/bmgd/workflows/1-preproduction/brainstorm-game/game-context.md +0 -115
- package/src/modules/bmgd/workflows/1-preproduction/brainstorm-game/instructions.md +0 -128
- package/src/modules/bmgd/workflows/1-preproduction/brainstorm-game/workflow.yaml +0 -41
- package/src/modules/bmgd/workflows/1-preproduction/game-brief/checklist.md +0 -128
- package/src/modules/bmgd/workflows/1-preproduction/game-brief/instructions.md +0 -371
- package/src/modules/bmgd/workflows/1-preproduction/game-brief/template.md +0 -205
- package/src/modules/bmgd/workflows/1-preproduction/game-brief/workflow.yaml +0 -44
- package/src/modules/bmgd/workflows/2-design/gdd/checklist.md +0 -148
- package/src/modules/bmgd/workflows/2-design/gdd/game-types/action-platformer.md +0 -45
- package/src/modules/bmgd/workflows/2-design/gdd/game-types/adventure.md +0 -84
- package/src/modules/bmgd/workflows/2-design/gdd/game-types/card-game.md +0 -76
- package/src/modules/bmgd/workflows/2-design/gdd/game-types/fighting.md +0 -89
- package/src/modules/bmgd/workflows/2-design/gdd/game-types/horror.md +0 -86
- package/src/modules/bmgd/workflows/2-design/gdd/game-types/idle-incremental.md +0 -78
- package/src/modules/bmgd/workflows/2-design/gdd/game-types/metroidvania.md +0 -87
- package/src/modules/bmgd/workflows/2-design/gdd/game-types/moba.md +0 -74
- package/src/modules/bmgd/workflows/2-design/gdd/game-types/party-game.md +0 -79
- package/src/modules/bmgd/workflows/2-design/gdd/game-types/puzzle.md +0 -58
- package/src/modules/bmgd/workflows/2-design/gdd/game-types/racing.md +0 -88
- package/src/modules/bmgd/workflows/2-design/gdd/game-types/rhythm.md +0 -79
- package/src/modules/bmgd/workflows/2-design/gdd/game-types/roguelike.md +0 -69
- package/src/modules/bmgd/workflows/2-design/gdd/game-types/rpg.md +0 -70
- package/src/modules/bmgd/workflows/2-design/gdd/game-types/sandbox.md +0 -79
- package/src/modules/bmgd/workflows/2-design/gdd/game-types/shooter.md +0 -62
- package/src/modules/bmgd/workflows/2-design/gdd/game-types/simulation.md +0 -73
- package/src/modules/bmgd/workflows/2-design/gdd/game-types/sports.md +0 -75
- package/src/modules/bmgd/workflows/2-design/gdd/game-types/strategy.md +0 -71
- package/src/modules/bmgd/workflows/2-design/gdd/game-types/survival.md +0 -79
- package/src/modules/bmgd/workflows/2-design/gdd/game-types/text-based.md +0 -91
- package/src/modules/bmgd/workflows/2-design/gdd/game-types/tower-defense.md +0 -79
- package/src/modules/bmgd/workflows/2-design/gdd/game-types/turn-based-tactics.md +0 -88
- package/src/modules/bmgd/workflows/2-design/gdd/game-types/visual-novel.md +0 -89
- package/src/modules/bmgd/workflows/2-design/gdd/game-types.csv +0 -25
- package/src/modules/bmgd/workflows/2-design/gdd/gdd-template.md +0 -153
- package/src/modules/bmgd/workflows/2-design/gdd/instructions-gdd.md +0 -501
- package/src/modules/bmgd/workflows/2-design/gdd/workflow.yaml +0 -81
- package/src/modules/bmgd/workflows/2-design/narrative/checklist.md +0 -139
- package/src/modules/bmgd/workflows/2-design/narrative/instructions-narrative.md +0 -603
- package/src/modules/bmgd/workflows/2-design/narrative/narrative-template.md +0 -195
- package/src/modules/bmgd/workflows/2-design/narrative/workflow.yaml +0 -38
- package/src/modules/bmgd/workflows/3-technical/game-architecture/architecture-patterns.yaml +0 -321
- package/src/modules/bmgd/workflows/3-technical/game-architecture/architecture-template.md +0 -103
- package/src/modules/bmgd/workflows/3-technical/game-architecture/checklist.md +0 -240
- package/src/modules/bmgd/workflows/3-technical/game-architecture/decision-catalog.yaml +0 -222
- package/src/modules/bmgd/workflows/3-technical/game-architecture/instructions.md +0 -699
- package/src/modules/bmgd/workflows/3-technical/game-architecture/pattern-categories.csv +0 -13
- package/src/modules/bmgd/workflows/3-technical/game-architecture/workflow.yaml +0 -67
- package/src/modules/bmgd/workflows/4-production/code-review/backlog_template.md +0 -12
- package/src/modules/bmgd/workflows/4-production/code-review/checklist.md +0 -22
- package/src/modules/bmgd/workflows/4-production/code-review/instructions.md +0 -398
- package/src/modules/bmgd/workflows/4-production/code-review/workflow.yaml +0 -57
- package/src/modules/bmgd/workflows/4-production/correct-course/checklist.md +0 -279
- package/src/modules/bmgd/workflows/4-production/correct-course/instructions.md +0 -206
- package/src/modules/bmgd/workflows/4-production/correct-course/workflow.yaml +0 -52
- package/src/modules/bmgd/workflows/4-production/create-story/checklist.md +0 -240
- package/src/modules/bmgd/workflows/4-production/create-story/instructions.md +0 -256
- package/src/modules/bmgd/workflows/4-production/create-story/template.md +0 -51
- package/src/modules/bmgd/workflows/4-production/create-story/workflow.yaml +0 -68
- package/src/modules/bmgd/workflows/4-production/dev-story/checklist.md +0 -38
- package/src/modules/bmgd/workflows/4-production/dev-story/instructions.md +0 -267
- package/src/modules/bmgd/workflows/4-production/dev-story/workflow.yaml +0 -53
- package/src/modules/bmgd/workflows/4-production/epic-tech-context/checklist.md +0 -17
- package/src/modules/bmgd/workflows/4-production/epic-tech-context/instructions.md +0 -164
- package/src/modules/bmgd/workflows/4-production/epic-tech-context/template.md +0 -76
- package/src/modules/bmgd/workflows/4-production/epic-tech-context/workflow.yaml +0 -52
- package/src/modules/bmgd/workflows/4-production/retrospective/instructions.md +0 -1442
- package/src/modules/bmgd/workflows/4-production/retrospective/workflow.yaml +0 -52
- package/src/modules/bmgd/workflows/4-production/sprint-planning/instructions.md +0 -234
- package/src/modules/bmgd/workflows/4-production/sprint-planning/sprint-status-template.yaml +0 -55
- package/src/modules/bmgd/workflows/4-production/sprint-planning/workflow.yaml +0 -50
- package/src/modules/bmgd/workflows/4-production/story-context/checklist.md +0 -16
- package/src/modules/bmgd/workflows/4-production/story-context/context-template.xml +0 -34
- package/src/modules/bmgd/workflows/4-production/story-context/instructions.md +0 -209
- package/src/modules/bmgd/workflows/4-production/story-context/workflow.yaml +0 -57
- package/src/modules/bmgd/workflows/4-production/story-done/instructions.md +0 -111
- package/src/modules/bmgd/workflows/4-production/story-done/workflow.yaml +0 -28
- package/src/modules/bmgd/workflows/4-production/story-ready/instructions.md +0 -117
- package/src/modules/bmgd/workflows/4-production/story-ready/workflow.yaml +0 -25
- package/src/modules/bmm/README.md +0 -128
- package/src/modules/bmm/_module-installer/assets/bmm-kb.md +0 -1
- package/src/modules/bmm/_module-installer/assets/technical-decisions.md +0 -30
- package/src/modules/bmm/_module-installer/install-config.yaml +0 -60
- package/src/modules/bmm/_module-installer/installer.js +0 -131
- package/src/modules/bmm/_module-installer/platform-specifics/claude-code.js +0 -35
- package/src/modules/bmm/_module-installer/platform-specifics/windsurf.js +0 -32
- package/src/modules/bmm/agents/analyst.agent.yaml +0 -48
- package/src/modules/bmm/agents/architect.agent.yaml +0 -42
- package/src/modules/bmm/agents/dev.agent.yaml +0 -40
- package/src/modules/bmm/agents/pm.agent.yaml +0 -61
- package/src/modules/bmm/agents/sm.agent.yaml +0 -72
- package/src/modules/bmm/agents/tea.agent.yaml +0 -65
- package/src/modules/bmm/agents/tech-writer.agent.yaml +0 -73
- package/src/modules/bmm/agents/ux-designer.agent.yaml +0 -38
- package/src/modules/bmm/docs/README.md +0 -235
- package/src/modules/bmm/docs/agents-guide.md +0 -1056
- package/src/modules/bmm/docs/brownfield-guide.md +0 -754
- package/src/modules/bmm/docs/enterprise-agentic-development.md +0 -680
- package/src/modules/bmm/docs/faq.md +0 -587
- package/src/modules/bmm/docs/glossary.md +0 -320
- package/src/modules/bmm/docs/party-mode.md +0 -224
- package/src/modules/bmm/docs/quick-spec-flow.md +0 -652
- package/src/modules/bmm/docs/quick-start.md +0 -366
- package/src/modules/bmm/docs/scale-adaptive-system.md +0 -599
- package/src/modules/bmm/docs/test-architecture.md +0 -394
- package/src/modules/bmm/docs/workflow-architecture-reference.md +0 -371
- package/src/modules/bmm/docs/workflow-document-project-reference.md +0 -487
- package/src/modules/bmm/docs/workflows-analysis.md +0 -370
- package/src/modules/bmm/docs/workflows-implementation.md +0 -284
- package/src/modules/bmm/docs/workflows-planning.md +0 -601
- package/src/modules/bmm/docs/workflows-solutioning.md +0 -501
- package/src/modules/bmm/teams/default-party.csv +0 -19
- package/src/modules/bmm/testarch/knowledge/ci-burn-in.md +0 -675
- package/src/modules/bmm/testarch/knowledge/component-tdd.md +0 -486
- package/src/modules/bmm/testarch/knowledge/contract-testing.md +0 -957
- package/src/modules/bmm/testarch/knowledge/data-factories.md +0 -500
- package/src/modules/bmm/testarch/knowledge/email-auth.md +0 -721
- package/src/modules/bmm/testarch/knowledge/error-handling.md +0 -725
- package/src/modules/bmm/testarch/knowledge/feature-flags.md +0 -750
- package/src/modules/bmm/testarch/knowledge/fixture-architecture.md +0 -401
- package/src/modules/bmm/testarch/knowledge/network-first.md +0 -486
- package/src/modules/bmm/testarch/knowledge/nfr-criteria.md +0 -670
- package/src/modules/bmm/testarch/knowledge/playwright-config.md +0 -730
- package/src/modules/bmm/testarch/knowledge/probability-impact.md +0 -601
- package/src/modules/bmm/testarch/knowledge/risk-governance.md +0 -615
- package/src/modules/bmm/testarch/knowledge/selective-testing.md +0 -732
- package/src/modules/bmm/testarch/knowledge/selector-resilience.md +0 -527
- package/src/modules/bmm/testarch/knowledge/test-healing-patterns.md +0 -644
- package/src/modules/bmm/testarch/knowledge/test-levels-framework.md +0 -473
- package/src/modules/bmm/testarch/knowledge/test-priorities-matrix.md +0 -373
- package/src/modules/bmm/testarch/knowledge/test-quality.md +0 -664
- package/src/modules/bmm/testarch/knowledge/timing-debugging.md +0 -372
- package/src/modules/bmm/testarch/knowledge/visual-debugging.md +0 -524
- package/src/modules/bmm/testarch/tea-index.csv +0 -22
- package/src/modules/bmm/workflows/1-analysis/brainstorm-project/instructions.md +0 -110
- package/src/modules/bmm/workflows/1-analysis/brainstorm-project/project-context.md +0 -25
- package/src/modules/bmm/workflows/1-analysis/brainstorm-project/workflow.yaml +0 -39
- package/src/modules/bmm/workflows/1-analysis/domain-research/instructions.md +0 -423
- package/src/modules/bmm/workflows/1-analysis/domain-research/template.md +0 -180
- package/src/modules/bmm/workflows/1-analysis/domain-research/workflow.yaml +0 -56
- package/src/modules/bmm/workflows/1-analysis/product-brief/checklist.md +0 -115
- package/src/modules/bmm/workflows/1-analysis/product-brief/instructions.md +0 -522
- package/src/modules/bmm/workflows/1-analysis/product-brief/template.md +0 -181
- package/src/modules/bmm/workflows/1-analysis/product-brief/workflow.yaml +0 -65
- package/src/modules/bmm/workflows/1-analysis/research/checklist-deep-prompt.md +0 -144
- package/src/modules/bmm/workflows/1-analysis/research/checklist-technical.md +0 -249
- package/src/modules/bmm/workflows/1-analysis/research/checklist.md +0 -299
- package/src/modules/bmm/workflows/1-analysis/research/claude-code/injections.yaml +0 -114
- package/src/modules/bmm/workflows/1-analysis/research/instructions-deep-prompt.md +0 -437
- package/src/modules/bmm/workflows/1-analysis/research/instructions-market.md +0 -674
- package/src/modules/bmm/workflows/1-analysis/research/instructions-router.md +0 -133
- package/src/modules/bmm/workflows/1-analysis/research/instructions-technical.md +0 -533
- package/src/modules/bmm/workflows/1-analysis/research/template-deep-prompt.md +0 -94
- package/src/modules/bmm/workflows/1-analysis/research/template-market.md +0 -347
- package/src/modules/bmm/workflows/1-analysis/research/template-technical.md +0 -245
- package/src/modules/bmm/workflows/1-analysis/research/workflow.yaml +0 -62
- package/src/modules/bmm/workflows/2-plan-workflows/create-ux-design/checklist.md +0 -310
- package/src/modules/bmm/workflows/2-plan-workflows/create-ux-design/instructions.md +0 -1306
- package/src/modules/bmm/workflows/2-plan-workflows/create-ux-design/ux-design-template.md +0 -145
- package/src/modules/bmm/workflows/2-plan-workflows/create-ux-design/workflow.yaml +0 -99
- package/src/modules/bmm/workflows/2-plan-workflows/prd/checklist.md +0 -346
- package/src/modules/bmm/workflows/2-plan-workflows/prd/create-epics-and-stories/epics-template.md +0 -80
- package/src/modules/bmm/workflows/2-plan-workflows/prd/create-epics-and-stories/instructions.md +0 -292
- package/src/modules/bmm/workflows/2-plan-workflows/prd/create-epics-and-stories/workflow.yaml +0 -53
- package/src/modules/bmm/workflows/2-plan-workflows/prd/domain-complexity.csv +0 -13
- package/src/modules/bmm/workflows/2-plan-workflows/prd/instructions.md +0 -525
- package/src/modules/bmm/workflows/2-plan-workflows/prd/prd-template.md +0 -237
- package/src/modules/bmm/workflows/2-plan-workflows/prd/project-types.csv +0 -11
- package/src/modules/bmm/workflows/2-plan-workflows/prd/workflow.yaml +0 -76
- package/src/modules/bmm/workflows/2-plan-workflows/tech-spec/checklist.md +0 -217
- package/src/modules/bmm/workflows/2-plan-workflows/tech-spec/epics-template.md +0 -74
- package/src/modules/bmm/workflows/2-plan-workflows/tech-spec/instructions-generate-stories.md +0 -434
- package/src/modules/bmm/workflows/2-plan-workflows/tech-spec/instructions.md +0 -978
- package/src/modules/bmm/workflows/2-plan-workflows/tech-spec/tech-spec-template.md +0 -181
- package/src/modules/bmm/workflows/2-plan-workflows/tech-spec/user-story-template.md +0 -90
- package/src/modules/bmm/workflows/2-plan-workflows/tech-spec/workflow.yaml +0 -57
- package/src/modules/bmm/workflows/3-solutioning/architecture/architecture-patterns.yaml +0 -321
- package/src/modules/bmm/workflows/3-solutioning/architecture/architecture-template.md +0 -103
- package/src/modules/bmm/workflows/3-solutioning/architecture/checklist.md +0 -240
- package/src/modules/bmm/workflows/3-solutioning/architecture/decision-catalog.yaml +0 -222
- package/src/modules/bmm/workflows/3-solutioning/architecture/instructions.md +0 -696
- package/src/modules/bmm/workflows/3-solutioning/architecture/pattern-categories.csv +0 -13
- package/src/modules/bmm/workflows/3-solutioning/architecture/workflow.yaml +0 -96
- package/src/modules/bmm/workflows/3-solutioning/solutioning-gate-check/checklist.md +0 -169
- package/src/modules/bmm/workflows/3-solutioning/solutioning-gate-check/instructions.md +0 -302
- package/src/modules/bmm/workflows/3-solutioning/solutioning-gate-check/template.md +0 -146
- package/src/modules/bmm/workflows/3-solutioning/solutioning-gate-check/workflow.yaml +0 -66
- package/src/modules/bmm/workflows/4-implementation/code-review/backlog_template.md +0 -12
- package/src/modules/bmm/workflows/4-implementation/code-review/checklist.md +0 -22
- package/src/modules/bmm/workflows/4-implementation/code-review/instructions.md +0 -398
- package/src/modules/bmm/workflows/4-implementation/code-review/workflow.yaml +0 -57
- package/src/modules/bmm/workflows/4-implementation/correct-course/checklist.md +0 -279
- package/src/modules/bmm/workflows/4-implementation/correct-course/instructions.md +0 -206
- package/src/modules/bmm/workflows/4-implementation/correct-course/workflow.yaml +0 -52
- package/src/modules/bmm/workflows/4-implementation/create-story/checklist.md +0 -240
- package/src/modules/bmm/workflows/4-implementation/create-story/instructions.md +0 -256
- package/src/modules/bmm/workflows/4-implementation/create-story/template.md +0 -51
- package/src/modules/bmm/workflows/4-implementation/create-story/workflow.yaml +0 -68
- package/src/modules/bmm/workflows/4-implementation/dev-story/checklist.md +0 -38
- package/src/modules/bmm/workflows/4-implementation/dev-story/instructions.md +0 -267
- package/src/modules/bmm/workflows/4-implementation/dev-story/workflow.yaml +0 -53
- package/src/modules/bmm/workflows/4-implementation/epic-tech-context/checklist.md +0 -17
- package/src/modules/bmm/workflows/4-implementation/epic-tech-context/instructions.md +0 -164
- package/src/modules/bmm/workflows/4-implementation/epic-tech-context/template.md +0 -76
- package/src/modules/bmm/workflows/4-implementation/epic-tech-context/workflow.yaml +0 -52
- package/src/modules/bmm/workflows/4-implementation/retrospective/instructions.md +0 -1442
- package/src/modules/bmm/workflows/4-implementation/retrospective/workflow.yaml +0 -52
- package/src/modules/bmm/workflows/4-implementation/sprint-planning/checklist.md +0 -33
- package/src/modules/bmm/workflows/4-implementation/sprint-planning/instructions.md +0 -234
- package/src/modules/bmm/workflows/4-implementation/sprint-planning/sprint-status-template.yaml +0 -55
- package/src/modules/bmm/workflows/4-implementation/sprint-planning/workflow.yaml +0 -50
- package/src/modules/bmm/workflows/4-implementation/story-context/checklist.md +0 -16
- package/src/modules/bmm/workflows/4-implementation/story-context/context-template.xml +0 -34
- package/src/modules/bmm/workflows/4-implementation/story-context/instructions.md +0 -209
- package/src/modules/bmm/workflows/4-implementation/story-context/workflow.yaml +0 -57
- package/src/modules/bmm/workflows/4-implementation/story-done/instructions.md +0 -111
- package/src/modules/bmm/workflows/4-implementation/story-done/workflow.yaml +0 -28
- package/src/modules/bmm/workflows/4-implementation/story-ready/instructions.md +0 -117
- package/src/modules/bmm/workflows/4-implementation/story-ready/workflow.yaml +0 -25
- package/src/modules/bmm/workflows/document-project/checklist.md +0 -245
- package/src/modules/bmm/workflows/document-project/instructions.md +0 -222
- package/src/modules/bmm/workflows/document-project/templates/project-scan-report-schema.json +0 -160
- package/src/modules/bmm/workflows/document-project/workflow.yaml +0 -36
- package/src/modules/bmm/workflows/document-project/workflows/deep-dive-instructions.md +0 -298
- package/src/modules/bmm/workflows/document-project/workflows/deep-dive.yaml +0 -31
- package/src/modules/bmm/workflows/document-project/workflows/full-scan-instructions.md +0 -1106
- package/src/modules/bmm/workflows/document-project/workflows/full-scan.yaml +0 -31
- package/src/modules/bmm/workflows/techdoc/documentation-standards.md +0 -262
- package/src/modules/bmm/workflows/testarch/atdd/atdd-checklist-template.md +0 -363
- package/src/modules/bmm/workflows/testarch/atdd/checklist.md +0 -373
- package/src/modules/bmm/workflows/testarch/atdd/instructions.md +0 -785
- package/src/modules/bmm/workflows/testarch/atdd/workflow.yaml +0 -54
- package/src/modules/bmm/workflows/testarch/automate/checklist.md +0 -580
- package/src/modules/bmm/workflows/testarch/automate/instructions.md +0 -1303
- package/src/modules/bmm/workflows/testarch/automate/workflow.yaml +0 -63
- package/src/modules/bmm/workflows/testarch/ci/checklist.md +0 -246
- package/src/modules/bmm/workflows/testarch/ci/github-actions-template.yaml +0 -165
- package/src/modules/bmm/workflows/testarch/ci/gitlab-ci-template.yaml +0 -128
- package/src/modules/bmm/workflows/testarch/ci/instructions.md +0 -517
- package/src/modules/bmm/workflows/testarch/ci/workflow.yaml +0 -55
- package/src/modules/bmm/workflows/testarch/framework/checklist.md +0 -321
- package/src/modules/bmm/workflows/testarch/framework/instructions.md +0 -455
- package/src/modules/bmm/workflows/testarch/framework/workflow.yaml +0 -55
- package/src/modules/bmm/workflows/testarch/nfr-assess/checklist.md +0 -405
- package/src/modules/bmm/workflows/testarch/nfr-assess/instructions.md +0 -722
- package/src/modules/bmm/workflows/testarch/nfr-assess/nfr-report-template.md +0 -443
- package/src/modules/bmm/workflows/testarch/nfr-assess/workflow.yaml +0 -58
- package/src/modules/bmm/workflows/testarch/test-design/checklist.md +0 -234
- package/src/modules/bmm/workflows/testarch/test-design/instructions.md +0 -782
- package/src/modules/bmm/workflows/testarch/test-design/test-design-template.md +0 -285
- package/src/modules/bmm/workflows/testarch/test-design/workflow.yaml +0 -58
- package/src/modules/bmm/workflows/testarch/test-review/checklist.md +0 -470
- package/src/modules/bmm/workflows/testarch/test-review/instructions.md +0 -608
- package/src/modules/bmm/workflows/testarch/test-review/test-review-template.md +0 -388
- package/src/modules/bmm/workflows/testarch/test-review/workflow.yaml +0 -55
- package/src/modules/bmm/workflows/testarch/trace/checklist.md +0 -654
- package/src/modules/bmm/workflows/testarch/trace/instructions.md +0 -1045
- package/src/modules/bmm/workflows/testarch/trace/trace-template.md +0 -673
- package/src/modules/bmm/workflows/testarch/trace/workflow.yaml +0 -68
- package/src/modules/bmm/workflows/workflow-status/init/instructions.md +0 -823
- package/src/modules/bmm/workflows/workflow-status/init/workflow.yaml +0 -29
- package/src/modules/bmm/workflows/workflow-status/instructions.md +0 -387
- package/src/modules/bmm/workflows/workflow-status/paths/enterprise-brownfield.yaml +0 -120
- package/src/modules/bmm/workflows/workflow-status/paths/enterprise-greenfield.yaml +0 -108
- package/src/modules/bmm/workflows/workflow-status/paths/game-design.yaml +0 -52
- package/src/modules/bmm/workflows/workflow-status/paths/method-brownfield.yaml +0 -104
- package/src/modules/bmm/workflows/workflow-status/paths/method-greenfield.yaml +0 -95
- package/src/modules/bmm/workflows/workflow-status/paths/quick-flow-brownfield.yaml +0 -58
- package/src/modules/bmm/workflows/workflow-status/paths/quick-flow-greenfield.yaml +0 -47
- package/src/modules/bmm/workflows/workflow-status/project-levels.yaml +0 -59
- package/src/modules/bmm/workflows/workflow-status/workflow-status-template.yaml +0 -24
- package/src/modules/bmm/workflows/workflow-status/workflow.yaml +0 -30
- package/src/modules/cis/README.md +0 -153
- package/src/modules/cis/_module-installer/install-config.yaml +0 -16
- package/src/modules/cis/_module-installer/installer.js +0 -92
- package/src/modules/cis/agents/README.md +0 -104
- package/src/modules/cis/agents/brainstorming-coach.agent.yaml +0 -28
- package/src/modules/cis/agents/creative-problem-solver.agent.yaml +0 -28
- package/src/modules/cis/agents/design-thinking-coach.agent.yaml +0 -28
- package/src/modules/cis/agents/innovation-strategist.agent.yaml +0 -28
- package/src/modules/cis/agents/storyteller.agent.yaml +0 -28
- package/src/modules/cis/teams/creative-squad.yaml +0 -7
- package/src/modules/cis/teams/default-party.csv +0 -11
- package/src/modules/cis/workflows/README.md +0 -139
- package/src/modules/cis/workflows/design-thinking/README.md +0 -56
- package/src/modules/cis/workflows/design-thinking/design-methods.csv +0 -31
- package/src/modules/cis/workflows/design-thinking/instructions.md +0 -200
- package/src/modules/cis/workflows/design-thinking/template.md +0 -111
- package/src/modules/cis/workflows/design-thinking/workflow.yaml +0 -43
- package/src/modules/cis/workflows/innovation-strategy/README.md +0 -56
- package/src/modules/cis/workflows/innovation-strategy/innovation-frameworks.csv +0 -31
- package/src/modules/cis/workflows/innovation-strategy/instructions.md +0 -274
- package/src/modules/cis/workflows/innovation-strategy/template.md +0 -189
- package/src/modules/cis/workflows/innovation-strategy/workflow.yaml +0 -43
- package/src/modules/cis/workflows/problem-solving/README.md +0 -56
- package/src/modules/cis/workflows/problem-solving/instructions.md +0 -250
- package/src/modules/cis/workflows/problem-solving/solving-methods.csv +0 -31
- package/src/modules/cis/workflows/problem-solving/template.md +0 -165
- package/src/modules/cis/workflows/problem-solving/workflow.yaml +0 -43
- package/src/modules/cis/workflows/storytelling/README.md +0 -58
- package/src/modules/cis/workflows/storytelling/instructions.md +0 -291
- package/src/modules/cis/workflows/storytelling/story-types.csv +0 -26
- package/src/modules/cis/workflows/storytelling/template.md +0 -113
- package/src/modules/cis/workflows/storytelling/workflow.yaml +0 -43
- package/src/utility/models/agent-activation-ide.xml +0 -51
- package/src/utility/models/agent-activation-web.xml +0 -60
- package/src/utility/models/agent-command-header.md +0 -1
- package/src/utility/models/agent-config-template.md +0 -23
- package/src/utility/models/agent-in-team-activation.xml +0 -3
- package/src/utility/models/fragments/activation-rules.xml +0 -8
- package/src/utility/models/fragments/activation-steps.xml +0 -16
- package/src/utility/models/fragments/handler-action.xml +0 -4
- package/src/utility/models/fragments/handler-exec.xml +0 -5
- package/src/utility/models/fragments/handler-tmpl.xml +0 -5
- package/src/utility/models/fragments/handler-validate-workflow.xml +0 -7
- package/src/utility/models/fragments/handler-workflow.xml +0 -9
- package/src/utility/models/fragments/menu-handlers.xml +0 -6
- package/src/utility/models/fragments/web-bundle-activation-steps.xml +0 -32
- package/src/utility/templates/agent.customize.template.yaml +0 -42
- package/test/fixtures/agent-schema/invalid/metadata/core-agent-with-module.agent.yaml +0 -26
- package/test/fixtures/agent-schema/invalid/metadata/module-agent-missing-module.agent.yaml +0 -25
- package/test/fixtures/agent-schema/invalid/metadata/wrong-module-value.agent.yaml +0 -26
- package/tools/cli/bundlers/bundle-web.js +0 -179
- package/tools/cli/bundlers/test-analyst.js +0 -28
- package/tools/cli/bundlers/test-bundler.js +0 -118
- package/tools/cli/bundlers/web-bundler.js +0 -1667
- package/tools/cli/commands/build.js +0 -458
- package/tools/cli/commands/list.js +0 -28
- package/tools/cli/commands/update.js +0 -28
- package/tools/cli/installers/lib/ide/auggie.js +0 -179
- package/tools/cli/installers/lib/ide/claude-code.js +0 -471
- package/tools/cli/installers/lib/ide/cline.js +0 -220
- package/tools/cli/installers/lib/ide/crush.js +0 -240
- package/tools/cli/installers/lib/ide/cursor.js +0 -352
- package/tools/cli/installers/lib/ide/gemini.js +0 -206
- package/tools/cli/installers/lib/ide/iflow.js +0 -125
- package/tools/cli/installers/lib/ide/opencode.js +0 -212
- package/tools/cli/installers/lib/ide/qwen.js +0 -318
- package/tools/cli/installers/lib/ide/roo.js +0 -253
- package/tools/cli/installers/lib/ide/templates/gemini-agent-command.toml +0 -14
- package/tools/cli/installers/lib/ide/templates/gemini-task-command.toml +0 -12
- package/tools/cli/installers/lib/ide/trae.js +0 -266
- package/tools/cli/installers/lib/ide/windsurf.js +0 -211
- package/tools/cli/lib/replace-project-root.js +0 -239
- package/tools/cli/regenerate-manifests.js +0 -28
- package/tools/cli/test-yaml-builder.js +0 -43
- package/tools/flattener/aggregate.js +0 -76
- package/tools/flattener/binary.js +0 -80
- package/tools/flattener/discovery.js +0 -71
- package/tools/flattener/files.js +0 -35
- package/tools/flattener/ignoreRules.js +0 -172
- package/tools/flattener/main.js +0 -483
- package/tools/flattener/projectRoot.js +0 -201
- package/tools/flattener/prompts.js +0 -44
- package/tools/flattener/stats.helpers.js +0 -368
- package/tools/flattener/stats.js +0 -75
- package/tools/flattener/test-matrix.js +0 -409
- package/tools/flattener/xml.js +0 -88
- package/tools/validate-bundles.js +0 -87
- package/v6-open-items.md +0 -17
- /package/src/{modules/bmm → bmm}/teams/team-fullstack.yaml +0 -0
- /package/src/{modules/bmgd/workflows/4-production → bmm/workflows/4-implementation}/sprint-planning/checklist.md +0 -0
- /package/src/{modules/bmm → bmm}/workflows/document-project/documentation-requirements.csv +0 -0
- /package/src/{modules/bmm → bmm}/workflows/document-project/templates/deep-dive-template.md +0 -0
- /package/src/{modules/bmm → bmm}/workflows/document-project/templates/index-template.md +0 -0
- /package/src/{modules/bmm → bmm}/workflows/document-project/templates/project-overview-template.md +0 -0
- /package/src/{modules/bmm → bmm}/workflows/document-project/templates/source-tree-template.md +0 -0
- /package/src/utility/{models/fragments/handler-data.xml → agent-components/handler-data.txt} +0 -0
- /package/{src/utility/models/action-command-header.md → tools/cli/installers/lib/ide/templates/split/.gitkeep} +0 -0
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
const path = require('node:path');
|
|
2
2
|
const fs = require('fs-extra');
|
|
3
|
-
const chalk = require('chalk');
|
|
4
|
-
const ora = require('ora');
|
|
5
3
|
const { Detector } = require('./detector');
|
|
6
4
|
const { Manifest } = require('./manifest');
|
|
7
5
|
const { ModuleManager } = require('../modules/manager');
|
|
@@ -11,12 +9,13 @@ const { Config } = require('../../../lib/config');
|
|
|
11
9
|
const { XmlHandler } = require('../../../lib/xml-handler');
|
|
12
10
|
const { DependencyResolver } = require('./dependency-resolver');
|
|
13
11
|
const { ConfigCollector } = require('./config-collector');
|
|
14
|
-
// processInstallation no longer needed - LLMs understand {project-root}
|
|
15
12
|
const { getProjectRoot, getSourcePath, getModulePath } = require('../../../lib/project-root');
|
|
16
|
-
const { AgentPartyGenerator } = require('../../../lib/agent-party-generator');
|
|
17
13
|
const { CLIUtils } = require('../../../lib/cli-utils');
|
|
18
14
|
const { ManifestGenerator } = require('./manifest-generator');
|
|
19
15
|
const { IdeConfigManager } = require('./ide-config-manager');
|
|
16
|
+
const { CustomHandler } = require('../custom/handler');
|
|
17
|
+
const prompts = require('../../../lib/prompts');
|
|
18
|
+
const { BMAD_FOLDER_NAME } = require('../ide/shared/path-utils');
|
|
20
19
|
|
|
21
20
|
class Installer {
|
|
22
21
|
constructor() {
|
|
@@ -30,53 +29,58 @@ class Installer {
|
|
|
30
29
|
this.dependencyResolver = new DependencyResolver();
|
|
31
30
|
this.configCollector = new ConfigCollector();
|
|
32
31
|
this.ideConfigManager = new IdeConfigManager();
|
|
33
|
-
this.installedFiles =
|
|
32
|
+
this.installedFiles = new Set(); // Track all installed files
|
|
33
|
+
this.bmadFolderName = BMAD_FOLDER_NAME;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
37
|
* Find the bmad installation directory in a project
|
|
38
|
-
*
|
|
38
|
+
* Always uses the standard _bmad folder name
|
|
39
|
+
* Also checks for legacy _cfg folder for migration
|
|
39
40
|
* @param {string} projectDir - Project directory
|
|
40
|
-
* @returns {Promise<
|
|
41
|
+
* @returns {Promise<Object>} { bmadDir: string, hasLegacyCfg: boolean }
|
|
41
42
|
*/
|
|
42
43
|
async findBmadDir(projectDir) {
|
|
44
|
+
const bmadDir = path.join(projectDir, BMAD_FOLDER_NAME);
|
|
45
|
+
|
|
43
46
|
// Check if project directory exists
|
|
44
47
|
if (!(await fs.pathExists(projectDir))) {
|
|
45
48
|
// Project doesn't exist yet, return default
|
|
46
|
-
return
|
|
49
|
+
return { bmadDir, hasLegacyCfg: false };
|
|
47
50
|
}
|
|
48
51
|
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const manifestPath = path.join(projectDir, entry.name, '_cfg', 'manifest.yaml');
|
|
56
|
-
if (await fs.pathExists(manifestPath)) {
|
|
57
|
-
// Found a V6+ installation
|
|
58
|
-
return path.join(projectDir, entry.name);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
52
|
+
// Check for legacy _cfg folder if bmad directory exists
|
|
53
|
+
let hasLegacyCfg = false;
|
|
54
|
+
if (await fs.pathExists(bmadDir)) {
|
|
55
|
+
const legacyCfgPath = path.join(bmadDir, '_cfg');
|
|
56
|
+
if (await fs.pathExists(legacyCfgPath)) {
|
|
57
|
+
hasLegacyCfg = true;
|
|
61
58
|
}
|
|
62
|
-
} catch {
|
|
63
|
-
// Ignore errors, fall through to default
|
|
64
59
|
}
|
|
65
60
|
|
|
66
|
-
|
|
67
|
-
// This will be used for new installations
|
|
68
|
-
return path.join(projectDir, 'bmad');
|
|
61
|
+
return { bmadDir, hasLegacyCfg };
|
|
69
62
|
}
|
|
70
63
|
|
|
71
64
|
/**
|
|
72
|
-
*
|
|
73
|
-
* @
|
|
74
|
-
* @
|
|
75
|
-
* @param {string}
|
|
65
|
+
* @function copyFileWithPlaceholderReplacement
|
|
66
|
+
* @intent Copy files from BMAD source to installation directory with dynamic content transformation
|
|
67
|
+
* @why Enables installation-time customization: _bmad replacement
|
|
68
|
+
* @param {string} sourcePath - Absolute path to source file in BMAD repository
|
|
69
|
+
* @param {string} targetPath - Absolute path to destination file in user's project
|
|
70
|
+
* @param {string} bmadFolderName - User's chosen bmad folder name (default: 'bmad')
|
|
71
|
+
* @returns {Promise<void>} Resolves when file copy and transformation complete
|
|
72
|
+
* @sideeffects Writes transformed file to targetPath, creates parent directories if needed
|
|
73
|
+
* @edgecases Binary files bypass transformation, falls back to raw copy if UTF-8 read fails
|
|
74
|
+
* @calledby installCore(), installModule(), IDE installers during file vendoring
|
|
75
|
+
* @calls fs.readFile(), fs.writeFile(), fs.copy()
|
|
76
|
+
*
|
|
77
|
+
|
|
78
|
+
*
|
|
79
|
+
* 3. Document marker in instructions.md (if applicable)
|
|
76
80
|
*/
|
|
77
|
-
async copyFileWithPlaceholderReplacement(sourcePath, targetPath
|
|
81
|
+
async copyFileWithPlaceholderReplacement(sourcePath, targetPath) {
|
|
78
82
|
// List of text file extensions that should have placeholder replacement
|
|
79
|
-
const textExtensions = ['.md', '.yaml', '.yml', '.txt', '.json', '.js', '.ts', '.html', '.css', '.sh', '.bat', '.csv'];
|
|
83
|
+
const textExtensions = ['.md', '.yaml', '.yml', '.txt', '.json', '.js', '.ts', '.html', '.css', '.sh', '.bat', '.csv', '.xml'];
|
|
80
84
|
const ext = path.extname(sourcePath).toLowerCase();
|
|
81
85
|
|
|
82
86
|
// Check if this is a text file that might contain placeholders
|
|
@@ -85,11 +89,6 @@ class Installer {
|
|
|
85
89
|
// Read the file content
|
|
86
90
|
let content = await fs.readFile(sourcePath, 'utf8');
|
|
87
91
|
|
|
88
|
-
// Replace {bmad_folder} placeholder with actual folder name
|
|
89
|
-
if (content.includes('{bmad_folder}')) {
|
|
90
|
-
content = content.replaceAll('{bmad_folder}', bmadFolderName);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
92
|
// Write to target with replaced content
|
|
94
93
|
await fs.ensureDir(path.dirname(targetPath));
|
|
95
94
|
await fs.writeFile(targetPath, content, 'utf8');
|
|
@@ -110,16 +109,24 @@ class Installer {
|
|
|
110
109
|
* @param {boolean} isFullReinstall - Whether this is a full reinstall
|
|
111
110
|
* @param {Array} previousIdes - Previously configured IDEs (for reinstalls)
|
|
112
111
|
* @param {Array} preSelectedIdes - Pre-selected IDEs from early prompt (optional)
|
|
112
|
+
* @param {boolean} skipPrompts - Skip prompts and use defaults (for --yes flag)
|
|
113
113
|
* @returns {Object} Tool/IDE selection and configurations
|
|
114
114
|
*/
|
|
115
|
-
async collectToolConfigurations(
|
|
115
|
+
async collectToolConfigurations(
|
|
116
|
+
projectDir,
|
|
117
|
+
selectedModules,
|
|
118
|
+
isFullReinstall = false,
|
|
119
|
+
previousIdes = [],
|
|
120
|
+
preSelectedIdes = null,
|
|
121
|
+
skipPrompts = false,
|
|
122
|
+
) {
|
|
116
123
|
// Use pre-selected IDEs if provided, otherwise prompt
|
|
117
124
|
let toolConfig;
|
|
118
125
|
if (preSelectedIdes === null) {
|
|
119
126
|
// Fallback: prompt for tool selection (backwards compatibility)
|
|
120
127
|
const { UI } = require('../../../lib/ui');
|
|
121
128
|
const ui = new UI();
|
|
122
|
-
toolConfig = await ui.promptToolSelection(projectDir
|
|
129
|
+
toolConfig = await ui.promptToolSelection(projectDir);
|
|
123
130
|
} else {
|
|
124
131
|
// IDEs were already selected during initial prompts
|
|
125
132
|
toolConfig = {
|
|
@@ -131,7 +138,7 @@ class Installer {
|
|
|
131
138
|
// Check for already configured IDEs
|
|
132
139
|
const { Detector } = require('./detector');
|
|
133
140
|
const detector = new Detector();
|
|
134
|
-
const bmadDir = path.join(projectDir,
|
|
141
|
+
const bmadDir = path.join(projectDir, BMAD_FOLDER_NAME);
|
|
135
142
|
|
|
136
143
|
// During full reinstall, use the saved previous IDEs since bmad dir was deleted
|
|
137
144
|
// Otherwise detect from existing installation
|
|
@@ -158,54 +165,40 @@ class Installer {
|
|
|
158
165
|
}
|
|
159
166
|
|
|
160
167
|
if (!toolConfig.skipIde && toolConfig.ides && toolConfig.ides.length > 0) {
|
|
168
|
+
// Ensure IDE manager is initialized
|
|
169
|
+
await this.ideManager.ensureInitialized();
|
|
170
|
+
|
|
161
171
|
// Determine which IDEs are newly selected (not previously configured)
|
|
162
172
|
const newlySelectedIdes = toolConfig.ides.filter((ide) => !previouslyConfiguredIdes.includes(ide));
|
|
163
173
|
|
|
164
174
|
if (newlySelectedIdes.length > 0) {
|
|
165
|
-
|
|
166
|
-
|
|
175
|
+
// Collect configuration for IDEs that support it
|
|
167
176
|
for (const ide of newlySelectedIdes) {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
if (needsPrompts) {
|
|
172
|
-
// Get IDE handler and collect configuration
|
|
173
|
-
try {
|
|
174
|
-
// Dynamically load the IDE setup module
|
|
175
|
-
const ideModule = require(`../ide/${ide}`);
|
|
176
|
-
|
|
177
|
-
// Get the setup class (handle different export formats)
|
|
178
|
-
let SetupClass;
|
|
179
|
-
const className =
|
|
180
|
-
ide
|
|
181
|
-
.split('-')
|
|
182
|
-
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
183
|
-
.join('') + 'Setup';
|
|
184
|
-
|
|
185
|
-
if (ideModule[className]) {
|
|
186
|
-
SetupClass = ideModule[className];
|
|
187
|
-
} else if (ideModule.default) {
|
|
188
|
-
SetupClass = ideModule.default;
|
|
189
|
-
} else {
|
|
190
|
-
// Skip if no setup class found
|
|
191
|
-
continue;
|
|
192
|
-
}
|
|
177
|
+
try {
|
|
178
|
+
const handler = this.ideManager.handlers.get(ide);
|
|
193
179
|
|
|
194
|
-
|
|
180
|
+
if (!handler) {
|
|
181
|
+
await prompts.log.warn(`Warning: IDE '${ide}' handler not found`);
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
195
184
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
185
|
+
// Check if this IDE handler has a collectConfiguration method
|
|
186
|
+
// (custom installers like Codex, Kilo may have this)
|
|
187
|
+
if (typeof handler.collectConfiguration === 'function') {
|
|
188
|
+
await prompts.log.info(`Configuring ${ide}...`);
|
|
189
|
+
ideConfigurations[ide] = await handler.collectConfiguration({
|
|
190
|
+
selectedModules: selectedModules || [],
|
|
191
|
+
projectDir,
|
|
192
|
+
bmadDir,
|
|
193
|
+
skipPrompts,
|
|
194
|
+
});
|
|
195
|
+
} else {
|
|
196
|
+
// Config-driven IDEs don't need configuration - mark as ready
|
|
197
|
+
ideConfigurations[ide] = { _noConfigNeeded: true };
|
|
208
198
|
}
|
|
199
|
+
} catch (error) {
|
|
200
|
+
// IDE doesn't support configuration or has an error
|
|
201
|
+
await prompts.log.warn(`Warning: Could not load configuration for ${ide}: ${error.message}`);
|
|
209
202
|
}
|
|
210
203
|
}
|
|
211
204
|
}
|
|
@@ -213,7 +206,7 @@ class Installer {
|
|
|
213
206
|
// Log which IDEs are already configured and being kept
|
|
214
207
|
const keptIdes = toolConfig.ides.filter((ide) => previouslyConfiguredIdes.includes(ide));
|
|
215
208
|
if (keptIdes.length > 0) {
|
|
216
|
-
|
|
209
|
+
await prompts.log.message(`Keeping existing configuration for: ${keptIdes.join(', ')}`);
|
|
217
210
|
}
|
|
218
211
|
}
|
|
219
212
|
|
|
@@ -233,20 +226,30 @@ class Installer {
|
|
|
233
226
|
* @param {string[]} config.ides - IDEs to configure
|
|
234
227
|
* @param {boolean} config.skipIde - Skip IDE configuration
|
|
235
228
|
*/
|
|
236
|
-
async install(
|
|
237
|
-
//
|
|
238
|
-
|
|
229
|
+
async install(originalConfig) {
|
|
230
|
+
// Clone config to avoid mutating the caller's object
|
|
231
|
+
const config = { ...originalConfig };
|
|
232
|
+
|
|
233
|
+
// Check if core config was already collected in UI
|
|
234
|
+
const hasCoreConfig = config.coreConfig && Object.keys(config.coreConfig).length > 0;
|
|
239
235
|
|
|
240
|
-
//
|
|
241
|
-
|
|
236
|
+
// Only display logo if core config wasn't already collected (meaning we're not continuing from UI)
|
|
237
|
+
if (!hasCoreConfig) {
|
|
238
|
+
// Display BMAD logo
|
|
239
|
+
await CLIUtils.displayLogo();
|
|
240
|
+
|
|
241
|
+
// Display welcome message
|
|
242
|
+
await CLIUtils.displaySection('BMad™ Installation', 'Version ' + require(path.join(getProjectRoot(), 'package.json')).version);
|
|
243
|
+
}
|
|
242
244
|
|
|
243
245
|
// Note: Legacy V4 detection now happens earlier in UI.promptInstall()
|
|
244
246
|
// before any config collection, so we don't need to check again here
|
|
245
247
|
|
|
246
248
|
const projectDir = path.resolve(config.directory);
|
|
249
|
+
const bmadDir = path.join(projectDir, BMAD_FOLDER_NAME);
|
|
247
250
|
|
|
248
251
|
// If core config was pre-collected (from interactive mode), use it
|
|
249
|
-
if (config.coreConfig) {
|
|
252
|
+
if (config.coreConfig && Object.keys(config.coreConfig).length > 0) {
|
|
250
253
|
this.configCollector.collectedConfig.core = config.coreConfig;
|
|
251
254
|
// Also store in allAnswers for cross-referencing
|
|
252
255
|
this.configCollector.allAnswers = {};
|
|
@@ -257,190 +260,307 @@ class Installer {
|
|
|
257
260
|
|
|
258
261
|
// Collect configurations for modules (skip if quick update already collected them)
|
|
259
262
|
let moduleConfigs;
|
|
263
|
+
let customModulePaths = new Map();
|
|
264
|
+
|
|
260
265
|
if (config._quickUpdate) {
|
|
261
266
|
// Quick update already collected all configs, use them directly
|
|
262
267
|
moduleConfigs = this.configCollector.collectedConfig;
|
|
263
|
-
} else {
|
|
264
|
-
// Regular install - collect configurations (core was already collected in UI.promptInstall if interactive)
|
|
265
|
-
moduleConfigs = await this.configCollector.collectAllConfigurations(config.modules || [], path.resolve(config.directory));
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// Get bmad_folder from config (default to 'bmad' for backwards compatibility)
|
|
269
|
-
const bmadFolderName = moduleConfigs.core && moduleConfigs.core.bmad_folder ? moduleConfigs.core.bmad_folder : 'bmad';
|
|
270
|
-
this.bmadFolderName = bmadFolderName; // Store for use in other methods
|
|
271
|
-
|
|
272
|
-
// Set bmad folder name on module manager and IDE manager for placeholder replacement
|
|
273
|
-
this.moduleManager.setBmadFolderName(bmadFolderName);
|
|
274
|
-
this.ideManager.setBmadFolderName(bmadFolderName);
|
|
275
268
|
|
|
276
|
-
|
|
269
|
+
// For quick update, populate customModulePaths from _customModuleSources
|
|
270
|
+
if (config._customModuleSources) {
|
|
271
|
+
for (const [moduleId, customInfo] of config._customModuleSources) {
|
|
272
|
+
customModulePaths.set(moduleId, customInfo.sourcePath);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
} else {
|
|
276
|
+
// For regular updates (modify flow), check manifest for custom module sources
|
|
277
|
+
if (config._isUpdate && config._existingInstall && config._existingInstall.customModules) {
|
|
278
|
+
for (const customModule of config._existingInstall.customModules) {
|
|
279
|
+
// Ensure we have an absolute sourcePath
|
|
280
|
+
let absoluteSourcePath = customModule.sourcePath;
|
|
281
|
+
|
|
282
|
+
// Check if sourcePath is a cache-relative path (starts with _config)
|
|
283
|
+
if (absoluteSourcePath && absoluteSourcePath.startsWith('_config')) {
|
|
284
|
+
// Convert cache-relative path to absolute path
|
|
285
|
+
absoluteSourcePath = path.join(bmadDir, absoluteSourcePath);
|
|
286
|
+
}
|
|
287
|
+
// If no sourcePath but we have relativePath, convert it
|
|
288
|
+
else if (!absoluteSourcePath && customModule.relativePath) {
|
|
289
|
+
// relativePath is relative to the project root (parent of bmad dir)
|
|
290
|
+
absoluteSourcePath = path.resolve(projectDir, customModule.relativePath);
|
|
291
|
+
}
|
|
292
|
+
// Ensure sourcePath is absolute for anything else
|
|
293
|
+
else if (absoluteSourcePath && !path.isAbsolute(absoluteSourcePath)) {
|
|
294
|
+
absoluteSourcePath = path.resolve(absoluteSourcePath);
|
|
295
|
+
}
|
|
277
296
|
|
|
278
|
-
|
|
297
|
+
if (absoluteSourcePath) {
|
|
298
|
+
customModulePaths.set(customModule.id, absoluteSourcePath);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
279
302
|
|
|
280
|
-
|
|
281
|
-
// Resolve target directory (path.resolve handles platform differences)
|
|
282
|
-
const projectDir = path.resolve(config.directory);
|
|
303
|
+
// Build custom module paths map from customContent
|
|
283
304
|
|
|
284
|
-
//
|
|
285
|
-
|
|
286
|
-
|
|
305
|
+
// Handle selectedFiles (from existing install path or manual directory input)
|
|
306
|
+
if (config.customContent && config.customContent.selected && config.customContent.selectedFiles) {
|
|
307
|
+
const customHandler = new CustomHandler();
|
|
308
|
+
for (const customFile of config.customContent.selectedFiles) {
|
|
309
|
+
const customInfo = await customHandler.getCustomInfo(customFile, path.resolve(config.directory));
|
|
310
|
+
if (customInfo && customInfo.id) {
|
|
311
|
+
customModulePaths.set(customInfo.id, customInfo.path);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
287
315
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
316
|
+
// Handle new custom content sources from UI
|
|
317
|
+
if (config.customContent && config.customContent.sources) {
|
|
318
|
+
for (const source of config.customContent.sources) {
|
|
319
|
+
customModulePaths.set(source.id, source.path);
|
|
320
|
+
}
|
|
291
321
|
}
|
|
292
322
|
|
|
293
|
-
|
|
323
|
+
// Handle cachedModules (from new install path where modules are cached)
|
|
324
|
+
// Only include modules that were actually selected for installation
|
|
325
|
+
if (config.customContent && config.customContent.cachedModules) {
|
|
326
|
+
// Get selected cached module IDs (if available)
|
|
327
|
+
const selectedCachedIds = config.customContent.selectedCachedModules || [];
|
|
328
|
+
// If no selection info, include all cached modules (for backward compatibility)
|
|
329
|
+
const shouldIncludeAll = selectedCachedIds.length === 0 && config.customContent.selected;
|
|
330
|
+
|
|
331
|
+
for (const cachedModule of config.customContent.cachedModules) {
|
|
332
|
+
// For cached modules, the path is the cachePath which contains the module.yaml
|
|
333
|
+
if (
|
|
334
|
+
cachedModule.id &&
|
|
335
|
+
cachedModule.cachePath && // Include if selected or if we should include all
|
|
336
|
+
(shouldIncludeAll || selectedCachedIds.includes(cachedModule.id))
|
|
337
|
+
) {
|
|
338
|
+
customModulePaths.set(cachedModule.id, cachedModule.cachePath);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
294
342
|
|
|
295
|
-
//
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
console.log(chalk.yellow(`\n⚠️ bmad_folder has changed: ${existingBmadFolderName} → ${bmadFolderName}`));
|
|
299
|
-
console.log(chalk.yellow('This will result in a fresh installation to the new folder.'));
|
|
343
|
+
// Get list of all modules including custom modules
|
|
344
|
+
// Order: core first, then official modules, then custom modules
|
|
345
|
+
const allModulesForConfig = ['core'];
|
|
300
346
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
type: 'confirm',
|
|
305
|
-
name: 'confirmFreshInstall',
|
|
306
|
-
message: chalk.cyan('Proceed with fresh install? (Your old folder will be backed up)'),
|
|
307
|
-
default: true,
|
|
308
|
-
},
|
|
309
|
-
]);
|
|
347
|
+
// Add official modules (excluding core and any custom modules)
|
|
348
|
+
const officialModules = (config.modules || []).filter((m) => m !== 'core' && !customModulePaths.has(m));
|
|
349
|
+
allModulesForConfig.push(...officialModules);
|
|
310
350
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
351
|
+
// Add custom modules at the end
|
|
352
|
+
for (const [moduleId] of customModulePaths) {
|
|
353
|
+
if (!allModulesForConfig.includes(moduleId)) {
|
|
354
|
+
allModulesForConfig.push(moduleId);
|
|
314
355
|
}
|
|
356
|
+
}
|
|
315
357
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
//
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
358
|
+
// Check if core was already collected in UI
|
|
359
|
+
if (config.coreConfig && Object.keys(config.coreConfig).length > 0) {
|
|
360
|
+
// Core already collected, skip it in config collection
|
|
361
|
+
const modulesWithoutCore = allModulesForConfig.filter((m) => m !== 'core');
|
|
362
|
+
moduleConfigs = await this.configCollector.collectAllConfigurations(modulesWithoutCore, path.resolve(config.directory), {
|
|
363
|
+
customModulePaths,
|
|
364
|
+
skipPrompts: config.skipPrompts,
|
|
365
|
+
});
|
|
366
|
+
} else {
|
|
367
|
+
// Core not collected yet, include it
|
|
368
|
+
moduleConfigs = await this.configCollector.collectAllConfigurations(allModulesForConfig, path.resolve(config.directory), {
|
|
369
|
+
customModulePaths,
|
|
370
|
+
skipPrompts: config.skipPrompts,
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
}
|
|
325
374
|
|
|
326
|
-
|
|
327
|
-
|
|
375
|
+
// Set bmad folder name on module manager and IDE manager for placeholder replacement
|
|
376
|
+
this.moduleManager.setBmadFolderName(BMAD_FOLDER_NAME);
|
|
377
|
+
this.moduleManager.setCoreConfig(moduleConfigs.core || {});
|
|
378
|
+
this.moduleManager.setCustomModulePaths(customModulePaths);
|
|
379
|
+
this.ideManager.setBmadFolderName(BMAD_FOLDER_NAME);
|
|
328
380
|
|
|
329
|
-
|
|
330
|
-
console.log(chalk.cyan('\n📋 Important:'));
|
|
331
|
-
console.log(chalk.dim(` - Your old installation has been backed up to: ${path.basename(backupDir)}`));
|
|
332
|
-
console.log(chalk.dim(` - If you had custom agents or configurations, copy them from:`));
|
|
333
|
-
console.log(chalk.dim(` ${path.basename(backupDir)}/_cfg/`));
|
|
334
|
-
console.log(chalk.dim(` - To the new location:`));
|
|
335
|
-
console.log(chalk.dim(` ${bmadFolderName}/_cfg/`));
|
|
336
|
-
console.log('');
|
|
381
|
+
// Tool selection will be collected after we determine if it's a reinstall/update/new install
|
|
337
382
|
|
|
338
|
-
|
|
339
|
-
|
|
383
|
+
const spinner = await prompts.spinner();
|
|
384
|
+
spinner.start('Preparing installation...');
|
|
340
385
|
|
|
386
|
+
try {
|
|
341
387
|
// Create a project directory if it doesn't exist (user already confirmed)
|
|
342
388
|
if (!(await fs.pathExists(projectDir))) {
|
|
343
|
-
spinner.
|
|
389
|
+
spinner.message('Creating installation directory...');
|
|
344
390
|
try {
|
|
345
391
|
// fs.ensureDir handles platform-specific directory creation
|
|
346
392
|
// It will recursively create all necessary parent directories
|
|
347
393
|
await fs.ensureDir(projectDir);
|
|
348
394
|
} catch (error) {
|
|
349
|
-
spinner.
|
|
350
|
-
|
|
395
|
+
spinner.error('Failed to create installation directory');
|
|
396
|
+
await prompts.log.error(`Error: ${error.message}`);
|
|
351
397
|
// More detailed error for common issues
|
|
352
398
|
if (error.code === 'EACCES') {
|
|
353
|
-
|
|
399
|
+
await prompts.log.error('Permission denied. Check parent directory permissions.');
|
|
354
400
|
} else if (error.code === 'ENOSPC') {
|
|
355
|
-
|
|
401
|
+
await prompts.log.error('No space left on device.');
|
|
356
402
|
}
|
|
357
403
|
throw new Error(`Cannot create directory: ${projectDir}`);
|
|
358
404
|
}
|
|
359
405
|
}
|
|
360
406
|
|
|
361
|
-
const bmadDir = path.join(projectDir, bmadFolderName);
|
|
362
|
-
|
|
363
407
|
// Check existing installation
|
|
364
|
-
spinner.
|
|
408
|
+
spinner.message('Checking for existing installation...');
|
|
365
409
|
const existingInstall = await this.detector.detect(bmadDir);
|
|
366
410
|
|
|
367
411
|
if (existingInstall.installed && !config.force && !config._quickUpdate) {
|
|
368
|
-
spinner.stop();
|
|
412
|
+
spinner.stop('Existing installation detected');
|
|
369
413
|
|
|
370
414
|
// Check if user already decided what to do (from early menu in ui.js)
|
|
371
415
|
let action = null;
|
|
372
|
-
if (config.
|
|
373
|
-
action = '
|
|
374
|
-
} else if (config.
|
|
416
|
+
if (config.actionType === 'update') {
|
|
417
|
+
action = 'update';
|
|
418
|
+
} else if (config.skipPrompts) {
|
|
419
|
+
// Non-interactive mode: default to update
|
|
375
420
|
action = 'update';
|
|
376
421
|
} else {
|
|
377
422
|
// Fallback: Ask the user (backwards compatibility for other code paths)
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
423
|
+
await prompts.log.warn('Existing BMAD installation detected');
|
|
424
|
+
await prompts.log.message(` Location: ${bmadDir}`);
|
|
425
|
+
await prompts.log.message(` Version: ${existingInstall.version}`);
|
|
381
426
|
|
|
382
427
|
const promptResult = await this.promptUpdateAction();
|
|
383
428
|
action = promptResult.action;
|
|
384
429
|
}
|
|
385
430
|
|
|
386
|
-
if (action === '
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
431
|
+
if (action === 'update') {
|
|
432
|
+
// Store that we're updating for later processing
|
|
433
|
+
config._isUpdate = true;
|
|
434
|
+
config._existingInstall = existingInstall;
|
|
390
435
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
const
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
default: false
|
|
403
|
-
|
|
404
|
-
|
|
436
|
+
// Detect modules that were previously installed but are NOT in the new selection (to be removed)
|
|
437
|
+
const previouslyInstalledModules = new Set(existingInstall.modules.map((m) => m.id));
|
|
438
|
+
const newlySelectedModules = new Set(config.modules || []);
|
|
439
|
+
|
|
440
|
+
// Find modules to remove (installed but not in new selection)
|
|
441
|
+
// Exclude 'core' from being removable
|
|
442
|
+
const modulesToRemove = [...previouslyInstalledModules].filter((m) => !newlySelectedModules.has(m) && m !== 'core');
|
|
443
|
+
|
|
444
|
+
// If there are modules to remove, ask for confirmation
|
|
445
|
+
if (modulesToRemove.length > 0) {
|
|
446
|
+
if (config.skipPrompts) {
|
|
447
|
+
// Non-interactive mode: preserve modules (matches prompt default: false)
|
|
448
|
+
for (const moduleId of modulesToRemove) {
|
|
449
|
+
if (!config.modules) config.modules = [];
|
|
450
|
+
config.modules.push(moduleId);
|
|
451
|
+
}
|
|
452
|
+
spinner.start('Preparing update...');
|
|
453
|
+
} else {
|
|
454
|
+
if (spinner.isSpinning) {
|
|
455
|
+
spinner.stop('Module changes reviewed');
|
|
456
|
+
}
|
|
405
457
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
458
|
+
await prompts.log.warn('Modules to be removed:');
|
|
459
|
+
for (const moduleId of modulesToRemove) {
|
|
460
|
+
const moduleInfo = existingInstall.modules.find((m) => m.id === moduleId);
|
|
461
|
+
const displayName = moduleInfo?.name || moduleId;
|
|
462
|
+
const modulePath = path.join(bmadDir, moduleId);
|
|
463
|
+
await prompts.log.error(` - ${displayName} (${modulePath})`);
|
|
464
|
+
}
|
|
410
465
|
|
|
411
|
-
|
|
412
|
-
|
|
466
|
+
const confirmRemoval = await prompts.confirm({
|
|
467
|
+
message: `Remove ${modulesToRemove.length} module(s) from BMAD installation?`,
|
|
468
|
+
default: false,
|
|
469
|
+
});
|
|
413
470
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
471
|
+
if (confirmRemoval) {
|
|
472
|
+
// Remove module folders
|
|
473
|
+
for (const moduleId of modulesToRemove) {
|
|
474
|
+
const modulePath = path.join(bmadDir, moduleId);
|
|
475
|
+
try {
|
|
476
|
+
if (await fs.pathExists(modulePath)) {
|
|
477
|
+
await fs.remove(modulePath);
|
|
478
|
+
await prompts.log.message(` Removed: ${moduleId}`);
|
|
479
|
+
}
|
|
480
|
+
} catch (error) {
|
|
481
|
+
await prompts.log.warn(` Warning: Failed to remove ${moduleId}: ${error.message}`);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
await prompts.log.success(` Removed ${modulesToRemove.length} module(s)`);
|
|
485
|
+
} else {
|
|
486
|
+
await prompts.log.message(' Module removal cancelled');
|
|
487
|
+
// Add the modules back to the selection since user cancelled removal
|
|
488
|
+
for (const moduleId of modulesToRemove) {
|
|
489
|
+
if (!config.modules) config.modules = [];
|
|
490
|
+
config.modules.push(moduleId);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
417
493
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
// Store that we're updating for later processing
|
|
422
|
-
config._isUpdate = true;
|
|
423
|
-
config._existingInstall = existingInstall;
|
|
494
|
+
spinner.start('Preparing update...');
|
|
495
|
+
}
|
|
496
|
+
}
|
|
424
497
|
|
|
425
498
|
// Detect custom and modified files BEFORE updating (compare current files vs files-manifest.csv)
|
|
426
499
|
const existingFilesManifest = await this.readFilesManifest(bmadDir);
|
|
427
|
-
console.log(chalk.dim(`DEBUG: Read ${existingFilesManifest.length} files from manifest`));
|
|
428
|
-
console.log(chalk.dim(`DEBUG: Manifest has hashes: ${existingFilesManifest.some((f) => f.hash)}`));
|
|
429
|
-
|
|
430
500
|
const { customFiles, modifiedFiles } = await this.detectCustomFiles(bmadDir, existingFilesManifest);
|
|
431
501
|
|
|
432
|
-
console.log(chalk.dim(`DEBUG: Found ${customFiles.length} custom files, ${modifiedFiles.length} modified files`));
|
|
433
|
-
if (modifiedFiles.length > 0) {
|
|
434
|
-
console.log(chalk.yellow('DEBUG: Modified files:'));
|
|
435
|
-
for (const f of modifiedFiles) console.log(chalk.dim(` - ${f.path}`));
|
|
436
|
-
}
|
|
437
|
-
|
|
438
502
|
config._customFiles = customFiles;
|
|
439
503
|
config._modifiedFiles = modifiedFiles;
|
|
440
504
|
|
|
505
|
+
// Preserve existing core configuration during updates
|
|
506
|
+
// Read the current core config.yaml to maintain user's settings
|
|
507
|
+
const coreConfigPath = path.join(bmadDir, 'core', 'config.yaml');
|
|
508
|
+
if ((await fs.pathExists(coreConfigPath)) && (!config.coreConfig || Object.keys(config.coreConfig).length === 0)) {
|
|
509
|
+
try {
|
|
510
|
+
const yaml = require('yaml');
|
|
511
|
+
const coreConfigContent = await fs.readFile(coreConfigPath, 'utf8');
|
|
512
|
+
const existingCoreConfig = yaml.parse(coreConfigContent);
|
|
513
|
+
|
|
514
|
+
// Store in config.coreConfig so it's preserved through the installation
|
|
515
|
+
config.coreConfig = existingCoreConfig;
|
|
516
|
+
|
|
517
|
+
// Also store in configCollector for use during config collection
|
|
518
|
+
this.configCollector.collectedConfig.core = existingCoreConfig;
|
|
519
|
+
} catch (error) {
|
|
520
|
+
await prompts.log.warn(`Warning: Could not read existing core config: ${error.message}`);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// Also check cache directory for custom modules (like quick update does)
|
|
525
|
+
const cacheDir = path.join(bmadDir, '_config', 'custom');
|
|
526
|
+
if (await fs.pathExists(cacheDir)) {
|
|
527
|
+
const cachedModules = await fs.readdir(cacheDir, { withFileTypes: true });
|
|
528
|
+
|
|
529
|
+
for (const cachedModule of cachedModules) {
|
|
530
|
+
const moduleId = cachedModule.name;
|
|
531
|
+
const cachedPath = path.join(cacheDir, moduleId);
|
|
532
|
+
|
|
533
|
+
// Skip if path doesn't exist (broken symlink, deleted dir) - avoids lstat ENOENT
|
|
534
|
+
if (!(await fs.pathExists(cachedPath)) || !cachedModule.isDirectory()) {
|
|
535
|
+
continue;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// Skip if we already have this module from manifest
|
|
539
|
+
if (customModulePaths.has(moduleId)) {
|
|
540
|
+
continue;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// Check if this is an external official module - skip cache for those
|
|
544
|
+
const isExternal = await this.moduleManager.isExternalModule(moduleId);
|
|
545
|
+
if (isExternal) {
|
|
546
|
+
// External modules are handled via cloneExternalModule, not from cache
|
|
547
|
+
continue;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// Check if this is actually a custom module (has module.yaml)
|
|
551
|
+
const moduleYamlPath = path.join(cachedPath, 'module.yaml');
|
|
552
|
+
if (await fs.pathExists(moduleYamlPath)) {
|
|
553
|
+
customModulePaths.set(moduleId, cachedPath);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// Update module manager with the new custom module paths from cache
|
|
558
|
+
this.moduleManager.setCustomModulePaths(customModulePaths);
|
|
559
|
+
}
|
|
560
|
+
|
|
441
561
|
// If there are custom files, back them up temporarily
|
|
442
562
|
if (customFiles.length > 0) {
|
|
443
|
-
const tempBackupDir = path.join(projectDir, '
|
|
563
|
+
const tempBackupDir = path.join(projectDir, '_bmad-custom-backup-temp');
|
|
444
564
|
await fs.ensureDir(tempBackupDir);
|
|
445
565
|
|
|
446
566
|
spinner.start(`Backing up ${customFiles.length} custom files...`);
|
|
@@ -450,35 +570,31 @@ class Installer {
|
|
|
450
570
|
await fs.ensureDir(path.dirname(backupPath));
|
|
451
571
|
await fs.copy(customFile, backupPath);
|
|
452
572
|
}
|
|
453
|
-
spinner.
|
|
573
|
+
spinner.stop(`Backed up ${customFiles.length} custom files`);
|
|
454
574
|
|
|
455
575
|
config._tempBackupDir = tempBackupDir;
|
|
456
576
|
}
|
|
457
577
|
|
|
458
578
|
// For modified files, back them up to temp directory (will be restored as .bak files after install)
|
|
459
579
|
if (modifiedFiles.length > 0) {
|
|
460
|
-
const tempModifiedBackupDir = path.join(projectDir, '
|
|
580
|
+
const tempModifiedBackupDir = path.join(projectDir, '_bmad-modified-backup-temp');
|
|
461
581
|
await fs.ensureDir(tempModifiedBackupDir);
|
|
462
582
|
|
|
463
|
-
console.log(chalk.yellow(`\nDEBUG: Backing up ${modifiedFiles.length} modified files to temp location`));
|
|
464
583
|
spinner.start(`Backing up ${modifiedFiles.length} modified files...`);
|
|
465
584
|
for (const modifiedFile of modifiedFiles) {
|
|
466
585
|
const relativePath = path.relative(bmadDir, modifiedFile.path);
|
|
467
586
|
const tempBackupPath = path.join(tempModifiedBackupDir, relativePath);
|
|
468
|
-
console.log(chalk.dim(`DEBUG: Backing up ${relativePath} to temp`));
|
|
469
587
|
await fs.ensureDir(path.dirname(tempBackupPath));
|
|
470
588
|
await fs.copy(modifiedFile.path, tempBackupPath, { overwrite: true });
|
|
471
589
|
}
|
|
472
|
-
spinner.
|
|
590
|
+
spinner.stop(`Backed up ${modifiedFiles.length} modified files`);
|
|
473
591
|
|
|
474
592
|
config._tempModifiedBackupDir = tempModifiedBackupDir;
|
|
475
|
-
} else {
|
|
476
|
-
console.log(chalk.dim('DEBUG: No modified files detected'));
|
|
477
593
|
}
|
|
478
594
|
}
|
|
479
595
|
} else if (existingInstall.installed && config._quickUpdate) {
|
|
480
596
|
// Quick update mode - automatically treat as update without prompting
|
|
481
|
-
spinner.
|
|
597
|
+
spinner.message('Preparing quick update...');
|
|
482
598
|
config._isUpdate = true;
|
|
483
599
|
config._existingInstall = existingInstall;
|
|
484
600
|
|
|
@@ -489,9 +605,46 @@ class Installer {
|
|
|
489
605
|
config._customFiles = customFiles;
|
|
490
606
|
config._modifiedFiles = modifiedFiles;
|
|
491
607
|
|
|
608
|
+
// Also check cache directory for custom modules (like quick update does)
|
|
609
|
+
const cacheDir = path.join(bmadDir, '_config', 'custom');
|
|
610
|
+
if (await fs.pathExists(cacheDir)) {
|
|
611
|
+
const cachedModules = await fs.readdir(cacheDir, { withFileTypes: true });
|
|
612
|
+
|
|
613
|
+
for (const cachedModule of cachedModules) {
|
|
614
|
+
const moduleId = cachedModule.name;
|
|
615
|
+
const cachedPath = path.join(cacheDir, moduleId);
|
|
616
|
+
|
|
617
|
+
// Skip if path doesn't exist (broken symlink, deleted dir) - avoids lstat ENOENT
|
|
618
|
+
if (!(await fs.pathExists(cachedPath)) || !cachedModule.isDirectory()) {
|
|
619
|
+
continue;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// Skip if we already have this module from manifest
|
|
623
|
+
if (customModulePaths.has(moduleId)) {
|
|
624
|
+
continue;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// Check if this is an external official module - skip cache for those
|
|
628
|
+
const isExternal = await this.moduleManager.isExternalModule(moduleId);
|
|
629
|
+
if (isExternal) {
|
|
630
|
+
// External modules are handled via cloneExternalModule, not from cache
|
|
631
|
+
continue;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// Check if this is actually a custom module (has module.yaml)
|
|
635
|
+
const moduleYamlPath = path.join(cachedPath, 'module.yaml');
|
|
636
|
+
if (await fs.pathExists(moduleYamlPath)) {
|
|
637
|
+
customModulePaths.set(moduleId, cachedPath);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
// Update module manager with the new custom module paths from cache
|
|
642
|
+
this.moduleManager.setCustomModulePaths(customModulePaths);
|
|
643
|
+
}
|
|
644
|
+
|
|
492
645
|
// Back up custom files
|
|
493
646
|
if (customFiles.length > 0) {
|
|
494
|
-
const tempBackupDir = path.join(projectDir, '
|
|
647
|
+
const tempBackupDir = path.join(projectDir, '_bmad-custom-backup-temp');
|
|
495
648
|
await fs.ensureDir(tempBackupDir);
|
|
496
649
|
|
|
497
650
|
spinner.start(`Backing up ${customFiles.length} custom files...`);
|
|
@@ -501,13 +654,13 @@ class Installer {
|
|
|
501
654
|
await fs.ensureDir(path.dirname(backupPath));
|
|
502
655
|
await fs.copy(customFile, backupPath);
|
|
503
656
|
}
|
|
504
|
-
spinner.
|
|
657
|
+
spinner.stop(`Backed up ${customFiles.length} custom files`);
|
|
505
658
|
config._tempBackupDir = tempBackupDir;
|
|
506
659
|
}
|
|
507
660
|
|
|
508
661
|
// Back up modified files
|
|
509
662
|
if (modifiedFiles.length > 0) {
|
|
510
|
-
const tempModifiedBackupDir = path.join(projectDir, '
|
|
663
|
+
const tempModifiedBackupDir = path.join(projectDir, '_bmad-modified-backup-temp');
|
|
511
664
|
await fs.ensureDir(tempModifiedBackupDir);
|
|
512
665
|
|
|
513
666
|
spinner.start(`Backing up ${modifiedFiles.length} modified files...`);
|
|
@@ -517,14 +670,14 @@ class Installer {
|
|
|
517
670
|
await fs.ensureDir(path.dirname(tempBackupPath));
|
|
518
671
|
await fs.copy(modifiedFile.path, tempBackupPath, { overwrite: true });
|
|
519
672
|
}
|
|
520
|
-
spinner.
|
|
673
|
+
spinner.stop(`Backed up ${modifiedFiles.length} modified files`);
|
|
521
674
|
config._tempModifiedBackupDir = tempModifiedBackupDir;
|
|
522
675
|
}
|
|
523
676
|
}
|
|
524
677
|
|
|
525
678
|
// Now collect tool configurations after we know if it's a reinstall
|
|
526
679
|
// Skip for quick update since we already have the IDE list
|
|
527
|
-
spinner.stop();
|
|
680
|
+
spinner.stop('Pre-checks complete');
|
|
528
681
|
let toolSelection;
|
|
529
682
|
if (config._quickUpdate) {
|
|
530
683
|
// Quick update already has IDEs configured, use saved configurations
|
|
@@ -547,13 +700,15 @@ class Installer {
|
|
|
547
700
|
} else {
|
|
548
701
|
// Pass pre-selected IDEs from early prompt (if available)
|
|
549
702
|
// This allows IDE selection to happen before file copying, improving UX
|
|
550
|
-
|
|
703
|
+
// Use config.ides if it's an array (even if empty), null means prompt
|
|
704
|
+
const preSelectedIdes = Array.isArray(config.ides) ? config.ides : null;
|
|
551
705
|
toolSelection = await this.collectToolConfigurations(
|
|
552
706
|
path.resolve(config.directory),
|
|
553
707
|
config.modules,
|
|
554
708
|
config._isFullReinstall || false,
|
|
555
709
|
config._previouslyConfiguredIdes || [],
|
|
556
710
|
preSelectedIdes,
|
|
711
|
+
config.skipPrompts || false,
|
|
557
712
|
);
|
|
558
713
|
}
|
|
559
714
|
|
|
@@ -562,356 +717,802 @@ class Installer {
|
|
|
562
717
|
config.skipIde = toolSelection.skipIde;
|
|
563
718
|
const ideConfigurations = toolSelection.configurations;
|
|
564
719
|
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
720
|
+
// Detect IDEs that were previously installed but are NOT in the new selection (to be removed)
|
|
721
|
+
if (config._isUpdate && config._existingInstall) {
|
|
722
|
+
const previouslyInstalledIdes = new Set(config._existingInstall.ides || []);
|
|
723
|
+
const newlySelectedIdes = new Set(config.ides || []);
|
|
724
|
+
|
|
725
|
+
const idesToRemove = [...previouslyInstalledIdes].filter((ide) => !newlySelectedIdes.has(ide));
|
|
726
|
+
|
|
727
|
+
if (idesToRemove.length > 0) {
|
|
728
|
+
if (config.skipPrompts) {
|
|
729
|
+
// Non-interactive mode: silently preserve existing IDE configs
|
|
730
|
+
if (!config.ides) config.ides = [];
|
|
731
|
+
const savedIdeConfigs = await this.ideConfigManager.loadAllIdeConfigs(bmadDir);
|
|
732
|
+
for (const ide of idesToRemove) {
|
|
733
|
+
config.ides.push(ide);
|
|
734
|
+
if (savedIdeConfigs[ide] && !ideConfigurations[ide]) {
|
|
735
|
+
ideConfigurations[ide] = savedIdeConfigs[ide];
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
} else {
|
|
739
|
+
if (spinner.isSpinning) {
|
|
740
|
+
spinner.stop('IDE changes reviewed');
|
|
741
|
+
}
|
|
584
742
|
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
spinner.succeed('Core installed');
|
|
590
|
-
}
|
|
743
|
+
await prompts.log.warn('IDEs to be removed:');
|
|
744
|
+
for (const ide of idesToRemove) {
|
|
745
|
+
await prompts.log.error(` - ${ide}`);
|
|
746
|
+
}
|
|
591
747
|
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
await this.installModuleWithDependencies(moduleName, bmadDir, resolution.byModule[moduleName]);
|
|
597
|
-
spinner.succeed(`Module installed: ${moduleName}`);
|
|
598
|
-
}
|
|
748
|
+
const confirmRemoval = await prompts.confirm({
|
|
749
|
+
message: `Remove BMAD configuration for ${idesToRemove.length} IDE(s)?`,
|
|
750
|
+
default: false,
|
|
751
|
+
});
|
|
599
752
|
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
753
|
+
if (confirmRemoval) {
|
|
754
|
+
await this.ideManager.ensureInitialized();
|
|
755
|
+
for (const ide of idesToRemove) {
|
|
756
|
+
try {
|
|
757
|
+
const handler = this.ideManager.handlers.get(ide);
|
|
758
|
+
if (handler) {
|
|
759
|
+
await handler.cleanup(projectDir);
|
|
760
|
+
}
|
|
761
|
+
await this.ideConfigManager.deleteIdeConfig(bmadDir, ide);
|
|
762
|
+
await prompts.log.message(` Removed: ${ide}`);
|
|
763
|
+
} catch (error) {
|
|
764
|
+
await prompts.log.warn(` Warning: Failed to remove ${ide}: ${error.message}`);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
await prompts.log.success(` Removed ${idesToRemove.length} IDE(s)`);
|
|
768
|
+
} else {
|
|
769
|
+
await prompts.log.message(' IDE removal cancelled');
|
|
770
|
+
// Add IDEs back to selection and restore their saved configurations
|
|
771
|
+
if (!config.ides) config.ides = [];
|
|
772
|
+
const savedIdeConfigs = await this.ideConfigManager.loadAllIdeConfigs(bmadDir);
|
|
773
|
+
for (const ide of idesToRemove) {
|
|
774
|
+
config.ides.push(ide);
|
|
775
|
+
if (savedIdeConfigs[ide] && !ideConfigurations[ide]) {
|
|
776
|
+
ideConfigurations[ide] = savedIdeConfigs[ide];
|
|
777
|
+
}
|
|
778
|
+
}
|
|
614
779
|
}
|
|
780
|
+
|
|
781
|
+
spinner.start('Preparing installation...');
|
|
615
782
|
}
|
|
616
783
|
}
|
|
617
784
|
}
|
|
618
785
|
|
|
619
|
-
//
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
spinner.succeed('Module configurations generated');
|
|
786
|
+
// Results collector for consolidated summary
|
|
787
|
+
const results = [];
|
|
788
|
+
const addResult = (step, status, detail = '') => results.push({ step, status, detail });
|
|
623
789
|
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
790
|
+
if (spinner.isSpinning) {
|
|
791
|
+
spinner.message('Preparing installation...');
|
|
792
|
+
} else {
|
|
793
|
+
spinner.start('Preparing installation...');
|
|
794
|
+
}
|
|
627
795
|
|
|
628
|
-
//
|
|
629
|
-
|
|
630
|
-
this.
|
|
631
|
-
path.join(cfgDir, 'manifest.yaml'),
|
|
632
|
-
path.join(cfgDir, 'workflow-manifest.csv'),
|
|
633
|
-
path.join(cfgDir, 'agent-manifest.csv'),
|
|
634
|
-
path.join(cfgDir, 'task-manifest.csv'),
|
|
635
|
-
);
|
|
796
|
+
// Create bmad directory structure
|
|
797
|
+
spinner.message('Creating directory structure...');
|
|
798
|
+
await this.createDirectoryStructure(bmadDir);
|
|
636
799
|
|
|
637
|
-
//
|
|
638
|
-
|
|
639
|
-
|
|
800
|
+
// Cache custom modules if any
|
|
801
|
+
if (customModulePaths && customModulePaths.size > 0) {
|
|
802
|
+
spinner.message('Caching custom modules...');
|
|
803
|
+
const { CustomModuleCache } = require('./custom-module-cache');
|
|
804
|
+
const customCache = new CustomModuleCache(bmadDir);
|
|
640
805
|
|
|
641
|
-
|
|
642
|
-
|
|
806
|
+
for (const [moduleId, sourcePath] of customModulePaths) {
|
|
807
|
+
const cachedInfo = await customCache.cacheModule(moduleId, sourcePath, {
|
|
808
|
+
sourcePath: sourcePath, // Store original path for updates
|
|
809
|
+
});
|
|
643
810
|
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
});
|
|
811
|
+
// Update the customModulePaths to use the cached location
|
|
812
|
+
customModulePaths.set(moduleId, cachedInfo.cachePath);
|
|
813
|
+
}
|
|
648
814
|
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
815
|
+
// Update module manager with the cached paths
|
|
816
|
+
this.moduleManager.setCustomModulePaths(customModulePaths);
|
|
817
|
+
addResult('Custom modules cached', 'ok');
|
|
818
|
+
}
|
|
652
819
|
|
|
653
|
-
|
|
654
|
-
if (!config.skipIde && config.ides && config.ides.length > 0) {
|
|
655
|
-
// Filter out any undefined/null values from the IDE list
|
|
656
|
-
const validIdes = config.ides.filter((ide) => ide && typeof ide === 'string');
|
|
820
|
+
const projectRoot = getProjectRoot();
|
|
657
821
|
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
} else {
|
|
661
|
-
// Check if any IDE might need prompting (no pre-collected config)
|
|
662
|
-
const needsPrompting = validIdes.some((ide) => !ideConfigurations[ide]);
|
|
822
|
+
// Custom content is already handled in UI before module selection
|
|
823
|
+
const finalCustomContent = config.customContent;
|
|
663
824
|
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
}
|
|
825
|
+
// Prepare modules list including cached custom modules
|
|
826
|
+
let allModules = [...(config.modules || [])];
|
|
667
827
|
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
828
|
+
// During quick update, we might have custom module sources from the manifest
|
|
829
|
+
if (config._customModuleSources) {
|
|
830
|
+
// Add custom modules from stored sources
|
|
831
|
+
for (const [moduleId, customInfo] of config._customModuleSources) {
|
|
832
|
+
if (!allModules.includes(moduleId) && (await fs.pathExists(customInfo.sourcePath))) {
|
|
833
|
+
allModules.push(moduleId);
|
|
672
834
|
}
|
|
835
|
+
}
|
|
836
|
+
}
|
|
673
837
|
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
// Stop spinner before prompting
|
|
680
|
-
if (spinner.isSpinning) {
|
|
681
|
-
spinner.stop();
|
|
682
|
-
}
|
|
683
|
-
console.log(chalk.cyan(`\nConfiguring ${ide}...`));
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
// Pass pre-collected configuration to avoid re-prompting
|
|
687
|
-
await this.ideManager.setup(ide, projectDir, bmadDir, {
|
|
688
|
-
selectedModules: config.modules || [],
|
|
689
|
-
preCollectedConfig: ideConfigurations[ide] || null,
|
|
690
|
-
verbose: config.verbose,
|
|
691
|
-
});
|
|
692
|
-
|
|
693
|
-
// Save IDE configuration for future updates
|
|
694
|
-
if (ideConfigurations[ide] && !ideConfigurations[ide]._alreadyConfigured) {
|
|
695
|
-
await this.ideConfigManager.saveIdeConfig(bmadDir, ide, ideConfigurations[ide]);
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
// Restart spinner if we stopped it
|
|
699
|
-
if (!ideConfigurations[ide] && !spinner.isSpinning) {
|
|
700
|
-
spinner.start('Configuring IDEs...');
|
|
701
|
-
}
|
|
838
|
+
// Add cached custom modules
|
|
839
|
+
if (finalCustomContent && finalCustomContent.cachedModules) {
|
|
840
|
+
for (const cachedModule of finalCustomContent.cachedModules) {
|
|
841
|
+
if (!allModules.includes(cachedModule.id)) {
|
|
842
|
+
allModules.push(cachedModule.id);
|
|
702
843
|
}
|
|
844
|
+
}
|
|
845
|
+
}
|
|
703
846
|
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
847
|
+
// Regular custom content from user input (non-cached)
|
|
848
|
+
if (finalCustomContent && finalCustomContent.selected && finalCustomContent.selectedFiles) {
|
|
849
|
+
// Add custom modules to the installation list
|
|
850
|
+
const customHandler = new CustomHandler();
|
|
851
|
+
for (const customFile of finalCustomContent.selectedFiles) {
|
|
852
|
+
const customInfo = await customHandler.getCustomInfo(customFile, projectDir);
|
|
853
|
+
if (customInfo && customInfo.id) {
|
|
854
|
+
allModules.push(customInfo.id);
|
|
711
855
|
}
|
|
712
856
|
}
|
|
857
|
+
}
|
|
713
858
|
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
spinner.start('Copying IDE documentation...');
|
|
718
|
-
await this.copyIdeDocumentation(validIdesForDocs, bmadDir);
|
|
719
|
-
spinner.succeed('IDE documentation copied');
|
|
720
|
-
}
|
|
859
|
+
// Don't include core again if already installed
|
|
860
|
+
if (config.installCore) {
|
|
861
|
+
allModules = allModules.filter((m) => m !== 'core');
|
|
721
862
|
}
|
|
722
863
|
|
|
723
|
-
//
|
|
724
|
-
|
|
864
|
+
// For dependency resolution, we only need regular modules (not custom modules)
|
|
865
|
+
// Custom modules are already installed in _bmad and don't need dependency resolution from source
|
|
866
|
+
const regularModulesForResolution = allModules.filter((module) => {
|
|
867
|
+
// Check if this is a custom module
|
|
868
|
+
const isCustom =
|
|
869
|
+
customModulePaths.has(module) ||
|
|
870
|
+
(finalCustomContent && finalCustomContent.cachedModules && finalCustomContent.cachedModules.some((cm) => cm.id === module)) ||
|
|
871
|
+
(finalCustomContent &&
|
|
872
|
+
finalCustomContent.selected &&
|
|
873
|
+
finalCustomContent.selectedFiles &&
|
|
874
|
+
finalCustomContent.selectedFiles.some((f) => f.includes(module)));
|
|
875
|
+
return !isCustom;
|
|
876
|
+
});
|
|
877
|
+
|
|
878
|
+
// Stop spinner before tasks() takes over progress display
|
|
879
|
+
spinner.stop('Preparation complete');
|
|
880
|
+
|
|
881
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
882
|
+
// FIRST TASKS BLOCK: Core installation through manifests (non-interactive)
|
|
883
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
884
|
+
const isQuickUpdate = config._quickUpdate || false;
|
|
725
885
|
|
|
726
|
-
//
|
|
727
|
-
|
|
728
|
-
spinner.text = 'Running core module installer...';
|
|
886
|
+
// Shared resolution result across task callbacks (closure-scoped, not on `this`)
|
|
887
|
+
let taskResolution;
|
|
729
888
|
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
889
|
+
// Collect directory creation results for output after tasks() completes
|
|
890
|
+
const dirResults = { createdDirs: [], movedDirs: [], createdWdsFolders: [] };
|
|
891
|
+
|
|
892
|
+
// Build task list conditionally
|
|
893
|
+
const installTasks = [];
|
|
894
|
+
|
|
895
|
+
// Core installation task
|
|
896
|
+
if (config.installCore) {
|
|
897
|
+
installTasks.push({
|
|
898
|
+
title: isQuickUpdate ? 'Updating BMAD core' : 'Installing BMAD core',
|
|
899
|
+
task: async (message) => {
|
|
900
|
+
await this.installCoreWithDependencies(bmadDir, { core: {} });
|
|
901
|
+
addResult('Core', 'ok', isQuickUpdate ? 'updated' : 'installed');
|
|
902
|
+
await this.generateModuleConfigs(bmadDir, { core: config.coreConfig || {} });
|
|
903
|
+
return isQuickUpdate ? 'Core updated' : 'Core installed';
|
|
737
904
|
},
|
|
738
905
|
});
|
|
739
906
|
}
|
|
740
907
|
|
|
741
|
-
//
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
908
|
+
// Dependency resolution task
|
|
909
|
+
installTasks.push({
|
|
910
|
+
title: 'Resolving dependencies',
|
|
911
|
+
task: async (message) => {
|
|
912
|
+
// Create a temporary module manager that knows about custom content locations
|
|
913
|
+
const tempModuleManager = new ModuleManager({
|
|
914
|
+
bmadDir: bmadDir,
|
|
915
|
+
});
|
|
745
916
|
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
moduleConfig: moduleConfigs[moduleName] || {},
|
|
750
|
-
logger: {
|
|
751
|
-
log: (msg) => console.log(msg),
|
|
752
|
-
error: (msg) => console.error(msg),
|
|
753
|
-
warn: (msg) => console.warn(msg),
|
|
754
|
-
},
|
|
917
|
+
taskResolution = await this.dependencyResolver.resolve(projectRoot, regularModulesForResolution, {
|
|
918
|
+
verbose: config.verbose,
|
|
919
|
+
moduleManager: tempModuleManager,
|
|
755
920
|
});
|
|
756
|
-
|
|
757
|
-
|
|
921
|
+
return 'Dependencies resolved';
|
|
922
|
+
},
|
|
923
|
+
});
|
|
924
|
+
|
|
925
|
+
// Module installation task
|
|
926
|
+
if (allModules && allModules.length > 0) {
|
|
927
|
+
installTasks.push({
|
|
928
|
+
title: isQuickUpdate ? `Updating ${allModules.length} module(s)` : `Installing ${allModules.length} module(s)`,
|
|
929
|
+
task: async (message) => {
|
|
930
|
+
const resolution = taskResolution;
|
|
931
|
+
const installedModuleNames = new Set();
|
|
932
|
+
|
|
933
|
+
for (const moduleName of allModules) {
|
|
934
|
+
if (installedModuleNames.has(moduleName)) continue;
|
|
935
|
+
installedModuleNames.add(moduleName);
|
|
936
|
+
|
|
937
|
+
message(`${isQuickUpdate ? 'Updating' : 'Installing'} ${moduleName}...`);
|
|
938
|
+
|
|
939
|
+
// Check if this is a custom module
|
|
940
|
+
let isCustomModule = false;
|
|
941
|
+
let customInfo = null;
|
|
942
|
+
|
|
943
|
+
// First check if we have a cached version
|
|
944
|
+
if (finalCustomContent && finalCustomContent.cachedModules) {
|
|
945
|
+
const cachedModule = finalCustomContent.cachedModules.find((m) => m.id === moduleName);
|
|
946
|
+
if (cachedModule) {
|
|
947
|
+
isCustomModule = true;
|
|
948
|
+
customInfo = { id: moduleName, path: cachedModule.cachePath, config: {} };
|
|
949
|
+
}
|
|
950
|
+
}
|
|
758
951
|
|
|
759
|
-
|
|
952
|
+
// Then check custom module sources from manifest (for quick update)
|
|
953
|
+
if (!isCustomModule && config._customModuleSources && config._customModuleSources.has(moduleName)) {
|
|
954
|
+
customInfo = config._customModuleSources.get(moduleName);
|
|
955
|
+
isCustomModule = true;
|
|
956
|
+
if (customInfo.sourcePath && !customInfo.path) {
|
|
957
|
+
customInfo.path = path.isAbsolute(customInfo.sourcePath)
|
|
958
|
+
? customInfo.sourcePath
|
|
959
|
+
: path.join(bmadDir, customInfo.sourcePath);
|
|
960
|
+
}
|
|
961
|
+
}
|
|
760
962
|
|
|
761
|
-
|
|
762
|
-
|
|
963
|
+
// Finally check regular custom content
|
|
964
|
+
if (!isCustomModule && finalCustomContent && finalCustomContent.selected && finalCustomContent.selectedFiles) {
|
|
965
|
+
const customHandler = new CustomHandler();
|
|
966
|
+
for (const customFile of finalCustomContent.selectedFiles) {
|
|
967
|
+
const info = await customHandler.getCustomInfo(customFile, projectDir);
|
|
968
|
+
if (info && info.id === moduleName) {
|
|
969
|
+
isCustomModule = true;
|
|
970
|
+
customInfo = info;
|
|
971
|
+
break;
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
}
|
|
763
975
|
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
spinner.start(`Restoring ${config._customFiles.length} custom files...`);
|
|
976
|
+
if (isCustomModule && customInfo) {
|
|
977
|
+
if (!customModulePaths.has(moduleName) && customInfo.path) {
|
|
978
|
+
customModulePaths.set(moduleName, customInfo.path);
|
|
979
|
+
this.moduleManager.setCustomModulePaths(customModulePaths);
|
|
980
|
+
}
|
|
770
981
|
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
982
|
+
const collectedModuleConfig = moduleConfigs[moduleName] || {};
|
|
983
|
+
await this.moduleManager.install(
|
|
984
|
+
moduleName,
|
|
985
|
+
bmadDir,
|
|
986
|
+
(filePath) => {
|
|
987
|
+
this.installedFiles.add(filePath);
|
|
988
|
+
},
|
|
989
|
+
{
|
|
990
|
+
isCustom: true,
|
|
991
|
+
moduleConfig: collectedModuleConfig,
|
|
992
|
+
isQuickUpdate: isQuickUpdate,
|
|
993
|
+
installer: this,
|
|
994
|
+
silent: true,
|
|
995
|
+
},
|
|
996
|
+
);
|
|
997
|
+
await this.generateModuleConfigs(bmadDir, {
|
|
998
|
+
[moduleName]: { ...config.coreConfig, ...customInfo.config, ...collectedModuleConfig },
|
|
999
|
+
});
|
|
1000
|
+
} else {
|
|
1001
|
+
if (!resolution || !resolution.byModule) {
|
|
1002
|
+
addResult(`Module: ${moduleName}`, 'warn', 'skipped (no resolution data)');
|
|
1003
|
+
continue;
|
|
1004
|
+
}
|
|
1005
|
+
if (moduleName === 'core') {
|
|
1006
|
+
await this.installCoreWithDependencies(bmadDir, resolution.byModule[moduleName]);
|
|
1007
|
+
} else {
|
|
1008
|
+
await this.installModuleWithDependencies(moduleName, bmadDir, resolution.byModule[moduleName]);
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
774
1011
|
|
|
775
|
-
|
|
776
|
-
await fs.ensureDir(path.dirname(originalPath));
|
|
777
|
-
await fs.copy(backupPath, originalPath, { overwrite: true });
|
|
1012
|
+
addResult(`Module: ${moduleName}`, 'ok', isQuickUpdate ? 'updated' : 'installed');
|
|
778
1013
|
}
|
|
779
|
-
}
|
|
780
1014
|
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
1015
|
+
// Install partial modules (only dependencies)
|
|
1016
|
+
if (!resolution || !resolution.byModule) {
|
|
1017
|
+
return `${allModules.length} module(s) ${isQuickUpdate ? 'updated' : 'installed'}`;
|
|
1018
|
+
}
|
|
1019
|
+
for (const [module, files] of Object.entries(resolution.byModule)) {
|
|
1020
|
+
if (!allModules.includes(module) && module !== 'core') {
|
|
1021
|
+
const totalFiles =
|
|
1022
|
+
files.agents.length +
|
|
1023
|
+
files.tasks.length +
|
|
1024
|
+
files.tools.length +
|
|
1025
|
+
files.templates.length +
|
|
1026
|
+
files.data.length +
|
|
1027
|
+
files.other.length;
|
|
1028
|
+
if (totalFiles > 0) {
|
|
1029
|
+
message(`Installing ${module} dependencies...`);
|
|
1030
|
+
await this.installPartialModule(module, bmadDir, files);
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
789
1034
|
|
|
790
|
-
|
|
791
|
-
|
|
1035
|
+
return `${allModules.length} module(s) ${isQuickUpdate ? 'updated' : 'installed'}`;
|
|
1036
|
+
},
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
792
1039
|
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
1040
|
+
// Module directory creation task
|
|
1041
|
+
installTasks.push({
|
|
1042
|
+
title: 'Creating module directories',
|
|
1043
|
+
task: async (message) => {
|
|
1044
|
+
const resolution = taskResolution;
|
|
1045
|
+
if (!resolution || !resolution.byModule) {
|
|
1046
|
+
addResult('Module directories', 'warn', 'no resolution data');
|
|
1047
|
+
return 'Module directories skipped (no resolution data)';
|
|
1048
|
+
}
|
|
1049
|
+
const verboseMode = process.env.BMAD_VERBOSE_INSTALL === 'true' || config.verbose;
|
|
1050
|
+
const moduleLogger = {
|
|
1051
|
+
log: async (msg) => (verboseMode ? await prompts.log.message(msg) : undefined),
|
|
1052
|
+
error: async (msg) => await prompts.log.error(msg),
|
|
1053
|
+
warn: async (msg) => await prompts.log.warn(msg),
|
|
1054
|
+
};
|
|
796
1055
|
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
1056
|
+
// Core module directories
|
|
1057
|
+
if (config.installCore || resolution.byModule.core) {
|
|
1058
|
+
const result = await this.moduleManager.createModuleDirectories('core', bmadDir, {
|
|
1059
|
+
installedIDEs: config.ides || [],
|
|
1060
|
+
moduleConfig: moduleConfigs.core || {},
|
|
1061
|
+
existingModuleConfig: this.configCollector.existingConfig?.core || {},
|
|
1062
|
+
coreConfig: moduleConfigs.core || {},
|
|
1063
|
+
logger: moduleLogger,
|
|
1064
|
+
silent: true,
|
|
1065
|
+
});
|
|
1066
|
+
if (result) {
|
|
1067
|
+
dirResults.createdDirs.push(...result.createdDirs);
|
|
1068
|
+
dirResults.movedDirs.push(...(result.movedDirs || []));
|
|
1069
|
+
dirResults.createdWdsFolders.push(...result.createdWdsFolders);
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
801
1072
|
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
1073
|
+
// User-selected module directories
|
|
1074
|
+
if (config.modules && config.modules.length > 0) {
|
|
1075
|
+
for (const moduleName of config.modules) {
|
|
1076
|
+
message(`Setting up ${moduleName}...`);
|
|
1077
|
+
const result = await this.moduleManager.createModuleDirectories(moduleName, bmadDir, {
|
|
1078
|
+
installedIDEs: config.ides || [],
|
|
1079
|
+
moduleConfig: moduleConfigs[moduleName] || {},
|
|
1080
|
+
existingModuleConfig: this.configCollector.existingConfig?.[moduleName] || {},
|
|
1081
|
+
coreConfig: moduleConfigs.core || {},
|
|
1082
|
+
logger: moduleLogger,
|
|
1083
|
+
silent: true,
|
|
1084
|
+
});
|
|
1085
|
+
if (result) {
|
|
1086
|
+
dirResults.createdDirs.push(...result.createdDirs);
|
|
1087
|
+
dirResults.movedDirs.push(...(result.movedDirs || []));
|
|
1088
|
+
dirResults.createdWdsFolders.push(...result.createdWdsFolders);
|
|
805
1089
|
}
|
|
806
1090
|
}
|
|
1091
|
+
}
|
|
807
1092
|
|
|
808
|
-
|
|
809
|
-
|
|
1093
|
+
addResult('Module directories', 'ok');
|
|
1094
|
+
return 'Module directories created';
|
|
1095
|
+
},
|
|
1096
|
+
});
|
|
810
1097
|
|
|
811
|
-
|
|
1098
|
+
// Configuration generation task (stored as named reference for deferred execution)
|
|
1099
|
+
const configTask = {
|
|
1100
|
+
title: 'Generating configurations',
|
|
1101
|
+
task: async (message) => {
|
|
1102
|
+
// Generate clean config.yaml files for each installed module
|
|
1103
|
+
await this.generateModuleConfigs(bmadDir, moduleConfigs);
|
|
1104
|
+
addResult('Configurations', 'ok', 'generated');
|
|
1105
|
+
|
|
1106
|
+
// Pre-register manifest files
|
|
1107
|
+
const cfgDir = path.join(bmadDir, '_config');
|
|
1108
|
+
this.installedFiles.add(path.join(cfgDir, 'manifest.yaml'));
|
|
1109
|
+
this.installedFiles.add(path.join(cfgDir, 'workflow-manifest.csv'));
|
|
1110
|
+
this.installedFiles.add(path.join(cfgDir, 'agent-manifest.csv'));
|
|
1111
|
+
this.installedFiles.add(path.join(cfgDir, 'task-manifest.csv'));
|
|
1112
|
+
|
|
1113
|
+
// Generate CSV manifests for workflows, agents, tasks AND ALL FILES with hashes
|
|
1114
|
+
// This must happen BEFORE mergeModuleHelpCatalogs because it depends on agent-manifest.csv
|
|
1115
|
+
message('Generating manifests...');
|
|
1116
|
+
const manifestGen = new ManifestGenerator();
|
|
1117
|
+
|
|
1118
|
+
const allModulesForManifest = config._quickUpdate
|
|
1119
|
+
? config._existingModules || allModules || []
|
|
1120
|
+
: config._preserveModules
|
|
1121
|
+
? [...allModules, ...config._preserveModules]
|
|
1122
|
+
: allModules || [];
|
|
1123
|
+
|
|
1124
|
+
let modulesForCsvPreserve;
|
|
1125
|
+
if (config._quickUpdate) {
|
|
1126
|
+
modulesForCsvPreserve = config._existingModules || allModules || [];
|
|
1127
|
+
} else {
|
|
1128
|
+
modulesForCsvPreserve = config._preserveModules ? [...allModules, ...config._preserveModules] : allModules;
|
|
812
1129
|
}
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
1130
|
|
|
816
|
-
|
|
1131
|
+
const manifestStats = await manifestGen.generateManifests(bmadDir, allModulesForManifest, [...this.installedFiles], {
|
|
1132
|
+
ides: config.ides || [],
|
|
1133
|
+
preservedModules: modulesForCsvPreserve,
|
|
1134
|
+
});
|
|
817
1135
|
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
console.log(chalk.dim(` - ${path.relative(bmadDir, file)}`));
|
|
824
|
-
}
|
|
825
|
-
console.log('');
|
|
826
|
-
}
|
|
1136
|
+
addResult(
|
|
1137
|
+
'Manifests',
|
|
1138
|
+
'ok',
|
|
1139
|
+
`${manifestStats.workflows} workflows, ${manifestStats.agents} agents, ${manifestStats.tasks} tasks, ${manifestStats.tools} tools`,
|
|
1140
|
+
);
|
|
827
1141
|
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
console.log(chalk.dim(` - ${file.relativePath} → ${file.relativePath}.bak`));
|
|
833
|
-
}
|
|
834
|
-
console.log(chalk.dim('\nThese files have been updated with the new version.'));
|
|
835
|
-
console.log(chalk.dim('Review the .bak files to see your changes and merge if needed.\n'));
|
|
836
|
-
}
|
|
1142
|
+
// Merge help catalogs
|
|
1143
|
+
message('Generating help catalog...');
|
|
1144
|
+
await this.mergeModuleHelpCatalogs(bmadDir);
|
|
1145
|
+
addResult('Help catalog', 'ok');
|
|
837
1146
|
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
path: bmadDir,
|
|
843
|
-
modules: config.modules,
|
|
844
|
-
ides: config.ides,
|
|
845
|
-
customFiles: customFiles.length > 0 ? customFiles : undefined,
|
|
846
|
-
});
|
|
1147
|
+
return 'Configurations generated';
|
|
1148
|
+
},
|
|
1149
|
+
};
|
|
1150
|
+
installTasks.push(configTask);
|
|
847
1151
|
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
1152
|
+
// Run all tasks except config (which runs after directory output)
|
|
1153
|
+
const mainTasks = installTasks.filter((t) => t !== configTask);
|
|
1154
|
+
await prompts.tasks(mainTasks);
|
|
1155
|
+
|
|
1156
|
+
// Render directory creation output right after directory task
|
|
1157
|
+
const color = await prompts.getColor();
|
|
1158
|
+
if (dirResults.movedDirs.length > 0) {
|
|
1159
|
+
const lines = dirResults.movedDirs.map((d) => ` ${d}`).join('\n');
|
|
1160
|
+
await prompts.log.message(color.cyan(`Moved directories:\n${lines}`));
|
|
1161
|
+
}
|
|
1162
|
+
if (dirResults.createdDirs.length > 0) {
|
|
1163
|
+
const lines = dirResults.createdDirs.map((d) => ` ${d}`).join('\n');
|
|
1164
|
+
await prompts.log.message(color.yellow(`Created directories:\n${lines}`));
|
|
1165
|
+
}
|
|
1166
|
+
if (dirResults.createdWdsFolders.length > 0) {
|
|
1167
|
+
const lines = dirResults.createdWdsFolders.map((f) => color.dim(` \u2713 ${f}/`)).join('\n');
|
|
1168
|
+
await prompts.log.message(color.cyan(`Created WDS folder structure:\n${lines}`));
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
// Now run configuration generation
|
|
1172
|
+
await prompts.tasks([configTask]);
|
|
1173
|
+
|
|
1174
|
+
// Resolution is now available via closure-scoped taskResolution
|
|
1175
|
+
const resolution = taskResolution;
|
|
1176
|
+
|
|
1177
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1178
|
+
// IDE SETUP: Keep as spinner since it may prompt for user input
|
|
1179
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1180
|
+
if (!config.skipIde && config.ides && config.ides.length > 0) {
|
|
1181
|
+
await this.ideManager.ensureInitialized();
|
|
1182
|
+
const validIdes = config.ides.filter((ide) => ide && typeof ide === 'string');
|
|
1183
|
+
|
|
1184
|
+
if (validIdes.length === 0) {
|
|
1185
|
+
addResult('IDE configuration', 'warn', 'no valid IDEs selected');
|
|
1186
|
+
} else {
|
|
1187
|
+
const needsPrompting = validIdes.some((ide) => !ideConfigurations[ide]);
|
|
1188
|
+
const ideSpinner = await prompts.spinner();
|
|
1189
|
+
ideSpinner.start('Configuring tools...');
|
|
1190
|
+
|
|
1191
|
+
try {
|
|
1192
|
+
for (const ide of validIdes) {
|
|
1193
|
+
if (!needsPrompting || ideConfigurations[ide]) {
|
|
1194
|
+
ideSpinner.message(`Configuring ${ide}...`);
|
|
1195
|
+
} else {
|
|
1196
|
+
if (ideSpinner.isSpinning) {
|
|
1197
|
+
ideSpinner.stop('Ready for IDE configuration');
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
// Suppress stray console output for pre-configured IDEs (no user interaction)
|
|
1202
|
+
const ideHasConfig = Boolean(ideConfigurations[ide]);
|
|
1203
|
+
const originalLog = console.log;
|
|
1204
|
+
if (!config.verbose && ideHasConfig) {
|
|
1205
|
+
console.log = () => {};
|
|
1206
|
+
}
|
|
1207
|
+
try {
|
|
1208
|
+
const setupResult = await this.ideManager.setup(ide, projectDir, bmadDir, {
|
|
1209
|
+
selectedModules: allModules || [],
|
|
1210
|
+
preCollectedConfig: ideConfigurations[ide] || null,
|
|
1211
|
+
verbose: config.verbose,
|
|
1212
|
+
silent: ideHasConfig,
|
|
1213
|
+
});
|
|
1214
|
+
|
|
1215
|
+
if (ideConfigurations[ide] && !ideConfigurations[ide]._alreadyConfigured) {
|
|
1216
|
+
await this.ideConfigManager.saveIdeConfig(bmadDir, ide, ideConfigurations[ide]);
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
if (setupResult.success) {
|
|
1220
|
+
addResult(ide, 'ok', setupResult.detail || '');
|
|
1221
|
+
} else {
|
|
1222
|
+
addResult(ide, 'error', setupResult.error || 'failed');
|
|
1223
|
+
}
|
|
1224
|
+
} finally {
|
|
1225
|
+
console.log = originalLog;
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
if (needsPrompting && !ideSpinner.isSpinning) {
|
|
1229
|
+
ideSpinner.start('Configuring tools...');
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
} finally {
|
|
1233
|
+
if (ideSpinner.isSpinning) {
|
|
1234
|
+
ideSpinner.stop('Tool configuration complete');
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1241
|
+
// SECOND TASKS BLOCK: Post-IDE operations (non-interactive)
|
|
1242
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1243
|
+
const postIdeTasks = [];
|
|
1244
|
+
|
|
1245
|
+
// File restoration task (only for updates)
|
|
1246
|
+
if (
|
|
1247
|
+
config._isUpdate &&
|
|
1248
|
+
((config._customFiles && config._customFiles.length > 0) || (config._modifiedFiles && config._modifiedFiles.length > 0))
|
|
1249
|
+
) {
|
|
1250
|
+
postIdeTasks.push({
|
|
1251
|
+
title: 'Finalizing installation',
|
|
1252
|
+
task: async (message) => {
|
|
1253
|
+
let customFiles = [];
|
|
1254
|
+
let modifiedFiles = [];
|
|
1255
|
+
|
|
1256
|
+
if (config._customFiles && config._customFiles.length > 0) {
|
|
1257
|
+
message(`Restoring ${config._customFiles.length} custom files...`);
|
|
1258
|
+
|
|
1259
|
+
for (const originalPath of config._customFiles) {
|
|
1260
|
+
const relativePath = path.relative(bmadDir, originalPath);
|
|
1261
|
+
const backupPath = path.join(config._tempBackupDir, relativePath);
|
|
1262
|
+
|
|
1263
|
+
if (await fs.pathExists(backupPath)) {
|
|
1264
|
+
await fs.ensureDir(path.dirname(originalPath));
|
|
1265
|
+
await fs.copy(backupPath, originalPath, { overwrite: true });
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
if (config._tempBackupDir && (await fs.pathExists(config._tempBackupDir))) {
|
|
1270
|
+
await fs.remove(config._tempBackupDir);
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
customFiles = config._customFiles;
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
if (config._modifiedFiles && config._modifiedFiles.length > 0) {
|
|
1277
|
+
modifiedFiles = config._modifiedFiles;
|
|
1278
|
+
|
|
1279
|
+
if (config._tempModifiedBackupDir && (await fs.pathExists(config._tempModifiedBackupDir))) {
|
|
1280
|
+
message(`Restoring ${modifiedFiles.length} modified files as .bak...`);
|
|
1281
|
+
|
|
1282
|
+
for (const modifiedFile of modifiedFiles) {
|
|
1283
|
+
const relativePath = path.relative(bmadDir, modifiedFile.path);
|
|
1284
|
+
const tempBackupPath = path.join(config._tempModifiedBackupDir, relativePath);
|
|
1285
|
+
const bakPath = modifiedFile.path + '.bak';
|
|
1286
|
+
|
|
1287
|
+
if (await fs.pathExists(tempBackupPath)) {
|
|
1288
|
+
await fs.ensureDir(path.dirname(bakPath));
|
|
1289
|
+
await fs.copy(tempBackupPath, bakPath, { overwrite: true });
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
await fs.remove(config._tempModifiedBackupDir);
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
// Store for summary access
|
|
1298
|
+
config._restoredCustomFiles = customFiles;
|
|
1299
|
+
config._restoredModifiedFiles = modifiedFiles;
|
|
1300
|
+
|
|
1301
|
+
return 'Installation finalized';
|
|
1302
|
+
},
|
|
1303
|
+
});
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
await prompts.tasks(postIdeTasks);
|
|
1307
|
+
|
|
1308
|
+
// Retrieve restored file info for summary
|
|
1309
|
+
const customFiles = config._restoredCustomFiles || [];
|
|
1310
|
+
const modifiedFiles = config._restoredModifiedFiles || [];
|
|
1311
|
+
|
|
1312
|
+
// Render consolidated summary
|
|
1313
|
+
await this.renderInstallSummary(results, {
|
|
1314
|
+
bmadDir,
|
|
1315
|
+
modules: config.modules,
|
|
1316
|
+
ides: config.ides,
|
|
1317
|
+
customFiles: customFiles.length > 0 ? customFiles : undefined,
|
|
1318
|
+
modifiedFiles: modifiedFiles.length > 0 ? modifiedFiles : undefined,
|
|
1319
|
+
});
|
|
1320
|
+
|
|
1321
|
+
return {
|
|
1322
|
+
success: true,
|
|
1323
|
+
path: bmadDir,
|
|
1324
|
+
modules: config.modules,
|
|
1325
|
+
ides: config.ides,
|
|
1326
|
+
projectDir: projectDir,
|
|
1327
|
+
};
|
|
1328
|
+
} catch (error) {
|
|
1329
|
+
try {
|
|
1330
|
+
if (spinner.isSpinning) {
|
|
1331
|
+
spinner.error('Installation failed');
|
|
1332
|
+
} else {
|
|
1333
|
+
await prompts.log.error('Installation failed');
|
|
1334
|
+
}
|
|
1335
|
+
} catch {
|
|
1336
|
+
// Ensure the original error is never swallowed by a logging failure
|
|
1337
|
+
}
|
|
1338
|
+
throw error;
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
/**
|
|
1343
|
+
* Render a consolidated install summary using prompts.note()
|
|
1344
|
+
* @param {Array} results - Array of {step, status: 'ok'|'error'|'warn', detail}
|
|
1345
|
+
* @param {Object} context - {bmadDir, modules, ides, customFiles, modifiedFiles}
|
|
1346
|
+
*/
|
|
1347
|
+
async renderInstallSummary(results, context = {}) {
|
|
1348
|
+
const color = await prompts.getColor();
|
|
1349
|
+
|
|
1350
|
+
// Build step lines with status indicators
|
|
1351
|
+
const lines = [];
|
|
1352
|
+
for (const r of results) {
|
|
1353
|
+
let icon;
|
|
1354
|
+
if (r.status === 'ok') {
|
|
1355
|
+
icon = color.green('\u2713');
|
|
1356
|
+
} else if (r.status === 'warn') {
|
|
1357
|
+
icon = color.yellow('!');
|
|
1358
|
+
} else {
|
|
1359
|
+
icon = color.red('\u2717');
|
|
1360
|
+
}
|
|
1361
|
+
const detail = r.detail ? color.dim(` (${r.detail})`) : '';
|
|
1362
|
+
lines.push(` ${icon} ${r.step}${detail}`);
|
|
852
1363
|
}
|
|
1364
|
+
|
|
1365
|
+
// Context and warnings
|
|
1366
|
+
lines.push('');
|
|
1367
|
+
if (context.bmadDir) {
|
|
1368
|
+
lines.push(` Installed to: ${color.dim(context.bmadDir)}`);
|
|
1369
|
+
}
|
|
1370
|
+
if (context.customFiles && context.customFiles.length > 0) {
|
|
1371
|
+
lines.push(` ${color.cyan(`Custom files preserved: ${context.customFiles.length}`)}`);
|
|
1372
|
+
}
|
|
1373
|
+
if (context.modifiedFiles && context.modifiedFiles.length > 0) {
|
|
1374
|
+
lines.push(` ${color.yellow(`Modified files backed up (.bak): ${context.modifiedFiles.length}`)}`);
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
// Next steps
|
|
1378
|
+
lines.push(
|
|
1379
|
+
'',
|
|
1380
|
+
' Next steps:',
|
|
1381
|
+
` Read our new Docs Site: ${color.dim('https://docs.bmad-method.org/')}`,
|
|
1382
|
+
` Join our Discord: ${color.dim('https://discord.gg/gk8jAdXWmj')}`,
|
|
1383
|
+
` Star us on GitHub: ${color.dim('https://github.com/bmad-code-org/BMAD-METHOD/')}`,
|
|
1384
|
+
` Subscribe on YouTube: ${color.dim('https://www.youtube.com/@BMadCode')}`,
|
|
1385
|
+
` Run ${color.cyan('/bmad-help')} with your IDE Agent and ask it how to get started`,
|
|
1386
|
+
);
|
|
1387
|
+
|
|
1388
|
+
await prompts.note(lines.join('\n'), 'BMAD is ready to use!');
|
|
853
1389
|
}
|
|
854
1390
|
|
|
855
1391
|
/**
|
|
856
1392
|
* Update existing installation
|
|
857
1393
|
*/
|
|
858
1394
|
async update(config) {
|
|
859
|
-
const spinner =
|
|
1395
|
+
const spinner = await prompts.spinner();
|
|
1396
|
+
spinner.start('Checking installation...');
|
|
860
1397
|
|
|
861
1398
|
try {
|
|
862
1399
|
const projectDir = path.resolve(config.directory);
|
|
863
|
-
const bmadDir = await this.findBmadDir(projectDir);
|
|
1400
|
+
const { bmadDir } = await this.findBmadDir(projectDir);
|
|
864
1401
|
const existingInstall = await this.detector.detect(bmadDir);
|
|
865
1402
|
|
|
866
1403
|
if (!existingInstall.installed) {
|
|
867
|
-
spinner.
|
|
1404
|
+
spinner.stop('No BMAD installation found');
|
|
868
1405
|
throw new Error(`No BMAD installation found at ${bmadDir}`);
|
|
869
1406
|
}
|
|
870
1407
|
|
|
871
|
-
spinner.
|
|
1408
|
+
spinner.message('Analyzing update requirements...');
|
|
872
1409
|
|
|
873
1410
|
// Compare versions and determine what needs updating
|
|
874
1411
|
const currentVersion = existingInstall.version;
|
|
875
1412
|
const newVersion = require(path.join(getProjectRoot(), 'package.json')).version;
|
|
876
1413
|
|
|
1414
|
+
// Check for custom modules with missing sources before update
|
|
1415
|
+
const customModuleSources = new Map();
|
|
1416
|
+
|
|
1417
|
+
// Check manifest for backward compatibility
|
|
1418
|
+
if (existingInstall.customModules) {
|
|
1419
|
+
for (const customModule of existingInstall.customModules) {
|
|
1420
|
+
customModuleSources.set(customModule.id, customModule);
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
// Also check cache directory
|
|
1425
|
+
const cacheDir = path.join(bmadDir, '_config', 'custom');
|
|
1426
|
+
if (await fs.pathExists(cacheDir)) {
|
|
1427
|
+
const cachedModules = await fs.readdir(cacheDir, { withFileTypes: true });
|
|
1428
|
+
|
|
1429
|
+
for (const cachedModule of cachedModules) {
|
|
1430
|
+
if (cachedModule.isDirectory()) {
|
|
1431
|
+
const moduleId = cachedModule.name;
|
|
1432
|
+
|
|
1433
|
+
// Skip if we already have this module
|
|
1434
|
+
if (customModuleSources.has(moduleId)) {
|
|
1435
|
+
continue;
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
// Check if this is an external official module - skip cache for those
|
|
1439
|
+
const isExternal = await this.moduleManager.isExternalModule(moduleId);
|
|
1440
|
+
if (isExternal) {
|
|
1441
|
+
// External modules are handled via cloneExternalModule, not from cache
|
|
1442
|
+
continue;
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
const cachedPath = path.join(cacheDir, moduleId);
|
|
1446
|
+
|
|
1447
|
+
// Check if this is actually a custom module (has module.yaml)
|
|
1448
|
+
const moduleYamlPath = path.join(cachedPath, 'module.yaml');
|
|
1449
|
+
if (await fs.pathExists(moduleYamlPath)) {
|
|
1450
|
+
customModuleSources.set(moduleId, {
|
|
1451
|
+
id: moduleId,
|
|
1452
|
+
name: moduleId,
|
|
1453
|
+
sourcePath: path.join('_config', 'custom', moduleId), // Relative path
|
|
1454
|
+
cached: true,
|
|
1455
|
+
});
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
if (customModuleSources.size > 0) {
|
|
1462
|
+
spinner.stop('Update analysis complete');
|
|
1463
|
+
await prompts.log.warn('Checking custom module sources before update...');
|
|
1464
|
+
|
|
1465
|
+
const projectRoot = getProjectRoot();
|
|
1466
|
+
await this.handleMissingCustomSources(
|
|
1467
|
+
customModuleSources,
|
|
1468
|
+
bmadDir,
|
|
1469
|
+
projectRoot,
|
|
1470
|
+
'update',
|
|
1471
|
+
existingInstall.modules.map((m) => m.id),
|
|
1472
|
+
config.skipPrompts || false,
|
|
1473
|
+
);
|
|
1474
|
+
|
|
1475
|
+
spinner.start('Preparing update...');
|
|
1476
|
+
}
|
|
1477
|
+
|
|
877
1478
|
if (config.dryRun) {
|
|
878
|
-
spinner.stop();
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
console.log(chalk.bold('Core:'), existingInstall.hasCore ? 'Will be updated' : 'Not installed');
|
|
1479
|
+
spinner.stop('Dry run analysis complete');
|
|
1480
|
+
let dryRunContent = `Current version: ${currentVersion}\n`;
|
|
1481
|
+
dryRunContent += `New version: ${newVersion}\n`;
|
|
1482
|
+
dryRunContent += `Core: ${existingInstall.hasCore ? 'Will be updated' : 'Not installed'}`;
|
|
883
1483
|
|
|
884
1484
|
if (existingInstall.modules.length > 0) {
|
|
885
|
-
|
|
1485
|
+
dryRunContent += '\n\nModules to update:';
|
|
886
1486
|
for (const mod of existingInstall.modules) {
|
|
887
|
-
|
|
1487
|
+
dryRunContent += `\n - ${mod.id}`;
|
|
888
1488
|
}
|
|
889
1489
|
}
|
|
1490
|
+
await prompts.note(dryRunContent, 'Update Preview (Dry Run)');
|
|
890
1491
|
return;
|
|
891
1492
|
}
|
|
892
1493
|
|
|
893
1494
|
// Perform actual update
|
|
894
1495
|
if (existingInstall.hasCore) {
|
|
895
|
-
spinner.
|
|
1496
|
+
spinner.message('Updating core...');
|
|
896
1497
|
await this.updateCore(bmadDir, config.force);
|
|
897
1498
|
}
|
|
898
1499
|
|
|
899
1500
|
for (const module of existingInstall.modules) {
|
|
900
|
-
spinner.
|
|
901
|
-
await this.moduleManager.update(module.id, bmadDir, config.force);
|
|
1501
|
+
spinner.message(`Updating module: ${module.id}...`);
|
|
1502
|
+
await this.moduleManager.update(module.id, bmadDir, config.force, { installer: this });
|
|
902
1503
|
}
|
|
903
1504
|
|
|
904
1505
|
// Update manifest
|
|
905
|
-
spinner.
|
|
1506
|
+
spinner.message('Updating manifest...');
|
|
906
1507
|
await this.manifest.update(bmadDir, {
|
|
907
1508
|
version: newVersion,
|
|
908
1509
|
updateDate: new Date().toISOString(),
|
|
909
1510
|
});
|
|
910
1511
|
|
|
911
|
-
spinner.
|
|
1512
|
+
spinner.stop('Update complete');
|
|
912
1513
|
return { success: true };
|
|
913
1514
|
} catch (error) {
|
|
914
|
-
spinner.
|
|
1515
|
+
spinner.error('Update failed');
|
|
915
1516
|
throw error;
|
|
916
1517
|
}
|
|
917
1518
|
}
|
|
@@ -921,7 +1522,7 @@ class Installer {
|
|
|
921
1522
|
*/
|
|
922
1523
|
async getStatus(directory) {
|
|
923
1524
|
const projectDir = path.resolve(directory);
|
|
924
|
-
const bmadDir = await this.findBmadDir(projectDir);
|
|
1525
|
+
const { bmadDir } = await this.findBmadDir(projectDir);
|
|
925
1526
|
return await this.detector.detect(bmadDir);
|
|
926
1527
|
}
|
|
927
1528
|
|
|
@@ -933,29 +1534,401 @@ class Installer {
|
|
|
933
1534
|
}
|
|
934
1535
|
|
|
935
1536
|
/**
|
|
936
|
-
* Uninstall BMAD
|
|
1537
|
+
* Uninstall BMAD with selective removal options
|
|
1538
|
+
* @param {string} directory - Project directory
|
|
1539
|
+
* @param {Object} options - Uninstall options
|
|
1540
|
+
* @param {boolean} [options.removeModules=true] - Remove _bmad/ directory
|
|
1541
|
+
* @param {boolean} [options.removeIdeConfigs=true] - Remove IDE configurations
|
|
1542
|
+
* @param {boolean} [options.removeOutputFolder=false] - Remove user artifacts output folder
|
|
1543
|
+
* @returns {Object} Result with success status and removed components
|
|
937
1544
|
*/
|
|
938
|
-
async uninstall(directory) {
|
|
1545
|
+
async uninstall(directory, options = {}) {
|
|
939
1546
|
const projectDir = path.resolve(directory);
|
|
940
|
-
const bmadDir = await this.findBmadDir(projectDir);
|
|
1547
|
+
const { bmadDir } = await this.findBmadDir(projectDir);
|
|
1548
|
+
|
|
1549
|
+
if (!(await fs.pathExists(bmadDir))) {
|
|
1550
|
+
return { success: false, reason: 'not-installed' };
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
// 1. DETECT: Read state BEFORE deleting anything
|
|
1554
|
+
const existingInstall = await this.detector.detect(bmadDir);
|
|
1555
|
+
const outputFolder = await this._readOutputFolder(bmadDir);
|
|
1556
|
+
|
|
1557
|
+
const removed = { modules: false, ideConfigs: false, outputFolder: false };
|
|
1558
|
+
|
|
1559
|
+
// 2. IDE CLEANUP (before _bmad/ deletion so configs are accessible)
|
|
1560
|
+
if (options.removeIdeConfigs !== false) {
|
|
1561
|
+
await this.uninstallIdeConfigs(projectDir, existingInstall, { silent: options.silent });
|
|
1562
|
+
removed.ideConfigs = true;
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
// 3. OUTPUT FOLDER (only if explicitly requested)
|
|
1566
|
+
if (options.removeOutputFolder === true && outputFolder) {
|
|
1567
|
+
removed.outputFolder = await this.uninstallOutputFolder(projectDir, outputFolder);
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
// 4. BMAD DIRECTORY (last, after everything that needs it)
|
|
1571
|
+
if (options.removeModules !== false) {
|
|
1572
|
+
removed.modules = await this.uninstallModules(projectDir);
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
return { success: true, removed, version: existingInstall.version };
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
/**
|
|
1579
|
+
* Uninstall IDE configurations only
|
|
1580
|
+
* @param {string} projectDir - Project directory
|
|
1581
|
+
* @param {Object} existingInstall - Detection result from detector.detect()
|
|
1582
|
+
* @param {Object} [options] - Options (e.g. { silent: true })
|
|
1583
|
+
* @returns {Promise<Object>} Results from IDE cleanup
|
|
1584
|
+
*/
|
|
1585
|
+
async uninstallIdeConfigs(projectDir, existingInstall, options = {}) {
|
|
1586
|
+
await this.ideManager.ensureInitialized();
|
|
1587
|
+
const cleanupOptions = { isUninstall: true, silent: options.silent };
|
|
1588
|
+
const ideList = existingInstall.ides || [];
|
|
1589
|
+
if (ideList.length > 0) {
|
|
1590
|
+
return this.ideManager.cleanupByList(projectDir, ideList, cleanupOptions);
|
|
1591
|
+
}
|
|
1592
|
+
return this.ideManager.cleanup(projectDir, cleanupOptions);
|
|
1593
|
+
}
|
|
941
1594
|
|
|
1595
|
+
/**
|
|
1596
|
+
* Remove user artifacts output folder
|
|
1597
|
+
* @param {string} projectDir - Project directory
|
|
1598
|
+
* @param {string} outputFolder - Output folder name (relative)
|
|
1599
|
+
* @returns {Promise<boolean>} Whether the folder was removed
|
|
1600
|
+
*/
|
|
1601
|
+
async uninstallOutputFolder(projectDir, outputFolder) {
|
|
1602
|
+
if (!outputFolder) return false;
|
|
1603
|
+
const resolvedProject = path.resolve(projectDir);
|
|
1604
|
+
const outputPath = path.resolve(resolvedProject, outputFolder);
|
|
1605
|
+
if (!outputPath.startsWith(resolvedProject + path.sep)) {
|
|
1606
|
+
return false;
|
|
1607
|
+
}
|
|
1608
|
+
if (await fs.pathExists(outputPath)) {
|
|
1609
|
+
await fs.remove(outputPath);
|
|
1610
|
+
return true;
|
|
1611
|
+
}
|
|
1612
|
+
return false;
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
/**
|
|
1616
|
+
* Remove the _bmad/ directory
|
|
1617
|
+
* @param {string} projectDir - Project directory
|
|
1618
|
+
* @returns {Promise<boolean>} Whether the directory was removed
|
|
1619
|
+
*/
|
|
1620
|
+
async uninstallModules(projectDir) {
|
|
1621
|
+
const { bmadDir } = await this.findBmadDir(projectDir);
|
|
942
1622
|
if (await fs.pathExists(bmadDir)) {
|
|
943
1623
|
await fs.remove(bmadDir);
|
|
1624
|
+
return true;
|
|
1625
|
+
}
|
|
1626
|
+
return false;
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
/**
|
|
1630
|
+
* Get the configured output folder name for a project
|
|
1631
|
+
* Resolves bmadDir internally from projectDir
|
|
1632
|
+
* @param {string} projectDir - Project directory
|
|
1633
|
+
* @returns {string} Output folder name (relative, default: '_bmad-output')
|
|
1634
|
+
*/
|
|
1635
|
+
async getOutputFolder(projectDir) {
|
|
1636
|
+
const { bmadDir } = await this.findBmadDir(projectDir);
|
|
1637
|
+
return this._readOutputFolder(bmadDir);
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
/**
|
|
1641
|
+
* Read the output_folder setting from module config files
|
|
1642
|
+
* Checks bmm/config.yaml first, then other module configs
|
|
1643
|
+
* @param {string} bmadDir - BMAD installation directory
|
|
1644
|
+
* @returns {string} Output folder path or default
|
|
1645
|
+
*/
|
|
1646
|
+
async _readOutputFolder(bmadDir) {
|
|
1647
|
+
const yaml = require('yaml');
|
|
1648
|
+
|
|
1649
|
+
// Check bmm/config.yaml first (most common)
|
|
1650
|
+
const bmmConfigPath = path.join(bmadDir, 'bmm', 'config.yaml');
|
|
1651
|
+
if (await fs.pathExists(bmmConfigPath)) {
|
|
1652
|
+
try {
|
|
1653
|
+
const content = await fs.readFile(bmmConfigPath, 'utf8');
|
|
1654
|
+
const config = yaml.parse(content);
|
|
1655
|
+
if (config && config.output_folder) {
|
|
1656
|
+
// Strip {project-root}/ prefix if present
|
|
1657
|
+
return config.output_folder.replace(/^\{project-root\}[/\\]/, '');
|
|
1658
|
+
}
|
|
1659
|
+
} catch {
|
|
1660
|
+
// Fall through to other modules
|
|
1661
|
+
}
|
|
944
1662
|
}
|
|
945
1663
|
|
|
946
|
-
//
|
|
947
|
-
|
|
1664
|
+
// Scan other module config.yaml files
|
|
1665
|
+
try {
|
|
1666
|
+
const entries = await fs.readdir(bmadDir, { withFileTypes: true });
|
|
1667
|
+
for (const entry of entries) {
|
|
1668
|
+
if (!entry.isDirectory() || entry.name === 'bmm' || entry.name.startsWith('_')) continue;
|
|
1669
|
+
const configPath = path.join(bmadDir, entry.name, 'config.yaml');
|
|
1670
|
+
if (await fs.pathExists(configPath)) {
|
|
1671
|
+
try {
|
|
1672
|
+
const content = await fs.readFile(configPath, 'utf8');
|
|
1673
|
+
const config = yaml.parse(content);
|
|
1674
|
+
if (config && config.output_folder) {
|
|
1675
|
+
return config.output_folder.replace(/^\{project-root\}[/\\]/, '');
|
|
1676
|
+
}
|
|
1677
|
+
} catch {
|
|
1678
|
+
// Continue scanning
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
} catch {
|
|
1683
|
+
// Directory scan failed
|
|
1684
|
+
}
|
|
948
1685
|
|
|
949
|
-
|
|
1686
|
+
// Default fallback
|
|
1687
|
+
return '_bmad-output';
|
|
950
1688
|
}
|
|
951
1689
|
|
|
952
1690
|
/**
|
|
953
1691
|
* Private: Create directory structure
|
|
954
1692
|
*/
|
|
1693
|
+
/**
|
|
1694
|
+
* Merge all module-help.csv files into a single bmad-help.csv
|
|
1695
|
+
* Scans all installed modules for module-help.csv and merges them
|
|
1696
|
+
* Enriches agent info from agent-manifest.csv
|
|
1697
|
+
* Output is written to _bmad/_config/bmad-help.csv
|
|
1698
|
+
* @param {string} bmadDir - BMAD installation directory
|
|
1699
|
+
*/
|
|
1700
|
+
async mergeModuleHelpCatalogs(bmadDir) {
|
|
1701
|
+
const allRows = [];
|
|
1702
|
+
const headerRow =
|
|
1703
|
+
'module,phase,name,code,sequence,workflow-file,command,required,agent-name,agent-command,agent-display-name,agent-title,options,description,output-location,outputs';
|
|
1704
|
+
|
|
1705
|
+
// Load agent manifest for agent info lookup
|
|
1706
|
+
const agentManifestPath = path.join(bmadDir, '_config', 'agent-manifest.csv');
|
|
1707
|
+
const agentInfo = new Map(); // agent-name -> {command, displayName, title+icon}
|
|
1708
|
+
|
|
1709
|
+
if (await fs.pathExists(agentManifestPath)) {
|
|
1710
|
+
const manifestContent = await fs.readFile(agentManifestPath, 'utf8');
|
|
1711
|
+
const lines = manifestContent.split('\n').filter((line) => line.trim());
|
|
1712
|
+
|
|
1713
|
+
for (const line of lines) {
|
|
1714
|
+
if (line.startsWith('name,')) continue; // Skip header
|
|
1715
|
+
|
|
1716
|
+
const cols = line.split(',');
|
|
1717
|
+
if (cols.length >= 4) {
|
|
1718
|
+
const agentName = cols[0].replaceAll('"', '').trim();
|
|
1719
|
+
const displayName = cols[1].replaceAll('"', '').trim();
|
|
1720
|
+
const title = cols[2].replaceAll('"', '').trim();
|
|
1721
|
+
const icon = cols[3].replaceAll('"', '').trim();
|
|
1722
|
+
const module = cols[10] ? cols[10].replaceAll('"', '').trim() : '';
|
|
1723
|
+
|
|
1724
|
+
// Build agent command: bmad:module:agent:name
|
|
1725
|
+
const agentCommand = module ? `bmad:${module}:agent:${agentName}` : `bmad:agent:${agentName}`;
|
|
1726
|
+
|
|
1727
|
+
agentInfo.set(agentName, {
|
|
1728
|
+
command: agentCommand,
|
|
1729
|
+
displayName: displayName || agentName,
|
|
1730
|
+
title: icon && title ? `${icon} ${title}` : title || agentName,
|
|
1731
|
+
});
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
// Get all installed module directories
|
|
1737
|
+
const entries = await fs.readdir(bmadDir, { withFileTypes: true });
|
|
1738
|
+
const installedModules = entries
|
|
1739
|
+
.filter((entry) => entry.isDirectory() && entry.name !== '_config' && entry.name !== 'docs' && entry.name !== '_memory')
|
|
1740
|
+
.map((entry) => entry.name);
|
|
1741
|
+
|
|
1742
|
+
// Add core module to scan (it's installed at root level as _config, but we check src/core)
|
|
1743
|
+
const coreModulePath = getSourcePath('core');
|
|
1744
|
+
const modulePaths = new Map();
|
|
1745
|
+
|
|
1746
|
+
// Map all module source paths
|
|
1747
|
+
if (await fs.pathExists(coreModulePath)) {
|
|
1748
|
+
modulePaths.set('core', coreModulePath);
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
// Map installed module paths
|
|
1752
|
+
for (const moduleName of installedModules) {
|
|
1753
|
+
const modulePath = path.join(bmadDir, moduleName);
|
|
1754
|
+
modulePaths.set(moduleName, modulePath);
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
// Scan each module for module-help.csv
|
|
1758
|
+
for (const [moduleName, modulePath] of modulePaths) {
|
|
1759
|
+
const helpFilePath = path.join(modulePath, 'module-help.csv');
|
|
1760
|
+
|
|
1761
|
+
if (await fs.pathExists(helpFilePath)) {
|
|
1762
|
+
try {
|
|
1763
|
+
const content = await fs.readFile(helpFilePath, 'utf8');
|
|
1764
|
+
const lines = content.split('\n').filter((line) => line.trim() && !line.startsWith('#'));
|
|
1765
|
+
|
|
1766
|
+
for (const line of lines) {
|
|
1767
|
+
// Skip header row
|
|
1768
|
+
if (line.startsWith('module,')) {
|
|
1769
|
+
continue;
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
// Parse the line - handle quoted fields with commas
|
|
1773
|
+
const columns = this.parseCSVLine(line);
|
|
1774
|
+
if (columns.length >= 12) {
|
|
1775
|
+
// Map old schema to new schema
|
|
1776
|
+
// Old: module,phase,name,code,sequence,workflow-file,command,required,agent,options,description,output-location,outputs
|
|
1777
|
+
// New: module,phase,name,code,sequence,workflow-file,command,required,agent-name,agent-command,agent-display-name,agent-title,options,description,output-location,outputs
|
|
1778
|
+
|
|
1779
|
+
const [
|
|
1780
|
+
module,
|
|
1781
|
+
phase,
|
|
1782
|
+
name,
|
|
1783
|
+
code,
|
|
1784
|
+
sequence,
|
|
1785
|
+
workflowFile,
|
|
1786
|
+
command,
|
|
1787
|
+
required,
|
|
1788
|
+
agentName,
|
|
1789
|
+
options,
|
|
1790
|
+
description,
|
|
1791
|
+
outputLocation,
|
|
1792
|
+
outputs,
|
|
1793
|
+
] = columns;
|
|
1794
|
+
|
|
1795
|
+
// If module column is empty, set it to this module's name (except for core which stays empty for universal tools)
|
|
1796
|
+
const finalModule = (!module || module.trim() === '') && moduleName !== 'core' ? moduleName : module || '';
|
|
1797
|
+
|
|
1798
|
+
// Lookup agent info
|
|
1799
|
+
const cleanAgentName = agentName ? agentName.trim() : '';
|
|
1800
|
+
const agentData = agentInfo.get(cleanAgentName) || { command: '', displayName: '', title: '' };
|
|
1801
|
+
|
|
1802
|
+
// Build new row with agent info
|
|
1803
|
+
const newRow = [
|
|
1804
|
+
finalModule,
|
|
1805
|
+
phase || '',
|
|
1806
|
+
name || '',
|
|
1807
|
+
code || '',
|
|
1808
|
+
sequence || '',
|
|
1809
|
+
workflowFile || '',
|
|
1810
|
+
command || '',
|
|
1811
|
+
required || 'false',
|
|
1812
|
+
cleanAgentName,
|
|
1813
|
+
agentData.command,
|
|
1814
|
+
agentData.displayName,
|
|
1815
|
+
agentData.title,
|
|
1816
|
+
options || '',
|
|
1817
|
+
description || '',
|
|
1818
|
+
outputLocation || '',
|
|
1819
|
+
outputs || '',
|
|
1820
|
+
];
|
|
1821
|
+
|
|
1822
|
+
allRows.push(newRow.map((c) => this.escapeCSVField(c)).join(','));
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
|
|
1827
|
+
await prompts.log.message(` Merged module-help from: ${moduleName}`);
|
|
1828
|
+
}
|
|
1829
|
+
} catch (error) {
|
|
1830
|
+
await prompts.log.warn(` Warning: Failed to read module-help.csv from ${moduleName}: ${error.message}`);
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1835
|
+
// Sort by module, then phase, then sequence
|
|
1836
|
+
allRows.sort((a, b) => {
|
|
1837
|
+
const colsA = this.parseCSVLine(a);
|
|
1838
|
+
const colsB = this.parseCSVLine(b);
|
|
1839
|
+
|
|
1840
|
+
// Module comparison (empty module/universal tools come first)
|
|
1841
|
+
const moduleA = (colsA[0] || '').toLowerCase();
|
|
1842
|
+
const moduleB = (colsB[0] || '').toLowerCase();
|
|
1843
|
+
if (moduleA !== moduleB) {
|
|
1844
|
+
return moduleA.localeCompare(moduleB);
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
// Phase comparison
|
|
1848
|
+
const phaseA = colsA[1] || '';
|
|
1849
|
+
const phaseB = colsB[1] || '';
|
|
1850
|
+
if (phaseA !== phaseB) {
|
|
1851
|
+
return phaseA.localeCompare(phaseB);
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
// Sequence comparison
|
|
1855
|
+
const seqA = parseInt(colsA[4] || '0', 10);
|
|
1856
|
+
const seqB = parseInt(colsB[4] || '0', 10);
|
|
1857
|
+
return seqA - seqB;
|
|
1858
|
+
});
|
|
1859
|
+
|
|
1860
|
+
// Write merged catalog
|
|
1861
|
+
const outputDir = path.join(bmadDir, '_config');
|
|
1862
|
+
await fs.ensureDir(outputDir);
|
|
1863
|
+
const outputPath = path.join(outputDir, 'bmad-help.csv');
|
|
1864
|
+
|
|
1865
|
+
const mergedContent = [headerRow, ...allRows].join('\n');
|
|
1866
|
+
await fs.writeFile(outputPath, mergedContent, 'utf8');
|
|
1867
|
+
|
|
1868
|
+
// Track the installed file
|
|
1869
|
+
this.installedFiles.add(outputPath);
|
|
1870
|
+
|
|
1871
|
+
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
|
|
1872
|
+
await prompts.log.message(` Generated bmad-help.csv: ${allRows.length} workflows`);
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
/**
|
|
1877
|
+
* Parse a CSV line, handling quoted fields
|
|
1878
|
+
* @param {string} line - CSV line to parse
|
|
1879
|
+
* @returns {Array} Array of field values
|
|
1880
|
+
*/
|
|
1881
|
+
parseCSVLine(line) {
|
|
1882
|
+
const result = [];
|
|
1883
|
+
let current = '';
|
|
1884
|
+
let inQuotes = false;
|
|
1885
|
+
|
|
1886
|
+
for (let i = 0; i < line.length; i++) {
|
|
1887
|
+
const char = line[i];
|
|
1888
|
+
const nextChar = line[i + 1];
|
|
1889
|
+
|
|
1890
|
+
if (char === '"') {
|
|
1891
|
+
if (inQuotes && nextChar === '"') {
|
|
1892
|
+
// Escaped quote
|
|
1893
|
+
current += '"';
|
|
1894
|
+
i++; // Skip next quote
|
|
1895
|
+
} else {
|
|
1896
|
+
// Toggle quote mode
|
|
1897
|
+
inQuotes = !inQuotes;
|
|
1898
|
+
}
|
|
1899
|
+
} else if (char === ',' && !inQuotes) {
|
|
1900
|
+
result.push(current);
|
|
1901
|
+
current = '';
|
|
1902
|
+
} else {
|
|
1903
|
+
current += char;
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
result.push(current);
|
|
1907
|
+
return result;
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1910
|
+
/**
|
|
1911
|
+
* Escape a CSV field if it contains special characters
|
|
1912
|
+
* @param {string} field - Field value to escape
|
|
1913
|
+
* @returns {string} Escaped field
|
|
1914
|
+
*/
|
|
1915
|
+
escapeCSVField(field) {
|
|
1916
|
+
if (field === null || field === undefined) {
|
|
1917
|
+
return '';
|
|
1918
|
+
}
|
|
1919
|
+
const str = String(field);
|
|
1920
|
+
// If field contains comma, quote, or newline, wrap in quotes and escape inner quotes
|
|
1921
|
+
if (str.includes(',') || str.includes('"') || str.includes('\n')) {
|
|
1922
|
+
return `"${str.replaceAll('"', '""')}"`;
|
|
1923
|
+
}
|
|
1924
|
+
return str;
|
|
1925
|
+
}
|
|
1926
|
+
|
|
955
1927
|
async createDirectoryStructure(bmadDir) {
|
|
956
1928
|
await fs.ensureDir(bmadDir);
|
|
957
|
-
await fs.ensureDir(path.join(bmadDir, '
|
|
958
|
-
await fs.ensureDir(path.join(bmadDir, '
|
|
1929
|
+
await fs.ensureDir(path.join(bmadDir, '_config'));
|
|
1930
|
+
await fs.ensureDir(path.join(bmadDir, '_config', 'agents'));
|
|
1931
|
+
await fs.ensureDir(path.join(bmadDir, '_config', 'custom'));
|
|
959
1932
|
}
|
|
960
1933
|
|
|
961
1934
|
/**
|
|
@@ -964,7 +1937,7 @@ class Installer {
|
|
|
964
1937
|
* @param {Object} moduleConfigs - Collected configuration values
|
|
965
1938
|
*/
|
|
966
1939
|
async generateModuleConfigs(bmadDir, moduleConfigs) {
|
|
967
|
-
const yaml = require('
|
|
1940
|
+
const yaml = require('yaml');
|
|
968
1941
|
|
|
969
1942
|
// Extract core config values to share with other modules
|
|
970
1943
|
const coreConfig = moduleConfigs.core || {};
|
|
@@ -972,7 +1945,7 @@ class Installer {
|
|
|
972
1945
|
// Get all installed module directories
|
|
973
1946
|
const entries = await fs.readdir(bmadDir, { withFileTypes: true });
|
|
974
1947
|
const installedModules = entries
|
|
975
|
-
.filter((entry) => entry.isDirectory() && entry.name !== '
|
|
1948
|
+
.filter((entry) => entry.isDirectory() && entry.name !== '_config' && entry.name !== 'docs')
|
|
976
1949
|
.map((entry) => entry.name);
|
|
977
1950
|
|
|
978
1951
|
// Generate config.yaml for each installed module
|
|
@@ -1010,12 +1983,14 @@ class Installer {
|
|
|
1010
1983
|
coreSection = '\n# Core Configuration Values\n';
|
|
1011
1984
|
}
|
|
1012
1985
|
|
|
1986
|
+
// Clean the config to remove any non-serializable values (like functions)
|
|
1987
|
+
const cleanConfig = structuredClone(finalConfig);
|
|
1988
|
+
|
|
1013
1989
|
// Convert config to YAML
|
|
1014
|
-
let yamlContent = yaml.
|
|
1990
|
+
let yamlContent = yaml.stringify(cleanConfig, {
|
|
1015
1991
|
indent: 2,
|
|
1016
|
-
lineWidth:
|
|
1017
|
-
|
|
1018
|
-
sortKeys: false,
|
|
1992
|
+
lineWidth: 0,
|
|
1993
|
+
minContentWidth: 0,
|
|
1019
1994
|
});
|
|
1020
1995
|
|
|
1021
1996
|
// If we have core values, reorganize the YAML to group them with their comment
|
|
@@ -1047,7 +2022,7 @@ class Installer {
|
|
|
1047
2022
|
await fs.writeFile(configPath, content.endsWith('\n') ? content : content + '\n', 'utf8');
|
|
1048
2023
|
|
|
1049
2024
|
// Track the config file in installedFiles
|
|
1050
|
-
this.installedFiles.
|
|
2025
|
+
this.installedFiles.add(configPath);
|
|
1051
2026
|
}
|
|
1052
2027
|
}
|
|
1053
2028
|
}
|
|
@@ -1060,14 +2035,7 @@ class Installer {
|
|
|
1060
2035
|
async installCoreWithDependencies(bmadDir, coreFiles) {
|
|
1061
2036
|
const sourcePath = getModulePath('core');
|
|
1062
2037
|
const targetPath = path.join(bmadDir, 'core');
|
|
1063
|
-
|
|
1064
|
-
// Install full core
|
|
1065
2038
|
await this.installCore(bmadDir);
|
|
1066
|
-
|
|
1067
|
-
// If there are specific dependency files, ensure they're included
|
|
1068
|
-
if (coreFiles) {
|
|
1069
|
-
// Already handled by installCore for core module
|
|
1070
|
-
}
|
|
1071
2039
|
}
|
|
1072
2040
|
|
|
1073
2041
|
/**
|
|
@@ -1086,11 +2054,13 @@ class Installer {
|
|
|
1086
2054
|
moduleName,
|
|
1087
2055
|
bmadDir,
|
|
1088
2056
|
(filePath) => {
|
|
1089
|
-
this.installedFiles.
|
|
2057
|
+
this.installedFiles.add(filePath);
|
|
1090
2058
|
},
|
|
1091
2059
|
{
|
|
1092
2060
|
skipModuleInstaller: true, // We'll run it later after IDE setup
|
|
1093
2061
|
moduleConfig: moduleConfig, // Pass module config for conditional filtering
|
|
2062
|
+
installer: this,
|
|
2063
|
+
silent: true,
|
|
1094
2064
|
},
|
|
1095
2065
|
);
|
|
1096
2066
|
|
|
@@ -1122,8 +2092,8 @@ class Installer {
|
|
|
1122
2092
|
const targetPath = path.join(agentsDir, fileName);
|
|
1123
2093
|
|
|
1124
2094
|
if (await fs.pathExists(sourcePath)) {
|
|
1125
|
-
await this.copyFileWithPlaceholderReplacement(sourcePath, targetPath
|
|
1126
|
-
this.installedFiles.
|
|
2095
|
+
await this.copyFileWithPlaceholderReplacement(sourcePath, targetPath);
|
|
2096
|
+
this.installedFiles.add(targetPath);
|
|
1127
2097
|
}
|
|
1128
2098
|
}
|
|
1129
2099
|
}
|
|
@@ -1138,8 +2108,8 @@ class Installer {
|
|
|
1138
2108
|
const targetPath = path.join(tasksDir, fileName);
|
|
1139
2109
|
|
|
1140
2110
|
if (await fs.pathExists(sourcePath)) {
|
|
1141
|
-
await this.copyFileWithPlaceholderReplacement(sourcePath, targetPath
|
|
1142
|
-
this.installedFiles.
|
|
2111
|
+
await this.copyFileWithPlaceholderReplacement(sourcePath, targetPath);
|
|
2112
|
+
this.installedFiles.add(targetPath);
|
|
1143
2113
|
}
|
|
1144
2114
|
}
|
|
1145
2115
|
}
|
|
@@ -1154,8 +2124,8 @@ class Installer {
|
|
|
1154
2124
|
const targetPath = path.join(toolsDir, fileName);
|
|
1155
2125
|
|
|
1156
2126
|
if (await fs.pathExists(sourcePath)) {
|
|
1157
|
-
await this.copyFileWithPlaceholderReplacement(sourcePath, targetPath
|
|
1158
|
-
this.installedFiles.
|
|
2127
|
+
await this.copyFileWithPlaceholderReplacement(sourcePath, targetPath);
|
|
2128
|
+
this.installedFiles.add(targetPath);
|
|
1159
2129
|
}
|
|
1160
2130
|
}
|
|
1161
2131
|
}
|
|
@@ -1170,8 +2140,8 @@ class Installer {
|
|
|
1170
2140
|
const targetPath = path.join(templatesDir, fileName);
|
|
1171
2141
|
|
|
1172
2142
|
if (await fs.pathExists(sourcePath)) {
|
|
1173
|
-
await this.copyFileWithPlaceholderReplacement(sourcePath, targetPath
|
|
1174
|
-
this.installedFiles.
|
|
2143
|
+
await this.copyFileWithPlaceholderReplacement(sourcePath, targetPath);
|
|
2144
|
+
this.installedFiles.add(targetPath);
|
|
1175
2145
|
}
|
|
1176
2146
|
}
|
|
1177
2147
|
}
|
|
@@ -1185,8 +2155,8 @@ class Installer {
|
|
|
1185
2155
|
await fs.ensureDir(path.dirname(targetPath));
|
|
1186
2156
|
|
|
1187
2157
|
if (await fs.pathExists(dataPath)) {
|
|
1188
|
-
await this.copyFileWithPlaceholderReplacement(dataPath, targetPath
|
|
1189
|
-
this.installedFiles.
|
|
2158
|
+
await this.copyFileWithPlaceholderReplacement(dataPath, targetPath);
|
|
2159
|
+
this.installedFiles.add(targetPath);
|
|
1190
2160
|
}
|
|
1191
2161
|
}
|
|
1192
2162
|
}
|
|
@@ -1207,25 +2177,55 @@ class Installer {
|
|
|
1207
2177
|
const sourcePath = getModulePath('core');
|
|
1208
2178
|
const targetPath = path.join(bmadDir, 'core');
|
|
1209
2179
|
|
|
1210
|
-
// Copy core files
|
|
1211
|
-
await this.
|
|
2180
|
+
// Copy core files (skip .agent.yaml files like modules do)
|
|
2181
|
+
await this.copyCoreFiles(sourcePath, targetPath);
|
|
2182
|
+
|
|
2183
|
+
// Compile agents using the same compiler as modules
|
|
2184
|
+
const { ModuleManager } = require('../modules/manager');
|
|
2185
|
+
const moduleManager = new ModuleManager();
|
|
2186
|
+
await moduleManager.compileModuleAgents(sourcePath, targetPath, 'core', bmadDir, this);
|
|
1212
2187
|
|
|
1213
2188
|
// Process agent files to inject activation block
|
|
1214
2189
|
await this.processAgentFiles(targetPath, 'core');
|
|
1215
2190
|
}
|
|
1216
2191
|
|
|
1217
2192
|
/**
|
|
1218
|
-
* Copy
|
|
1219
|
-
* @param {string} sourcePath - Source
|
|
1220
|
-
* @param {string} targetPath - Target
|
|
2193
|
+
* Copy core files (similar to copyModuleWithFiltering but for core)
|
|
2194
|
+
* @param {string} sourcePath - Source path
|
|
2195
|
+
* @param {string} targetPath - Target path
|
|
1221
2196
|
*/
|
|
1222
|
-
async
|
|
1223
|
-
// Get all files in source
|
|
2197
|
+
async copyCoreFiles(sourcePath, targetPath) {
|
|
2198
|
+
// Get all files in source
|
|
1224
2199
|
const files = await this.getFileList(sourcePath);
|
|
1225
2200
|
|
|
1226
2201
|
for (const file of files) {
|
|
2202
|
+
// Skip sub-modules directory - these are IDE-specific and handled separately
|
|
2203
|
+
if (file.startsWith('sub-modules/')) {
|
|
2204
|
+
continue;
|
|
2205
|
+
}
|
|
2206
|
+
|
|
2207
|
+
// Skip sidecar directories - they are handled separately during agent compilation
|
|
2208
|
+
if (
|
|
2209
|
+
path
|
|
2210
|
+
.dirname(file)
|
|
2211
|
+
.split('/')
|
|
2212
|
+
.some((dir) => dir.toLowerCase().includes('sidecar'))
|
|
2213
|
+
) {
|
|
2214
|
+
continue;
|
|
2215
|
+
}
|
|
2216
|
+
|
|
2217
|
+
// Skip module.yaml at root - it's only needed at install time
|
|
2218
|
+
if (file === 'module.yaml') {
|
|
2219
|
+
continue;
|
|
2220
|
+
}
|
|
2221
|
+
|
|
1227
2222
|
// Skip config.yaml templates - we'll generate clean ones with actual values
|
|
1228
|
-
if (file === 'config.yaml' || file.endsWith('/config.yaml')) {
|
|
2223
|
+
if (file === 'config.yaml' || file.endsWith('/config.yaml') || file === 'custom.yaml' || file.endsWith('/custom.yaml')) {
|
|
2224
|
+
continue;
|
|
2225
|
+
}
|
|
2226
|
+
|
|
2227
|
+
// Skip .agent.yaml files - they will be compiled separately
|
|
2228
|
+
if (file.endsWith('.agent.yaml')) {
|
|
1229
2229
|
continue;
|
|
1230
2230
|
}
|
|
1231
2231
|
|
|
@@ -1233,23 +2233,24 @@ class Installer {
|
|
|
1233
2233
|
const targetFile = path.join(targetPath, file);
|
|
1234
2234
|
|
|
1235
2235
|
// Check if this is an agent file
|
|
1236
|
-
if (file.
|
|
2236
|
+
if (file.startsWith('agents/') && file.endsWith('.md')) {
|
|
1237
2237
|
// Read the file to check for localskip
|
|
1238
2238
|
const content = await fs.readFile(sourceFile, 'utf8');
|
|
1239
2239
|
|
|
1240
2240
|
// Check for localskip="true" in the agent tag
|
|
1241
2241
|
const agentMatch = content.match(/<agent[^>]*\slocalskip="true"[^>]*>/);
|
|
1242
2242
|
if (agentMatch) {
|
|
1243
|
-
|
|
2243
|
+
await prompts.log.message(` Skipping web-only agent: ${path.basename(file)}`);
|
|
1244
2244
|
continue; // Skip this agent
|
|
1245
2245
|
}
|
|
1246
2246
|
}
|
|
1247
2247
|
|
|
1248
2248
|
// Copy the file with placeholder replacement
|
|
1249
|
-
await
|
|
2249
|
+
await fs.ensureDir(path.dirname(targetFile));
|
|
2250
|
+
await this.copyFileWithPlaceholderReplacement(sourceFile, targetFile);
|
|
1250
2251
|
|
|
1251
2252
|
// Track the installed file
|
|
1252
|
-
this.installedFiles.
|
|
2253
|
+
this.installedFiles.add(targetFile);
|
|
1253
2254
|
}
|
|
1254
2255
|
}
|
|
1255
2256
|
|
|
@@ -1267,10 +2268,6 @@ class Installer {
|
|
|
1267
2268
|
const fullPath = path.join(dir, entry.name);
|
|
1268
2269
|
|
|
1269
2270
|
if (entry.isDirectory()) {
|
|
1270
|
-
// Skip _module-installer directories
|
|
1271
|
-
if (entry.name === '_module-installer') {
|
|
1272
|
-
continue;
|
|
1273
|
-
}
|
|
1274
2271
|
const subFiles = await this.getFileList(fullPath, baseDir);
|
|
1275
2272
|
files.push(...subFiles);
|
|
1276
2273
|
} else {
|
|
@@ -1296,372 +2293,42 @@ class Installer {
|
|
|
1296
2293
|
|
|
1297
2294
|
// Determine project directory (parent of bmad/ directory)
|
|
1298
2295
|
const bmadDir = path.dirname(modulePath);
|
|
1299
|
-
const
|
|
1300
|
-
const cfgAgentsDir = path.join(bmadDir, '_cfg', 'agents');
|
|
2296
|
+
const cfgAgentsDir = path.join(bmadDir, '_config', 'agents');
|
|
1301
2297
|
|
|
1302
|
-
// Ensure
|
|
2298
|
+
// Ensure _config/agents directory exists
|
|
1303
2299
|
await fs.ensureDir(cfgAgentsDir);
|
|
1304
2300
|
|
|
1305
2301
|
// Get all agent files
|
|
1306
2302
|
const agentFiles = await fs.readdir(agentsPath);
|
|
1307
2303
|
|
|
1308
2304
|
for (const agentFile of agentFiles) {
|
|
1309
|
-
//
|
|
2305
|
+
// Skip .agent.yaml files - they should already be compiled by compileModuleAgents
|
|
1310
2306
|
if (agentFile.endsWith('.agent.yaml')) {
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
2307
|
+
continue;
|
|
2308
|
+
}
|
|
2309
|
+
|
|
2310
|
+
// Only process .md files (already compiled from YAML)
|
|
2311
|
+
if (!agentFile.endsWith('.md')) {
|
|
2312
|
+
continue;
|
|
2313
|
+
}
|
|
2314
|
+
|
|
2315
|
+
const agentName = agentFile.replace('.md', '');
|
|
2316
|
+
const mdPath = path.join(agentsPath, agentFile);
|
|
2317
|
+
const customizePath = path.join(cfgAgentsDir, `${moduleName}-${agentName}.customize.yaml`);
|
|
2318
|
+
|
|
2319
|
+
// For .md files that are already compiled, we don't need to do much
|
|
2320
|
+
// Just ensure the customize template exists
|
|
2321
|
+
if (!(await fs.pathExists(customizePath))) {
|
|
2322
|
+
const genericTemplatePath = getSourcePath('utility', 'agent-components', 'agent.customize.template.yaml');
|
|
2323
|
+
if (await fs.pathExists(genericTemplatePath)) {
|
|
2324
|
+
await this.copyFileWithPlaceholderReplacement(genericTemplatePath, customizePath);
|
|
2325
|
+
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
|
|
2326
|
+
await prompts.log.message(` Created customize: ${moduleName}-${agentName}.customize.yaml`);
|
|
1322
2327
|
}
|
|
1323
2328
|
}
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
const xmlContent = await this.xmlHandler.buildFromYaml(yamlPath, customizeExists ? customizePath : null, {
|
|
1328
|
-
includeMetadata: true,
|
|
1329
|
-
});
|
|
1330
|
-
|
|
1331
|
-
// DO NOT replace {project-root} - LLMs understand this placeholder at runtime
|
|
1332
|
-
// const processedContent = xmlContent.replaceAll('{project-root}', projectDir);
|
|
1333
|
-
|
|
1334
|
-
// Write the built .md file to bmad/{module}/agents/ with POSIX-compliant final newline
|
|
1335
|
-
const content = xmlContent.endsWith('\n') ? xmlContent : xmlContent + '\n';
|
|
1336
|
-
await fs.writeFile(mdPath, content, 'utf8');
|
|
1337
|
-
this.installedFiles.push(mdPath);
|
|
1338
|
-
|
|
1339
|
-
// Remove the source YAML file - we can regenerate from installer source if needed
|
|
1340
|
-
await fs.remove(yamlPath);
|
|
1341
|
-
|
|
1342
|
-
console.log(chalk.dim(` Built agent: ${agentName}.md`));
|
|
1343
|
-
}
|
|
1344
|
-
// Handle legacy .md agents - inject activation if needed
|
|
1345
|
-
else if (agentFile.endsWith('.md')) {
|
|
1346
|
-
const agentPath = path.join(agentsPath, agentFile);
|
|
1347
|
-
let content = await fs.readFile(agentPath, 'utf8');
|
|
1348
|
-
|
|
1349
|
-
// Check if content has agent XML and no activation block
|
|
1350
|
-
if (content.includes('<agent') && !content.includes('<activation')) {
|
|
1351
|
-
// Inject the activation block using XML handler
|
|
1352
|
-
content = this.xmlHandler.injectActivationSimple(content);
|
|
1353
|
-
// Ensure POSIX-compliant final newline
|
|
1354
|
-
const finalContent = content.endsWith('\n') ? content : content + '\n';
|
|
1355
|
-
await fs.writeFile(agentPath, finalContent, 'utf8');
|
|
1356
|
-
}
|
|
1357
|
-
}
|
|
1358
|
-
}
|
|
1359
|
-
}
|
|
1360
|
-
|
|
1361
|
-
/**
|
|
1362
|
-
* Build standalone agents in bmad/agents/ directory
|
|
1363
|
-
* @param {string} bmadDir - Path to bmad directory
|
|
1364
|
-
* @param {string} projectDir - Path to project directory
|
|
1365
|
-
*/
|
|
1366
|
-
async buildStandaloneAgents(bmadDir, projectDir) {
|
|
1367
|
-
const standaloneAgentsPath = path.join(bmadDir, 'agents');
|
|
1368
|
-
const cfgAgentsDir = path.join(bmadDir, '_cfg', 'agents');
|
|
1369
|
-
|
|
1370
|
-
// Check if standalone agents directory exists
|
|
1371
|
-
if (!(await fs.pathExists(standaloneAgentsPath))) {
|
|
1372
|
-
return;
|
|
1373
|
-
}
|
|
1374
|
-
|
|
1375
|
-
// Get all subdirectories in agents/
|
|
1376
|
-
const agentDirs = await fs.readdir(standaloneAgentsPath, { withFileTypes: true });
|
|
1377
|
-
|
|
1378
|
-
for (const agentDir of agentDirs) {
|
|
1379
|
-
if (!agentDir.isDirectory()) continue;
|
|
1380
|
-
|
|
1381
|
-
const agentDirPath = path.join(standaloneAgentsPath, agentDir.name);
|
|
1382
|
-
|
|
1383
|
-
// Find any .agent.yaml file in the directory
|
|
1384
|
-
const files = await fs.readdir(agentDirPath);
|
|
1385
|
-
const yamlFile = files.find((f) => f.endsWith('.agent.yaml'));
|
|
1386
|
-
|
|
1387
|
-
if (!yamlFile) continue;
|
|
1388
|
-
|
|
1389
|
-
const agentName = path.basename(yamlFile, '.agent.yaml');
|
|
1390
|
-
const sourceYamlPath = path.join(agentDirPath, yamlFile);
|
|
1391
|
-
const targetMdPath = path.join(agentDirPath, `${agentName}.md`);
|
|
1392
|
-
const customizePath = path.join(cfgAgentsDir, `${agentName}.customize.yaml`);
|
|
1393
|
-
|
|
1394
|
-
// Check for customizations
|
|
1395
|
-
const customizeExists = await fs.pathExists(customizePath);
|
|
1396
|
-
let customizedFields = [];
|
|
1397
|
-
|
|
1398
|
-
if (customizeExists) {
|
|
1399
|
-
const customizeContent = await fs.readFile(customizePath, 'utf8');
|
|
1400
|
-
const yaml = require('js-yaml');
|
|
1401
|
-
const customizeYaml = yaml.load(customizeContent);
|
|
1402
|
-
|
|
1403
|
-
// Detect what fields are customized (similar to rebuildAgentFiles)
|
|
1404
|
-
if (customizeYaml) {
|
|
1405
|
-
if (customizeYaml.persona) {
|
|
1406
|
-
for (const [key, value] of Object.entries(customizeYaml.persona)) {
|
|
1407
|
-
if (value !== '' && value !== null && !(Array.isArray(value) && value.length === 0)) {
|
|
1408
|
-
customizedFields.push(`persona.${key}`);
|
|
1409
|
-
}
|
|
1410
|
-
}
|
|
1411
|
-
}
|
|
1412
|
-
if (customizeYaml.agent?.metadata) {
|
|
1413
|
-
for (const [key, value] of Object.entries(customizeYaml.agent.metadata)) {
|
|
1414
|
-
if (value !== '' && value !== null) {
|
|
1415
|
-
customizedFields.push(`metadata.${key}`);
|
|
1416
|
-
}
|
|
1417
|
-
}
|
|
1418
|
-
}
|
|
1419
|
-
if (customizeYaml.critical_actions && customizeYaml.critical_actions.length > 0) {
|
|
1420
|
-
customizedFields.push('critical_actions');
|
|
1421
|
-
}
|
|
1422
|
-
if (customizeYaml.menu && customizeYaml.menu.length > 0) {
|
|
1423
|
-
customizedFields.push('menu');
|
|
1424
|
-
}
|
|
1425
|
-
}
|
|
1426
|
-
}
|
|
1427
|
-
|
|
1428
|
-
// Build YAML to XML .md
|
|
1429
|
-
const xmlContent = await this.xmlHandler.buildFromYaml(sourceYamlPath, customizeExists ? customizePath : null, {
|
|
1430
|
-
includeMetadata: true,
|
|
1431
|
-
});
|
|
1432
|
-
|
|
1433
|
-
// DO NOT replace {project-root} - LLMs understand this placeholder at runtime
|
|
1434
|
-
// const processedContent = xmlContent.replaceAll('{project-root}', projectDir);
|
|
1435
|
-
|
|
1436
|
-
// Write the built .md file with POSIX-compliant final newline
|
|
1437
|
-
const content = xmlContent.endsWith('\n') ? xmlContent : xmlContent + '\n';
|
|
1438
|
-
await fs.writeFile(targetMdPath, content, 'utf8');
|
|
1439
|
-
|
|
1440
|
-
// Display result
|
|
1441
|
-
if (customizedFields.length > 0) {
|
|
1442
|
-
console.log(chalk.dim(` Built standalone agent: ${agentName}.md `) + chalk.yellow(`(customized: ${customizedFields.join(', ')})`));
|
|
1443
|
-
} else {
|
|
1444
|
-
console.log(chalk.dim(` Built standalone agent: ${agentName}.md`));
|
|
1445
|
-
}
|
|
1446
|
-
}
|
|
1447
|
-
}
|
|
1448
|
-
|
|
1449
|
-
/**
|
|
1450
|
-
* Rebuild agent files from installer source (for compile command)
|
|
1451
|
-
* @param {string} modulePath - Path to module in bmad/ installation
|
|
1452
|
-
* @param {string} moduleName - Module name
|
|
1453
|
-
*/
|
|
1454
|
-
async rebuildAgentFiles(modulePath, moduleName) {
|
|
1455
|
-
// Get source agents directory from installer
|
|
1456
|
-
const sourceAgentsPath =
|
|
1457
|
-
moduleName === 'core' ? path.join(getModulePath('core'), 'agents') : path.join(getSourcePath(`modules/${moduleName}`), 'agents');
|
|
1458
|
-
|
|
1459
|
-
if (!(await fs.pathExists(sourceAgentsPath))) {
|
|
1460
|
-
return; // No source agents to rebuild
|
|
1461
|
-
}
|
|
1462
|
-
|
|
1463
|
-
// Determine project directory (parent of bmad/ directory)
|
|
1464
|
-
const bmadDir = path.dirname(modulePath);
|
|
1465
|
-
const projectDir = path.dirname(bmadDir);
|
|
1466
|
-
const cfgAgentsDir = path.join(bmadDir, '_cfg', 'agents');
|
|
1467
|
-
const targetAgentsPath = path.join(modulePath, 'agents');
|
|
1468
|
-
|
|
1469
|
-
// Ensure target directory exists
|
|
1470
|
-
await fs.ensureDir(targetAgentsPath);
|
|
1471
|
-
|
|
1472
|
-
// Get all YAML agent files from source
|
|
1473
|
-
const sourceFiles = await fs.readdir(sourceAgentsPath);
|
|
1474
|
-
|
|
1475
|
-
for (const file of sourceFiles) {
|
|
1476
|
-
if (file.endsWith('.agent.yaml')) {
|
|
1477
|
-
const agentName = file.replace('.agent.yaml', '');
|
|
1478
|
-
const sourceYamlPath = path.join(sourceAgentsPath, file);
|
|
1479
|
-
const targetMdPath = path.join(targetAgentsPath, `${agentName}.md`);
|
|
1480
|
-
const customizePath = path.join(cfgAgentsDir, `${moduleName}-${agentName}.customize.yaml`);
|
|
1481
|
-
|
|
1482
|
-
// Check for customizations
|
|
1483
|
-
const customizeExists = await fs.pathExists(customizePath);
|
|
1484
|
-
let customizedFields = [];
|
|
1485
|
-
|
|
1486
|
-
if (customizeExists) {
|
|
1487
|
-
const customizeContent = await fs.readFile(customizePath, 'utf8');
|
|
1488
|
-
const yaml = require('js-yaml');
|
|
1489
|
-
const customizeYaml = yaml.load(customizeContent);
|
|
1490
|
-
|
|
1491
|
-
// Detect what fields are customized
|
|
1492
|
-
if (customizeYaml) {
|
|
1493
|
-
if (customizeYaml.persona) {
|
|
1494
|
-
for (const [key, value] of Object.entries(customizeYaml.persona)) {
|
|
1495
|
-
if (value !== '' && value !== null && !(Array.isArray(value) && value.length === 0)) {
|
|
1496
|
-
customizedFields.push(`persona.${key}`);
|
|
1497
|
-
}
|
|
1498
|
-
}
|
|
1499
|
-
}
|
|
1500
|
-
if (customizeYaml.agent?.metadata) {
|
|
1501
|
-
for (const [key, value] of Object.entries(customizeYaml.agent.metadata)) {
|
|
1502
|
-
if (value !== '' && value !== null) {
|
|
1503
|
-
customizedFields.push(`metadata.${key}`);
|
|
1504
|
-
}
|
|
1505
|
-
}
|
|
1506
|
-
}
|
|
1507
|
-
if (customizeYaml.critical_actions && customizeYaml.critical_actions.length > 0) {
|
|
1508
|
-
customizedFields.push('critical_actions');
|
|
1509
|
-
}
|
|
1510
|
-
if (customizeYaml.memories && customizeYaml.memories.length > 0) {
|
|
1511
|
-
customizedFields.push('memories');
|
|
1512
|
-
}
|
|
1513
|
-
if (customizeYaml.menu && customizeYaml.menu.length > 0) {
|
|
1514
|
-
customizedFields.push('menu');
|
|
1515
|
-
}
|
|
1516
|
-
if (customizeYaml.prompts && customizeYaml.prompts.length > 0) {
|
|
1517
|
-
customizedFields.push('prompts');
|
|
1518
|
-
}
|
|
1519
|
-
}
|
|
1520
|
-
}
|
|
1521
|
-
|
|
1522
|
-
// Build YAML + customize to .md
|
|
1523
|
-
const xmlContent = await this.xmlHandler.buildFromYaml(sourceYamlPath, customizeExists ? customizePath : null, {
|
|
1524
|
-
includeMetadata: true,
|
|
1525
|
-
});
|
|
1526
|
-
|
|
1527
|
-
// DO NOT replace {project-root} - LLMs understand this placeholder at runtime
|
|
1528
|
-
// const processedContent = xmlContent.replaceAll('{project-root}', projectDir);
|
|
1529
|
-
|
|
1530
|
-
// Write the rebuilt .md file with POSIX-compliant final newline
|
|
1531
|
-
const content = xmlContent.endsWith('\n') ? xmlContent : xmlContent + '\n';
|
|
1532
|
-
await fs.writeFile(targetMdPath, content, 'utf8');
|
|
1533
|
-
|
|
1534
|
-
// Display result with customizations if any
|
|
1535
|
-
if (customizedFields.length > 0) {
|
|
1536
|
-
console.log(chalk.dim(` Rebuilt agent: ${agentName}.md `) + chalk.yellow(`(customized: ${customizedFields.join(', ')})`));
|
|
1537
|
-
} else {
|
|
1538
|
-
console.log(chalk.dim(` Rebuilt agent: ${agentName}.md`));
|
|
1539
|
-
}
|
|
1540
|
-
}
|
|
1541
|
-
}
|
|
1542
|
-
}
|
|
1543
|
-
|
|
1544
|
-
/**
|
|
1545
|
-
* Compile/rebuild all agents and tasks for quick updates
|
|
1546
|
-
* @param {Object} config - Compilation configuration
|
|
1547
|
-
* @returns {Object} Compilation results
|
|
1548
|
-
*/
|
|
1549
|
-
async compileAgents(config) {
|
|
1550
|
-
const ora = require('ora');
|
|
1551
|
-
const spinner = ora('Starting agent compilation...').start();
|
|
1552
|
-
|
|
1553
|
-
try {
|
|
1554
|
-
const projectDir = path.resolve(config.directory);
|
|
1555
|
-
const bmadDir = await this.findBmadDir(projectDir);
|
|
1556
|
-
|
|
1557
|
-
// Check if bmad directory exists
|
|
1558
|
-
if (!(await fs.pathExists(bmadDir))) {
|
|
1559
|
-
spinner.fail('No BMAD installation found');
|
|
1560
|
-
throw new Error(`BMAD not installed at ${bmadDir}`);
|
|
1561
|
-
}
|
|
1562
|
-
|
|
1563
|
-
let agentCount = 0;
|
|
1564
|
-
let taskCount = 0;
|
|
1565
|
-
|
|
1566
|
-
// Process all modules in bmad directory
|
|
1567
|
-
spinner.text = 'Rebuilding agent files...';
|
|
1568
|
-
const entries = await fs.readdir(bmadDir, { withFileTypes: true });
|
|
1569
|
-
|
|
1570
|
-
for (const entry of entries) {
|
|
1571
|
-
if (entry.isDirectory() && entry.name !== '_cfg' && entry.name !== 'docs') {
|
|
1572
|
-
const modulePath = path.join(bmadDir, entry.name);
|
|
1573
|
-
|
|
1574
|
-
// Special handling for standalone agents in bmad/agents/ directory
|
|
1575
|
-
if (entry.name === 'agents') {
|
|
1576
|
-
spinner.text = 'Building standalone agents...';
|
|
1577
|
-
await this.buildStandaloneAgents(bmadDir, projectDir);
|
|
1578
|
-
|
|
1579
|
-
// Count standalone agents
|
|
1580
|
-
const standaloneAgentsPath = path.join(bmadDir, 'agents');
|
|
1581
|
-
const standaloneAgentDirs = await fs.readdir(standaloneAgentsPath, { withFileTypes: true });
|
|
1582
|
-
for (const agentDir of standaloneAgentDirs) {
|
|
1583
|
-
if (agentDir.isDirectory()) {
|
|
1584
|
-
const agentDirPath = path.join(standaloneAgentsPath, agentDir.name);
|
|
1585
|
-
const agentFiles = await fs.readdir(agentDirPath);
|
|
1586
|
-
agentCount += agentFiles.filter((f) => f.endsWith('.md') && !f.endsWith('.agent.yaml')).length;
|
|
1587
|
-
}
|
|
1588
|
-
}
|
|
1589
|
-
} else {
|
|
1590
|
-
// Rebuild module agents from installer source
|
|
1591
|
-
const agentsPath = path.join(modulePath, 'agents');
|
|
1592
|
-
if (await fs.pathExists(agentsPath)) {
|
|
1593
|
-
await this.rebuildAgentFiles(modulePath, entry.name);
|
|
1594
|
-
const agentFiles = await fs.readdir(agentsPath);
|
|
1595
|
-
agentCount += agentFiles.filter((f) => f.endsWith('.md')).length;
|
|
1596
|
-
}
|
|
1597
|
-
|
|
1598
|
-
// Count tasks (already built)
|
|
1599
|
-
const tasksPath = path.join(modulePath, 'tasks');
|
|
1600
|
-
if (await fs.pathExists(tasksPath)) {
|
|
1601
|
-
const taskFiles = await fs.readdir(tasksPath);
|
|
1602
|
-
taskCount += taskFiles.filter((f) => f.endsWith('.md')).length;
|
|
1603
|
-
}
|
|
1604
|
-
}
|
|
1605
|
-
}
|
|
1606
|
-
}
|
|
1607
|
-
|
|
1608
|
-
// Regenerate manifests after compilation
|
|
1609
|
-
spinner.start('Regenerating manifests...');
|
|
1610
|
-
const installedModules = entries
|
|
1611
|
-
.filter((e) => e.isDirectory() && e.name !== '_cfg' && e.name !== 'docs' && e.name !== 'agents' && e.name !== 'core')
|
|
1612
|
-
.map((e) => e.name);
|
|
1613
|
-
const manifestGen = new ManifestGenerator();
|
|
1614
|
-
|
|
1615
|
-
// Get existing IDE list from manifest
|
|
1616
|
-
const existingManifestPath = path.join(bmadDir, '_cfg', 'manifest.yaml');
|
|
1617
|
-
let existingIdes = [];
|
|
1618
|
-
if (await fs.pathExists(existingManifestPath)) {
|
|
1619
|
-
const manifestContent = await fs.readFile(existingManifestPath, 'utf8');
|
|
1620
|
-
const yaml = require('js-yaml');
|
|
1621
|
-
const manifest = yaml.load(manifestContent);
|
|
1622
|
-
existingIdes = manifest.ides || [];
|
|
1623
|
-
}
|
|
1624
|
-
|
|
1625
|
-
await manifestGen.generateManifests(bmadDir, installedModules, [], {
|
|
1626
|
-
ides: existingIdes,
|
|
1627
|
-
});
|
|
1628
|
-
spinner.succeed('Manifests regenerated');
|
|
1629
|
-
|
|
1630
|
-
// Update IDE configurations using the existing IDE list from manifest
|
|
1631
|
-
if (existingIdes && existingIdes.length > 0) {
|
|
1632
|
-
spinner.start('Updating IDE configurations...');
|
|
1633
|
-
|
|
1634
|
-
for (const ide of existingIdes) {
|
|
1635
|
-
spinner.text = `Updating ${ide}...`;
|
|
1636
|
-
|
|
1637
|
-
// Stop spinner before IDE setup to prevent blocking any potential prompts
|
|
1638
|
-
// However, we pass _alreadyConfigured to skip all prompts during compile
|
|
1639
|
-
spinner.stop();
|
|
1640
|
-
|
|
1641
|
-
await this.ideManager.setup(ide, projectDir, bmadDir, {
|
|
1642
|
-
selectedModules: installedModules,
|
|
1643
|
-
skipModuleInstall: true, // Skip module installation, just update IDE files
|
|
1644
|
-
verbose: config.verbose,
|
|
1645
|
-
preCollectedConfig: { _alreadyConfigured: true }, // Skip all interactive prompts during compile
|
|
1646
|
-
});
|
|
1647
|
-
|
|
1648
|
-
// Restart spinner for next IDE
|
|
1649
|
-
if (existingIdes.indexOf(ide) < existingIdes.length - 1) {
|
|
1650
|
-
spinner.start('Updating IDE configurations...');
|
|
1651
|
-
}
|
|
1652
|
-
}
|
|
1653
|
-
|
|
1654
|
-
console.log(chalk.green('✓ IDE configurations updated'));
|
|
1655
|
-
} else {
|
|
1656
|
-
console.log(chalk.yellow('⚠️ No IDEs configured. Skipping IDE update.'));
|
|
1657
|
-
}
|
|
1658
|
-
|
|
1659
|
-
return { agentCount, taskCount };
|
|
1660
|
-
} catch (error) {
|
|
1661
|
-
spinner.fail('Compilation failed');
|
|
1662
|
-
throw error;
|
|
1663
|
-
}
|
|
1664
|
-
}
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2331
|
+
}
|
|
1665
2332
|
|
|
1666
2333
|
/**
|
|
1667
2334
|
* Private: Update core
|
|
@@ -1676,6 +2343,12 @@ class Installer {
|
|
|
1676
2343
|
} else {
|
|
1677
2344
|
// Selective update - preserve user modifications
|
|
1678
2345
|
await this.fileOps.syncDirectory(sourcePath, targetPath);
|
|
2346
|
+
|
|
2347
|
+
// Recompile agents (#1133)
|
|
2348
|
+
const { ModuleManager } = require('../modules/manager');
|
|
2349
|
+
const moduleManager = new ModuleManager();
|
|
2350
|
+
await moduleManager.compileModuleAgents(sourcePath, targetPath, 'core', bmadDir, this);
|
|
2351
|
+
await this.processAgentFiles(targetPath, 'core');
|
|
1679
2352
|
}
|
|
1680
2353
|
}
|
|
1681
2354
|
|
|
@@ -1685,45 +2358,172 @@ class Installer {
|
|
|
1685
2358
|
* @returns {Object} Update result
|
|
1686
2359
|
*/
|
|
1687
2360
|
async quickUpdate(config) {
|
|
1688
|
-
const
|
|
1689
|
-
|
|
2361
|
+
const spinner = await prompts.spinner();
|
|
2362
|
+
spinner.start('Starting quick update...');
|
|
1690
2363
|
|
|
1691
2364
|
try {
|
|
1692
2365
|
const projectDir = path.resolve(config.directory);
|
|
1693
|
-
const bmadDir = await this.findBmadDir(projectDir);
|
|
2366
|
+
const { bmadDir } = await this.findBmadDir(projectDir);
|
|
1694
2367
|
|
|
1695
2368
|
// Check if bmad directory exists
|
|
1696
2369
|
if (!(await fs.pathExists(bmadDir))) {
|
|
1697
|
-
spinner.
|
|
2370
|
+
spinner.stop('No BMAD installation found');
|
|
1698
2371
|
throw new Error(`BMAD not installed at ${bmadDir}. Use regular install for first-time setup.`);
|
|
1699
2372
|
}
|
|
1700
2373
|
|
|
1701
|
-
spinner.
|
|
2374
|
+
spinner.message('Detecting installed modules and configuration...');
|
|
1702
2375
|
|
|
1703
2376
|
// Detect existing installation
|
|
1704
2377
|
const existingInstall = await this.detector.detect(bmadDir);
|
|
1705
2378
|
const installedModules = existingInstall.modules.map((m) => m.id);
|
|
1706
2379
|
const configuredIdes = existingInstall.ides || [];
|
|
2380
|
+
const projectRoot = path.dirname(bmadDir);
|
|
2381
|
+
|
|
2382
|
+
// Get custom module sources: first from --custom-content (re-cache from source), then from cache
|
|
2383
|
+
const customModuleSources = new Map();
|
|
2384
|
+
if (config.customContent?.sources?.length > 0) {
|
|
2385
|
+
for (const source of config.customContent.sources) {
|
|
2386
|
+
if (source.id && source.path && (await fs.pathExists(source.path))) {
|
|
2387
|
+
customModuleSources.set(source.id, {
|
|
2388
|
+
id: source.id,
|
|
2389
|
+
name: source.name || source.id,
|
|
2390
|
+
sourcePath: source.path,
|
|
2391
|
+
cached: false, // From CLI, will be re-cached
|
|
2392
|
+
});
|
|
2393
|
+
}
|
|
2394
|
+
}
|
|
2395
|
+
}
|
|
2396
|
+
const cacheDir = path.join(bmadDir, '_config', 'custom');
|
|
2397
|
+
if (await fs.pathExists(cacheDir)) {
|
|
2398
|
+
const cachedModules = await fs.readdir(cacheDir, { withFileTypes: true });
|
|
2399
|
+
|
|
2400
|
+
for (const cachedModule of cachedModules) {
|
|
2401
|
+
const moduleId = cachedModule.name;
|
|
2402
|
+
const cachedPath = path.join(cacheDir, moduleId);
|
|
2403
|
+
|
|
2404
|
+
// Skip if path doesn't exist (broken symlink, deleted dir) - avoids lstat ENOENT
|
|
2405
|
+
if (!(await fs.pathExists(cachedPath))) {
|
|
2406
|
+
continue;
|
|
2407
|
+
}
|
|
2408
|
+
if (!cachedModule.isDirectory()) {
|
|
2409
|
+
continue;
|
|
2410
|
+
}
|
|
2411
|
+
|
|
2412
|
+
// Skip if we already have this module from manifest
|
|
2413
|
+
if (customModuleSources.has(moduleId)) {
|
|
2414
|
+
continue;
|
|
2415
|
+
}
|
|
2416
|
+
|
|
2417
|
+
// Check if this is an external official module - skip cache for those
|
|
2418
|
+
const isExternal = await this.moduleManager.isExternalModule(moduleId);
|
|
2419
|
+
if (isExternal) {
|
|
2420
|
+
// External modules are handled via cloneExternalModule, not from cache
|
|
2421
|
+
continue;
|
|
2422
|
+
}
|
|
2423
|
+
|
|
2424
|
+
// Check if this is actually a custom module (has module.yaml)
|
|
2425
|
+
const moduleYamlPath = path.join(cachedPath, 'module.yaml');
|
|
2426
|
+
if (await fs.pathExists(moduleYamlPath)) {
|
|
2427
|
+
// For quick update, we always rebuild from cache
|
|
2428
|
+
customModuleSources.set(moduleId, {
|
|
2429
|
+
id: moduleId,
|
|
2430
|
+
name: moduleId, // We'll read the actual name if needed
|
|
2431
|
+
sourcePath: cachedPath,
|
|
2432
|
+
cached: true, // Flag to indicate this is from cache
|
|
2433
|
+
});
|
|
2434
|
+
}
|
|
2435
|
+
}
|
|
2436
|
+
}
|
|
1707
2437
|
|
|
1708
2438
|
// Load saved IDE configurations
|
|
1709
2439
|
const savedIdeConfigs = await this.ideConfigManager.loadAllIdeConfigs(bmadDir);
|
|
1710
2440
|
|
|
1711
2441
|
// Get available modules (what we have source for)
|
|
1712
|
-
const
|
|
1713
|
-
const
|
|
2442
|
+
const availableModulesData = await this.moduleManager.listAvailable();
|
|
2443
|
+
const availableModules = [...availableModulesData.modules, ...availableModulesData.customModules];
|
|
2444
|
+
|
|
2445
|
+
// Add external official modules to available modules
|
|
2446
|
+
// These can always be obtained by cloning from their remote URLs
|
|
2447
|
+
const { ExternalModuleManager } = require('../modules/external-manager');
|
|
2448
|
+
const externalManager = new ExternalModuleManager();
|
|
2449
|
+
const externalModules = await externalManager.listAvailable();
|
|
2450
|
+
for (const externalModule of externalModules) {
|
|
2451
|
+
// Only add if not already in the list and is installed
|
|
2452
|
+
if (installedModules.includes(externalModule.code) && !availableModules.some((m) => m.id === externalModule.code)) {
|
|
2453
|
+
availableModules.push({
|
|
2454
|
+
id: externalModule.code,
|
|
2455
|
+
name: externalModule.name,
|
|
2456
|
+
isExternal: true,
|
|
2457
|
+
fromExternal: true,
|
|
2458
|
+
});
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
|
|
2462
|
+
// Add custom modules from manifest if their sources exist
|
|
2463
|
+
for (const [moduleId, customModule] of customModuleSources) {
|
|
2464
|
+
// Use the absolute sourcePath
|
|
2465
|
+
const sourcePath = customModule.sourcePath;
|
|
2466
|
+
|
|
2467
|
+
// Check if source exists at the recorded path
|
|
2468
|
+
if (
|
|
2469
|
+
sourcePath &&
|
|
2470
|
+
(await fs.pathExists(sourcePath)) && // Add to available modules if not already there
|
|
2471
|
+
!availableModules.some((m) => m.id === moduleId)
|
|
2472
|
+
) {
|
|
2473
|
+
availableModules.push({
|
|
2474
|
+
id: moduleId,
|
|
2475
|
+
name: customModule.name || moduleId,
|
|
2476
|
+
path: sourcePath,
|
|
2477
|
+
isCustom: true,
|
|
2478
|
+
fromManifest: true,
|
|
2479
|
+
});
|
|
2480
|
+
}
|
|
2481
|
+
}
|
|
2482
|
+
|
|
2483
|
+
// Handle missing custom module sources using shared method
|
|
2484
|
+
const customModuleResult = await this.handleMissingCustomSources(
|
|
2485
|
+
customModuleSources,
|
|
2486
|
+
bmadDir,
|
|
2487
|
+
projectRoot,
|
|
2488
|
+
'update',
|
|
2489
|
+
installedModules,
|
|
2490
|
+
config.skipPrompts || false,
|
|
2491
|
+
);
|
|
2492
|
+
|
|
2493
|
+
const { validCustomModules, keptModulesWithoutSources } = customModuleResult;
|
|
2494
|
+
|
|
2495
|
+
const customModulesFromManifest = validCustomModules.map((m) => ({
|
|
2496
|
+
...m,
|
|
2497
|
+
isCustom: true,
|
|
2498
|
+
hasUpdate: true,
|
|
2499
|
+
}));
|
|
2500
|
+
|
|
2501
|
+
const allAvailableModules = [...availableModules, ...customModulesFromManifest];
|
|
2502
|
+
const availableModuleIds = new Set(allAvailableModules.map((m) => m.id));
|
|
2503
|
+
|
|
2504
|
+
// Core module is special - never include it in update flow
|
|
2505
|
+
const nonCoreInstalledModules = installedModules.filter((id) => id !== 'core');
|
|
1714
2506
|
|
|
1715
2507
|
// Only update modules that are BOTH installed AND available (we have source for)
|
|
1716
|
-
const modulesToUpdate =
|
|
1717
|
-
const skippedModules =
|
|
2508
|
+
const modulesToUpdate = nonCoreInstalledModules.filter((id) => availableModuleIds.has(id));
|
|
2509
|
+
const skippedModules = nonCoreInstalledModules.filter((id) => !availableModuleIds.has(id));
|
|
2510
|
+
|
|
2511
|
+
// Add custom modules that were kept without sources to the skipped modules
|
|
2512
|
+
// This ensures their agents are preserved in the manifest
|
|
2513
|
+
for (const keptModule of keptModulesWithoutSources) {
|
|
2514
|
+
if (!skippedModules.includes(keptModule)) {
|
|
2515
|
+
skippedModules.push(keptModule);
|
|
2516
|
+
}
|
|
2517
|
+
}
|
|
1718
2518
|
|
|
1719
|
-
spinner.
|
|
2519
|
+
spinner.stop(`Found ${modulesToUpdate.length} module(s) to update and ${configuredIdes.length} configured tool(s)`);
|
|
1720
2520
|
|
|
1721
2521
|
if (skippedModules.length > 0) {
|
|
1722
|
-
|
|
2522
|
+
await prompts.log.warn(`Skipping ${skippedModules.length} module(s) - no source available: ${skippedModules.join(', ')}`);
|
|
1723
2523
|
}
|
|
1724
2524
|
|
|
1725
2525
|
// Load existing configs and collect new fields (if any)
|
|
1726
|
-
|
|
2526
|
+
await prompts.log.info('Checking for new configuration options...');
|
|
1727
2527
|
await this.configCollector.loadExistingConfig(projectDir);
|
|
1728
2528
|
|
|
1729
2529
|
let promptedForNewFields = false;
|
|
@@ -1743,7 +2543,7 @@ class Installer {
|
|
|
1743
2543
|
}
|
|
1744
2544
|
|
|
1745
2545
|
if (!promptedForNewFields) {
|
|
1746
|
-
|
|
2546
|
+
await prompts.log.success('All configuration is up to date, no new options to configure');
|
|
1747
2547
|
}
|
|
1748
2548
|
|
|
1749
2549
|
// Add metadata
|
|
@@ -1753,9 +2553,6 @@ class Installer {
|
|
|
1753
2553
|
lastModified: new Date().toISOString(),
|
|
1754
2554
|
};
|
|
1755
2555
|
|
|
1756
|
-
// Now run the full installation with the collected configs
|
|
1757
|
-
spinner.start('Updating BMAD installation...');
|
|
1758
|
-
|
|
1759
2556
|
// Build the config object for the installer
|
|
1760
2557
|
const installConfig = {
|
|
1761
2558
|
directory: projectDir,
|
|
@@ -1768,12 +2565,19 @@ class Installer {
|
|
|
1768
2565
|
_quickUpdate: true, // Flag to skip certain prompts
|
|
1769
2566
|
_preserveModules: skippedModules, // Preserve these in manifest even though we didn't update them
|
|
1770
2567
|
_savedIdeConfigs: savedIdeConfigs, // Pass saved IDE configs to installer
|
|
2568
|
+
_customModuleSources: customModuleSources, // Pass custom module sources for updates
|
|
2569
|
+
_existingModules: installedModules, // Pass all installed modules for manifest generation
|
|
2570
|
+
customContent: config.customContent, // Pass through for re-caching from source
|
|
1771
2571
|
};
|
|
1772
2572
|
|
|
1773
2573
|
// Call the standard install method
|
|
1774
2574
|
const result = await this.install(installConfig);
|
|
1775
2575
|
|
|
1776
|
-
spinner
|
|
2576
|
+
// Only succeed the spinner if it's still spinning
|
|
2577
|
+
// (install method might have stopped it if folder name changed)
|
|
2578
|
+
if (spinner.isSpinning) {
|
|
2579
|
+
spinner.stop('Quick update complete!');
|
|
2580
|
+
}
|
|
1777
2581
|
|
|
1778
2582
|
return {
|
|
1779
2583
|
success: true,
|
|
@@ -1784,115 +2588,170 @@ class Installer {
|
|
|
1784
2588
|
ides: configuredIdes,
|
|
1785
2589
|
};
|
|
1786
2590
|
} catch (error) {
|
|
1787
|
-
spinner.
|
|
2591
|
+
spinner.error('Quick update failed');
|
|
1788
2592
|
throw error;
|
|
1789
2593
|
}
|
|
1790
2594
|
}
|
|
1791
2595
|
|
|
1792
2596
|
/**
|
|
1793
|
-
*
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
const inquirer = require('inquirer');
|
|
1797
|
-
return await inquirer.prompt([
|
|
1798
|
-
{
|
|
1799
|
-
type: 'list',
|
|
1800
|
-
name: 'action',
|
|
1801
|
-
message: 'What would you like to do?',
|
|
1802
|
-
choices: [
|
|
1803
|
-
{ name: 'Update existing installation', value: 'update' },
|
|
1804
|
-
{ name: 'Remove and reinstall', value: 'reinstall' },
|
|
1805
|
-
{ name: 'Cancel', value: 'cancel' },
|
|
1806
|
-
],
|
|
1807
|
-
},
|
|
1808
|
-
]);
|
|
1809
|
-
}
|
|
1810
|
-
|
|
1811
|
-
/**
|
|
1812
|
-
* Handle legacy BMAD v4 migration with automatic backup
|
|
1813
|
-
* @param {string} projectDir - Project directory
|
|
1814
|
-
* @param {Object} legacyV4 - Legacy V4 detection result with offenders array
|
|
2597
|
+
* Compile agents with customizations only
|
|
2598
|
+
* @param {Object} config - Configuration with directory
|
|
2599
|
+
* @returns {Object} Compilation result
|
|
1815
2600
|
*/
|
|
1816
|
-
async
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
// Separate .bmad* folders (auto-backup) from other offending paths (manual cleanup)
|
|
1821
|
-
const bmadFolders = legacyV4.offenders.filter((p) => {
|
|
1822
|
-
const name = path.basename(p);
|
|
1823
|
-
return name.startsWith('.bmad'); // Only dot-prefixed folders get auto-backed up
|
|
1824
|
-
});
|
|
1825
|
-
const otherOffenders = legacyV4.offenders.filter((p) => {
|
|
1826
|
-
const name = path.basename(p);
|
|
1827
|
-
return !name.startsWith('.bmad'); // Everything else is manual cleanup
|
|
1828
|
-
});
|
|
2601
|
+
async compileAgents(config) {
|
|
2602
|
+
// Using @clack prompts
|
|
2603
|
+
const { ModuleManager } = require('../modules/manager');
|
|
2604
|
+
const { getSourcePath } = require('../../../lib/project-root');
|
|
1829
2605
|
|
|
1830
|
-
const
|
|
2606
|
+
const spinner = await prompts.spinner();
|
|
2607
|
+
spinner.start('Recompiling agents with customizations...');
|
|
1831
2608
|
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
console.log(chalk.dim('It is recommended to remove the following items before proceeding:\n'));
|
|
1836
|
-
for (const p of otherOffenders) console.log(chalk.dim(` - ${p}`));
|
|
2609
|
+
try {
|
|
2610
|
+
const projectDir = path.resolve(config.directory);
|
|
2611
|
+
const { bmadDir } = await this.findBmadDir(projectDir);
|
|
1837
2612
|
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
2613
|
+
// Check if bmad directory exists
|
|
2614
|
+
if (!(await fs.pathExists(bmadDir))) {
|
|
2615
|
+
spinner.stop('No BMAD installation found');
|
|
2616
|
+
throw new Error(`BMAD not installed at ${bmadDir}. Use regular install for first-time setup.`);
|
|
2617
|
+
}
|
|
1843
2618
|
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
name: 'cleanedUp',
|
|
1848
|
-
message: 'Have you completed the recommended cleanup? (You can proceed without it, but it is recommended)',
|
|
1849
|
-
default: false,
|
|
1850
|
-
},
|
|
1851
|
-
]);
|
|
2619
|
+
// Detect existing installation
|
|
2620
|
+
const existingInstall = await this.detector.detect(bmadDir);
|
|
2621
|
+
const installedModules = existingInstall.modules.map((m) => m.id);
|
|
1852
2622
|
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
2623
|
+
// Initialize module manager
|
|
2624
|
+
const moduleManager = new ModuleManager();
|
|
2625
|
+
moduleManager.setBmadFolderName(path.basename(bmadDir));
|
|
2626
|
+
|
|
2627
|
+
let totalAgentCount = 0;
|
|
2628
|
+
|
|
2629
|
+
// Get custom module sources from cache
|
|
2630
|
+
const customModuleSources = new Map();
|
|
2631
|
+
const cacheDir = path.join(bmadDir, '_config', 'custom');
|
|
2632
|
+
if (await fs.pathExists(cacheDir)) {
|
|
2633
|
+
const cachedModules = await fs.readdir(cacheDir, { withFileTypes: true });
|
|
2634
|
+
|
|
2635
|
+
for (const cachedModule of cachedModules) {
|
|
2636
|
+
if (cachedModule.isDirectory()) {
|
|
2637
|
+
const moduleId = cachedModule.name;
|
|
2638
|
+
const cachedPath = path.join(cacheDir, moduleId);
|
|
2639
|
+
const moduleYamlPath = path.join(cachedPath, 'module.yaml');
|
|
2640
|
+
|
|
2641
|
+
// Check if this is actually a custom module
|
|
2642
|
+
if (await fs.pathExists(moduleYamlPath)) {
|
|
2643
|
+
// Check if this is an external official module - skip cache for those
|
|
2644
|
+
const isExternal = await this.moduleManager.isExternalModule(moduleId);
|
|
2645
|
+
if (isExternal) {
|
|
2646
|
+
// External modules are handled via cloneExternalModule, not from cache
|
|
2647
|
+
continue;
|
|
2648
|
+
}
|
|
2649
|
+
customModuleSources.set(moduleId, cachedPath);
|
|
2650
|
+
}
|
|
2651
|
+
}
|
|
2652
|
+
}
|
|
1857
2653
|
}
|
|
1858
|
-
}
|
|
1859
2654
|
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
for (const p of bmadFolders) console.log(chalk.dim(` - ${p}`));
|
|
2655
|
+
// Process each installed module
|
|
2656
|
+
for (const moduleId of installedModules) {
|
|
2657
|
+
spinner.message(`Recompiling agents in ${moduleId}...`);
|
|
1864
2658
|
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
2659
|
+
// Get source path
|
|
2660
|
+
let sourcePath;
|
|
2661
|
+
if (moduleId === 'core') {
|
|
2662
|
+
sourcePath = getSourcePath('core');
|
|
2663
|
+
} else {
|
|
2664
|
+
// First check if it's in the custom cache
|
|
2665
|
+
if (customModuleSources.has(moduleId)) {
|
|
2666
|
+
sourcePath = customModuleSources.get(moduleId);
|
|
2667
|
+
} else {
|
|
2668
|
+
sourcePath = await moduleManager.findModuleSource(moduleId);
|
|
2669
|
+
}
|
|
2670
|
+
}
|
|
1873
2671
|
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
2672
|
+
if (!sourcePath) {
|
|
2673
|
+
await prompts.log.warn(`Source not found for module ${moduleId}, skipping...`);
|
|
2674
|
+
continue;
|
|
2675
|
+
}
|
|
1877
2676
|
|
|
1878
|
-
|
|
1879
|
-
const folderName = path.basename(folder);
|
|
1880
|
-
const backupPath = path.join(backupDir, folderName);
|
|
2677
|
+
const targetPath = path.join(bmadDir, moduleId);
|
|
1881
2678
|
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
if (await fs.pathExists(backupPath)) {
|
|
1885
|
-
const timestamp = new Date().toISOString().replaceAll(/[:.]/g, '-').split('T')[0];
|
|
1886
|
-
finalBackupPath = path.join(backupDir, `${folderName}-${timestamp}`);
|
|
1887
|
-
}
|
|
2679
|
+
// Compile agents for this module
|
|
2680
|
+
await moduleManager.compileModuleAgents(sourcePath, targetPath, moduleId, bmadDir, this);
|
|
1888
2681
|
|
|
1889
|
-
|
|
1890
|
-
|
|
2682
|
+
// Count agents (rough estimate based on files)
|
|
2683
|
+
const agentsPath = path.join(targetPath, 'agents');
|
|
2684
|
+
if (await fs.pathExists(agentsPath)) {
|
|
2685
|
+
const agentFiles = await fs.readdir(agentsPath);
|
|
2686
|
+
const agentCount = agentFiles.filter((f) => f.endsWith('.md')).length;
|
|
2687
|
+
totalAgentCount += agentCount;
|
|
1891
2688
|
}
|
|
1892
|
-
} else {
|
|
1893
|
-
throw new Error('Installation cancelled by user');
|
|
1894
2689
|
}
|
|
2690
|
+
|
|
2691
|
+
spinner.stop('Agent recompilation complete!');
|
|
2692
|
+
|
|
2693
|
+
return {
|
|
2694
|
+
success: true,
|
|
2695
|
+
agentCount: totalAgentCount,
|
|
2696
|
+
modules: installedModules,
|
|
2697
|
+
};
|
|
2698
|
+
} catch (error) {
|
|
2699
|
+
spinner.error('Agent recompilation failed');
|
|
2700
|
+
throw error;
|
|
2701
|
+
}
|
|
2702
|
+
}
|
|
2703
|
+
|
|
2704
|
+
/**
|
|
2705
|
+
* Private: Prompt for update action
|
|
2706
|
+
*/
|
|
2707
|
+
async promptUpdateAction() {
|
|
2708
|
+
const action = await prompts.select({
|
|
2709
|
+
message: 'What would you like to do?',
|
|
2710
|
+
choices: [{ name: 'Update existing installation', value: 'update' }],
|
|
2711
|
+
});
|
|
2712
|
+
return { action };
|
|
2713
|
+
}
|
|
2714
|
+
|
|
2715
|
+
/**
|
|
2716
|
+
* Handle legacy BMAD v4 detection with simple warning
|
|
2717
|
+
* @param {string} _projectDir - Project directory (unused in simplified version)
|
|
2718
|
+
* @param {Object} _legacyV4 - Legacy V4 detection result (unused in simplified version)
|
|
2719
|
+
*/
|
|
2720
|
+
async handleLegacyV4Migration(_projectDir, _legacyV4) {
|
|
2721
|
+
await prompts.note(
|
|
2722
|
+
'Found .bmad-method folder from BMAD v4 installation.\n\n' +
|
|
2723
|
+
'Before continuing with installation, we recommend:\n' +
|
|
2724
|
+
' 1. Remove the .bmad-method folder, OR\n' +
|
|
2725
|
+
' 2. Back it up by renaming it to another name (e.g., bmad-method-backup)\n\n' +
|
|
2726
|
+
'If your v4 installation set up rules or commands, you should remove those as well.',
|
|
2727
|
+
'Legacy BMAD v4 detected',
|
|
2728
|
+
);
|
|
2729
|
+
|
|
2730
|
+
const proceed = await prompts.select({
|
|
2731
|
+
message: 'What would you like to do?',
|
|
2732
|
+
choices: [
|
|
2733
|
+
{
|
|
2734
|
+
name: 'Exit and clean up manually (recommended)',
|
|
2735
|
+
value: 'exit',
|
|
2736
|
+
hint: 'Exit installation',
|
|
2737
|
+
},
|
|
2738
|
+
{
|
|
2739
|
+
name: 'Continue with installation anyway',
|
|
2740
|
+
value: 'continue',
|
|
2741
|
+
hint: 'Continue',
|
|
2742
|
+
},
|
|
2743
|
+
],
|
|
2744
|
+
default: 'exit',
|
|
2745
|
+
});
|
|
2746
|
+
|
|
2747
|
+
if (proceed === 'exit') {
|
|
2748
|
+
await prompts.log.info('Please remove the .bmad-method folder and any v4 rules/commands, then run the installer again.');
|
|
2749
|
+
// Allow event loop to flush pending I/O before exit
|
|
2750
|
+
setImmediate(() => process.exit(0));
|
|
2751
|
+
return;
|
|
1895
2752
|
}
|
|
2753
|
+
|
|
2754
|
+
await prompts.log.warn('Proceeding with installation despite legacy v4 folder');
|
|
1896
2755
|
}
|
|
1897
2756
|
|
|
1898
2757
|
/**
|
|
@@ -1901,7 +2760,7 @@ class Installer {
|
|
|
1901
2760
|
* @returns {Array} Array of file entries from files-manifest.csv
|
|
1902
2761
|
*/
|
|
1903
2762
|
async readFilesManifest(bmadDir) {
|
|
1904
|
-
const filesManifestPath = path.join(bmadDir, '
|
|
2763
|
+
const filesManifestPath = path.join(bmadDir, '_config', 'files-manifest.csv');
|
|
1905
2764
|
if (!(await fs.pathExists(filesManifestPath))) {
|
|
1906
2765
|
return [];
|
|
1907
2766
|
}
|
|
@@ -1946,7 +2805,7 @@ class Installer {
|
|
|
1946
2805
|
|
|
1947
2806
|
return files;
|
|
1948
2807
|
} catch (error) {
|
|
1949
|
-
|
|
2808
|
+
await prompts.log.warn('Could not read files-manifest.csv: ' + error.message);
|
|
1950
2809
|
return [];
|
|
1951
2810
|
}
|
|
1952
2811
|
}
|
|
@@ -1961,6 +2820,9 @@ class Installer {
|
|
|
1961
2820
|
const customFiles = [];
|
|
1962
2821
|
const modifiedFiles = [];
|
|
1963
2822
|
|
|
2823
|
+
// Memory is always in _bmad/_memory
|
|
2824
|
+
const bmadMemoryPath = '_memory';
|
|
2825
|
+
|
|
1964
2826
|
// Check if the manifest has hashes - if not, we can't detect modifications
|
|
1965
2827
|
let manifestHasHashes = false;
|
|
1966
2828
|
if (existingFilesManifest && existingFilesManifest.length > 0) {
|
|
@@ -1971,13 +2833,10 @@ class Installer {
|
|
|
1971
2833
|
const installedFilesMap = new Map();
|
|
1972
2834
|
for (const fileEntry of existingFilesManifest) {
|
|
1973
2835
|
if (fileEntry.path) {
|
|
1974
|
-
|
|
1975
|
-
// Convert to absolute path
|
|
1976
|
-
const relativePath = fileEntry.path.startsWith('bmad/') ? fileEntry.path.slice(5) : fileEntry.path;
|
|
1977
|
-
const absolutePath = path.join(bmadDir, relativePath);
|
|
2836
|
+
const absolutePath = path.join(bmadDir, fileEntry.path);
|
|
1978
2837
|
installedFilesMap.set(path.normalize(absolutePath), {
|
|
1979
2838
|
hash: fileEntry.hash,
|
|
1980
|
-
relativePath:
|
|
2839
|
+
relativePath: fileEntry.path,
|
|
1981
2840
|
});
|
|
1982
2841
|
}
|
|
1983
2842
|
}
|
|
@@ -2003,20 +2862,47 @@ class Installer {
|
|
|
2003
2862
|
const relativePath = path.relative(bmadDir, fullPath);
|
|
2004
2863
|
const fileName = path.basename(fullPath);
|
|
2005
2864
|
|
|
2006
|
-
// Skip
|
|
2007
|
-
if (relativePath.startsWith('
|
|
2865
|
+
// Skip _config directory EXCEPT for modified agent customizations
|
|
2866
|
+
if (relativePath.startsWith('_config/') || relativePath.startsWith('_config\\')) {
|
|
2867
|
+
// Special handling for .customize.yaml files - only preserve if modified
|
|
2868
|
+
if (relativePath.includes('/agents/') && fileName.endsWith('.customize.yaml')) {
|
|
2869
|
+
// Check if the customization file has been modified from manifest
|
|
2870
|
+
const manifestPath = path.join(bmadDir, '_config', 'manifest.yaml');
|
|
2871
|
+
if (await fs.pathExists(manifestPath)) {
|
|
2872
|
+
const crypto = require('node:crypto');
|
|
2873
|
+
const currentContent = await fs.readFile(fullPath, 'utf8');
|
|
2874
|
+
const currentHash = crypto.createHash('sha256').update(currentContent).digest('hex');
|
|
2875
|
+
|
|
2876
|
+
const yaml = require('yaml');
|
|
2877
|
+
const manifestContent = await fs.readFile(manifestPath, 'utf8');
|
|
2878
|
+
const manifestData = yaml.parse(manifestContent);
|
|
2879
|
+
const originalHash = manifestData.agentCustomizations?.[relativePath];
|
|
2880
|
+
|
|
2881
|
+
// Only add to customFiles if hash differs (user modified)
|
|
2882
|
+
if (originalHash && currentHash !== originalHash) {
|
|
2883
|
+
customFiles.push(fullPath);
|
|
2884
|
+
}
|
|
2885
|
+
}
|
|
2886
|
+
}
|
|
2887
|
+
continue;
|
|
2888
|
+
}
|
|
2889
|
+
|
|
2890
|
+
if (relativePath.startsWith(bmadMemoryPath + '/') && path.dirname(relativePath).includes('-sidecar')) {
|
|
2008
2891
|
continue;
|
|
2009
2892
|
}
|
|
2010
2893
|
|
|
2011
2894
|
// Skip config.yaml files - these are regenerated on each install/update
|
|
2012
|
-
// Users should use _cfg/agents/ override files instead
|
|
2013
2895
|
if (fileName === 'config.yaml') {
|
|
2014
2896
|
continue;
|
|
2015
2897
|
}
|
|
2016
2898
|
|
|
2017
2899
|
if (!fileInfo) {
|
|
2018
2900
|
// File not in manifest = custom file
|
|
2019
|
-
|
|
2901
|
+
// EXCEPT: Agent .md files in module folders are generated files, not custom
|
|
2902
|
+
// Only treat .md files under _config/agents/ as custom
|
|
2903
|
+
if (!(fileName.endsWith('.md') && relativePath.includes('/agents/') && !relativePath.startsWith('_config/'))) {
|
|
2904
|
+
customFiles.push(fullPath);
|
|
2905
|
+
}
|
|
2020
2906
|
} else if (manifestHasHashes && fileInfo.hash) {
|
|
2021
2907
|
// File in manifest with hash - check if it was modified
|
|
2022
2908
|
const currentHash = await this.manifest.calculateFileHash(fullPath);
|
|
@@ -2028,8 +2914,6 @@ class Installer {
|
|
|
2028
2914
|
});
|
|
2029
2915
|
}
|
|
2030
2916
|
}
|
|
2031
|
-
// If manifest doesn't have hashes, we can't detect modifications
|
|
2032
|
-
// so we just skip files that are in the manifest
|
|
2033
2917
|
}
|
|
2034
2918
|
}
|
|
2035
2919
|
} catch {
|
|
@@ -2042,206 +2926,236 @@ class Installer {
|
|
|
2042
2926
|
}
|
|
2043
2927
|
|
|
2044
2928
|
/**
|
|
2045
|
-
*
|
|
2046
|
-
* @param {
|
|
2047
|
-
* @param {
|
|
2929
|
+
* Handle missing custom module sources interactively
|
|
2930
|
+
* @param {Map} customModuleSources - Map of custom module ID to info
|
|
2931
|
+
* @param {string} bmadDir - BMAD directory
|
|
2932
|
+
* @param {string} projectRoot - Project root directory
|
|
2933
|
+
* @param {string} operation - Current operation ('update', 'compile', etc.)
|
|
2934
|
+
* @param {Array} installedModules - Array of installed module IDs (will be modified)
|
|
2935
|
+
* @param {boolean} [skipPrompts=false] - Skip interactive prompts and keep all modules with missing sources
|
|
2936
|
+
* @returns {Object} Object with validCustomModules array and keptModulesWithoutSources array
|
|
2048
2937
|
*/
|
|
2049
|
-
async
|
|
2050
|
-
const
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
const
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
agents.push({
|
|
2081
|
-
name: agentName,
|
|
2082
|
-
module: entry.name,
|
|
2083
|
-
agentConfigNodes: agentConfigNodes,
|
|
2084
|
-
});
|
|
2085
|
-
|
|
2086
|
-
// Use shared AgentPartyGenerator to extract details
|
|
2087
|
-
let details = AgentPartyGenerator.extractAgentDetails(agentContent, entry.name, agentName);
|
|
2088
|
-
|
|
2089
|
-
// Apply config overrides if they exist
|
|
2090
|
-
if (details) {
|
|
2091
|
-
const configPath = path.join(agentConfigDir, `${entry.name}-${agentName}.md`);
|
|
2092
|
-
if (await fs.pathExists(configPath)) {
|
|
2093
|
-
const configContent = await fs.readFile(configPath, 'utf8');
|
|
2094
|
-
details = AgentPartyGenerator.applyConfigOverrides(details, configContent);
|
|
2095
|
-
}
|
|
2096
|
-
agentDetails.push(details);
|
|
2097
|
-
}
|
|
2098
|
-
}
|
|
2099
|
-
}
|
|
2938
|
+
async handleMissingCustomSources(customModuleSources, bmadDir, projectRoot, operation, installedModules, skipPrompts = false) {
|
|
2939
|
+
const validCustomModules = [];
|
|
2940
|
+
const keptModulesWithoutSources = []; // Track modules kept without sources
|
|
2941
|
+
const customModulesWithMissingSources = [];
|
|
2942
|
+
|
|
2943
|
+
// Check which sources exist
|
|
2944
|
+
for (const [moduleId, customInfo] of customModuleSources) {
|
|
2945
|
+
if (await fs.pathExists(customInfo.sourcePath)) {
|
|
2946
|
+
validCustomModules.push({
|
|
2947
|
+
id: moduleId,
|
|
2948
|
+
name: customInfo.name,
|
|
2949
|
+
path: customInfo.sourcePath,
|
|
2950
|
+
info: customInfo,
|
|
2951
|
+
});
|
|
2952
|
+
} else {
|
|
2953
|
+
// For cached modules that are missing, we just skip them without prompting
|
|
2954
|
+
if (customInfo.cached) {
|
|
2955
|
+
// Skip cached modules without prompting
|
|
2956
|
+
keptModulesWithoutSources.push({
|
|
2957
|
+
id: moduleId,
|
|
2958
|
+
name: customInfo.name,
|
|
2959
|
+
cached: true,
|
|
2960
|
+
});
|
|
2961
|
+
} else {
|
|
2962
|
+
customModulesWithMissingSources.push({
|
|
2963
|
+
id: moduleId,
|
|
2964
|
+
name: customInfo.name,
|
|
2965
|
+
sourcePath: customInfo.sourcePath,
|
|
2966
|
+
relativePath: customInfo.relativePath,
|
|
2967
|
+
info: customInfo,
|
|
2968
|
+
});
|
|
2100
2969
|
}
|
|
2101
2970
|
}
|
|
2102
2971
|
}
|
|
2103
2972
|
|
|
2104
|
-
//
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
for (const agent of agents) {
|
|
2113
|
-
const configPath = path.join(agentConfigDir, `${agent.module}-${agent.name}.md`);
|
|
2973
|
+
// If no missing sources, return immediately
|
|
2974
|
+
if (customModulesWithMissingSources.length === 0) {
|
|
2975
|
+
return {
|
|
2976
|
+
validCustomModules,
|
|
2977
|
+
keptModulesWithoutSources: [],
|
|
2978
|
+
};
|
|
2979
|
+
}
|
|
2114
2980
|
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2981
|
+
// Non-interactive mode: keep all modules with missing sources
|
|
2982
|
+
if (skipPrompts) {
|
|
2983
|
+
for (const missing of customModulesWithMissingSources) {
|
|
2984
|
+
keptModulesWithoutSources.push(missing.id);
|
|
2119
2985
|
}
|
|
2986
|
+
return { validCustomModules, keptModulesWithoutSources };
|
|
2987
|
+
}
|
|
2120
2988
|
|
|
2121
|
-
|
|
2122
|
-
let configContent = `# Agent Config: ${agent.name}\n\n`;
|
|
2989
|
+
await prompts.log.warn(`Found ${customModulesWithMissingSources.length} custom module(s) with missing sources:`);
|
|
2123
2990
|
|
|
2124
|
-
|
|
2125
|
-
|
|
2991
|
+
let keptCount = 0;
|
|
2992
|
+
let updatedCount = 0;
|
|
2993
|
+
let removedCount = 0;
|
|
2126
2994
|
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2995
|
+
for (const missing of customModulesWithMissingSources) {
|
|
2996
|
+
await prompts.log.message(
|
|
2997
|
+
`${missing.name} (${missing.id})\n Original source: ${missing.relativePath}\n Full path: ${missing.sourcePath}`,
|
|
2998
|
+
);
|
|
2131
2999
|
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
3000
|
+
const choices = [
|
|
3001
|
+
{
|
|
3002
|
+
name: 'Keep installed (will not be processed)',
|
|
3003
|
+
value: 'keep',
|
|
3004
|
+
hint: 'Keep',
|
|
3005
|
+
},
|
|
3006
|
+
{
|
|
3007
|
+
name: 'Specify new source location',
|
|
3008
|
+
value: 'update',
|
|
3009
|
+
hint: 'Update',
|
|
3010
|
+
},
|
|
3011
|
+
];
|
|
3012
|
+
|
|
3013
|
+
// Only add remove option if not just compiling agents
|
|
3014
|
+
if (operation !== 'compile-agents') {
|
|
3015
|
+
choices.push({
|
|
3016
|
+
name: '⚠️ REMOVE module completely (destructive!)',
|
|
3017
|
+
value: 'remove',
|
|
3018
|
+
hint: 'Remove',
|
|
3019
|
+
});
|
|
2135
3020
|
}
|
|
2136
3021
|
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
3022
|
+
const action = await prompts.select({
|
|
3023
|
+
message: `How would you like to handle "${missing.name}"?`,
|
|
3024
|
+
choices,
|
|
3025
|
+
});
|
|
3026
|
+
|
|
3027
|
+
switch (action) {
|
|
3028
|
+
case 'update': {
|
|
3029
|
+
// Use sync validation because @clack/prompts doesn't support async validate
|
|
3030
|
+
const newSourcePath = await prompts.text({
|
|
3031
|
+
message: 'Enter the new path to the custom module:',
|
|
3032
|
+
default: missing.sourcePath,
|
|
3033
|
+
validate: (input) => {
|
|
3034
|
+
if (!input || input.trim() === '') {
|
|
3035
|
+
return 'Please enter a path';
|
|
3036
|
+
}
|
|
3037
|
+
const expandedPath = path.resolve(input.trim());
|
|
3038
|
+
if (!fs.pathExistsSync(expandedPath)) {
|
|
3039
|
+
return 'Path does not exist';
|
|
3040
|
+
}
|
|
3041
|
+
// Check if it looks like a valid module
|
|
3042
|
+
const moduleYamlPath = path.join(expandedPath, 'module.yaml');
|
|
3043
|
+
const agentsPath = path.join(expandedPath, 'agents');
|
|
3044
|
+
const workflowsPath = path.join(expandedPath, 'workflows');
|
|
3045
|
+
|
|
3046
|
+
if (!fs.pathExistsSync(moduleYamlPath) && !fs.pathExistsSync(agentsPath) && !fs.pathExistsSync(workflowsPath)) {
|
|
3047
|
+
return 'Path does not appear to contain a valid custom module';
|
|
3048
|
+
}
|
|
3049
|
+
return; // clack expects undefined for valid input
|
|
3050
|
+
},
|
|
3051
|
+
});
|
|
2142
3052
|
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
3053
|
+
// Defensive: handleCancel should have exited, but guard against symbol propagation
|
|
3054
|
+
if (typeof newSourcePath !== 'string') {
|
|
3055
|
+
keptCount++;
|
|
3056
|
+
keptModulesWithoutSources.push(missing.id);
|
|
3057
|
+
continue;
|
|
2148
3058
|
}
|
|
2149
3059
|
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
3060
|
+
// Update the source in manifest
|
|
3061
|
+
const resolvedPath = path.resolve(newSourcePath.trim());
|
|
3062
|
+
missing.info.sourcePath = resolvedPath;
|
|
3063
|
+
// Remove relativePath - we only store absolute sourcePath now
|
|
3064
|
+
delete missing.info.relativePath;
|
|
3065
|
+
await this.manifest.addCustomModule(bmadDir, missing.info);
|
|
3066
|
+
|
|
3067
|
+
validCustomModules.push({
|
|
3068
|
+
id: missing.id,
|
|
3069
|
+
name: missing.name,
|
|
3070
|
+
path: resolvedPath,
|
|
3071
|
+
info: missing.info,
|
|
3072
|
+
});
|
|
2153
3073
|
|
|
2154
|
-
|
|
3074
|
+
updatedCount++;
|
|
3075
|
+
await prompts.log.success('Updated source location');
|
|
2155
3076
|
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
3077
|
+
break;
|
|
3078
|
+
}
|
|
3079
|
+
case 'remove': {
|
|
3080
|
+
// Extra confirmation for destructive remove
|
|
3081
|
+
await prompts.log.error(
|
|
3082
|
+
`WARNING: This will PERMANENTLY DELETE "${missing.name}" and all its files!\n Module location: ${path.join(bmadDir, missing.id)}`,
|
|
3083
|
+
);
|
|
3084
|
+
|
|
3085
|
+
const confirmDelete = await prompts.confirm({
|
|
3086
|
+
message: 'Are you absolutely sure you want to delete this module?',
|
|
3087
|
+
default: false,
|
|
3088
|
+
});
|
|
2160
3089
|
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
3090
|
+
if (confirmDelete) {
|
|
3091
|
+
const typedConfirm = await prompts.text({
|
|
3092
|
+
message: 'Type "DELETE" to confirm permanent deletion:',
|
|
3093
|
+
validate: (input) => {
|
|
3094
|
+
if (input !== 'DELETE') {
|
|
3095
|
+
return 'You must type "DELETE" exactly to proceed';
|
|
3096
|
+
}
|
|
3097
|
+
return; // clack expects undefined for valid input
|
|
3098
|
+
},
|
|
3099
|
+
});
|
|
2165
3100
|
|
|
2166
|
-
|
|
2167
|
-
|
|
3101
|
+
if (typedConfirm === 'DELETE') {
|
|
3102
|
+
// Remove the module from filesystem and manifest
|
|
3103
|
+
const modulePath = path.join(bmadDir, missing.id);
|
|
3104
|
+
if (await fs.pathExists(modulePath)) {
|
|
3105
|
+
const fsExtra = require('fs-extra');
|
|
3106
|
+
await fsExtra.remove(modulePath);
|
|
3107
|
+
await prompts.log.warn(`Deleted module directory: ${path.relative(projectRoot, modulePath)}`);
|
|
3108
|
+
}
|
|
2168
3109
|
|
|
2169
|
-
|
|
2170
|
-
|
|
3110
|
+
await this.manifest.removeModule(bmadDir, missing.id);
|
|
3111
|
+
await this.manifest.removeCustomModule(bmadDir, missing.id);
|
|
3112
|
+
await prompts.log.warn('Removed from manifest');
|
|
2171
3113
|
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
await AgentPartyGenerator.writeAgentParty(manifestPath, agentDetails, { forWeb: false });
|
|
2180
|
-
}
|
|
3114
|
+
// Also remove from installedModules list
|
|
3115
|
+
if (installedModules && installedModules.includes(missing.id)) {
|
|
3116
|
+
const index = installedModules.indexOf(missing.id);
|
|
3117
|
+
if (index !== -1) {
|
|
3118
|
+
installedModules.splice(index, 1);
|
|
3119
|
+
}
|
|
3120
|
+
}
|
|
2181
3121
|
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
3122
|
+
removedCount++;
|
|
3123
|
+
await prompts.log.error(`"${missing.name}" has been permanently removed`);
|
|
3124
|
+
} else {
|
|
3125
|
+
await prompts.log.message('Removal cancelled - module will be kept');
|
|
3126
|
+
keptCount++;
|
|
3127
|
+
}
|
|
3128
|
+
} else {
|
|
3129
|
+
await prompts.log.message('Removal cancelled - module will be kept');
|
|
3130
|
+
keptCount++;
|
|
3131
|
+
}
|
|
2189
3132
|
|
|
2190
|
-
|
|
2191
|
-
// Find all XML nodes with agentConfig="true"
|
|
2192
|
-
// Match self-closing tags and tags with content
|
|
2193
|
-
const selfClosingPattern = /<([a-zA-Z][a-zA-Z0-9_-]*)\s+[^>]*agentConfig="true"[^>]*\/>/g;
|
|
2194
|
-
const withContentPattern = /<([a-zA-Z][a-zA-Z0-9_-]*)\s+[^>]*agentConfig="true"[^>]*>([\s\S]*?)<\/\1>/g;
|
|
2195
|
-
|
|
2196
|
-
// Extract self-closing tags
|
|
2197
|
-
let match;
|
|
2198
|
-
while ((match = selfClosingPattern.exec(content)) !== null) {
|
|
2199
|
-
// Extract just the tag without children (structure only)
|
|
2200
|
-
const tagMatch = match[0].match(/<([a-zA-Z][a-zA-Z0-9_-]*)([^>]*)\/>/);
|
|
2201
|
-
if (tagMatch) {
|
|
2202
|
-
const tagName = tagMatch[1];
|
|
2203
|
-
const attributes = tagMatch[2].replace(/\s*agentConfig="true"/, ''); // Remove agentConfig attribute
|
|
2204
|
-
nodes.push(`<${tagName}${attributes}></${tagName}>`);
|
|
3133
|
+
break;
|
|
2205
3134
|
}
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
const fullMatch = match[0];
|
|
2211
|
-
const tagName = match[1];
|
|
3135
|
+
case 'keep': {
|
|
3136
|
+
keptCount++;
|
|
3137
|
+
keptModulesWithoutSources.push(missing.id);
|
|
3138
|
+
await prompts.log.message('Module will be kept as-is');
|
|
2212
3139
|
|
|
2213
|
-
|
|
2214
|
-
const openingTagMatch = fullMatch.match(new RegExp(`<${tagName}([^>]*)>`));
|
|
2215
|
-
if (openingTagMatch) {
|
|
2216
|
-
const attributes = openingTagMatch[1].replace(/\s*agentConfig="true"/, '');
|
|
2217
|
-
// Add empty node structure (no children)
|
|
2218
|
-
nodes.push(`<${tagName}${attributes}></${tagName}>`);
|
|
3140
|
+
break;
|
|
2219
3141
|
}
|
|
3142
|
+
// No default
|
|
2220
3143
|
}
|
|
2221
|
-
} catch (error) {
|
|
2222
|
-
console.error('Error extracting agentConfig nodes:', error);
|
|
2223
3144
|
}
|
|
2224
3145
|
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
*/
|
|
2233
|
-
async copyIdeDocumentation(ides, bmadDir) {
|
|
2234
|
-
const docsDir = path.join(bmadDir, 'docs');
|
|
2235
|
-
await fs.ensureDir(docsDir);
|
|
2236
|
-
|
|
2237
|
-
for (const ide of ides) {
|
|
2238
|
-
const sourceDocPath = path.join(getProjectRoot(), 'docs', 'ide-info', `${ide}.md`);
|
|
2239
|
-
const targetDocPath = path.join(docsDir, `${ide}-instructions.md`);
|
|
2240
|
-
|
|
2241
|
-
if (await fs.pathExists(sourceDocPath)) {
|
|
2242
|
-
await this.copyFileWithPlaceholderReplacement(sourceDocPath, targetDocPath, this.bmadFolderName || 'bmad');
|
|
2243
|
-
}
|
|
3146
|
+
// Show summary
|
|
3147
|
+
if (keptCount > 0 || updatedCount > 0 || removedCount > 0) {
|
|
3148
|
+
let summary = 'Summary for custom modules with missing sources:';
|
|
3149
|
+
if (keptCount > 0) summary += `\n • ${keptCount} module(s) kept as-is`;
|
|
3150
|
+
if (updatedCount > 0) summary += `\n • ${updatedCount} module(s) updated with new sources`;
|
|
3151
|
+
if (removedCount > 0) summary += `\n • ${removedCount} module(s) permanently deleted`;
|
|
3152
|
+
await prompts.log.message(summary);
|
|
2244
3153
|
}
|
|
3154
|
+
|
|
3155
|
+
return {
|
|
3156
|
+
validCustomModules,
|
|
3157
|
+
keptModulesWithoutSources,
|
|
3158
|
+
};
|
|
2245
3159
|
}
|
|
2246
3160
|
}
|
|
2247
3161
|
|