bmad-fh 6.0.0-alpha.052779ef
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/.coderabbit.yaml +40 -0
- package/.githooks/post-checkout +129 -0
- package/.githooks/pre-commit +63 -0
- package/.githooks/pre-push +135 -0
- package/.github/CODE_OF_CONDUCT.md +128 -0
- package/.github/FUNDING.yaml +15 -0
- package/.github/ISSUE_TEMPLATE/config.yaml +8 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +22 -0
- package/.github/ISSUE_TEMPLATE/issue.md +32 -0
- package/.github/scripts/discord-helpers.sh +34 -0
- package/.github/workflows/bundle-latest.yaml +330 -0
- package/.github/workflows/discord.yaml +90 -0
- package/.github/workflows/docs.yaml +63 -0
- package/.github/workflows/manual-release.yaml +190 -0
- package/.github/workflows/publish-multi-artifact.yaml +54 -0
- package/.github/workflows/quality.yaml +115 -0
- package/.husky/pre-commit +20 -0
- package/.markdownlint-cli2.yaml +41 -0
- package/.nvmrc +1 -0
- package/.prettierignore +9 -0
- package/.vscode/settings.json +97 -0
- package/CHANGELOG.md +1394 -0
- package/CNAME +1 -0
- package/CONTRIBUTING.md +306 -0
- package/CONTRIBUTORS.md +32 -0
- package/LICENSE +30 -0
- package/README.md +126 -0
- 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/_README_WORKFLOW_DIAGRAMS.md +40 -0
- package/docs/_STYLE_GUIDE.md +367 -0
- package/docs/_archive/customize-workflows.md +30 -0
- package/docs/_archive/getting-started-bmadv4.md +247 -0
- package/docs/_archive/vendor-workflows.md +52 -0
- package/docs/downloads.md +72 -0
- package/docs/explanation/agents/barry-quick-flow.md +328 -0
- package/docs/explanation/agents/index.md +19 -0
- package/docs/explanation/architecture/four-phases.md +107 -0
- package/docs/explanation/architecture/preventing-agent-conflicts.md +111 -0
- package/docs/explanation/architecture/why-solutioning-matters.md +75 -0
- package/docs/explanation/bmm/index.md +131 -0
- package/docs/explanation/core/index.md +18 -0
- package/docs/explanation/core-concepts/agent-roles.md +179 -0
- package/docs/explanation/core-concepts/index.md +35 -0
- package/docs/explanation/core-concepts/what-are-agents.md +97 -0
- package/docs/explanation/core-concepts/what-are-modules.md +85 -0
- package/docs/explanation/core-concepts/what-are-workflows.md +204 -0
- package/docs/explanation/faq/brownfield-faq.md +73 -0
- package/docs/explanation/faq/getting-started-faq.md +67 -0
- package/docs/explanation/faq/implementation-faq.md +52 -0
- package/docs/explanation/faq/index.md +16 -0
- package/docs/explanation/faq/levels-and-tracks-faq.md +52 -0
- package/docs/explanation/faq/planning-faq.md +41 -0
- package/docs/explanation/faq/tools-faq.md +277 -0
- package/docs/explanation/faq/workflows-faq.md +61 -0
- package/docs/explanation/features/advanced-elicitation.md +95 -0
- package/docs/explanation/features/brainstorming-techniques.md +92 -0
- package/docs/explanation/features/party-mode.md +95 -0
- package/docs/explanation/features/quick-flow.md +149 -0
- package/docs/explanation/features/tea-overview.md +410 -0
- package/docs/explanation/features/web-bundles.md +34 -0
- package/docs/explanation/philosophy/facilitation-over-generation.md +333 -0
- package/docs/explanation/philosophy/testing-as-engineering.md +112 -0
- package/docs/explanation/tea/engagement-models.md +710 -0
- package/docs/explanation/tea/fixture-architecture.md +457 -0
- package/docs/explanation/tea/knowledge-base-system.md +554 -0
- package/docs/explanation/tea/network-first-patterns.md +853 -0
- package/docs/explanation/tea/risk-based-testing.md +586 -0
- package/docs/explanation/tea/test-quality-standards.md +907 -0
- package/docs/how-to/brownfield/add-feature-to-existing.md +74 -0
- package/docs/how-to/brownfield/document-existing-project.md +66 -0
- package/docs/how-to/brownfield/index.md +84 -0
- package/docs/how-to/brownfield/quick-fix-in-brownfield.md +77 -0
- package/docs/how-to/brownfield/use-tea-for-enterprise.md +526 -0
- package/docs/how-to/brownfield/use-tea-with-existing-tests.md +577 -0
- package/docs/how-to/customization/customize-agents.md +212 -0
- package/docs/how-to/customization/enable-tea-mcp-enhancements.md +424 -0
- package/docs/how-to/customization/index.md +23 -0
- package/docs/how-to/customization/integrate-playwright-utils.md +813 -0
- package/docs/how-to/customization/shard-large-documents.md +101 -0
- package/docs/how-to/get-answers-about-bmad.md +102 -0
- package/docs/how-to/installation/index.md +12 -0
- package/docs/how-to/installation/install-bmad.md +111 -0
- package/docs/how-to/installation/install-custom-modules.md +118 -0
- package/docs/how-to/installation/upgrade-to-v6.md +131 -0
- package/docs/how-to/workflows/bmgd-quick-flow.md +156 -0
- package/docs/how-to/workflows/conduct-research.md +97 -0
- package/docs/how-to/workflows/create-architecture.md +119 -0
- package/docs/how-to/workflows/create-epics-and-stories.md +109 -0
- package/docs/how-to/workflows/create-prd.md +91 -0
- package/docs/how-to/workflows/create-product-brief.md +94 -0
- package/docs/how-to/workflows/create-story.md +102 -0
- package/docs/how-to/workflows/create-ux-design.md +100 -0
- package/docs/how-to/workflows/implement-story.md +97 -0
- package/docs/how-to/workflows/quick-spec.md +122 -0
- package/docs/how-to/workflows/run-atdd.md +436 -0
- package/docs/how-to/workflows/run-automate.md +653 -0
- package/docs/how-to/workflows/run-brainstorming-session.md +73 -0
- package/docs/how-to/workflows/run-code-review.md +89 -0
- package/docs/how-to/workflows/run-implementation-readiness.md +125 -0
- package/docs/how-to/workflows/run-nfr-assess.md +679 -0
- package/docs/how-to/workflows/run-sprint-planning.md +94 -0
- package/docs/how-to/workflows/run-test-design.md +98 -0
- package/docs/how-to/workflows/run-test-review.md +605 -0
- package/docs/how-to/workflows/run-trace.md +883 -0
- package/docs/how-to/workflows/setup-ci.md +712 -0
- package/docs/how-to/workflows/setup-party-mode.md +89 -0
- package/docs/how-to/workflows/setup-test-framework.md +98 -0
- package/docs/index.md +63 -0
- package/docs/migration-guide.md +365 -0
- package/docs/multi-scope-guide.md +379 -0
- package/docs/plans/multi-scope-parallel-artifacts-plan.md +695 -0
- package/docs/reference/agents/index.md +109 -0
- package/docs/reference/configuration/core-tasks.md +67 -0
- package/docs/reference/configuration/global-config.md +28 -0
- package/docs/reference/glossary/index.md +159 -0
- package/docs/reference/tea/commands.md +254 -0
- package/docs/reference/tea/configuration.md +678 -0
- package/docs/reference/tea/knowledge-base.md +340 -0
- package/docs/reference/workflows/core-workflows.md +32 -0
- package/docs/reference/workflows/document-project.md +73 -0
- package/docs/reference/workflows/index.md +12 -0
- package/docs/tutorials/getting-started/getting-started-bmadv6.md +246 -0
- package/docs/tutorials/getting-started/images/workflow-method-greenfield.excalidraw +5034 -0
- package/docs/tutorials/getting-started/images/workflow-method-greenfield.svg +4 -0
- package/docs/tutorials/getting-started/images/workflow-overview.jpg +0 -0
- package/docs/tutorials/getting-started/tea-lite-quickstart.md +444 -0
- package/docs/tutorials/getting-started/workflow-overview.jpg +0 -0
- package/eslint.config.mjs +152 -0
- package/package.json +117 -0
- package/prettier.config.mjs +32 -0
- package/src/bmm/_module-installer/installer.js +48 -0
- package/src/bmm/_module-installer/platform-specifics/claude-code.js +35 -0
- package/src/bmm/_module-installer/platform-specifics/windsurf.js +32 -0
- package/src/bmm/agents/analyst.agent.yaml +41 -0
- package/src/bmm/agents/architect.agent.yaml +33 -0
- package/src/bmm/agents/dev.agent.yaml +38 -0
- package/src/bmm/agents/pm.agent.yaml +51 -0
- package/src/bmm/agents/quick-flow-solo-dev.agent.yaml +32 -0
- package/src/bmm/agents/sm.agent.yaml +47 -0
- package/src/bmm/agents/tea.agent.yaml +68 -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 +49 -0
- package/src/bmm/agents/ux-designer.agent.yaml +30 -0
- package/src/bmm/data/README.md +29 -0
- package/src/bmm/data/project-context-template.md +40 -0
- package/src/bmm/module.yaml +64 -0
- package/src/bmm/sub-modules/claude-code/config.yaml +4 -0
- package/src/bmm/sub-modules/claude-code/injections.yaml +242 -0
- package/src/bmm/sub-modules/claude-code/readme.md +87 -0
- package/src/bmm/teams/default-party.csv +21 -0
- package/src/bmm/teams/team-fullstack.yaml +12 -0
- package/src/bmm/testarch/knowledge/api-request.md +442 -0
- package/src/bmm/testarch/knowledge/api-testing-patterns.md +843 -0
- package/src/bmm/testarch/knowledge/auth-session.md +552 -0
- package/src/bmm/testarch/knowledge/burn-in.md +273 -0
- package/src/bmm/testarch/knowledge/ci-burn-in.md +675 -0
- package/src/bmm/testarch/knowledge/component-tdd.md +486 -0
- package/src/bmm/testarch/knowledge/contract-testing.md +957 -0
- package/src/bmm/testarch/knowledge/data-factories.md +500 -0
- package/src/bmm/testarch/knowledge/email-auth.md +721 -0
- package/src/bmm/testarch/knowledge/error-handling.md +725 -0
- package/src/bmm/testarch/knowledge/feature-flags.md +750 -0
- package/src/bmm/testarch/knowledge/file-utils.md +463 -0
- package/src/bmm/testarch/knowledge/fixture-architecture.md +401 -0
- package/src/bmm/testarch/knowledge/fixtures-composition.md +382 -0
- package/src/bmm/testarch/knowledge/intercept-network-call.md +430 -0
- package/src/bmm/testarch/knowledge/log.md +429 -0
- package/src/bmm/testarch/knowledge/network-error-monitor.md +405 -0
- package/src/bmm/testarch/knowledge/network-first.md +486 -0
- package/src/bmm/testarch/knowledge/network-recorder.md +527 -0
- package/src/bmm/testarch/knowledge/nfr-criteria.md +670 -0
- package/src/bmm/testarch/knowledge/overview.md +286 -0
- package/src/bmm/testarch/knowledge/playwright-config.md +730 -0
- package/src/bmm/testarch/knowledge/probability-impact.md +601 -0
- package/src/bmm/testarch/knowledge/recurse.md +421 -0
- package/src/bmm/testarch/knowledge/risk-governance.md +615 -0
- package/src/bmm/testarch/knowledge/selective-testing.md +732 -0
- package/src/bmm/testarch/knowledge/selector-resilience.md +527 -0
- package/src/bmm/testarch/knowledge/test-healing-patterns.md +644 -0
- package/src/bmm/testarch/knowledge/test-levels-framework.md +473 -0
- package/src/bmm/testarch/knowledge/test-priorities-matrix.md +373 -0
- package/src/bmm/testarch/knowledge/test-quality.md +664 -0
- package/src/bmm/testarch/knowledge/timing-debugging.md +372 -0
- package/src/bmm/testarch/knowledge/visual-debugging.md +524 -0
- package/src/bmm/testarch/tea-index.csv +34 -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 +194 -0
- package/src/bmm/workflows/1-analysis/create-product-brief/workflow.md +58 -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-02-customer-insights.md +200 -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 +239 -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.md +173 -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 +228 -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 +43 -0
- package/src/bmm/workflows/2-plan-workflows/prd/data/domain-complexity.csv +13 -0
- package/src/bmm/workflows/2-plan-workflows/prd/data/prd-purpose.md +197 -0
- package/src/bmm/workflows/2-plan-workflows/prd/data/project-types.csv +11 -0
- package/src/bmm/workflows/2-plan-workflows/prd/steps-c/step-01-init.md +191 -0
- package/src/bmm/workflows/2-plan-workflows/prd/steps-c/step-01b-continue.md +153 -0
- package/src/bmm/workflows/2-plan-workflows/prd/steps-c/step-02-discovery.md +224 -0
- package/src/bmm/workflows/2-plan-workflows/prd/steps-c/step-03-success.md +226 -0
- package/src/bmm/workflows/2-plan-workflows/prd/steps-c/step-04-journeys.md +213 -0
- package/src/bmm/workflows/2-plan-workflows/prd/steps-c/step-05-domain.md +207 -0
- package/src/bmm/workflows/2-plan-workflows/prd/steps-c/step-06-innovation.md +226 -0
- package/src/bmm/workflows/2-plan-workflows/prd/steps-c/step-07-project-type.md +237 -0
- package/src/bmm/workflows/2-plan-workflows/prd/steps-c/step-08-scoping.md +228 -0
- package/src/bmm/workflows/2-plan-workflows/prd/steps-c/step-09-functional.md +231 -0
- package/src/bmm/workflows/2-plan-workflows/prd/steps-c/step-10-nonfunctional.md +242 -0
- package/src/bmm/workflows/2-plan-workflows/prd/steps-c/step-11-polish.md +217 -0
- package/src/bmm/workflows/2-plan-workflows/prd/steps-c/step-12-complete.md +180 -0
- package/src/bmm/workflows/2-plan-workflows/prd/steps-e/step-e-01-discovery.md +247 -0
- package/src/bmm/workflows/2-plan-workflows/prd/steps-e/step-e-01b-legacy-conversion.md +208 -0
- package/src/bmm/workflows/2-plan-workflows/prd/steps-e/step-e-02-review.md +249 -0
- package/src/bmm/workflows/2-plan-workflows/prd/steps-e/step-e-03-edit.md +253 -0
- package/src/bmm/workflows/2-plan-workflows/prd/steps-e/step-e-04-complete.md +168 -0
- package/src/bmm/workflows/2-plan-workflows/prd/steps-v/step-v-01-discovery.md +218 -0
- package/src/bmm/workflows/2-plan-workflows/prd/steps-v/step-v-02-format-detection.md +191 -0
- package/src/bmm/workflows/2-plan-workflows/prd/steps-v/step-v-02b-parity-check.md +209 -0
- package/src/bmm/workflows/2-plan-workflows/prd/steps-v/step-v-03-density-validation.md +174 -0
- package/src/bmm/workflows/2-plan-workflows/prd/steps-v/step-v-04-brief-coverage-validation.md +214 -0
- package/src/bmm/workflows/2-plan-workflows/prd/steps-v/step-v-05-measurability-validation.md +228 -0
- package/src/bmm/workflows/2-plan-workflows/prd/steps-v/step-v-06-traceability-validation.md +217 -0
- package/src/bmm/workflows/2-plan-workflows/prd/steps-v/step-v-07-implementation-leakage-validation.md +205 -0
- package/src/bmm/workflows/2-plan-workflows/prd/steps-v/step-v-08-domain-compliance-validation.md +243 -0
- package/src/bmm/workflows/2-plan-workflows/prd/steps-v/step-v-09-project-type-validation.md +263 -0
- package/src/bmm/workflows/2-plan-workflows/prd/steps-v/step-v-10-smart-validation.md +209 -0
- package/src/bmm/workflows/2-plan-workflows/prd/steps-v/step-v-11-holistic-quality-validation.md +264 -0
- package/src/bmm/workflows/2-plan-workflows/prd/steps-v/step-v-12-completeness-validation.md +242 -0
- package/src/bmm/workflows/2-plan-workflows/prd/steps-v/step-v-13-report-complete.md +232 -0
- package/src/bmm/workflows/2-plan-workflows/prd/templates/prd-template.md +10 -0
- package/src/bmm/workflows/2-plan-workflows/prd/validation-report-prd-workflow.md +433 -0
- package/src/bmm/workflows/2-plan-workflows/prd/workflow.md +150 -0
- package/src/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-01-document-discovery.md +190 -0
- package/src/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-02-prd-analysis.md +178 -0
- package/src/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-03-epic-coverage-validation.md +179 -0
- package/src/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-04-ux-alignment.md +139 -0
- package/src/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-05-epic-quality-review.md +252 -0
- package/src/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-06-final-assessment.md +133 -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 +55 -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 +11 -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 +352 -0
- package/src/bmm/workflows/3-solutioning/create-architecture/workflow.md +50 -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 +145 -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 +59 -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 +51 -0
- package/src/bmm/workflows/4-implementation/correct-course/checklist.md +288 -0
- package/src/bmm/workflows/4-implementation/correct-course/instructions.md +206 -0
- package/src/bmm/workflows/4-implementation/correct-course/workflow.yaml +60 -0
- package/src/bmm/workflows/4-implementation/create-story/checklist.md +358 -0
- package/src/bmm/workflows/4-implementation/create-story/instructions.xml +345 -0
- package/src/bmm/workflows/4-implementation/create-story/template.md +49 -0
- package/src/bmm/workflows/4-implementation/create-story/workflow.yaml +61 -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 +27 -0
- package/src/bmm/workflows/4-implementation/retrospective/instructions.md +1443 -0
- package/src/bmm/workflows/4-implementation/retrospective/workflow.yaml +58 -0
- package/src/bmm/workflows/4-implementation/sprint-planning/checklist.md +33 -0
- package/src/bmm/workflows/4-implementation/sprint-planning/instructions.md +225 -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 +54 -0
- package/src/bmm/workflows/4-implementation/sprint-status/instructions.md +229 -0
- package/src/bmm/workflows/4-implementation/sprint-status/workflow.yaml +36 -0
- package/src/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-01-mode-detection.md +156 -0
- package/src/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-02-context-gathering.md +120 -0
- package/src/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-03-execute.md +113 -0
- package/src/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-04-self-check.md +113 -0
- package/src/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-05-adversarial-review.md +106 -0
- package/src/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-06-resolve-findings.md +140 -0
- package/src/bmm/workflows/bmad-quick-flow/quick-dev/workflow.md +52 -0
- package/src/bmm/workflows/bmad-quick-flow/quick-spec/steps/step-01-understand.md +189 -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 +128 -0
- package/src/bmm/workflows/bmad-quick-flow/quick-spec/steps/step-04-review.md +191 -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/documentation-requirements.csv +12 -0
- package/src/bmm/workflows/document-project/instructions.md +221 -0
- package/src/bmm/workflows/document-project/templates/deep-dive-template.md +345 -0
- package/src/bmm/workflows/document-project/templates/index-template.md +169 -0
- package/src/bmm/workflows/document-project/templates/project-overview-template.md +103 -0
- package/src/bmm/workflows/document-project/templates/project-scan-report-schema.json +160 -0
- package/src/bmm/workflows/document-project/templates/source-tree-template.md +135 -0
- package/src/bmm/workflows/document-project/workflow.yaml +30 -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/excalidraw-diagrams/_shared/excalidraw-library.json +90 -0
- package/src/bmm/workflows/excalidraw-diagrams/_shared/excalidraw-templates.yaml +127 -0
- package/src/bmm/workflows/excalidraw-diagrams/create-dataflow/checklist.md +39 -0
- package/src/bmm/workflows/excalidraw-diagrams/create-dataflow/instructions.md +130 -0
- package/src/bmm/workflows/excalidraw-diagrams/create-dataflow/workflow.yaml +27 -0
- package/src/bmm/workflows/excalidraw-diagrams/create-diagram/checklist.md +43 -0
- package/src/bmm/workflows/excalidraw-diagrams/create-diagram/instructions.md +141 -0
- package/src/bmm/workflows/excalidraw-diagrams/create-diagram/workflow.yaml +27 -0
- package/src/bmm/workflows/excalidraw-diagrams/create-flowchart/checklist.md +49 -0
- package/src/bmm/workflows/excalidraw-diagrams/create-flowchart/instructions.md +241 -0
- package/src/bmm/workflows/excalidraw-diagrams/create-flowchart/workflow.yaml +27 -0
- package/src/bmm/workflows/excalidraw-diagrams/create-wireframe/checklist.md +38 -0
- package/src/bmm/workflows/excalidraw-diagrams/create-wireframe/instructions.md +133 -0
- package/src/bmm/workflows/excalidraw-diagrams/create-wireframe/workflow.yaml +27 -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/testarch/atdd/atdd-checklist-template.md +364 -0
- package/src/bmm/workflows/testarch/atdd/checklist.md +374 -0
- package/src/bmm/workflows/testarch/atdd/instructions.md +806 -0
- package/src/bmm/workflows/testarch/atdd/workflow.yaml +47 -0
- package/src/bmm/workflows/testarch/automate/checklist.md +582 -0
- package/src/bmm/workflows/testarch/automate/instructions.md +1324 -0
- package/src/bmm/workflows/testarch/automate/workflow.yaml +54 -0
- package/src/bmm/workflows/testarch/ci/checklist.md +248 -0
- package/src/bmm/workflows/testarch/ci/github-actions-template.yaml +198 -0
- package/src/bmm/workflows/testarch/ci/gitlab-ci-template.yaml +149 -0
- package/src/bmm/workflows/testarch/ci/instructions.md +536 -0
- package/src/bmm/workflows/testarch/ci/workflow.yaml +47 -0
- package/src/bmm/workflows/testarch/framework/checklist.md +321 -0
- package/src/bmm/workflows/testarch/framework/instructions.md +481 -0
- package/src/bmm/workflows/testarch/framework/workflow.yaml +49 -0
- package/src/bmm/workflows/testarch/nfr-assess/checklist.md +407 -0
- package/src/bmm/workflows/testarch/nfr-assess/instructions.md +722 -0
- package/src/bmm/workflows/testarch/nfr-assess/nfr-report-template.md +445 -0
- package/src/bmm/workflows/testarch/nfr-assess/workflow.yaml +49 -0
- package/src/bmm/workflows/testarch/test-design/checklist.md +235 -0
- package/src/bmm/workflows/testarch/test-design/instructions.md +788 -0
- package/src/bmm/workflows/testarch/test-design/test-design-template.md +294 -0
- package/src/bmm/workflows/testarch/test-design/workflow.yaml +56 -0
- package/src/bmm/workflows/testarch/test-review/checklist.md +472 -0
- package/src/bmm/workflows/testarch/test-review/instructions.md +628 -0
- package/src/bmm/workflows/testarch/test-review/test-review-template.md +390 -0
- package/src/bmm/workflows/testarch/test-review/workflow.yaml +48 -0
- package/src/bmm/workflows/testarch/trace/checklist.md +655 -0
- package/src/bmm/workflows/testarch/trace/instructions.md +1047 -0
- package/src/bmm/workflows/testarch/trace/trace-template.md +675 -0
- package/src/bmm/workflows/testarch/trace/workflow.yaml +57 -0
- package/src/bmm/workflows/workflow-status/init/instructions.md +346 -0
- package/src/bmm/workflows/workflow-status/init/workflow.yaml +30 -0
- package/src/bmm/workflows/workflow-status/instructions.md +397 -0
- package/src/bmm/workflows/workflow-status/paths/enterprise-brownfield.yaml +103 -0
- package/src/bmm/workflows/workflow-status/paths/enterprise-greenfield.yaml +100 -0
- package/src/bmm/workflows/workflow-status/paths/method-brownfield.yaml +103 -0
- package/src/bmm/workflows/workflow-status/paths/method-greenfield.yaml +100 -0
- package/src/bmm/workflows/workflow-status/project-levels.yaml +59 -0
- package/src/bmm/workflows/workflow-status/workflow-status-template.yaml +24 -0
- package/src/bmm/workflows/workflow-status/workflow.yaml +32 -0
- package/src/core/_module-installer/installer.js +60 -0
- package/src/core/agents/bmad-master.agent.yaml +30 -0
- package/src/core/lib/scope/artifact-resolver.js +298 -0
- package/src/core/lib/scope/event-logger.js +400 -0
- package/src/core/lib/scope/index.js +30 -0
- package/src/core/lib/scope/scope-context.js +301 -0
- package/src/core/lib/scope/scope-initializer.js +456 -0
- package/src/core/lib/scope/scope-manager.js +512 -0
- package/src/core/lib/scope/scope-migrator.js +434 -0
- package/src/core/lib/scope/scope-sync.js +483 -0
- package/src/core/lib/scope/scope-validator.js +294 -0
- package/src/core/lib/scope/state-lock.js +336 -0
- package/src/core/module.yaml +53 -0
- package/src/core/resources/excalidraw/README.md +160 -0
- package/src/core/resources/excalidraw/excalidraw-helpers.md +127 -0
- package/src/core/resources/excalidraw/library-loader.md +50 -0
- package/src/core/resources/excalidraw/validate-json-instructions.md +79 -0
- package/src/core/tasks/editorial-review-prose.xml +91 -0
- package/src/core/tasks/editorial-review-structure.xml +198 -0
- package/src/core/tasks/index-docs.xml +65 -0
- package/src/core/tasks/review-adversarial-general.xml +46 -0
- package/src/core/tasks/shard-doc.xml +109 -0
- package/src/core/tasks/workflow.xml +277 -0
- 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 -0
- 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 +15 -0
- 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 +157 -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 +28 -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-data.txt +5 -0
- package/src/utility/agent-components/handler-exec.txt +19 -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 +295 -0
- package/test/fixtures/agent-schema/invalid/critical-actions/actions-as-string.agent.yaml +27 -0
- package/test/fixtures/agent-schema/invalid/critical-actions/empty-string-in-actions.agent.yaml +30 -0
- package/test/fixtures/agent-schema/invalid/menu/empty-menu.agent.yaml +22 -0
- package/test/fixtures/agent-schema/invalid/menu/missing-menu.agent.yaml +20 -0
- package/test/fixtures/agent-schema/invalid/menu-commands/empty-command-target.agent.yaml +25 -0
- package/test/fixtures/agent-schema/invalid/menu-commands/no-command-target.agent.yaml +24 -0
- package/test/fixtures/agent-schema/invalid/menu-triggers/camel-case.agent.yaml +25 -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 +31 -0
- package/test/fixtures/agent-schema/invalid/menu-triggers/empty-trigger.agent.yaml +25 -0
- package/test/fixtures/agent-schema/invalid/menu-triggers/leading-asterisk.agent.yaml +25 -0
- package/test/fixtures/agent-schema/invalid/menu-triggers/snake-case.agent.yaml +25 -0
- package/test/fixtures/agent-schema/invalid/menu-triggers/trigger-with-spaces.agent.yaml +25 -0
- package/test/fixtures/agent-schema/invalid/metadata/empty-module-string.agent.yaml +26 -0
- package/test/fixtures/agent-schema/invalid/metadata/empty-name.agent.yaml +24 -0
- package/test/fixtures/agent-schema/invalid/metadata/extra-metadata-fields.agent.yaml +27 -0
- package/test/fixtures/agent-schema/invalid/metadata/missing-id.agent.yaml +23 -0
- package/test/fixtures/agent-schema/invalid/persona/empty-principles-array.agent.yaml +24 -0
- package/test/fixtures/agent-schema/invalid/persona/empty-string-in-principles.agent.yaml +27 -0
- package/test/fixtures/agent-schema/invalid/persona/extra-persona-fields.agent.yaml +27 -0
- package/test/fixtures/agent-schema/invalid/persona/missing-role.agent.yaml +24 -0
- package/test/fixtures/agent-schema/invalid/prompts/empty-content.agent.yaml +29 -0
- package/test/fixtures/agent-schema/invalid/prompts/extra-prompt-fields.agent.yaml +31 -0
- package/test/fixtures/agent-schema/invalid/prompts/missing-content.agent.yaml +28 -0
- package/test/fixtures/agent-schema/invalid/prompts/missing-id.agent.yaml +28 -0
- package/test/fixtures/agent-schema/invalid/top-level/empty-file.agent.yaml +5 -0
- package/test/fixtures/agent-schema/invalid/top-level/extra-top-level-keys.agent.yaml +28 -0
- package/test/fixtures/agent-schema/invalid/top-level/missing-agent-key.agent.yaml +11 -0
- package/test/fixtures/agent-schema/invalid/yaml-errors/invalid-indentation.agent.yaml +19 -0
- package/test/fixtures/agent-schema/invalid/yaml-errors/malformed-yaml.agent.yaml +18 -0
- package/test/fixtures/agent-schema/valid/critical-actions/empty-critical-actions.agent.yaml +24 -0
- package/test/fixtures/agent-schema/valid/critical-actions/no-critical-actions.agent.yaml +22 -0
- package/test/fixtures/agent-schema/valid/critical-actions/valid-critical-actions.agent.yaml +27 -0
- package/test/fixtures/agent-schema/valid/menu/multiple-menu-items.agent.yaml +31 -0
- package/test/fixtures/agent-schema/valid/menu/single-menu-item.agent.yaml +22 -0
- package/test/fixtures/agent-schema/valid/menu-commands/all-command-types.agent.yaml +38 -0
- package/test/fixtures/agent-schema/valid/menu-commands/multiple-commands.agent.yaml +24 -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 +34 -0
- 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 +24 -0
- package/test/fixtures/agent-schema/valid/metadata/malformed-path-treated-as-core.agent.yaml +24 -0
- package/test/fixtures/agent-schema/valid/metadata/module-agent-correct.agent.yaml +24 -0
- 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 +24 -0
- package/test/fixtures/agent-schema/valid/prompts/empty-prompts.agent.yaml +24 -0
- package/test/fixtures/agent-schema/valid/prompts/no-prompts.agent.yaml +22 -0
- package/test/fixtures/agent-schema/valid/prompts/valid-prompts-minimal.agent.yaml +28 -0
- package/test/fixtures/agent-schema/valid/prompts/valid-prompts-with-description.agent.yaml +30 -0
- package/test/fixtures/agent-schema/valid/top-level/minimal-core-agent.agent.yaml +24 -0
- package/test/test-agent-schema.js +387 -0
- package/test/test-cli-integration.sh +159 -0
- package/test/test-installation-components.js +214 -0
- package/test/test-scope-e2e.js +439 -0
- package/test/test-scope-system.js +781 -0
- package/test/unit-test-schema.js +133 -0
- package/tools/bmad-npx-wrapper.js +38 -0
- package/tools/build-docs.js +577 -0
- package/tools/cli/README.md +7 -0
- package/tools/cli/bmad-cli.js +58 -0
- package/tools/cli/commands/install.js +87 -0
- package/tools/cli/commands/scope.js +474 -0
- package/tools/cli/external-official-modules.yaml +41 -0
- package/tools/cli/installers/install-messages.yaml +58 -0
- package/tools/cli/installers/lib/core/config-collector.js +1079 -0
- package/tools/cli/installers/lib/core/custom-module-cache.js +259 -0
- package/tools/cli/installers/lib/core/dependency-resolver.js +739 -0
- package/tools/cli/installers/lib/core/detector.js +223 -0
- package/tools/cli/installers/lib/core/ide-config-manager.js +156 -0
- package/tools/cli/installers/lib/core/installer.js +2585 -0
- package/tools/cli/installers/lib/core/manifest-generator.js +963 -0
- package/tools/cli/installers/lib/core/manifest.js +590 -0
- package/tools/cli/installers/lib/custom/handler.js +363 -0
- package/tools/cli/installers/lib/ide/_base-ide.js +654 -0
- package/tools/cli/installers/lib/ide/antigravity.js +486 -0
- package/tools/cli/installers/lib/ide/auggie.js +244 -0
- package/tools/cli/installers/lib/ide/claude-code.js +487 -0
- package/tools/cli/installers/lib/ide/cline.js +269 -0
- package/tools/cli/installers/lib/ide/codex.js +375 -0
- package/tools/cli/installers/lib/ide/crush.js +300 -0
- package/tools/cli/installers/lib/ide/cursor.js +169 -0
- package/tools/cli/installers/lib/ide/gemini.js +301 -0
- package/tools/cli/installers/lib/ide/github-copilot.js +383 -0
- package/tools/cli/installers/lib/ide/iflow.js +191 -0
- package/tools/cli/installers/lib/ide/kilo.js +250 -0
- package/tools/cli/installers/lib/ide/kiro-cli.js +326 -0
- package/tools/cli/installers/lib/ide/manager.js +244 -0
- package/tools/cli/installers/lib/ide/opencode.js +257 -0
- package/tools/cli/installers/lib/ide/qwen.js +372 -0
- package/tools/cli/installers/lib/ide/roo.js +270 -0
- package/tools/cli/installers/lib/ide/rovo-dev.js +290 -0
- package/tools/cli/installers/lib/ide/shared/agent-command-generator.js +96 -0
- package/tools/cli/installers/lib/ide/shared/bmad-artifacts.js +158 -0
- package/tools/cli/installers/lib/ide/shared/module-injections.js +136 -0
- package/tools/cli/installers/lib/ide/shared/task-tool-command-generator.js +119 -0
- package/tools/cli/installers/lib/ide/shared/workflow-command-generator.js +242 -0
- package/tools/cli/installers/lib/ide/templates/agent-command-template.md +29 -0
- package/tools/cli/installers/lib/ide/templates/gemini-agent-command.toml +14 -0
- package/tools/cli/installers/lib/ide/templates/gemini-task-command.toml +12 -0
- package/tools/cli/installers/lib/ide/templates/workflow-command-template.md +30 -0
- package/tools/cli/installers/lib/ide/templates/workflow-commander.md +45 -0
- package/tools/cli/installers/lib/ide/trae.js +313 -0
- package/tools/cli/installers/lib/ide/windsurf.js +258 -0
- package/tools/cli/installers/lib/message-loader.js +85 -0
- package/tools/cli/installers/lib/modules/external-manager.js +133 -0
- package/tools/cli/installers/lib/modules/manager.js +1362 -0
- package/tools/cli/lib/activation-builder.js +163 -0
- package/tools/cli/lib/agent/compiler.js +522 -0
- package/tools/cli/lib/agent/installer.js +716 -0
- package/tools/cli/lib/agent/template-engine.js +152 -0
- package/tools/cli/lib/agent-analyzer.js +109 -0
- package/tools/cli/lib/agent-party-generator.js +194 -0
- package/tools/cli/lib/cli-utils.js +227 -0
- package/tools/cli/lib/config.js +213 -0
- package/tools/cli/lib/file-ops.js +204 -0
- package/tools/cli/lib/platform-codes.js +116 -0
- package/tools/cli/lib/project-root.js +77 -0
- package/tools/cli/lib/prompts.js +433 -0
- package/tools/cli/lib/ui.js +1591 -0
- package/tools/cli/lib/xml-handler.js +177 -0
- package/tools/cli/lib/xml-to-markdown.js +82 -0
- package/tools/cli/lib/yaml-format.js +245 -0
- package/tools/cli/lib/yaml-xml-builder.js +587 -0
- package/tools/cli/scripts/migrate-workflows.js +273 -0
- package/tools/docs/BUNDLE_DISTRIBUTION_SETUP.md +95 -0
- package/tools/docs/index.md +2 -0
- package/tools/fix-doc-links.js +288 -0
- package/tools/flattener/aggregate.js +76 -0
- package/tools/flattener/binary.js +80 -0
- package/tools/flattener/discovery.js +71 -0
- package/tools/flattener/files.js +35 -0
- package/tools/flattener/ignoreRules.js +172 -0
- package/tools/flattener/main.js +483 -0
- package/tools/flattener/projectRoot.js +201 -0
- package/tools/flattener/prompts.js +44 -0
- package/tools/flattener/stats.helpers.js +368 -0
- package/tools/flattener/stats.js +75 -0
- package/tools/flattener/test-matrix.js +409 -0
- package/tools/flattener/xml.js +82 -0
- package/tools/format-workflow-md.js +263 -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 +157 -0
- package/tools/schema/agent.js +493 -0
- package/tools/validate-agent-schema.js +110 -0
- package/tools/validate-doc-links.js +363 -0
- package/tools/validate-svg-changes.sh +356 -0
- package/website/README.md +76 -0
- package/website/astro.config.mjs +228 -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/img/logo.svg +4 -0
- package/website/public/robots.txt +37 -0
- package/website/src/components/Banner.astro +59 -0
- package/website/src/components/Header.astro +121 -0
- package/website/src/components/MobileMenuFooter.astro +53 -0
- package/website/src/content/config.ts +6 -0
- package/website/src/lib/site-url.js +25 -0
- package/website/src/rehype-markdown-links.js +102 -0
- package/website/src/styles/custom.css +485 -0
|
@@ -0,0 +1,1591 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const path = require('node:path');
|
|
3
|
+
const os = require('node:os');
|
|
4
|
+
const fs = require('fs-extra');
|
|
5
|
+
const { CLIUtils } = require('./cli-utils');
|
|
6
|
+
const { CustomHandler } = require('../installers/lib/custom/handler');
|
|
7
|
+
const { ExternalModuleManager } = require('../installers/lib/modules/external-manager');
|
|
8
|
+
const prompts = require('./prompts');
|
|
9
|
+
|
|
10
|
+
// Separator class for visual grouping in select/multiselect prompts
|
|
11
|
+
// Note: @clack/prompts doesn't support separators natively, they are filtered out
|
|
12
|
+
class Separator {
|
|
13
|
+
constructor(text = '────────') {
|
|
14
|
+
this.line = text;
|
|
15
|
+
this.name = text;
|
|
16
|
+
}
|
|
17
|
+
type = 'separator';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Separator for choice lists (compatible interface)
|
|
21
|
+
const choiceUtils = { Separator };
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* UI utilities for the installer
|
|
25
|
+
*/
|
|
26
|
+
class UI {
|
|
27
|
+
/**
|
|
28
|
+
* Prompt for installation configuration
|
|
29
|
+
* @returns {Object} Installation configuration
|
|
30
|
+
*/
|
|
31
|
+
async promptInstall() {
|
|
32
|
+
CLIUtils.displayLogo();
|
|
33
|
+
|
|
34
|
+
// Display version-specific start message from install-messages.yaml
|
|
35
|
+
const { MessageLoader } = require('../installers/lib/message-loader');
|
|
36
|
+
const messageLoader = new MessageLoader();
|
|
37
|
+
messageLoader.displayStartMessage();
|
|
38
|
+
|
|
39
|
+
const confirmedDirectory = await this.getConfirmedDirectory();
|
|
40
|
+
|
|
41
|
+
// Preflight: Check for legacy BMAD v4 footprints immediately after getting directory
|
|
42
|
+
const { Detector } = require('../installers/lib/core/detector');
|
|
43
|
+
const { Installer } = require('../installers/lib/core/installer');
|
|
44
|
+
const detector = new Detector();
|
|
45
|
+
const installer = new Installer();
|
|
46
|
+
const legacyV4 = await detector.detectLegacyV4(confirmedDirectory);
|
|
47
|
+
if (legacyV4.hasLegacyV4) {
|
|
48
|
+
await installer.handleLegacyV4Migration(confirmedDirectory, legacyV4);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Check for legacy folders and prompt for rename before showing any menus
|
|
52
|
+
let hasLegacyCfg = false;
|
|
53
|
+
let hasLegacyBmadFolder = false;
|
|
54
|
+
let bmadDir = null;
|
|
55
|
+
let legacyBmadPath = null;
|
|
56
|
+
|
|
57
|
+
// First check for legacy .bmad folder (instead of _bmad)
|
|
58
|
+
// Only check if directory exists
|
|
59
|
+
if (await fs.pathExists(confirmedDirectory)) {
|
|
60
|
+
const entries = await fs.readdir(confirmedDirectory, { withFileTypes: true });
|
|
61
|
+
for (const entry of entries) {
|
|
62
|
+
if (entry.isDirectory() && (entry.name === '.bmad' || entry.name === 'bmad')) {
|
|
63
|
+
hasLegacyBmadFolder = true;
|
|
64
|
+
legacyBmadPath = path.join(confirmedDirectory, '.bmad');
|
|
65
|
+
bmadDir = legacyBmadPath;
|
|
66
|
+
|
|
67
|
+
// Check if it has _cfg folder
|
|
68
|
+
const cfgPath = path.join(legacyBmadPath, '_cfg');
|
|
69
|
+
if (await fs.pathExists(cfgPath)) {
|
|
70
|
+
hasLegacyCfg = true;
|
|
71
|
+
}
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// If no .bmad or bmad found, check for current installations _bmad
|
|
78
|
+
if (!hasLegacyBmadFolder) {
|
|
79
|
+
const bmadResult = await installer.findBmadDir(confirmedDirectory);
|
|
80
|
+
bmadDir = bmadResult.bmadDir;
|
|
81
|
+
hasLegacyCfg = bmadResult.hasLegacyCfg;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Handle legacy .bmad or _cfg folder - these are very old (more than 2 versions behind)
|
|
85
|
+
// Show version warning instead of offering conversion
|
|
86
|
+
if (hasLegacyBmadFolder || hasLegacyCfg) {
|
|
87
|
+
console.log('');
|
|
88
|
+
console.log(chalk.yellow.bold('⚠️ LEGACY INSTALLATION DETECTED'));
|
|
89
|
+
console.log(chalk.yellow('─'.repeat(80)));
|
|
90
|
+
console.log(
|
|
91
|
+
chalk.yellow(
|
|
92
|
+
'Found a ".bmad"/"bmad" folder, or a legacy "_cfg" folder under the bmad folder - this is from a old BMAD version that is out of date for automatic upgrade, manual intervention required.',
|
|
93
|
+
),
|
|
94
|
+
);
|
|
95
|
+
console.log(chalk.yellow('This version is more than 2 alpha versions behind current.'));
|
|
96
|
+
console.log('');
|
|
97
|
+
console.log(chalk.dim('For stability, we only support updates from the previous 2 alpha versions.'));
|
|
98
|
+
console.log(chalk.dim('Legacy installations may have compatibility issues.'));
|
|
99
|
+
console.log('');
|
|
100
|
+
console.log(chalk.dim('For the best experience, we strongly recommend:'));
|
|
101
|
+
console.log(chalk.dim(' 1. Delete your current BMAD installation folder (.bmad or bmad)'));
|
|
102
|
+
console.log(
|
|
103
|
+
chalk.dim(
|
|
104
|
+
' 2. Run a fresh installation\n\nIf you do not want to start fresh, you can attempt to proceed beyond this point IF you have ensured the bmad folder is named _bmad, and under it there is a _config folder. If you have a folder under your bmad folder named _cfg, you would need to rename it _config, and then restart the installer.',
|
|
105
|
+
),
|
|
106
|
+
);
|
|
107
|
+
console.log('');
|
|
108
|
+
console.log(chalk.dim('Benefits of a fresh install:'));
|
|
109
|
+
console.log(chalk.dim(' • Cleaner configuration without legacy artifacts'));
|
|
110
|
+
console.log(chalk.dim(' • All new features properly configured'));
|
|
111
|
+
console.log(chalk.dim(' • Fewer potential conflicts'));
|
|
112
|
+
console.log(chalk.dim(''));
|
|
113
|
+
console.log(
|
|
114
|
+
chalk.dim(
|
|
115
|
+
'If you have already produced output from an earlier alpha version, you can still retain those artifacts. After installation, ensure you configured during install the proper file locations for artifacts depending on the module you are using, or move the files to the proper locations.',
|
|
116
|
+
),
|
|
117
|
+
);
|
|
118
|
+
console.log(chalk.yellow('─'.repeat(80)));
|
|
119
|
+
console.log('');
|
|
120
|
+
|
|
121
|
+
const proceed = await prompts.select({
|
|
122
|
+
message: 'How would you like to proceed?',
|
|
123
|
+
choices: [
|
|
124
|
+
{
|
|
125
|
+
name: 'Cancel and do a fresh install (recommended)',
|
|
126
|
+
value: 'cancel',
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
name: 'Proceed anyway (will attempt update, potentially may fail or have unstable behavior)',
|
|
130
|
+
value: 'proceed',
|
|
131
|
+
},
|
|
132
|
+
],
|
|
133
|
+
default: 'cancel',
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
if (proceed === 'cancel') {
|
|
137
|
+
console.log('');
|
|
138
|
+
console.log(chalk.cyan('To do a fresh install:'));
|
|
139
|
+
console.log(chalk.dim(' 1. Delete the existing bmad folder in your project'));
|
|
140
|
+
console.log(chalk.dim(" 2. Run 'bmad install' again"));
|
|
141
|
+
console.log('');
|
|
142
|
+
process.exit(0);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const ora = require('ora');
|
|
147
|
+
const spinner = ora('Updating folder structure...').start();
|
|
148
|
+
try {
|
|
149
|
+
// Handle .bmad folder
|
|
150
|
+
if (hasLegacyBmadFolder) {
|
|
151
|
+
const newBmadPath = path.join(confirmedDirectory, '_bmad');
|
|
152
|
+
await fs.move(legacyBmadPath, newBmadPath);
|
|
153
|
+
bmadDir = newBmadPath;
|
|
154
|
+
spinner.succeed('Renamed ".bmad" to "_bmad"');
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Handle _cfg folder (either from .bmad or standalone)
|
|
158
|
+
const cfgPath = path.join(bmadDir, '_cfg');
|
|
159
|
+
if (await fs.pathExists(cfgPath)) {
|
|
160
|
+
spinner.start('Renaming configuration folder...');
|
|
161
|
+
const newCfgPath = path.join(bmadDir, '_config');
|
|
162
|
+
await fs.move(cfgPath, newCfgPath);
|
|
163
|
+
spinner.succeed('Renamed "_cfg" to "_config"');
|
|
164
|
+
}
|
|
165
|
+
} catch (error) {
|
|
166
|
+
spinner.fail('Failed to update folder structure');
|
|
167
|
+
console.error(chalk.red(`Error: ${error.message}`));
|
|
168
|
+
process.exit(1);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Check if there's an existing BMAD installation (after any folder renames)
|
|
173
|
+
const hasExistingInstall = await fs.pathExists(bmadDir);
|
|
174
|
+
|
|
175
|
+
let customContentConfig = { hasCustomContent: false };
|
|
176
|
+
if (!hasExistingInstall) {
|
|
177
|
+
customContentConfig._shouldAsk = true;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Track action type (only set if there's an existing installation)
|
|
181
|
+
let actionType;
|
|
182
|
+
|
|
183
|
+
// Only show action menu if there's an existing installation
|
|
184
|
+
if (hasExistingInstall) {
|
|
185
|
+
// Get version information
|
|
186
|
+
const { existingInstall, bmadDir } = await this.getExistingInstallation(confirmedDirectory);
|
|
187
|
+
const packageJsonPath = path.join(__dirname, '../../../package.json');
|
|
188
|
+
const currentVersion = require(packageJsonPath).version;
|
|
189
|
+
const installedVersion = existingInstall.version || 'unknown';
|
|
190
|
+
|
|
191
|
+
// Check if version is too old and warn user
|
|
192
|
+
const shouldProceed = await this.showOldAlphaVersionWarning(installedVersion, currentVersion, path.basename(bmadDir));
|
|
193
|
+
|
|
194
|
+
// If user chose to cancel, exit the installer
|
|
195
|
+
if (!shouldProceed) {
|
|
196
|
+
process.exit(0);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Build menu choices dynamically
|
|
201
|
+
const choices = [];
|
|
202
|
+
|
|
203
|
+
// Always show Quick Update first (allows refreshing installation even on same version)
|
|
204
|
+
if (installedVersion !== 'unknown') {
|
|
205
|
+
choices.push({
|
|
206
|
+
name: `Quick Update (v${installedVersion} → v${currentVersion})`,
|
|
207
|
+
value: 'quick-update',
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Add custom agent compilation option
|
|
212
|
+
if (installedVersion !== 'unknown') {
|
|
213
|
+
choices.push({
|
|
214
|
+
name: 'Recompile Agents (apply customizations only)',
|
|
215
|
+
value: 'compile-agents',
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Common actions
|
|
220
|
+
choices.push({ name: 'Modify BMAD Installation', value: 'update' });
|
|
221
|
+
|
|
222
|
+
actionType = await prompts.select({
|
|
223
|
+
message: 'How would you like to proceed?',
|
|
224
|
+
choices: choices,
|
|
225
|
+
default: choices[0].value,
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// Handle quick update separately
|
|
229
|
+
if (actionType === 'quick-update') {
|
|
230
|
+
// Quick update doesn't install custom content - just updates existing modules
|
|
231
|
+
return {
|
|
232
|
+
actionType: 'quick-update',
|
|
233
|
+
directory: confirmedDirectory,
|
|
234
|
+
customContent: { hasCustomContent: false },
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Handle compile agents separately
|
|
239
|
+
if (actionType === 'compile-agents') {
|
|
240
|
+
// Only recompile agents with customizations, don't update any files
|
|
241
|
+
return {
|
|
242
|
+
actionType: 'compile-agents',
|
|
243
|
+
directory: confirmedDirectory,
|
|
244
|
+
customContent: { hasCustomContent: false },
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// If actionType === 'update', handle it with the new flow
|
|
249
|
+
// Return early with modify configuration
|
|
250
|
+
if (actionType === 'update') {
|
|
251
|
+
// Get existing installation info
|
|
252
|
+
const { installedModuleIds } = await this.getExistingInstallation(confirmedDirectory);
|
|
253
|
+
|
|
254
|
+
console.log(chalk.dim(` Found existing modules: ${[...installedModuleIds].join(', ')}`));
|
|
255
|
+
|
|
256
|
+
// Unified module selection - all modules in one grouped multiselect
|
|
257
|
+
let selectedModules = await this.selectAllModules(installedModuleIds);
|
|
258
|
+
|
|
259
|
+
// After module selection, ask about custom modules
|
|
260
|
+
console.log('');
|
|
261
|
+
const changeCustomModules = await prompts.confirm({
|
|
262
|
+
message: 'Modify custom modules, agents, or workflows?',
|
|
263
|
+
default: false,
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
let customModuleResult = { selectedCustomModules: [], customContentConfig: { hasCustomContent: false } };
|
|
267
|
+
if (changeCustomModules) {
|
|
268
|
+
customModuleResult = await this.handleCustomModulesInModifyFlow(confirmedDirectory, selectedModules);
|
|
269
|
+
} else {
|
|
270
|
+
// Preserve existing custom modules if user doesn't want to modify them
|
|
271
|
+
const { Installer } = require('../installers/lib/core/installer');
|
|
272
|
+
const installer = new Installer();
|
|
273
|
+
const { bmadDir } = await installer.findBmadDir(confirmedDirectory);
|
|
274
|
+
|
|
275
|
+
const cacheDir = path.join(bmadDir, '_config', 'custom');
|
|
276
|
+
if (await fs.pathExists(cacheDir)) {
|
|
277
|
+
const entries = await fs.readdir(cacheDir, { withFileTypes: true });
|
|
278
|
+
for (const entry of entries) {
|
|
279
|
+
if (entry.isDirectory()) {
|
|
280
|
+
customModuleResult.selectedCustomModules.push(entry.name);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Merge any selected custom modules
|
|
287
|
+
if (customModuleResult.selectedCustomModules.length > 0) {
|
|
288
|
+
selectedModules.push(...customModuleResult.selectedCustomModules);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Get tool selection
|
|
292
|
+
const toolSelection = await this.promptToolSelection(confirmedDirectory);
|
|
293
|
+
|
|
294
|
+
const coreConfig = await this.collectCoreConfig(confirmedDirectory);
|
|
295
|
+
|
|
296
|
+
return {
|
|
297
|
+
actionType: 'update',
|
|
298
|
+
directory: confirmedDirectory,
|
|
299
|
+
installCore: true,
|
|
300
|
+
modules: selectedModules,
|
|
301
|
+
ides: toolSelection.ides,
|
|
302
|
+
skipIde: toolSelection.skipIde,
|
|
303
|
+
coreConfig: coreConfig,
|
|
304
|
+
customContent: customModuleResult.customContentConfig,
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// This section is only for new installations (update returns early above)
|
|
310
|
+
const { installedModuleIds } = await this.getExistingInstallation(confirmedDirectory);
|
|
311
|
+
|
|
312
|
+
// Unified module selection - all modules in one grouped multiselect
|
|
313
|
+
let selectedModules = await this.selectAllModules(installedModuleIds);
|
|
314
|
+
|
|
315
|
+
// Ask about custom content (local modules/agents/workflows)
|
|
316
|
+
const wantsCustomContent = await prompts.confirm({
|
|
317
|
+
message: 'Add custom modules, agents, or workflows from your computer?',
|
|
318
|
+
default: false,
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
if (wantsCustomContent) {
|
|
322
|
+
customContentConfig = await this.promptCustomContentSource();
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Add custom content modules if any were selected
|
|
326
|
+
if (customContentConfig && customContentConfig.selectedModuleIds) {
|
|
327
|
+
selectedModules.push(...customContentConfig.selectedModuleIds);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
selectedModules = selectedModules.filter((m) => m !== 'core');
|
|
331
|
+
let toolSelection = await this.promptToolSelection(confirmedDirectory);
|
|
332
|
+
const coreConfig = await this.collectCoreConfig(confirmedDirectory);
|
|
333
|
+
|
|
334
|
+
return {
|
|
335
|
+
actionType: 'install',
|
|
336
|
+
directory: confirmedDirectory,
|
|
337
|
+
installCore: true,
|
|
338
|
+
modules: selectedModules,
|
|
339
|
+
ides: toolSelection.ides,
|
|
340
|
+
skipIde: toolSelection.skipIde,
|
|
341
|
+
coreConfig: coreConfig,
|
|
342
|
+
customContent: customContentConfig,
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Prompt for tool/IDE selection (called after module configuration)
|
|
348
|
+
* @param {string} projectDir - Project directory to check for existing IDEs
|
|
349
|
+
* @returns {Object} Tool configuration
|
|
350
|
+
*/
|
|
351
|
+
async promptToolSelection(projectDir) {
|
|
352
|
+
// Check for existing configured IDEs - use findBmadDir to detect custom folder names
|
|
353
|
+
const { Detector } = require('../installers/lib/core/detector');
|
|
354
|
+
const { Installer } = require('../installers/lib/core/installer');
|
|
355
|
+
const detector = new Detector();
|
|
356
|
+
const installer = new Installer();
|
|
357
|
+
const bmadResult = await installer.findBmadDir(projectDir || process.cwd());
|
|
358
|
+
const bmadDir = bmadResult.bmadDir;
|
|
359
|
+
const existingInstall = await detector.detect(bmadDir);
|
|
360
|
+
const configuredIdes = existingInstall.ides || [];
|
|
361
|
+
|
|
362
|
+
// Get IDE manager to fetch available IDEs dynamically
|
|
363
|
+
const { IdeManager } = require('../installers/lib/ide/manager');
|
|
364
|
+
const ideManager = new IdeManager();
|
|
365
|
+
|
|
366
|
+
const preferredIdes = ideManager.getPreferredIdes();
|
|
367
|
+
const otherIdes = ideManager.getOtherIdes();
|
|
368
|
+
|
|
369
|
+
// Build grouped options object for groupMultiselect
|
|
370
|
+
const groupedOptions = {};
|
|
371
|
+
const processedIdes = new Set();
|
|
372
|
+
const initialValues = [];
|
|
373
|
+
|
|
374
|
+
// First, add previously configured IDEs, marked with ✅
|
|
375
|
+
if (configuredIdes.length > 0) {
|
|
376
|
+
const configuredGroup = [];
|
|
377
|
+
for (const ideValue of configuredIdes) {
|
|
378
|
+
// Skip empty or invalid IDE values
|
|
379
|
+
if (!ideValue || typeof ideValue !== 'string') {
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Find the IDE in either preferred or other lists
|
|
384
|
+
const preferredIde = preferredIdes.find((ide) => ide.value === ideValue);
|
|
385
|
+
const otherIde = otherIdes.find((ide) => ide.value === ideValue);
|
|
386
|
+
const ide = preferredIde || otherIde;
|
|
387
|
+
|
|
388
|
+
if (ide) {
|
|
389
|
+
configuredGroup.push({
|
|
390
|
+
label: `${ide.name} ✅`,
|
|
391
|
+
value: ide.value,
|
|
392
|
+
});
|
|
393
|
+
processedIdes.add(ide.value);
|
|
394
|
+
initialValues.push(ide.value); // Pre-select configured IDEs
|
|
395
|
+
} else {
|
|
396
|
+
// Warn about unrecognized IDE (but don't fail)
|
|
397
|
+
console.log(chalk.yellow(`⚠️ Previously configured IDE '${ideValue}' is no longer available`));
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
if (configuredGroup.length > 0) {
|
|
401
|
+
groupedOptions['Previously Configured'] = configuredGroup;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Add preferred tools (excluding already processed)
|
|
406
|
+
const remainingPreferred = preferredIdes.filter((ide) => !processedIdes.has(ide.value));
|
|
407
|
+
if (remainingPreferred.length > 0) {
|
|
408
|
+
groupedOptions['Recommended Tools'] = remainingPreferred.map((ide) => {
|
|
409
|
+
processedIdes.add(ide.value);
|
|
410
|
+
return {
|
|
411
|
+
label: `${ide.name} ⭐`,
|
|
412
|
+
value: ide.value,
|
|
413
|
+
};
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Add other tools (excluding already processed)
|
|
418
|
+
const remainingOther = otherIdes.filter((ide) => !processedIdes.has(ide.value));
|
|
419
|
+
if (remainingOther.length > 0) {
|
|
420
|
+
groupedOptions['Additional Tools'] = remainingOther.map((ide) => ({
|
|
421
|
+
label: ide.name,
|
|
422
|
+
value: ide.value,
|
|
423
|
+
}));
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Add standalone "None" option at the end
|
|
427
|
+
groupedOptions[' '] = [
|
|
428
|
+
{
|
|
429
|
+
label: '⚠ None - I am not installing any tools',
|
|
430
|
+
value: '__NONE__',
|
|
431
|
+
},
|
|
432
|
+
];
|
|
433
|
+
|
|
434
|
+
let selectedIdes = [];
|
|
435
|
+
|
|
436
|
+
selectedIdes = await prompts.groupMultiselect({
|
|
437
|
+
message: `Select tools to configure ${chalk.dim('(↑/↓ navigates, SPACE toggles, ENTER to confirm)')}:`,
|
|
438
|
+
options: groupedOptions,
|
|
439
|
+
initialValues: initialValues.length > 0 ? initialValues : undefined,
|
|
440
|
+
required: true,
|
|
441
|
+
selectableGroups: false,
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
// If user selected both "__NONE__" and other tools, honor the "None" choice
|
|
445
|
+
if (selectedIdes && selectedIdes.includes('__NONE__') && selectedIdes.length > 1) {
|
|
446
|
+
console.log();
|
|
447
|
+
console.log(chalk.yellow('⚠️ "None - I am not installing any tools" was selected, so no tools will be configured.'));
|
|
448
|
+
console.log();
|
|
449
|
+
selectedIdes = [];
|
|
450
|
+
} else if (selectedIdes && selectedIdes.includes('__NONE__')) {
|
|
451
|
+
// Only "__NONE__" was selected
|
|
452
|
+
selectedIdes = [];
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
return {
|
|
456
|
+
ides: selectedIdes || [],
|
|
457
|
+
skipIde: !selectedIdes || selectedIdes.length === 0,
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Prompt for update configuration
|
|
463
|
+
* @returns {Object} Update configuration
|
|
464
|
+
*/
|
|
465
|
+
async promptUpdate() {
|
|
466
|
+
const backupFirst = await prompts.confirm({
|
|
467
|
+
message: 'Create backup before updating?',
|
|
468
|
+
default: true,
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
const preserveCustomizations = await prompts.confirm({
|
|
472
|
+
message: 'Preserve local customizations?',
|
|
473
|
+
default: true,
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
return { backupFirst, preserveCustomizations };
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Confirm action
|
|
481
|
+
* @param {string} message - Confirmation message
|
|
482
|
+
* @param {boolean} defaultValue - Default value
|
|
483
|
+
* @returns {boolean} User confirmation
|
|
484
|
+
*/
|
|
485
|
+
async confirm(message, defaultValue = false) {
|
|
486
|
+
return await prompts.confirm({
|
|
487
|
+
message,
|
|
488
|
+
default: defaultValue,
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Display installation summary
|
|
494
|
+
* @param {Object} result - Installation result
|
|
495
|
+
*/
|
|
496
|
+
showInstallSummary(result) {
|
|
497
|
+
// Clean, simple completion message
|
|
498
|
+
console.log('\n' + chalk.green.bold('✨ BMAD is ready to use!'));
|
|
499
|
+
|
|
500
|
+
// Show installation summary in a simple format
|
|
501
|
+
console.log(chalk.dim(`Installed to: ${result.path}`));
|
|
502
|
+
if (result.modules && result.modules.length > 0) {
|
|
503
|
+
console.log(chalk.dim(`Modules: ${result.modules.join(', ')}`));
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* Get confirmed directory from user
|
|
509
|
+
* @returns {string} Confirmed directory path
|
|
510
|
+
*/
|
|
511
|
+
async getConfirmedDirectory() {
|
|
512
|
+
let confirmedDirectory = null;
|
|
513
|
+
while (!confirmedDirectory) {
|
|
514
|
+
const directoryAnswer = await this.promptForDirectory();
|
|
515
|
+
await this.displayDirectoryInfo(directoryAnswer.directory);
|
|
516
|
+
|
|
517
|
+
if (await this.confirmDirectory(directoryAnswer.directory)) {
|
|
518
|
+
confirmedDirectory = directoryAnswer.directory;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
return confirmedDirectory;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Get existing installation info and installed modules
|
|
526
|
+
* @param {string} directory - Installation directory
|
|
527
|
+
* @returns {Object} Object with existingInstall, installedModuleIds, and bmadDir
|
|
528
|
+
*/
|
|
529
|
+
async getExistingInstallation(directory) {
|
|
530
|
+
const { Detector } = require('../installers/lib/core/detector');
|
|
531
|
+
const { Installer } = require('../installers/lib/core/installer');
|
|
532
|
+
const detector = new Detector();
|
|
533
|
+
const installer = new Installer();
|
|
534
|
+
const bmadDirResult = await installer.findBmadDir(directory);
|
|
535
|
+
const bmadDir = bmadDirResult.bmadDir;
|
|
536
|
+
const existingInstall = await detector.detect(bmadDir);
|
|
537
|
+
const installedModuleIds = new Set(existingInstall.modules.map((mod) => mod.id));
|
|
538
|
+
|
|
539
|
+
return { existingInstall, installedModuleIds, bmadDir };
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Collect core configuration
|
|
544
|
+
* @param {string} directory - Installation directory
|
|
545
|
+
* @returns {Object} Core configuration
|
|
546
|
+
*/
|
|
547
|
+
async collectCoreConfig(directory) {
|
|
548
|
+
const { ConfigCollector } = require('../installers/lib/core/config-collector');
|
|
549
|
+
const configCollector = new ConfigCollector();
|
|
550
|
+
// Load existing configs first if they exist
|
|
551
|
+
await configCollector.loadExistingConfig(directory);
|
|
552
|
+
// Now collect with existing values as defaults (false = don't skip loading, true = skip completion message)
|
|
553
|
+
await configCollector.collectModuleConfig('core', directory, false, true);
|
|
554
|
+
|
|
555
|
+
const coreConfig = configCollector.collectedConfig.core;
|
|
556
|
+
// Ensure we always have a core config object, even if empty
|
|
557
|
+
return coreConfig || {};
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* Get module choices for selection
|
|
562
|
+
* @param {Set} installedModuleIds - Currently installed module IDs
|
|
563
|
+
* @param {Object} customContentConfig - Custom content configuration
|
|
564
|
+
* @returns {Array} Module choices for prompt
|
|
565
|
+
*/
|
|
566
|
+
async getModuleChoices(installedModuleIds, customContentConfig = null) {
|
|
567
|
+
const moduleChoices = [];
|
|
568
|
+
const isNewInstallation = installedModuleIds.size === 0;
|
|
569
|
+
|
|
570
|
+
const customContentItems = [];
|
|
571
|
+
const hasCustomContentItems = false;
|
|
572
|
+
|
|
573
|
+
// Add custom content items
|
|
574
|
+
if (customContentConfig && customContentConfig.hasCustomContent && customContentConfig.customPath) {
|
|
575
|
+
// Existing installation - show from directory
|
|
576
|
+
const customHandler = new CustomHandler();
|
|
577
|
+
const customFiles = await customHandler.findCustomContent(customContentConfig.customPath);
|
|
578
|
+
|
|
579
|
+
for (const customFile of customFiles) {
|
|
580
|
+
const customInfo = await customHandler.getCustomInfo(customFile);
|
|
581
|
+
if (customInfo) {
|
|
582
|
+
customContentItems.push({
|
|
583
|
+
name: `${chalk.cyan('✓')} ${customInfo.name} ${chalk.gray(`(${customInfo.relativePath})`)}`,
|
|
584
|
+
value: `__CUSTOM_CONTENT__${customFile}`, // Unique value for each custom content
|
|
585
|
+
checked: true, // Default to selected since user chose to provide custom content
|
|
586
|
+
path: customInfo.path, // Track path to avoid duplicates
|
|
587
|
+
hint: customInfo.description || undefined,
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// Add official modules
|
|
594
|
+
const { ModuleManager } = require('../installers/lib/modules/manager');
|
|
595
|
+
const moduleManager = new ModuleManager();
|
|
596
|
+
const { modules: availableModules, customModules: customModulesFromCache } = await moduleManager.listAvailable();
|
|
597
|
+
|
|
598
|
+
// First, add all items to appropriate sections
|
|
599
|
+
const allCustomModules = [];
|
|
600
|
+
|
|
601
|
+
// Add custom content items from directory
|
|
602
|
+
allCustomModules.push(...customContentItems);
|
|
603
|
+
|
|
604
|
+
// Add custom modules from cache
|
|
605
|
+
for (const mod of customModulesFromCache) {
|
|
606
|
+
// Skip if this module is already in customContentItems (by path)
|
|
607
|
+
const isDuplicate = allCustomModules.some((item) => item.path && mod.path && path.resolve(item.path) === path.resolve(mod.path));
|
|
608
|
+
|
|
609
|
+
if (!isDuplicate) {
|
|
610
|
+
allCustomModules.push({
|
|
611
|
+
name: `${chalk.cyan('✓')} ${mod.name} ${chalk.gray(`(cached)`)}`,
|
|
612
|
+
value: mod.id,
|
|
613
|
+
checked: isNewInstallation ? mod.defaultSelected || false : installedModuleIds.has(mod.id),
|
|
614
|
+
hint: mod.description || undefined,
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// Add separators and modules in correct order
|
|
620
|
+
if (allCustomModules.length > 0) {
|
|
621
|
+
// Add separator for custom content, all custom modules, and official content separator
|
|
622
|
+
moduleChoices.push(
|
|
623
|
+
new choiceUtils.Separator('── Custom Content ──'),
|
|
624
|
+
...allCustomModules,
|
|
625
|
+
new choiceUtils.Separator('── Official Content ──'),
|
|
626
|
+
);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// Add official modules (only non-custom ones)
|
|
630
|
+
for (const mod of availableModules) {
|
|
631
|
+
if (!mod.isCustom) {
|
|
632
|
+
moduleChoices.push({
|
|
633
|
+
name: mod.name,
|
|
634
|
+
value: mod.id,
|
|
635
|
+
checked: isNewInstallation ? mod.defaultSelected || false : installedModuleIds.has(mod.id),
|
|
636
|
+
hint: mod.description || undefined,
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
return moduleChoices;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
/**
|
|
645
|
+
* Prompt for module selection
|
|
646
|
+
* @param {Array} moduleChoices - Available module choices
|
|
647
|
+
* @returns {Array} Selected module IDs
|
|
648
|
+
*/
|
|
649
|
+
async selectModules(moduleChoices, defaultSelections = null) {
|
|
650
|
+
// If defaultSelections is provided, use it to override checked state
|
|
651
|
+
// Otherwise preserve the checked state from moduleChoices (set by getModuleChoices)
|
|
652
|
+
const choicesWithDefaults = moduleChoices.map((choice) => ({
|
|
653
|
+
...choice,
|
|
654
|
+
...(defaultSelections === null ? {} : { checked: defaultSelections.includes(choice.value) }),
|
|
655
|
+
}));
|
|
656
|
+
|
|
657
|
+
// Add a "None" option at the end for users who changed their mind
|
|
658
|
+
const choicesWithSkipOption = [
|
|
659
|
+
...choicesWithDefaults,
|
|
660
|
+
{
|
|
661
|
+
value: '__NONE__',
|
|
662
|
+
label: '⚠ None / I changed my mind - skip module installation',
|
|
663
|
+
checked: false,
|
|
664
|
+
},
|
|
665
|
+
];
|
|
666
|
+
|
|
667
|
+
const selected = await prompts.multiselect({
|
|
668
|
+
message: `Select modules to install ${chalk.dim('(↑/↓ navigates, SPACE toggles, ENTER to confirm)')}:`,
|
|
669
|
+
choices: choicesWithSkipOption,
|
|
670
|
+
required: true,
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
// If user selected both "__NONE__" and other items, honor the "None" choice
|
|
674
|
+
if (selected && selected.includes('__NONE__') && selected.length > 1) {
|
|
675
|
+
console.log();
|
|
676
|
+
console.log(chalk.yellow('⚠️ "None / I changed my mind" was selected, so no modules will be installed.'));
|
|
677
|
+
console.log();
|
|
678
|
+
return [];
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
// Filter out the special '__NONE__' value
|
|
682
|
+
return selected ? selected.filter((m) => m !== '__NONE__') : [];
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
/**
|
|
686
|
+
* Get external module choices for selection
|
|
687
|
+
* @returns {Array} External module choices for prompt
|
|
688
|
+
*/
|
|
689
|
+
async getExternalModuleChoices() {
|
|
690
|
+
const externalManager = new ExternalModuleManager();
|
|
691
|
+
const modules = await externalManager.listAvailable();
|
|
692
|
+
|
|
693
|
+
return modules.map((mod) => ({
|
|
694
|
+
name: mod.name,
|
|
695
|
+
value: mod.code, // Use the code (e.g., 'cis') as the value
|
|
696
|
+
checked: mod.defaultSelected || false,
|
|
697
|
+
hint: mod.description || undefined, // Show description as hint
|
|
698
|
+
module: mod, // Store full module info for later use
|
|
699
|
+
}));
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
/**
|
|
703
|
+
* Prompt for external module selection
|
|
704
|
+
* @param {Array} externalModuleChoices - Available external module choices
|
|
705
|
+
* @param {Array} defaultSelections - Module codes to pre-select
|
|
706
|
+
* @returns {Array} Selected external module codes
|
|
707
|
+
*/
|
|
708
|
+
async selectExternalModules(externalModuleChoices, defaultSelections = []) {
|
|
709
|
+
// Build a message showing available modules
|
|
710
|
+
const availableNames = externalModuleChoices.map((c) => c.name).join(', ');
|
|
711
|
+
const message = `Select official BMad modules to install ${chalk.dim('(↑/↓ navigates, SPACE toggles, ENTER to confirm)')}:`;
|
|
712
|
+
|
|
713
|
+
// Mark choices as checked based on defaultSelections
|
|
714
|
+
const choicesWithDefaults = externalModuleChoices.map((choice) => ({
|
|
715
|
+
...choice,
|
|
716
|
+
checked: defaultSelections.includes(choice.value),
|
|
717
|
+
}));
|
|
718
|
+
|
|
719
|
+
// Add a "None" option at the end for users who changed their mind
|
|
720
|
+
const choicesWithSkipOption = [
|
|
721
|
+
...choicesWithDefaults,
|
|
722
|
+
{
|
|
723
|
+
name: '⚠ None / I changed my mind - skip external module installation',
|
|
724
|
+
value: '__NONE__',
|
|
725
|
+
checked: false,
|
|
726
|
+
},
|
|
727
|
+
];
|
|
728
|
+
|
|
729
|
+
const selected = await prompts.multiselect({
|
|
730
|
+
message,
|
|
731
|
+
choices: choicesWithSkipOption,
|
|
732
|
+
required: true,
|
|
733
|
+
});
|
|
734
|
+
|
|
735
|
+
// If user selected both "__NONE__" and other items, honor the "None" choice
|
|
736
|
+
if (selected && selected.includes('__NONE__') && selected.length > 1) {
|
|
737
|
+
console.log();
|
|
738
|
+
console.log(chalk.yellow('⚠️ "None / I changed my mind" was selected, so no external modules will be installed.'));
|
|
739
|
+
console.log();
|
|
740
|
+
return [];
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// Filter out the special '__NONE__' value
|
|
744
|
+
return selected ? selected.filter((m) => m !== '__NONE__') : [];
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
/**
|
|
748
|
+
* Select all modules (core + official + community) using grouped multiselect
|
|
749
|
+
* @param {Set} installedModuleIds - Currently installed module IDs
|
|
750
|
+
* @returns {Array} Selected module codes
|
|
751
|
+
*/
|
|
752
|
+
async selectAllModules(installedModuleIds = new Set()) {
|
|
753
|
+
const { ModuleManager } = require('../installers/lib/modules/manager');
|
|
754
|
+
const moduleManager = new ModuleManager();
|
|
755
|
+
const { modules: localModules } = await moduleManager.listAvailable();
|
|
756
|
+
|
|
757
|
+
// Get external modules
|
|
758
|
+
const externalManager = new ExternalModuleManager();
|
|
759
|
+
const externalModules = await externalManager.listAvailable();
|
|
760
|
+
|
|
761
|
+
// Build grouped options
|
|
762
|
+
const groupedOptions = {};
|
|
763
|
+
const initialValues = [];
|
|
764
|
+
|
|
765
|
+
// Helper to build module entry with proper sorting and selection
|
|
766
|
+
const buildModuleEntry = (mod, value) => {
|
|
767
|
+
const isInstalled = installedModuleIds.has(value);
|
|
768
|
+
const isDefault = mod.defaultSelected === true;
|
|
769
|
+
return {
|
|
770
|
+
label: mod.description ? `${mod.name} — ${mod.description}` : mod.name,
|
|
771
|
+
value,
|
|
772
|
+
// For sorting: defaultSelected=0, others=1
|
|
773
|
+
sortKey: isDefault ? 0 : 1,
|
|
774
|
+
// Pre-select if default selected OR already installed
|
|
775
|
+
selected: isDefault || isInstalled,
|
|
776
|
+
};
|
|
777
|
+
};
|
|
778
|
+
|
|
779
|
+
// Group 1: BMad Core (BMM, BMB)
|
|
780
|
+
const coreModules = [];
|
|
781
|
+
for (const mod of localModules) {
|
|
782
|
+
if (!mod.isCustom && (mod.id === 'bmm' || mod.id === 'bmb')) {
|
|
783
|
+
const entry = buildModuleEntry(mod, mod.id);
|
|
784
|
+
coreModules.push(entry);
|
|
785
|
+
if (entry.selected) {
|
|
786
|
+
initialValues.push(mod.id);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
// Sort: defaultSelected first, then others
|
|
791
|
+
coreModules.sort((a, b) => a.sortKey - b.sortKey);
|
|
792
|
+
// Remove sortKey from final entries
|
|
793
|
+
if (coreModules.length > 0) {
|
|
794
|
+
groupedOptions['BMad Core'] = coreModules.map(({ label, value }) => ({ label, value }));
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
// Group 2: BMad Official Modules (type: bmad-org)
|
|
798
|
+
const officialModules = [];
|
|
799
|
+
for (const mod of externalModules) {
|
|
800
|
+
if (mod.type === 'bmad-org') {
|
|
801
|
+
const entry = buildModuleEntry(mod, mod.code);
|
|
802
|
+
officialModules.push(entry);
|
|
803
|
+
if (entry.selected) {
|
|
804
|
+
initialValues.push(mod.code);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
officialModules.sort((a, b) => a.sortKey - b.sortKey);
|
|
809
|
+
if (officialModules.length > 0) {
|
|
810
|
+
groupedOptions['BMad Official Modules'] = officialModules.map(({ label, value }) => ({ label, value }));
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
// Group 3: Community Modules (type: community)
|
|
814
|
+
const communityModules = [];
|
|
815
|
+
for (const mod of externalModules) {
|
|
816
|
+
if (mod.type === 'community') {
|
|
817
|
+
const entry = buildModuleEntry(mod, mod.code);
|
|
818
|
+
communityModules.push(entry);
|
|
819
|
+
if (entry.selected) {
|
|
820
|
+
initialValues.push(mod.code);
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
communityModules.sort((a, b) => a.sortKey - b.sortKey);
|
|
825
|
+
if (communityModules.length > 0) {
|
|
826
|
+
groupedOptions['Community Modules'] = communityModules.map(({ label, value }) => ({ label, value }));
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
// Add "None" option at the end
|
|
830
|
+
groupedOptions[' '] = [
|
|
831
|
+
{
|
|
832
|
+
label: '⚠ None - Skip module installation',
|
|
833
|
+
value: '__NONE__',
|
|
834
|
+
},
|
|
835
|
+
];
|
|
836
|
+
|
|
837
|
+
const selected = await prompts.groupMultiselect({
|
|
838
|
+
message: `Select modules to install ${chalk.dim('(↑/↓ navigates, SPACE toggles, ENTER to confirm)')}:`,
|
|
839
|
+
options: groupedOptions,
|
|
840
|
+
initialValues: initialValues.length > 0 ? initialValues : undefined,
|
|
841
|
+
required: true,
|
|
842
|
+
selectableGroups: false,
|
|
843
|
+
});
|
|
844
|
+
|
|
845
|
+
// If user selected both "__NONE__" and other items, honor the "None" choice
|
|
846
|
+
if (selected && selected.includes('__NONE__') && selected.length > 1) {
|
|
847
|
+
console.log();
|
|
848
|
+
console.log(chalk.yellow('⚠️ "None" was selected, so no modules will be installed.'));
|
|
849
|
+
console.log();
|
|
850
|
+
return [];
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
// Filter out the special '__NONE__' value
|
|
854
|
+
return selected ? selected.filter((m) => m !== '__NONE__') : [];
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
/**
|
|
858
|
+
* Prompt for directory selection
|
|
859
|
+
* @returns {Object} Directory answer from prompt
|
|
860
|
+
*/
|
|
861
|
+
async promptForDirectory() {
|
|
862
|
+
// Use sync validation because @clack/prompts doesn't support async validate
|
|
863
|
+
const directory = await prompts.text({
|
|
864
|
+
message: 'Installation directory:',
|
|
865
|
+
default: process.cwd(),
|
|
866
|
+
placeholder: process.cwd(),
|
|
867
|
+
validate: (input) => this.validateDirectorySync(input),
|
|
868
|
+
});
|
|
869
|
+
|
|
870
|
+
// Apply filter logic
|
|
871
|
+
let filteredDir = directory;
|
|
872
|
+
if (!filteredDir || filteredDir.trim() === '') {
|
|
873
|
+
filteredDir = process.cwd();
|
|
874
|
+
} else {
|
|
875
|
+
filteredDir = this.expandUserPath(filteredDir);
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
return { directory: filteredDir };
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
/**
|
|
882
|
+
* Display directory information
|
|
883
|
+
* @param {string} directory - The directory path
|
|
884
|
+
*/
|
|
885
|
+
async displayDirectoryInfo(directory) {
|
|
886
|
+
console.log(chalk.cyan('\nResolved installation path:'), chalk.bold(directory));
|
|
887
|
+
|
|
888
|
+
const dirExists = await fs.pathExists(directory);
|
|
889
|
+
if (dirExists) {
|
|
890
|
+
// Show helpful context about the existing path
|
|
891
|
+
const stats = await fs.stat(directory);
|
|
892
|
+
if (stats.isDirectory()) {
|
|
893
|
+
const files = await fs.readdir(directory);
|
|
894
|
+
if (files.length > 0) {
|
|
895
|
+
// Check for any bmad installation (any folder with _config/manifest.yaml)
|
|
896
|
+
const { Installer } = require('../installers/lib/core/installer');
|
|
897
|
+
const installer = new Installer();
|
|
898
|
+
const bmadResult = await installer.findBmadDir(directory);
|
|
899
|
+
const hasBmadInstall =
|
|
900
|
+
(await fs.pathExists(bmadResult.bmadDir)) && (await fs.pathExists(path.join(bmadResult.bmadDir, '_config', 'manifest.yaml')));
|
|
901
|
+
|
|
902
|
+
console.log(
|
|
903
|
+
chalk.gray(`Directory exists and contains ${files.length} item(s)`) +
|
|
904
|
+
(hasBmadInstall ? chalk.yellow(` including existing BMAD installation (${path.basename(bmadResult.bmadDir)})`) : ''),
|
|
905
|
+
);
|
|
906
|
+
} else {
|
|
907
|
+
console.log(chalk.gray('Directory exists and is empty'));
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
/**
|
|
914
|
+
* Confirm directory selection
|
|
915
|
+
* @param {string} directory - The directory path
|
|
916
|
+
* @returns {boolean} Whether user confirmed
|
|
917
|
+
*/
|
|
918
|
+
async confirmDirectory(directory) {
|
|
919
|
+
const dirExists = await fs.pathExists(directory);
|
|
920
|
+
|
|
921
|
+
if (dirExists) {
|
|
922
|
+
const proceed = await prompts.confirm({
|
|
923
|
+
message: 'Install to this directory?',
|
|
924
|
+
default: true,
|
|
925
|
+
});
|
|
926
|
+
|
|
927
|
+
if (!proceed) {
|
|
928
|
+
console.log(chalk.yellow("\nLet's try again with a different path.\n"));
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
return proceed;
|
|
932
|
+
} else {
|
|
933
|
+
// Ask for confirmation to create the directory
|
|
934
|
+
const create = await prompts.confirm({
|
|
935
|
+
message: `Create directory: ${directory}?`,
|
|
936
|
+
default: false,
|
|
937
|
+
});
|
|
938
|
+
|
|
939
|
+
if (!create) {
|
|
940
|
+
console.log(chalk.yellow("\nLet's try again with a different path.\n"));
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
return create;
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
/**
|
|
948
|
+
* Validate directory path for installation (sync version for clack prompts)
|
|
949
|
+
* @param {string} input - User input path
|
|
950
|
+
* @returns {string|undefined} Error message or undefined if valid
|
|
951
|
+
*/
|
|
952
|
+
validateDirectorySync(input) {
|
|
953
|
+
// Allow empty input to use the default
|
|
954
|
+
if (!input || input.trim() === '') {
|
|
955
|
+
return; // Empty means use default, undefined = valid for clack
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
let expandedPath;
|
|
959
|
+
try {
|
|
960
|
+
expandedPath = this.expandUserPath(input.trim());
|
|
961
|
+
} catch (error) {
|
|
962
|
+
return error.message;
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
// Check if the path exists
|
|
966
|
+
const pathExists = fs.pathExistsSync(expandedPath);
|
|
967
|
+
|
|
968
|
+
if (!pathExists) {
|
|
969
|
+
// Find the first existing parent directory
|
|
970
|
+
const existingParent = this.findExistingParentSync(expandedPath);
|
|
971
|
+
|
|
972
|
+
if (!existingParent) {
|
|
973
|
+
return 'Cannot create directory: no existing parent directory found';
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
// Check if the existing parent is writable
|
|
977
|
+
try {
|
|
978
|
+
fs.accessSync(existingParent, fs.constants.W_OK);
|
|
979
|
+
// Path doesn't exist but can be created - will prompt for confirmation later
|
|
980
|
+
return;
|
|
981
|
+
} catch {
|
|
982
|
+
// Provide a detailed error message explaining both issues
|
|
983
|
+
return `Directory '${expandedPath}' does not exist and cannot be created: parent directory '${existingParent}' is not writable`;
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
// If it exists, validate it's a directory and writable
|
|
988
|
+
const stat = fs.statSync(expandedPath);
|
|
989
|
+
if (!stat.isDirectory()) {
|
|
990
|
+
return `Path exists but is not a directory: ${expandedPath}`;
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
// Check write permissions
|
|
994
|
+
try {
|
|
995
|
+
fs.accessSync(expandedPath, fs.constants.W_OK);
|
|
996
|
+
} catch {
|
|
997
|
+
return `Directory is not writable: ${expandedPath}`;
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
/**
|
|
1004
|
+
* Validate directory path for installation (async version)
|
|
1005
|
+
* @param {string} input - User input path
|
|
1006
|
+
* @returns {string|true} Error message or true if valid
|
|
1007
|
+
*/
|
|
1008
|
+
async validateDirectory(input) {
|
|
1009
|
+
// Allow empty input to use the default
|
|
1010
|
+
if (!input || input.trim() === '') {
|
|
1011
|
+
return true; // Empty means use default
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
let expandedPath;
|
|
1015
|
+
try {
|
|
1016
|
+
expandedPath = this.expandUserPath(input.trim());
|
|
1017
|
+
} catch (error) {
|
|
1018
|
+
return error.message;
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
// Check if the path exists
|
|
1022
|
+
const pathExists = await fs.pathExists(expandedPath);
|
|
1023
|
+
|
|
1024
|
+
if (!pathExists) {
|
|
1025
|
+
// Find the first existing parent directory
|
|
1026
|
+
const existingParent = await this.findExistingParent(expandedPath);
|
|
1027
|
+
|
|
1028
|
+
if (!existingParent) {
|
|
1029
|
+
return 'Cannot create directory: no existing parent directory found';
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
// Check if the existing parent is writable
|
|
1033
|
+
try {
|
|
1034
|
+
await fs.access(existingParent, fs.constants.W_OK);
|
|
1035
|
+
// Path doesn't exist but can be created - will prompt for confirmation later
|
|
1036
|
+
return true;
|
|
1037
|
+
} catch {
|
|
1038
|
+
// Provide a detailed error message explaining both issues
|
|
1039
|
+
return `Directory '${expandedPath}' does not exist and cannot be created: parent directory '${existingParent}' is not writable`;
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
// If it exists, validate it's a directory and writable
|
|
1044
|
+
const stat = await fs.stat(expandedPath);
|
|
1045
|
+
if (!stat.isDirectory()) {
|
|
1046
|
+
return `Path exists but is not a directory: ${expandedPath}`;
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
// Check write permissions
|
|
1050
|
+
try {
|
|
1051
|
+
await fs.access(expandedPath, fs.constants.W_OK);
|
|
1052
|
+
} catch {
|
|
1053
|
+
return `Directory is not writable: ${expandedPath}`;
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
return true;
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
/**
|
|
1060
|
+
* Find the first existing parent directory (sync version)
|
|
1061
|
+
* @param {string} targetPath - The path to check
|
|
1062
|
+
* @returns {string|null} The first existing parent directory, or null if none found
|
|
1063
|
+
*/
|
|
1064
|
+
findExistingParentSync(targetPath) {
|
|
1065
|
+
let currentPath = path.resolve(targetPath);
|
|
1066
|
+
|
|
1067
|
+
// Walk up the directory tree until we find an existing directory
|
|
1068
|
+
while (currentPath !== path.dirname(currentPath)) {
|
|
1069
|
+
// Stop at root
|
|
1070
|
+
const parent = path.dirname(currentPath);
|
|
1071
|
+
if (fs.pathExistsSync(parent)) {
|
|
1072
|
+
return parent;
|
|
1073
|
+
}
|
|
1074
|
+
currentPath = parent;
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
return null; // No existing parent found (shouldn't happen in practice)
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
/**
|
|
1081
|
+
* Find the first existing parent directory (async version)
|
|
1082
|
+
* @param {string} targetPath - The path to check
|
|
1083
|
+
* @returns {string|null} The first existing parent directory, or null if none found
|
|
1084
|
+
*/
|
|
1085
|
+
async findExistingParent(targetPath) {
|
|
1086
|
+
let currentPath = path.resolve(targetPath);
|
|
1087
|
+
|
|
1088
|
+
// Walk up the directory tree until we find an existing directory
|
|
1089
|
+
while (currentPath !== path.dirname(currentPath)) {
|
|
1090
|
+
// Stop at root
|
|
1091
|
+
const parent = path.dirname(currentPath);
|
|
1092
|
+
if (await fs.pathExists(parent)) {
|
|
1093
|
+
return parent;
|
|
1094
|
+
}
|
|
1095
|
+
currentPath = parent;
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
return null; // No existing parent found (shouldn't happen in practice)
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
/**
|
|
1102
|
+
* Expands the user-provided path: handles ~ and resolves to absolute.
|
|
1103
|
+
* @param {string} inputPath - User input path.
|
|
1104
|
+
* @returns {string} Absolute expanded path.
|
|
1105
|
+
*/
|
|
1106
|
+
expandUserPath(inputPath) {
|
|
1107
|
+
if (typeof inputPath !== 'string') {
|
|
1108
|
+
throw new TypeError('Path must be a string.');
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
let expanded = inputPath.trim();
|
|
1112
|
+
|
|
1113
|
+
// Handle tilde expansion
|
|
1114
|
+
if (expanded.startsWith('~')) {
|
|
1115
|
+
if (expanded === '~') {
|
|
1116
|
+
expanded = os.homedir();
|
|
1117
|
+
} else if (expanded.startsWith('~' + path.sep)) {
|
|
1118
|
+
const pathAfterHome = expanded.slice(2); // Remove ~/ or ~\
|
|
1119
|
+
expanded = path.join(os.homedir(), pathAfterHome);
|
|
1120
|
+
} else {
|
|
1121
|
+
const restOfPath = expanded.slice(1);
|
|
1122
|
+
const separatorIndex = restOfPath.indexOf(path.sep);
|
|
1123
|
+
const username = separatorIndex === -1 ? restOfPath : restOfPath.slice(0, separatorIndex);
|
|
1124
|
+
if (username) {
|
|
1125
|
+
throw new Error(`Path expansion for ~${username} is not supported. Please use an absolute path or ~${path.sep}`);
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
// Resolve to the absolute path relative to the current working directory
|
|
1131
|
+
return path.resolve(expanded);
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
/**
|
|
1135
|
+
* Load existing configurations to use as defaults
|
|
1136
|
+
* @param {string} directory - Installation directory
|
|
1137
|
+
* @returns {Object} Existing configurations
|
|
1138
|
+
*/
|
|
1139
|
+
async loadExistingConfigurations(directory) {
|
|
1140
|
+
const configs = {
|
|
1141
|
+
hasCustomContent: false,
|
|
1142
|
+
coreConfig: {},
|
|
1143
|
+
ideConfig: { ides: [], skipIde: false },
|
|
1144
|
+
};
|
|
1145
|
+
|
|
1146
|
+
try {
|
|
1147
|
+
// Load core config
|
|
1148
|
+
configs.coreConfig = await this.collectCoreConfig(directory);
|
|
1149
|
+
|
|
1150
|
+
// Load IDE configuration
|
|
1151
|
+
const configuredIdes = await this.getConfiguredIdes(directory);
|
|
1152
|
+
if (configuredIdes.length > 0) {
|
|
1153
|
+
configs.ideConfig.ides = configuredIdes;
|
|
1154
|
+
configs.ideConfig.skipIde = false;
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
return configs;
|
|
1158
|
+
} catch {
|
|
1159
|
+
// If loading fails, return empty configs
|
|
1160
|
+
console.warn('Warning: Could not load existing configurations');
|
|
1161
|
+
return configs;
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
/**
|
|
1166
|
+
* Get configured IDEs from existing installation
|
|
1167
|
+
* @param {string} directory - Installation directory
|
|
1168
|
+
* @returns {Array} List of configured IDEs
|
|
1169
|
+
*/
|
|
1170
|
+
async getConfiguredIdes(directory) {
|
|
1171
|
+
const { Detector } = require('../installers/lib/core/detector');
|
|
1172
|
+
const { Installer } = require('../installers/lib/core/installer');
|
|
1173
|
+
const detector = new Detector();
|
|
1174
|
+
const installer = new Installer();
|
|
1175
|
+
const bmadResult = await installer.findBmadDir(directory);
|
|
1176
|
+
const existingInstall = await detector.detect(bmadResult.bmadDir);
|
|
1177
|
+
return existingInstall.ides || [];
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
/**
|
|
1181
|
+
* Validate custom content path synchronously
|
|
1182
|
+
* @param {string} input - User input path
|
|
1183
|
+
* @returns {string|undefined} Error message or undefined if valid
|
|
1184
|
+
*/
|
|
1185
|
+
validateCustomContentPathSync(input) {
|
|
1186
|
+
// Allow empty input to cancel
|
|
1187
|
+
if (!input || input.trim() === '') {
|
|
1188
|
+
return; // Allow empty to exit
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
try {
|
|
1192
|
+
// Expand the path
|
|
1193
|
+
const expandedPath = this.expandUserPath(input.trim());
|
|
1194
|
+
|
|
1195
|
+
// Check if path exists
|
|
1196
|
+
if (!fs.pathExistsSync(expandedPath)) {
|
|
1197
|
+
return 'Path does not exist';
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
// Check if it's a directory
|
|
1201
|
+
const stat = fs.statSync(expandedPath);
|
|
1202
|
+
if (!stat.isDirectory()) {
|
|
1203
|
+
return 'Path must be a directory';
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
// Check for module.yaml in the root
|
|
1207
|
+
const moduleYamlPath = path.join(expandedPath, 'module.yaml');
|
|
1208
|
+
if (!fs.pathExistsSync(moduleYamlPath)) {
|
|
1209
|
+
return 'Directory must contain a module.yaml file in the root';
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
// Try to parse the module.yaml to get the module ID
|
|
1213
|
+
try {
|
|
1214
|
+
const yaml = require('yaml');
|
|
1215
|
+
const content = fs.readFileSync(moduleYamlPath, 'utf8');
|
|
1216
|
+
const moduleData = yaml.parse(content);
|
|
1217
|
+
if (!moduleData.code) {
|
|
1218
|
+
return 'module.yaml must contain a "code" field for the module ID';
|
|
1219
|
+
}
|
|
1220
|
+
} catch (error) {
|
|
1221
|
+
return 'Invalid module.yaml file: ' + error.message;
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
return; // Valid
|
|
1225
|
+
} catch (error) {
|
|
1226
|
+
return 'Error validating path: ' + error.message;
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
/**
|
|
1231
|
+
* Prompt user for custom content source location
|
|
1232
|
+
* @returns {Object} Custom content configuration
|
|
1233
|
+
*/
|
|
1234
|
+
async promptCustomContentSource() {
|
|
1235
|
+
const customContentConfig = { hasCustomContent: true, sources: [] };
|
|
1236
|
+
|
|
1237
|
+
// Keep asking for more sources until user is done
|
|
1238
|
+
while (true) {
|
|
1239
|
+
// First ask if user wants to add another module or continue
|
|
1240
|
+
if (customContentConfig.sources.length > 0) {
|
|
1241
|
+
const action = await prompts.select({
|
|
1242
|
+
message: 'Would you like to:',
|
|
1243
|
+
choices: [
|
|
1244
|
+
{ name: 'Add another custom module', value: 'add' },
|
|
1245
|
+
{ name: 'Continue with installation', value: 'continue' },
|
|
1246
|
+
],
|
|
1247
|
+
default: 'continue',
|
|
1248
|
+
});
|
|
1249
|
+
|
|
1250
|
+
if (action === 'continue') {
|
|
1251
|
+
break;
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
let sourcePath;
|
|
1256
|
+
let isValid = false;
|
|
1257
|
+
|
|
1258
|
+
while (!isValid) {
|
|
1259
|
+
// Use sync validation because @clack/prompts doesn't support async validate
|
|
1260
|
+
const inputPath = await prompts.text({
|
|
1261
|
+
message: 'Path to custom module folder (press Enter to skip):',
|
|
1262
|
+
validate: (input) => this.validateCustomContentPathSync(input),
|
|
1263
|
+
});
|
|
1264
|
+
|
|
1265
|
+
// If user pressed Enter without typing anything, exit the loop
|
|
1266
|
+
if (!inputPath || inputPath.trim() === '') {
|
|
1267
|
+
// If we have no modules yet, return false for no custom content
|
|
1268
|
+
if (customContentConfig.sources.length === 0) {
|
|
1269
|
+
return { hasCustomContent: false };
|
|
1270
|
+
}
|
|
1271
|
+
return customContentConfig;
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
sourcePath = this.expandUserPath(inputPath);
|
|
1275
|
+
isValid = true;
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
// Read module.yaml to get module info
|
|
1279
|
+
const yaml = require('yaml');
|
|
1280
|
+
const moduleYamlPath = path.join(sourcePath, 'module.yaml');
|
|
1281
|
+
const moduleContent = await fs.readFile(moduleYamlPath, 'utf8');
|
|
1282
|
+
const moduleData = yaml.parse(moduleContent);
|
|
1283
|
+
|
|
1284
|
+
// Add to sources
|
|
1285
|
+
customContentConfig.sources.push({
|
|
1286
|
+
path: sourcePath,
|
|
1287
|
+
id: moduleData.code,
|
|
1288
|
+
name: moduleData.name || moduleData.code,
|
|
1289
|
+
});
|
|
1290
|
+
|
|
1291
|
+
console.log(chalk.green(`✓ Confirmed local custom module: ${moduleData.name || moduleData.code}`));
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
// Ask if user wants to add these to the installation
|
|
1295
|
+
const shouldInstall = await prompts.confirm({
|
|
1296
|
+
message: `Install these ${customContentConfig.sources.length} custom modules?`,
|
|
1297
|
+
default: true,
|
|
1298
|
+
});
|
|
1299
|
+
|
|
1300
|
+
if (shouldInstall) {
|
|
1301
|
+
customContentConfig.selected = true;
|
|
1302
|
+
// Store paths to module.yaml files, not directories
|
|
1303
|
+
customContentConfig.selectedFiles = customContentConfig.sources.map((s) => path.join(s.path, 'module.yaml'));
|
|
1304
|
+
// Also include module IDs for installation
|
|
1305
|
+
customContentConfig.selectedModuleIds = customContentConfig.sources.map((s) => s.id);
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
return customContentConfig;
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
/**
|
|
1312
|
+
* Handle custom modules in the modify flow
|
|
1313
|
+
* @param {string} directory - Installation directory
|
|
1314
|
+
* @param {Array} selectedModules - Currently selected modules
|
|
1315
|
+
* @returns {Object} Result with selected custom modules and custom content config
|
|
1316
|
+
*/
|
|
1317
|
+
async handleCustomModulesInModifyFlow(directory, selectedModules) {
|
|
1318
|
+
// Get existing installation to find custom modules
|
|
1319
|
+
const { existingInstall } = await this.getExistingInstallation(directory);
|
|
1320
|
+
|
|
1321
|
+
// Check if there are any custom modules in cache
|
|
1322
|
+
const { Installer } = require('../installers/lib/core/installer');
|
|
1323
|
+
const installer = new Installer();
|
|
1324
|
+
const { bmadDir } = await installer.findBmadDir(directory);
|
|
1325
|
+
|
|
1326
|
+
const cacheDir = path.join(bmadDir, '_config', 'custom');
|
|
1327
|
+
const cachedCustomModules = [];
|
|
1328
|
+
|
|
1329
|
+
if (await fs.pathExists(cacheDir)) {
|
|
1330
|
+
const entries = await fs.readdir(cacheDir, { withFileTypes: true });
|
|
1331
|
+
for (const entry of entries) {
|
|
1332
|
+
if (entry.isDirectory()) {
|
|
1333
|
+
const moduleYamlPath = path.join(cacheDir, entry.name, 'module.yaml');
|
|
1334
|
+
if (await fs.pathExists(moduleYamlPath)) {
|
|
1335
|
+
const yaml = require('yaml');
|
|
1336
|
+
const content = await fs.readFile(moduleYamlPath, 'utf8');
|
|
1337
|
+
const moduleData = yaml.parse(content);
|
|
1338
|
+
|
|
1339
|
+
cachedCustomModules.push({
|
|
1340
|
+
id: entry.name,
|
|
1341
|
+
name: moduleData.name || entry.name,
|
|
1342
|
+
description: moduleData.description || 'Custom module from cache',
|
|
1343
|
+
checked: selectedModules.includes(entry.name),
|
|
1344
|
+
fromCache: true,
|
|
1345
|
+
});
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
const result = {
|
|
1352
|
+
selectedCustomModules: [],
|
|
1353
|
+
customContentConfig: { hasCustomContent: false },
|
|
1354
|
+
};
|
|
1355
|
+
|
|
1356
|
+
// Ask user about custom modules
|
|
1357
|
+
console.log(chalk.cyan('\n⚙️ Custom Modules'));
|
|
1358
|
+
if (cachedCustomModules.length > 0) {
|
|
1359
|
+
console.log(chalk.dim('Found custom modules in your installation:'));
|
|
1360
|
+
} else {
|
|
1361
|
+
console.log(chalk.dim('No custom modules currently installed.'));
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
// Build choices dynamically based on whether we have existing modules
|
|
1365
|
+
const choices = [];
|
|
1366
|
+
if (cachedCustomModules.length > 0) {
|
|
1367
|
+
choices.push(
|
|
1368
|
+
{ name: 'Keep all existing custom modules', value: 'keep' },
|
|
1369
|
+
{ name: 'Select which custom modules to keep', value: 'select' },
|
|
1370
|
+
{ name: 'Add new custom modules', value: 'add' },
|
|
1371
|
+
{ name: 'Remove all custom modules', value: 'remove' },
|
|
1372
|
+
);
|
|
1373
|
+
} else {
|
|
1374
|
+
choices.push({ name: 'Add new custom modules', value: 'add' }, { name: 'Cancel (no custom modules)', value: 'cancel' });
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
const customAction = await prompts.select({
|
|
1378
|
+
message: cachedCustomModules.length > 0 ? 'Manage custom modules?' : 'Add custom modules?',
|
|
1379
|
+
choices: choices,
|
|
1380
|
+
default: cachedCustomModules.length > 0 ? 'keep' : 'add',
|
|
1381
|
+
});
|
|
1382
|
+
|
|
1383
|
+
switch (customAction) {
|
|
1384
|
+
case 'keep': {
|
|
1385
|
+
// Keep all existing custom modules
|
|
1386
|
+
result.selectedCustomModules = cachedCustomModules.map((m) => m.id);
|
|
1387
|
+
console.log(chalk.dim(`Keeping ${result.selectedCustomModules.length} custom module(s)`));
|
|
1388
|
+
break;
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
case 'select': {
|
|
1392
|
+
// Let user choose which to keep
|
|
1393
|
+
const selectChoices = cachedCustomModules.map((m) => ({
|
|
1394
|
+
name: `${m.name} ${chalk.gray(`(${m.id})`)}`,
|
|
1395
|
+
value: m.id,
|
|
1396
|
+
checked: m.checked,
|
|
1397
|
+
}));
|
|
1398
|
+
|
|
1399
|
+
// Add "None / I changed my mind" option at the end
|
|
1400
|
+
const choicesWithSkip = [
|
|
1401
|
+
...selectChoices,
|
|
1402
|
+
{
|
|
1403
|
+
name: '⚠ None / I changed my mind - keep no custom modules',
|
|
1404
|
+
value: '__NONE__',
|
|
1405
|
+
checked: false,
|
|
1406
|
+
},
|
|
1407
|
+
];
|
|
1408
|
+
|
|
1409
|
+
const keepModules = await prompts.multiselect({
|
|
1410
|
+
message: `Select custom modules to keep ${chalk.dim('(↑/↓ navigates, SPACE toggles, ENTER to confirm)')}:`,
|
|
1411
|
+
choices: choicesWithSkip,
|
|
1412
|
+
required: true,
|
|
1413
|
+
});
|
|
1414
|
+
|
|
1415
|
+
// If user selected both "__NONE__" and other modules, honor the "None" choice
|
|
1416
|
+
if (keepModules && keepModules.includes('__NONE__') && keepModules.length > 1) {
|
|
1417
|
+
console.log();
|
|
1418
|
+
console.log(chalk.yellow('⚠️ "None / I changed my mind" was selected, so no custom modules will be kept.'));
|
|
1419
|
+
console.log();
|
|
1420
|
+
result.selectedCustomModules = [];
|
|
1421
|
+
} else {
|
|
1422
|
+
// Filter out the special '__NONE__' value
|
|
1423
|
+
result.selectedCustomModules = keepModules ? keepModules.filter((m) => m !== '__NONE__') : [];
|
|
1424
|
+
}
|
|
1425
|
+
break;
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
case 'add': {
|
|
1429
|
+
// By default, keep existing modules when adding new ones
|
|
1430
|
+
// User chose "Add new" not "Replace", so we assume they want to keep existing
|
|
1431
|
+
result.selectedCustomModules = cachedCustomModules.map((m) => m.id);
|
|
1432
|
+
|
|
1433
|
+
// Then prompt for new ones (reuse existing method)
|
|
1434
|
+
const newCustomContent = await this.promptCustomContentSource();
|
|
1435
|
+
if (newCustomContent.hasCustomContent && newCustomContent.selected) {
|
|
1436
|
+
result.selectedCustomModules.push(...newCustomContent.selectedModuleIds);
|
|
1437
|
+
result.customContentConfig = newCustomContent;
|
|
1438
|
+
}
|
|
1439
|
+
break;
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
case 'remove': {
|
|
1443
|
+
// Remove all custom modules
|
|
1444
|
+
console.log(chalk.yellow('All custom modules will be removed from the installation'));
|
|
1445
|
+
break;
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
case 'cancel': {
|
|
1449
|
+
// User cancelled - no custom modules
|
|
1450
|
+
console.log(chalk.dim('No custom modules will be added'));
|
|
1451
|
+
break;
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
return result;
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
/**
|
|
1459
|
+
* Parse alpha version string (e.g., "6.0.0-Alpha.20")
|
|
1460
|
+
* @param {string} version - Version string
|
|
1461
|
+
* @returns {Object|null} Object with alphaNumber and fullVersion, or null if invalid
|
|
1462
|
+
*/
|
|
1463
|
+
parseAlphaVersion(version) {
|
|
1464
|
+
if (!version || version === 'unknown') {
|
|
1465
|
+
return null;
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
// Remove 'v' prefix if present
|
|
1469
|
+
const cleanVersion = version.toString().replace(/^v/i, '');
|
|
1470
|
+
|
|
1471
|
+
// Match alpha version pattern: X.Y.Z-Alpha.N (case-insensitive)
|
|
1472
|
+
const match = cleanVersion.match(/[\d.]+-Alpha\.(\d+)/i);
|
|
1473
|
+
|
|
1474
|
+
if (!match) {
|
|
1475
|
+
return null;
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
return {
|
|
1479
|
+
alphaNumber: parseInt(match[1], 10),
|
|
1480
|
+
fullVersion: cleanVersion,
|
|
1481
|
+
};
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
/**
|
|
1485
|
+
* Check if installed version is more than 2 alpha versions behind current
|
|
1486
|
+
* @param {string} installedVersion - The installed version
|
|
1487
|
+
* @param {string} currentVersion - The current version
|
|
1488
|
+
* @returns {Object} Object with { isOldVersion, versionDiff, shouldWarn, installed, current }
|
|
1489
|
+
*/
|
|
1490
|
+
checkAlphaVersionAge(installedVersion, currentVersion) {
|
|
1491
|
+
const installed = this.parseAlphaVersion(installedVersion);
|
|
1492
|
+
const current = this.parseAlphaVersion(currentVersion);
|
|
1493
|
+
|
|
1494
|
+
// If we can't parse either version, don't warn
|
|
1495
|
+
if (!installed || !current) {
|
|
1496
|
+
return { isOldVersion: false, versionDiff: 0, shouldWarn: false };
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
// Calculate alpha version difference
|
|
1500
|
+
const versionDiff = current.alphaNumber - installed.alphaNumber;
|
|
1501
|
+
|
|
1502
|
+
// Consider it old if more than 2 versions behind
|
|
1503
|
+
const isOldVersion = versionDiff > 2;
|
|
1504
|
+
|
|
1505
|
+
return {
|
|
1506
|
+
isOldVersion,
|
|
1507
|
+
versionDiff,
|
|
1508
|
+
shouldWarn: isOldVersion,
|
|
1509
|
+
installed: installed.fullVersion,
|
|
1510
|
+
current: current.fullVersion,
|
|
1511
|
+
installedAlpha: installed.alphaNumber,
|
|
1512
|
+
currentAlpha: current.alphaNumber,
|
|
1513
|
+
};
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
/**
|
|
1517
|
+
* Show warning for old alpha version and ask if user wants to proceed
|
|
1518
|
+
* @param {string} installedVersion - The installed version
|
|
1519
|
+
* @param {string} currentVersion - The current version
|
|
1520
|
+
* @param {string} bmadFolderName - Name of the BMAD folder
|
|
1521
|
+
* @returns {Promise<boolean>} True if user wants to proceed, false if they cancel
|
|
1522
|
+
*/
|
|
1523
|
+
async showOldAlphaVersionWarning(installedVersion, currentVersion, bmadFolderName) {
|
|
1524
|
+
const versionInfo = this.checkAlphaVersionAge(installedVersion, currentVersion);
|
|
1525
|
+
|
|
1526
|
+
// Also warn if version is unknown or can't be parsed (legacy/unsupported)
|
|
1527
|
+
const isUnknownVersion = installedVersion === 'unknown' || !versionInfo.installed;
|
|
1528
|
+
|
|
1529
|
+
if (!versionInfo.shouldWarn && !isUnknownVersion) {
|
|
1530
|
+
return true; // Not old, proceed
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
console.log('');
|
|
1534
|
+
console.log(chalk.yellow.bold('⚠️ VERSION WARNING'));
|
|
1535
|
+
console.log(chalk.yellow('─'.repeat(80)));
|
|
1536
|
+
|
|
1537
|
+
if (isUnknownVersion) {
|
|
1538
|
+
console.log(chalk.yellow('Unable to detect your installed BMAD version.'));
|
|
1539
|
+
console.log(chalk.yellow('This appears to be a legacy or unsupported installation.'));
|
|
1540
|
+
console.log('');
|
|
1541
|
+
console.log(chalk.dim('For stability, we only support updates from the previous 2 alpha versions.'));
|
|
1542
|
+
console.log(chalk.dim('Legacy installations may have compatibility issues.'));
|
|
1543
|
+
} else {
|
|
1544
|
+
console.log(chalk.yellow(`You are updating from ${versionInfo.installed} to ${versionInfo.current}.`));
|
|
1545
|
+
console.log(chalk.yellow(`This is ${versionInfo.versionDiff} alpha versions behind.`));
|
|
1546
|
+
console.log('');
|
|
1547
|
+
console.log(chalk.dim(`For stability, we only support updates from the previous 2 alpha versions`));
|
|
1548
|
+
console.log(chalk.dim(`(Alpha.${versionInfo.currentAlpha - 2} through Alpha.${versionInfo.currentAlpha - 1}).`));
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
console.log('');
|
|
1552
|
+
console.log(chalk.dim('For the best experience, we recommend:'));
|
|
1553
|
+
console.log(chalk.dim(' 1. Delete your current BMAD installation folder'));
|
|
1554
|
+
console.log(chalk.dim(` (the "${bmadFolderName}/" folder in your project)`));
|
|
1555
|
+
console.log(chalk.dim(' 2. Run a fresh installation'));
|
|
1556
|
+
console.log('');
|
|
1557
|
+
console.log(chalk.dim('Benefits of a fresh install:'));
|
|
1558
|
+
console.log(chalk.dim(' • Cleaner configuration without legacy artifacts'));
|
|
1559
|
+
console.log(chalk.dim(' • All new features properly configured'));
|
|
1560
|
+
console.log(chalk.dim(' • Fewer potential conflicts'));
|
|
1561
|
+
console.log(chalk.yellow('─'.repeat(80)));
|
|
1562
|
+
console.log('');
|
|
1563
|
+
|
|
1564
|
+
const proceed = await prompts.select({
|
|
1565
|
+
message: 'How would you like to proceed?',
|
|
1566
|
+
choices: [
|
|
1567
|
+
{
|
|
1568
|
+
name: 'Proceed with update anyway (may have issues)',
|
|
1569
|
+
value: 'proceed',
|
|
1570
|
+
},
|
|
1571
|
+
{
|
|
1572
|
+
name: 'Cancel (recommended - do a fresh install instead)',
|
|
1573
|
+
value: 'cancel',
|
|
1574
|
+
},
|
|
1575
|
+
],
|
|
1576
|
+
default: 'cancel',
|
|
1577
|
+
});
|
|
1578
|
+
|
|
1579
|
+
if (proceed === 'cancel') {
|
|
1580
|
+
console.log('');
|
|
1581
|
+
console.log(chalk.cyan('To do a fresh install:'));
|
|
1582
|
+
console.log(chalk.dim(` 1. Delete the "${bmadFolderName}/" folder in your project`));
|
|
1583
|
+
console.log(chalk.dim(" 2. Run 'bmad install' again"));
|
|
1584
|
+
console.log('');
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
return proceed === 'proceed';
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
module.exports = { UI };
|