bmad-fh 6.0.0-alpha.23
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 +50 -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 +411 -0
- package/src/core/lib/scope/index.js +30 -0
- package/src/core/lib/scope/scope-context.js +307 -0
- package/src/core/lib/scope/scope-initializer.js +458 -0
- package/src/core/lib/scope/scope-manager.js +512 -0
- package/src/core/lib/scope/scope-migrator.js +442 -0
- package/src/core/lib/scope/scope-sync.js +489 -0
- package/src/core/lib/scope/scope-validator.js +299 -0
- package/src/core/lib/scope/state-lock.js +342 -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 +450 -0
- package/test/test-scope-system.js +787 -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 +281 -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,1079 @@
|
|
|
1
|
+
const path = require('node:path');
|
|
2
|
+
const fs = require('fs-extra');
|
|
3
|
+
const yaml = require('yaml');
|
|
4
|
+
const chalk = require('chalk');
|
|
5
|
+
const { getProjectRoot, getModulePath } = require('../../../lib/project-root');
|
|
6
|
+
const { CLIUtils } = require('../../../lib/cli-utils');
|
|
7
|
+
const prompts = require('../../../lib/prompts');
|
|
8
|
+
|
|
9
|
+
class ConfigCollector {
|
|
10
|
+
constructor() {
|
|
11
|
+
this.collectedConfig = {};
|
|
12
|
+
this.existingConfig = null;
|
|
13
|
+
this.currentProjectDir = null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Find the bmad installation directory in a project
|
|
18
|
+
* V6+ installations can use ANY folder name but ALWAYS have _config/manifest.yaml
|
|
19
|
+
* @param {string} projectDir - Project directory
|
|
20
|
+
* @returns {Promise<string>} Path to bmad directory
|
|
21
|
+
*/
|
|
22
|
+
async findBmadDir(projectDir) {
|
|
23
|
+
// Check if project directory exists
|
|
24
|
+
if (!(await fs.pathExists(projectDir))) {
|
|
25
|
+
// Project doesn't exist yet, return default
|
|
26
|
+
return path.join(projectDir, 'bmad');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// V6+ strategy: Look for ANY directory with _config/manifest.yaml
|
|
30
|
+
// This is the definitive marker of a V6+ installation
|
|
31
|
+
try {
|
|
32
|
+
const entries = await fs.readdir(projectDir, { withFileTypes: true });
|
|
33
|
+
for (const entry of entries) {
|
|
34
|
+
if (entry.isDirectory()) {
|
|
35
|
+
const manifestPath = path.join(projectDir, entry.name, '_config', 'manifest.yaml');
|
|
36
|
+
if (await fs.pathExists(manifestPath)) {
|
|
37
|
+
// Found a V6+ installation
|
|
38
|
+
return path.join(projectDir, entry.name);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
} catch {
|
|
43
|
+
// Ignore errors, fall through to default
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// No V6+ installation found, return default
|
|
47
|
+
// This will be used for new installations
|
|
48
|
+
return path.join(projectDir, 'bmad');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Detect the existing BMAD folder name in a project
|
|
53
|
+
* @param {string} projectDir - Project directory
|
|
54
|
+
* @returns {Promise<string|null>} Folder name (just the name, not full path) or null if not found
|
|
55
|
+
*/
|
|
56
|
+
async detectExistingBmadFolder(projectDir) {
|
|
57
|
+
// Check if project directory exists
|
|
58
|
+
if (!(await fs.pathExists(projectDir))) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Look for ANY directory with _config/manifest.yaml
|
|
63
|
+
try {
|
|
64
|
+
const entries = await fs.readdir(projectDir, { withFileTypes: true });
|
|
65
|
+
for (const entry of entries) {
|
|
66
|
+
if (entry.isDirectory()) {
|
|
67
|
+
const manifestPath = path.join(projectDir, entry.name, '_config', 'manifest.yaml');
|
|
68
|
+
if (await fs.pathExists(manifestPath)) {
|
|
69
|
+
// Found a V6+ installation, return just the folder name
|
|
70
|
+
return entry.name;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
} catch {
|
|
75
|
+
// Ignore errors
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Load existing config if it exists from module config files
|
|
83
|
+
* @param {string} projectDir - Target project directory
|
|
84
|
+
*/
|
|
85
|
+
async loadExistingConfig(projectDir) {
|
|
86
|
+
this.existingConfig = {};
|
|
87
|
+
|
|
88
|
+
// Check if project directory exists first
|
|
89
|
+
if (!(await fs.pathExists(projectDir))) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Find the actual bmad directory (handles custom folder names)
|
|
94
|
+
const bmadDir = await this.findBmadDir(projectDir);
|
|
95
|
+
|
|
96
|
+
// Check if bmad directory exists
|
|
97
|
+
if (!(await fs.pathExists(bmadDir))) {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Dynamically discover all installed modules by scanning bmad directory
|
|
102
|
+
// A directory is a module ONLY if it contains a config.yaml file
|
|
103
|
+
let foundAny = false;
|
|
104
|
+
const entries = await fs.readdir(bmadDir, { withFileTypes: true });
|
|
105
|
+
|
|
106
|
+
for (const entry of entries) {
|
|
107
|
+
if (entry.isDirectory()) {
|
|
108
|
+
// Skip the _config directory - it's for system use
|
|
109
|
+
if (entry.name === '_config' || entry.name === '_memory') {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const moduleConfigPath = path.join(bmadDir, entry.name, 'config.yaml');
|
|
114
|
+
|
|
115
|
+
if (await fs.pathExists(moduleConfigPath)) {
|
|
116
|
+
try {
|
|
117
|
+
const content = await fs.readFile(moduleConfigPath, 'utf8');
|
|
118
|
+
const moduleConfig = yaml.parse(content);
|
|
119
|
+
if (moduleConfig) {
|
|
120
|
+
this.existingConfig[entry.name] = moduleConfig;
|
|
121
|
+
foundAny = true;
|
|
122
|
+
}
|
|
123
|
+
} catch {
|
|
124
|
+
// Ignore parse errors for individual modules
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return foundAny;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Collect configuration for all modules
|
|
135
|
+
* @param {Array} modules - List of modules to configure (including 'core')
|
|
136
|
+
* @param {string} projectDir - Target project directory
|
|
137
|
+
* @param {Object} options - Additional options
|
|
138
|
+
* @param {Map} options.customModulePaths - Map of module ID to source path for custom modules
|
|
139
|
+
*/
|
|
140
|
+
async collectAllConfigurations(modules, projectDir, options = {}) {
|
|
141
|
+
// Store custom module paths for use in collectModuleConfig
|
|
142
|
+
this.customModulePaths = options.customModulePaths || new Map();
|
|
143
|
+
await this.loadExistingConfig(projectDir);
|
|
144
|
+
|
|
145
|
+
// Check if core was already collected (e.g., in early collection phase)
|
|
146
|
+
const coreAlreadyCollected = this.collectedConfig.core && Object.keys(this.collectedConfig.core).length > 0;
|
|
147
|
+
|
|
148
|
+
// If core wasn't already collected, include it
|
|
149
|
+
const allModules = coreAlreadyCollected ? modules.filter((m) => m !== 'core') : ['core', ...modules.filter((m) => m !== 'core')];
|
|
150
|
+
|
|
151
|
+
// Store all answers across modules for cross-referencing
|
|
152
|
+
if (!this.allAnswers) {
|
|
153
|
+
this.allAnswers = {};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
for (const moduleName of allModules) {
|
|
157
|
+
await this.collectModuleConfig(moduleName, projectDir);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Add metadata
|
|
161
|
+
this.collectedConfig._meta = {
|
|
162
|
+
version: require(path.join(getProjectRoot(), 'package.json')).version,
|
|
163
|
+
installDate: new Date().toISOString(),
|
|
164
|
+
lastModified: new Date().toISOString(),
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
return this.collectedConfig;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Collect configuration for a single module (Quick Update mode - only new fields)
|
|
172
|
+
* @param {string} moduleName - Module name
|
|
173
|
+
* @param {string} projectDir - Target project directory
|
|
174
|
+
* @param {boolean} silentMode - If true, only prompt for new/missing fields
|
|
175
|
+
* @returns {boolean} True if new fields were prompted, false if all fields existed
|
|
176
|
+
*/
|
|
177
|
+
async collectModuleConfigQuick(moduleName, projectDir, silentMode = true) {
|
|
178
|
+
this.currentProjectDir = projectDir;
|
|
179
|
+
|
|
180
|
+
// Load existing config if not already loaded
|
|
181
|
+
if (!this.existingConfig) {
|
|
182
|
+
await this.loadExistingConfig(projectDir);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Initialize allAnswers if not already initialized
|
|
186
|
+
if (!this.allAnswers) {
|
|
187
|
+
this.allAnswers = {};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Load module's install config schema
|
|
191
|
+
// First, try the standard src/modules location
|
|
192
|
+
let installerConfigPath = path.join(getModulePath(moduleName), '_module-installer', 'module.yaml');
|
|
193
|
+
let moduleConfigPath = path.join(getModulePath(moduleName), 'module.yaml');
|
|
194
|
+
|
|
195
|
+
// If not found in src/modules, we need to find it by searching the project
|
|
196
|
+
if (!(await fs.pathExists(installerConfigPath)) && !(await fs.pathExists(moduleConfigPath))) {
|
|
197
|
+
// Use the module manager to find the module source
|
|
198
|
+
const { ModuleManager } = require('../modules/manager');
|
|
199
|
+
const moduleManager = new ModuleManager();
|
|
200
|
+
const moduleSourcePath = await moduleManager.findModuleSource(moduleName);
|
|
201
|
+
|
|
202
|
+
if (moduleSourcePath) {
|
|
203
|
+
installerConfigPath = path.join(moduleSourcePath, '_module-installer', 'module.yaml');
|
|
204
|
+
moduleConfigPath = path.join(moduleSourcePath, 'module.yaml');
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
let configPath = null;
|
|
209
|
+
let isCustomModule = false;
|
|
210
|
+
|
|
211
|
+
if (await fs.pathExists(moduleConfigPath)) {
|
|
212
|
+
configPath = moduleConfigPath;
|
|
213
|
+
} else if (await fs.pathExists(installerConfigPath)) {
|
|
214
|
+
configPath = installerConfigPath;
|
|
215
|
+
} else {
|
|
216
|
+
// Check if this is a custom module with custom.yaml
|
|
217
|
+
const { ModuleManager } = require('../modules/manager');
|
|
218
|
+
const moduleManager = new ModuleManager();
|
|
219
|
+
const moduleSourcePath = await moduleManager.findModuleSource(moduleName);
|
|
220
|
+
|
|
221
|
+
if (moduleSourcePath) {
|
|
222
|
+
const rootCustomConfigPath = path.join(moduleSourcePath, 'custom.yaml');
|
|
223
|
+
const moduleInstallerCustomPath = path.join(moduleSourcePath, '_module-installer', 'custom.yaml');
|
|
224
|
+
|
|
225
|
+
if ((await fs.pathExists(rootCustomConfigPath)) || (await fs.pathExists(moduleInstallerCustomPath))) {
|
|
226
|
+
isCustomModule = true;
|
|
227
|
+
// For custom modules, we don't have an install-config schema, so just use existing values
|
|
228
|
+
// The custom.yaml values will be loaded and merged during installation
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// No config schema for this module - use existing values
|
|
233
|
+
if (this.existingConfig && this.existingConfig[moduleName]) {
|
|
234
|
+
if (!this.collectedConfig[moduleName]) {
|
|
235
|
+
this.collectedConfig[moduleName] = {};
|
|
236
|
+
}
|
|
237
|
+
this.collectedConfig[moduleName] = { ...this.existingConfig[moduleName] };
|
|
238
|
+
}
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const configContent = await fs.readFile(configPath, 'utf8');
|
|
243
|
+
const moduleConfig = yaml.parse(configContent);
|
|
244
|
+
|
|
245
|
+
if (!moduleConfig) {
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Compare schema with existing config to find new/missing fields
|
|
250
|
+
const configKeys = Object.keys(moduleConfig).filter((key) => key !== 'prompt');
|
|
251
|
+
const existingKeys = this.existingConfig && this.existingConfig[moduleName] ? Object.keys(this.existingConfig[moduleName]) : [];
|
|
252
|
+
|
|
253
|
+
// Check if this module has no configuration keys at all (like CIS)
|
|
254
|
+
// Filter out metadata fields and only count actual config objects
|
|
255
|
+
const metadataFields = new Set(['code', 'name', 'header', 'subheader', 'default_selected']);
|
|
256
|
+
const actualConfigKeys = configKeys.filter((key) => !metadataFields.has(key));
|
|
257
|
+
const hasNoConfig = actualConfigKeys.length === 0;
|
|
258
|
+
|
|
259
|
+
// If module has no config keys at all, handle it specially
|
|
260
|
+
if (hasNoConfig && moduleConfig.subheader) {
|
|
261
|
+
// Add blank line for better readability (matches other modules)
|
|
262
|
+
console.log();
|
|
263
|
+
const moduleDisplayName = moduleConfig.header || `${moduleName.toUpperCase()} Module`;
|
|
264
|
+
|
|
265
|
+
// Display the module name in color first (matches other modules)
|
|
266
|
+
console.log(chalk.cyan('?') + ' ' + chalk.magenta(moduleDisplayName));
|
|
267
|
+
|
|
268
|
+
// Show the subheader since there's no configuration to ask about
|
|
269
|
+
console.log(chalk.dim(` ✓ ${moduleConfig.subheader}`));
|
|
270
|
+
return false; // No new fields
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Find new interactive fields (with prompt)
|
|
274
|
+
const newKeys = configKeys.filter((key) => {
|
|
275
|
+
const item = moduleConfig[key];
|
|
276
|
+
// Check if it's a config item and doesn't exist in existing config
|
|
277
|
+
return item && typeof item === 'object' && item.prompt && !existingKeys.includes(key);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
// Find new static fields (without prompt, just result)
|
|
281
|
+
const newStaticKeys = configKeys.filter((key) => {
|
|
282
|
+
const item = moduleConfig[key];
|
|
283
|
+
return item && typeof item === 'object' && !item.prompt && item.result && !existingKeys.includes(key);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
// If in silent mode and no new keys (neither interactive nor static), use existing config and skip prompts
|
|
287
|
+
if (silentMode && newKeys.length === 0 && newStaticKeys.length === 0) {
|
|
288
|
+
if (this.existingConfig && this.existingConfig[moduleName]) {
|
|
289
|
+
if (!this.collectedConfig[moduleName]) {
|
|
290
|
+
this.collectedConfig[moduleName] = {};
|
|
291
|
+
}
|
|
292
|
+
this.collectedConfig[moduleName] = { ...this.existingConfig[moduleName] };
|
|
293
|
+
|
|
294
|
+
// Special handling for user_name: ensure it has a value
|
|
295
|
+
if (
|
|
296
|
+
moduleName === 'core' &&
|
|
297
|
+
(!this.collectedConfig[moduleName].user_name || this.collectedConfig[moduleName].user_name === '[USER_NAME]')
|
|
298
|
+
) {
|
|
299
|
+
this.collectedConfig[moduleName].user_name = this.getDefaultUsername();
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Also populate allAnswers for cross-referencing
|
|
303
|
+
for (const [key, value] of Object.entries(this.existingConfig[moduleName])) {
|
|
304
|
+
// Ensure user_name is properly set in allAnswers too
|
|
305
|
+
let finalValue = value;
|
|
306
|
+
if (moduleName === 'core' && key === 'user_name' && (!value || value === '[USER_NAME]')) {
|
|
307
|
+
finalValue = this.getDefaultUsername();
|
|
308
|
+
}
|
|
309
|
+
this.allAnswers[`${moduleName}_${key}`] = finalValue;
|
|
310
|
+
}
|
|
311
|
+
} else if (moduleName === 'core') {
|
|
312
|
+
// No existing core config - ensure we at least have user_name
|
|
313
|
+
if (!this.collectedConfig[moduleName]) {
|
|
314
|
+
this.collectedConfig[moduleName] = {};
|
|
315
|
+
}
|
|
316
|
+
if (!this.collectedConfig[moduleName].user_name) {
|
|
317
|
+
this.collectedConfig[moduleName].user_name = this.getDefaultUsername();
|
|
318
|
+
this.allAnswers[`${moduleName}_user_name`] = this.getDefaultUsername();
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Show "no config" message for modules with no new questions (that have config keys)
|
|
323
|
+
console.log(chalk.dim(` ✓ ${moduleName.toUpperCase()} module already up to date`));
|
|
324
|
+
return false; // No new fields
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// If we have new fields (interactive or static), process them
|
|
328
|
+
if (newKeys.length > 0 || newStaticKeys.length > 0) {
|
|
329
|
+
const questions = [];
|
|
330
|
+
const staticAnswers = {};
|
|
331
|
+
|
|
332
|
+
// Build questions for interactive fields
|
|
333
|
+
for (const key of newKeys) {
|
|
334
|
+
const item = moduleConfig[key];
|
|
335
|
+
const question = await this.buildQuestion(moduleName, key, item, moduleConfig);
|
|
336
|
+
if (question) {
|
|
337
|
+
questions.push(question);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Prepare static answers (no prompt, just result)
|
|
342
|
+
for (const key of newStaticKeys) {
|
|
343
|
+
staticAnswers[`${moduleName}_${key}`] = undefined;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Collect all answers (static + prompted)
|
|
347
|
+
let allAnswers = { ...staticAnswers };
|
|
348
|
+
|
|
349
|
+
if (questions.length > 0) {
|
|
350
|
+
// Only show header if we actually have questions
|
|
351
|
+
CLIUtils.displayModuleConfigHeader(moduleName, moduleConfig.header, moduleConfig.subheader);
|
|
352
|
+
console.log(); // Line break before questions
|
|
353
|
+
const promptedAnswers = await prompts.prompt(questions);
|
|
354
|
+
|
|
355
|
+
// Merge prompted answers with static answers
|
|
356
|
+
Object.assign(allAnswers, promptedAnswers);
|
|
357
|
+
} else if (newStaticKeys.length > 0) {
|
|
358
|
+
// Only static fields, no questions - show no config message
|
|
359
|
+
console.log(chalk.dim(` ✓ ${moduleName.toUpperCase()} module configuration updated`));
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Store all answers for cross-referencing
|
|
363
|
+
Object.assign(this.allAnswers, allAnswers);
|
|
364
|
+
|
|
365
|
+
// Process all answers (both static and prompted)
|
|
366
|
+
// First, copy existing config to preserve values that aren't being updated
|
|
367
|
+
if (this.existingConfig && this.existingConfig[moduleName]) {
|
|
368
|
+
this.collectedConfig[moduleName] = { ...this.existingConfig[moduleName] };
|
|
369
|
+
} else {
|
|
370
|
+
this.collectedConfig[moduleName] = {};
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
for (const key of Object.keys(allAnswers)) {
|
|
374
|
+
const originalKey = key.replace(`${moduleName}_`, '');
|
|
375
|
+
const item = moduleConfig[originalKey];
|
|
376
|
+
const value = allAnswers[key];
|
|
377
|
+
|
|
378
|
+
let result;
|
|
379
|
+
if (Array.isArray(value)) {
|
|
380
|
+
result = value;
|
|
381
|
+
} else if (item.result) {
|
|
382
|
+
result = this.processResultTemplate(item.result, value);
|
|
383
|
+
} else {
|
|
384
|
+
result = value;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Update the collected config with new/updated values
|
|
388
|
+
this.collectedConfig[moduleName][originalKey] = result;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Copy over existing values for fields that weren't prompted
|
|
393
|
+
if (this.existingConfig && this.existingConfig[moduleName]) {
|
|
394
|
+
if (!this.collectedConfig[moduleName]) {
|
|
395
|
+
this.collectedConfig[moduleName] = {};
|
|
396
|
+
}
|
|
397
|
+
for (const [key, value] of Object.entries(this.existingConfig[moduleName])) {
|
|
398
|
+
if (!this.collectedConfig[moduleName][key]) {
|
|
399
|
+
this.collectedConfig[moduleName][key] = value;
|
|
400
|
+
this.allAnswers[`${moduleName}_${key}`] = value;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return newKeys.length > 0 || newStaticKeys.length > 0; // Return true if we had any new fields (interactive or static)
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Process a result template with value substitution
|
|
410
|
+
* @param {*} resultTemplate - The result template
|
|
411
|
+
* @param {*} value - The value to substitute
|
|
412
|
+
* @returns {*} Processed result
|
|
413
|
+
*/
|
|
414
|
+
processResultTemplate(resultTemplate, value) {
|
|
415
|
+
let result = resultTemplate;
|
|
416
|
+
|
|
417
|
+
if (typeof result === 'string' && value !== undefined) {
|
|
418
|
+
if (typeof value === 'string') {
|
|
419
|
+
result = result.replace('{value}', value);
|
|
420
|
+
} else if (typeof value === 'boolean' || typeof value === 'number') {
|
|
421
|
+
if (result === '{value}') {
|
|
422
|
+
result = value;
|
|
423
|
+
} else {
|
|
424
|
+
result = result.replace('{value}', value);
|
|
425
|
+
}
|
|
426
|
+
} else {
|
|
427
|
+
result = value;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
if (typeof result === 'string') {
|
|
431
|
+
result = result.replaceAll(/{([^}]+)}/g, (match, configKey) => {
|
|
432
|
+
if (configKey === 'project-root') {
|
|
433
|
+
return '{project-root}';
|
|
434
|
+
}
|
|
435
|
+
if (configKey === 'value') {
|
|
436
|
+
return match;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
let configValue = this.allAnswers[configKey] || this.allAnswers[`${configKey}`];
|
|
440
|
+
if (!configValue) {
|
|
441
|
+
for (const [answerKey, answerValue] of Object.entries(this.allAnswers)) {
|
|
442
|
+
if (answerKey.endsWith(`_${configKey}`)) {
|
|
443
|
+
configValue = answerValue;
|
|
444
|
+
break;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
if (!configValue) {
|
|
450
|
+
for (const mod of Object.keys(this.collectedConfig)) {
|
|
451
|
+
if (mod !== '_meta' && this.collectedConfig[mod] && this.collectedConfig[mod][configKey]) {
|
|
452
|
+
configValue = this.collectedConfig[mod][configKey];
|
|
453
|
+
if (typeof configValue === 'string' && configValue.includes('{project-root}/')) {
|
|
454
|
+
configValue = configValue.replace('{project-root}/', '');
|
|
455
|
+
}
|
|
456
|
+
break;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
return configValue || match;
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
return result;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Get the default username from the system
|
|
471
|
+
* @returns {string} Capitalized username\
|
|
472
|
+
*/
|
|
473
|
+
getDefaultUsername() {
|
|
474
|
+
let result = 'BMad';
|
|
475
|
+
try {
|
|
476
|
+
const os = require('node:os');
|
|
477
|
+
const userInfo = os.userInfo();
|
|
478
|
+
if (userInfo && userInfo.username) {
|
|
479
|
+
const username = userInfo.username;
|
|
480
|
+
result = username.charAt(0).toUpperCase() + username.slice(1);
|
|
481
|
+
}
|
|
482
|
+
} catch {
|
|
483
|
+
// Do nothing, just return 'BMad'
|
|
484
|
+
}
|
|
485
|
+
return result;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Collect configuration for a single module
|
|
490
|
+
* @param {string} moduleName - Module name
|
|
491
|
+
* @param {string} projectDir - Target project directory
|
|
492
|
+
* @param {boolean} skipLoadExisting - Skip loading existing config (for early core collection)
|
|
493
|
+
* @param {boolean} skipCompletion - Skip showing completion message (for early core collection)
|
|
494
|
+
*/
|
|
495
|
+
async collectModuleConfig(moduleName, projectDir, skipLoadExisting = false, skipCompletion = false) {
|
|
496
|
+
this.currentProjectDir = projectDir;
|
|
497
|
+
// Load existing config if needed and not already loaded
|
|
498
|
+
if (!skipLoadExisting && !this.existingConfig) {
|
|
499
|
+
await this.loadExistingConfig(projectDir);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// Initialize allAnswers if not already initialized
|
|
503
|
+
if (!this.allAnswers) {
|
|
504
|
+
this.allAnswers = {};
|
|
505
|
+
}
|
|
506
|
+
// Load module's config
|
|
507
|
+
// First, check if we have a custom module path for this module
|
|
508
|
+
let installerConfigPath = null;
|
|
509
|
+
let moduleConfigPath = null;
|
|
510
|
+
|
|
511
|
+
if (this.customModulePaths && this.customModulePaths.has(moduleName)) {
|
|
512
|
+
const customPath = this.customModulePaths.get(moduleName);
|
|
513
|
+
installerConfigPath = path.join(customPath, '_module-installer', 'module.yaml');
|
|
514
|
+
moduleConfigPath = path.join(customPath, 'module.yaml');
|
|
515
|
+
} else {
|
|
516
|
+
// Try the standard src/modules location
|
|
517
|
+
installerConfigPath = path.join(getModulePath(moduleName), '_module-installer', 'module.yaml');
|
|
518
|
+
moduleConfigPath = path.join(getModulePath(moduleName), 'module.yaml');
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// If not found in src/modules or custom paths, search the project
|
|
522
|
+
if (!(await fs.pathExists(installerConfigPath)) && !(await fs.pathExists(moduleConfigPath))) {
|
|
523
|
+
// Use the module manager to find the module source
|
|
524
|
+
const { ModuleManager } = require('../modules/manager');
|
|
525
|
+
const moduleManager = new ModuleManager();
|
|
526
|
+
const moduleSourcePath = await moduleManager.findModuleSource(moduleName);
|
|
527
|
+
|
|
528
|
+
if (moduleSourcePath) {
|
|
529
|
+
installerConfigPath = path.join(moduleSourcePath, '_module-installer', 'module.yaml');
|
|
530
|
+
moduleConfigPath = path.join(moduleSourcePath, 'module.yaml');
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
let configPath = null;
|
|
535
|
+
if (await fs.pathExists(moduleConfigPath)) {
|
|
536
|
+
configPath = moduleConfigPath;
|
|
537
|
+
} else if (await fs.pathExists(installerConfigPath)) {
|
|
538
|
+
configPath = installerConfigPath;
|
|
539
|
+
} else {
|
|
540
|
+
// No config for this module
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
const configContent = await fs.readFile(configPath, 'utf8');
|
|
545
|
+
const moduleConfig = yaml.parse(configContent);
|
|
546
|
+
|
|
547
|
+
if (!moduleConfig) {
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// Process each config item
|
|
552
|
+
const questions = [];
|
|
553
|
+
const staticAnswers = {};
|
|
554
|
+
const configKeys = Object.keys(moduleConfig).filter((key) => key !== 'prompt');
|
|
555
|
+
|
|
556
|
+
for (const key of configKeys) {
|
|
557
|
+
const item = moduleConfig[key];
|
|
558
|
+
|
|
559
|
+
// Skip if not a config object
|
|
560
|
+
if (!item || typeof item !== 'object') {
|
|
561
|
+
continue;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// Handle static values (no prompt, just result)
|
|
565
|
+
if (!item.prompt && item.result) {
|
|
566
|
+
// Add to static answers with a marker value
|
|
567
|
+
staticAnswers[`${moduleName}_${key}`] = undefined;
|
|
568
|
+
continue;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// Handle interactive values (with prompt)
|
|
572
|
+
if (item.prompt) {
|
|
573
|
+
const question = await this.buildQuestion(moduleName, key, item, moduleConfig);
|
|
574
|
+
if (question) {
|
|
575
|
+
questions.push(question);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// Collect all answers (static + prompted)
|
|
581
|
+
let allAnswers = { ...staticAnswers };
|
|
582
|
+
|
|
583
|
+
// If there are questions to ask, prompt for accepting defaults vs customizing
|
|
584
|
+
if (questions.length > 0) {
|
|
585
|
+
const moduleDisplayName = moduleConfig.header || `${moduleName.toUpperCase()} Module`;
|
|
586
|
+
console.log();
|
|
587
|
+
console.log(chalk.cyan('?') + ' ' + chalk.magenta(moduleDisplayName));
|
|
588
|
+
let customize = true;
|
|
589
|
+
if (moduleName !== 'core') {
|
|
590
|
+
const customizeAnswer = await prompts.prompt([
|
|
591
|
+
{
|
|
592
|
+
type: 'confirm',
|
|
593
|
+
name: 'customize',
|
|
594
|
+
message: 'Accept Defaults (no to customize)?',
|
|
595
|
+
default: true,
|
|
596
|
+
},
|
|
597
|
+
]);
|
|
598
|
+
customize = customizeAnswer.customize;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
if (customize && moduleName !== 'core') {
|
|
602
|
+
// Accept defaults - only ask questions that have NO default value
|
|
603
|
+
const questionsWithoutDefaults = questions.filter((q) => q.default === undefined || q.default === null || q.default === '');
|
|
604
|
+
|
|
605
|
+
if (questionsWithoutDefaults.length > 0) {
|
|
606
|
+
console.log(chalk.dim(`\n Asking required questions for ${moduleName.toUpperCase()}...`));
|
|
607
|
+
const promptedAnswers = await prompts.prompt(questionsWithoutDefaults);
|
|
608
|
+
Object.assign(allAnswers, promptedAnswers);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// For questions with defaults that weren't asked, we need to process them with their default values
|
|
612
|
+
const questionsWithDefaults = questions.filter((q) => q.default !== undefined && q.default !== null && q.default !== '');
|
|
613
|
+
for (const question of questionsWithDefaults) {
|
|
614
|
+
// Skip function defaults - these are dynamic and will be evaluated later
|
|
615
|
+
if (typeof question.default === 'function') {
|
|
616
|
+
continue;
|
|
617
|
+
}
|
|
618
|
+
allAnswers[question.name] = question.default;
|
|
619
|
+
}
|
|
620
|
+
} else {
|
|
621
|
+
const promptedAnswers = await prompts.prompt(questions);
|
|
622
|
+
Object.assign(allAnswers, promptedAnswers);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// Store all answers for cross-referencing
|
|
627
|
+
Object.assign(this.allAnswers, allAnswers);
|
|
628
|
+
|
|
629
|
+
// Process all answers (both static and prompted)
|
|
630
|
+
// Always process if we have any answers or static answers
|
|
631
|
+
if (Object.keys(allAnswers).length > 0 || Object.keys(staticAnswers).length > 0) {
|
|
632
|
+
const answers = allAnswers;
|
|
633
|
+
|
|
634
|
+
// Process answers and build result values
|
|
635
|
+
for (const key of Object.keys(answers)) {
|
|
636
|
+
const originalKey = key.replace(`${moduleName}_`, '');
|
|
637
|
+
const item = moduleConfig[originalKey];
|
|
638
|
+
const value = answers[key];
|
|
639
|
+
|
|
640
|
+
// Build the result using the template
|
|
641
|
+
let result;
|
|
642
|
+
|
|
643
|
+
// For arrays (multi-select), handle differently
|
|
644
|
+
if (Array.isArray(value)) {
|
|
645
|
+
result = value;
|
|
646
|
+
} else if (item.result) {
|
|
647
|
+
result = item.result;
|
|
648
|
+
|
|
649
|
+
// Replace placeholders only for strings
|
|
650
|
+
if (typeof result === 'string' && value !== undefined) {
|
|
651
|
+
// Replace {value} with the actual value
|
|
652
|
+
if (typeof value === 'string') {
|
|
653
|
+
result = result.replace('{value}', value);
|
|
654
|
+
} else if (typeof value === 'boolean' || typeof value === 'number') {
|
|
655
|
+
// For boolean and number values, if result is just "{value}", use the raw value
|
|
656
|
+
if (result === '{value}') {
|
|
657
|
+
result = value;
|
|
658
|
+
} else {
|
|
659
|
+
result = result.replace('{value}', value);
|
|
660
|
+
}
|
|
661
|
+
} else {
|
|
662
|
+
result = value;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// Only do further replacements if result is still a string
|
|
666
|
+
if (typeof result === 'string') {
|
|
667
|
+
// Replace references to other config values
|
|
668
|
+
result = result.replaceAll(/{([^}]+)}/g, (match, configKey) => {
|
|
669
|
+
// Check if it's a special placeholder
|
|
670
|
+
if (configKey === 'project-root') {
|
|
671
|
+
return '{project-root}';
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
// Skip if it's the 'value' placeholder we already handled
|
|
675
|
+
if (configKey === 'value') {
|
|
676
|
+
return match;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// Look for the config value across all modules
|
|
680
|
+
// First check if it's in the current module's answers
|
|
681
|
+
let configValue = answers[`${moduleName}_${configKey}`];
|
|
682
|
+
|
|
683
|
+
// Then check all answers (for cross-module references like outputFolder)
|
|
684
|
+
if (!configValue) {
|
|
685
|
+
// Try with various module prefixes
|
|
686
|
+
for (const [answerKey, answerValue] of Object.entries(this.allAnswers)) {
|
|
687
|
+
if (answerKey.endsWith(`_${configKey}`)) {
|
|
688
|
+
configValue = answerValue;
|
|
689
|
+
break;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// Check in already collected config
|
|
695
|
+
if (!configValue) {
|
|
696
|
+
for (const mod of Object.keys(this.collectedConfig)) {
|
|
697
|
+
if (mod !== '_meta' && this.collectedConfig[mod] && this.collectedConfig[mod][configKey]) {
|
|
698
|
+
configValue = this.collectedConfig[mod][configKey];
|
|
699
|
+
break;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
return configValue || match;
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
} else {
|
|
709
|
+
result = value;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
// Store only the result value (no prompts, defaults, examples, etc.)
|
|
713
|
+
if (!this.collectedConfig[moduleName]) {
|
|
714
|
+
this.collectedConfig[moduleName] = {};
|
|
715
|
+
}
|
|
716
|
+
this.collectedConfig[moduleName][originalKey] = result;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
// No longer display completion boxes - keep output clean
|
|
720
|
+
} else {
|
|
721
|
+
// No questions for this module - show completion message with header if available
|
|
722
|
+
const moduleDisplayName = moduleConfig.header || `${moduleName.toUpperCase()} Module`;
|
|
723
|
+
|
|
724
|
+
// Check if this module has NO configuration keys at all (like CIS)
|
|
725
|
+
// Filter out metadata fields and only count actual config objects
|
|
726
|
+
const metadataFields = new Set(['code', 'name', 'header', 'subheader', 'default_selected']);
|
|
727
|
+
const actualConfigKeys = configKeys.filter((key) => !metadataFields.has(key));
|
|
728
|
+
const hasNoConfig = actualConfigKeys.length === 0;
|
|
729
|
+
|
|
730
|
+
if (hasNoConfig && (moduleConfig.subheader || moduleConfig.header)) {
|
|
731
|
+
// Module explicitly has no configuration - show with special styling
|
|
732
|
+
// Add blank line for better readability (matches other modules)
|
|
733
|
+
console.log();
|
|
734
|
+
|
|
735
|
+
// Display the module name in color first (matches other modules)
|
|
736
|
+
console.log(chalk.cyan('?') + ' ' + chalk.magenta(moduleDisplayName));
|
|
737
|
+
|
|
738
|
+
// Ask user if they want to accept defaults or customize on the next line
|
|
739
|
+
const { customize } = await prompts.prompt([
|
|
740
|
+
{
|
|
741
|
+
type: 'confirm',
|
|
742
|
+
name: 'customize',
|
|
743
|
+
message: 'Accept Defaults (no to customize)?',
|
|
744
|
+
default: true,
|
|
745
|
+
},
|
|
746
|
+
]);
|
|
747
|
+
|
|
748
|
+
// Show the subheader if available, otherwise show a default message
|
|
749
|
+
if (moduleConfig.subheader) {
|
|
750
|
+
console.log(chalk.dim(` ✓ ${moduleConfig.subheader}`));
|
|
751
|
+
} else {
|
|
752
|
+
console.log(chalk.dim(` ✓ No custom configuration required`));
|
|
753
|
+
}
|
|
754
|
+
} else {
|
|
755
|
+
// Module has config but just no questions to ask
|
|
756
|
+
console.log(chalk.dim(` ✓ ${moduleName.toUpperCase()} module configured`));
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// If we have no collected config for this module, but we have a module schema,
|
|
761
|
+
// ensure we have at least an empty object
|
|
762
|
+
if (!this.collectedConfig[moduleName]) {
|
|
763
|
+
this.collectedConfig[moduleName] = {};
|
|
764
|
+
|
|
765
|
+
// If we accepted defaults and have no answers, we still need to check
|
|
766
|
+
// if there are any static values in the schema that should be applied
|
|
767
|
+
if (moduleConfig) {
|
|
768
|
+
for (const key of Object.keys(moduleConfig)) {
|
|
769
|
+
if (key !== 'prompt' && moduleConfig[key] && typeof moduleConfig[key] === 'object') {
|
|
770
|
+
const item = moduleConfig[key];
|
|
771
|
+
// For static items (no prompt, just result), apply the result
|
|
772
|
+
if (!item.prompt && item.result) {
|
|
773
|
+
// Apply any placeholder replacements to the result
|
|
774
|
+
let result = item.result;
|
|
775
|
+
if (typeof result === 'string') {
|
|
776
|
+
result = this.replacePlaceholders(result, moduleName, moduleConfig);
|
|
777
|
+
}
|
|
778
|
+
this.collectedConfig[moduleName][key] = result;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
/**
|
|
787
|
+
* Replace placeholders in a string with collected config values
|
|
788
|
+
* @param {string} str - String with placeholders
|
|
789
|
+
* @param {string} currentModule - Current module name (to look up defaults in same module)
|
|
790
|
+
* @param {Object} moduleConfig - Current module's config schema (to look up defaults)
|
|
791
|
+
* @returns {string} String with placeholders replaced
|
|
792
|
+
*/
|
|
793
|
+
replacePlaceholders(str, currentModule = null, moduleConfig = null) {
|
|
794
|
+
if (typeof str !== 'string') {
|
|
795
|
+
return str;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
return str.replaceAll(/{([^}]+)}/g, (match, configKey) => {
|
|
799
|
+
// Preserve special placeholders
|
|
800
|
+
if (configKey === 'project-root' || configKey === 'value' || configKey === 'directory_name') {
|
|
801
|
+
return match;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
// Look for the config value in allAnswers (already answered questions)
|
|
805
|
+
let configValue = this.allAnswers[configKey] || this.allAnswers[`core_${configKey}`];
|
|
806
|
+
|
|
807
|
+
// Check in already collected config
|
|
808
|
+
if (!configValue) {
|
|
809
|
+
for (const mod of Object.keys(this.collectedConfig)) {
|
|
810
|
+
if (mod !== '_meta' && this.collectedConfig[mod] && this.collectedConfig[mod][configKey]) {
|
|
811
|
+
configValue = this.collectedConfig[mod][configKey];
|
|
812
|
+
break;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
// If still not found and we're in the same module, use the default from the config schema
|
|
818
|
+
if (!configValue && currentModule && moduleConfig && moduleConfig[configKey]) {
|
|
819
|
+
const referencedItem = moduleConfig[configKey];
|
|
820
|
+
if (referencedItem && referencedItem.default !== undefined) {
|
|
821
|
+
configValue = referencedItem.default;
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
return configValue || match;
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
/**
|
|
830
|
+
* Build a prompt question from a config item
|
|
831
|
+
* @param {string} moduleName - Module name
|
|
832
|
+
* @param {string} key - Config key
|
|
833
|
+
* @param {Object} item - Config item definition
|
|
834
|
+
* @param {Object} moduleConfig - Full module config schema (for resolving defaults)
|
|
835
|
+
*/
|
|
836
|
+
async buildQuestion(moduleName, key, item, moduleConfig = null) {
|
|
837
|
+
const questionName = `${moduleName}_${key}`;
|
|
838
|
+
|
|
839
|
+
// Check for existing value
|
|
840
|
+
let existingValue = null;
|
|
841
|
+
if (this.existingConfig && this.existingConfig[moduleName]) {
|
|
842
|
+
existingValue = this.existingConfig[moduleName][key];
|
|
843
|
+
|
|
844
|
+
// Clean up existing value - remove {project-root}/ prefix if present
|
|
845
|
+
// This prevents duplication when the result template adds it back
|
|
846
|
+
if (typeof existingValue === 'string' && existingValue.startsWith('{project-root}/')) {
|
|
847
|
+
existingValue = existingValue.replace('{project-root}/', '');
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
// Special handling for user_name: default to system user
|
|
852
|
+
if (moduleName === 'core' && key === 'user_name' && !existingValue) {
|
|
853
|
+
item.default = this.getDefaultUsername();
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
// Determine question type and default value
|
|
857
|
+
let questionType = 'input';
|
|
858
|
+
let defaultValue = item.default;
|
|
859
|
+
let choices = null;
|
|
860
|
+
|
|
861
|
+
// Check if default contains references to other fields in the same module
|
|
862
|
+
const hasSameModuleReference = typeof defaultValue === 'string' && defaultValue.match(/{([^}]+)}/);
|
|
863
|
+
let dynamicDefault = false;
|
|
864
|
+
|
|
865
|
+
// Replace placeholders in default value with collected config values
|
|
866
|
+
if (typeof defaultValue === 'string') {
|
|
867
|
+
if (defaultValue.includes('{directory_name}') && this.currentProjectDir) {
|
|
868
|
+
const dirName = path.basename(this.currentProjectDir);
|
|
869
|
+
defaultValue = defaultValue.replaceAll('{directory_name}', dirName);
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
// Check if this references another field in the same module (for dynamic defaults)
|
|
873
|
+
if (hasSameModuleReference && moduleConfig) {
|
|
874
|
+
const matches = defaultValue.match(/{([^}]+)}/g);
|
|
875
|
+
if (matches) {
|
|
876
|
+
for (const match of matches) {
|
|
877
|
+
const fieldName = match.slice(1, -1); // Remove { }
|
|
878
|
+
// Check if this field exists in the same module config
|
|
879
|
+
if (moduleConfig[fieldName]) {
|
|
880
|
+
dynamicDefault = true;
|
|
881
|
+
break;
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
// If not dynamic, replace placeholders now
|
|
888
|
+
if (!dynamicDefault) {
|
|
889
|
+
defaultValue = this.replacePlaceholders(defaultValue, moduleName, moduleConfig);
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
// Strip {project-root}/ from defaults since it will be added back by result template
|
|
893
|
+
// This makes the display cleaner and user input simpler
|
|
894
|
+
if (defaultValue.includes('{project-root}/')) {
|
|
895
|
+
defaultValue = defaultValue.replace('{project-root}/', '');
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
// Handle different question types
|
|
900
|
+
if (item['single-select']) {
|
|
901
|
+
questionType = 'list';
|
|
902
|
+
choices = item['single-select'].map((choice) => {
|
|
903
|
+
// If choice is an object with label and value
|
|
904
|
+
if (typeof choice === 'object' && choice.label && choice.value !== undefined) {
|
|
905
|
+
return {
|
|
906
|
+
name: choice.label,
|
|
907
|
+
value: choice.value,
|
|
908
|
+
};
|
|
909
|
+
}
|
|
910
|
+
// Otherwise it's a simple string choice
|
|
911
|
+
return {
|
|
912
|
+
name: choice,
|
|
913
|
+
value: choice,
|
|
914
|
+
};
|
|
915
|
+
});
|
|
916
|
+
if (existingValue) {
|
|
917
|
+
defaultValue = existingValue;
|
|
918
|
+
}
|
|
919
|
+
} else if (item['multi-select']) {
|
|
920
|
+
questionType = 'checkbox';
|
|
921
|
+
choices = item['multi-select'].map((choice) => {
|
|
922
|
+
// If choice is an object with label and value
|
|
923
|
+
if (typeof choice === 'object' && choice.label && choice.value !== undefined) {
|
|
924
|
+
return {
|
|
925
|
+
name: choice.label,
|
|
926
|
+
value: choice.value,
|
|
927
|
+
checked: existingValue
|
|
928
|
+
? existingValue.includes(choice.value)
|
|
929
|
+
: item.default && Array.isArray(item.default)
|
|
930
|
+
? item.default.includes(choice.value)
|
|
931
|
+
: false,
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
// Otherwise it's a simple string choice
|
|
935
|
+
return {
|
|
936
|
+
name: choice,
|
|
937
|
+
value: choice,
|
|
938
|
+
checked: existingValue
|
|
939
|
+
? existingValue.includes(choice)
|
|
940
|
+
: item.default && Array.isArray(item.default)
|
|
941
|
+
? item.default.includes(choice)
|
|
942
|
+
: false,
|
|
943
|
+
};
|
|
944
|
+
});
|
|
945
|
+
} else if (typeof defaultValue === 'boolean') {
|
|
946
|
+
questionType = 'confirm';
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
// Build the prompt message
|
|
950
|
+
let message = '';
|
|
951
|
+
|
|
952
|
+
// Handle array prompts for multi-line messages
|
|
953
|
+
if (Array.isArray(item.prompt)) {
|
|
954
|
+
message = item.prompt.join('\n');
|
|
955
|
+
} else {
|
|
956
|
+
message = item.prompt;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
// Replace placeholders in prompt message with collected config values
|
|
960
|
+
if (typeof message === 'string') {
|
|
961
|
+
message = this.replacePlaceholders(message, moduleName, moduleConfig);
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
// Add current value indicator for existing configs
|
|
965
|
+
if (existingValue !== null && existingValue !== undefined) {
|
|
966
|
+
if (typeof existingValue === 'boolean') {
|
|
967
|
+
message += chalk.dim(` (current: ${existingValue ? 'true' : 'false'})`);
|
|
968
|
+
} else if (Array.isArray(existingValue)) {
|
|
969
|
+
message += chalk.dim(` (current: ${existingValue.join(', ')})`);
|
|
970
|
+
} else if (questionType !== 'list') {
|
|
971
|
+
// Show the cleaned value (without {project-root}/) for display
|
|
972
|
+
message += chalk.dim(` (current: ${existingValue})`);
|
|
973
|
+
}
|
|
974
|
+
} else if (item.example && questionType === 'input') {
|
|
975
|
+
// Show example for input fields
|
|
976
|
+
let exampleText = typeof item.example === 'string' ? item.example : JSON.stringify(item.example);
|
|
977
|
+
// Replace placeholders in example
|
|
978
|
+
if (typeof exampleText === 'string') {
|
|
979
|
+
exampleText = this.replacePlaceholders(exampleText, moduleName, moduleConfig);
|
|
980
|
+
exampleText = exampleText.replace('{project-root}/', '');
|
|
981
|
+
}
|
|
982
|
+
message += chalk.dim(` (e.g., ${exampleText})`);
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
// Build the question object
|
|
986
|
+
const question = {
|
|
987
|
+
type: questionType,
|
|
988
|
+
name: questionName,
|
|
989
|
+
message: message,
|
|
990
|
+
};
|
|
991
|
+
|
|
992
|
+
// Set default - if it's dynamic, use a function that the prompt will evaluate with current answers
|
|
993
|
+
// But if we have an existing value, always use that instead
|
|
994
|
+
if (existingValue !== null && existingValue !== undefined && questionType !== 'list') {
|
|
995
|
+
question.default = existingValue;
|
|
996
|
+
} else if (dynamicDefault && typeof item.default === 'string') {
|
|
997
|
+
const originalDefault = item.default;
|
|
998
|
+
question.default = (answers) => {
|
|
999
|
+
// Replace placeholders using answers from previous questions in the same batch
|
|
1000
|
+
let resolved = originalDefault;
|
|
1001
|
+
resolved = resolved.replaceAll(/{([^}]+)}/g, (match, fieldName) => {
|
|
1002
|
+
// Look for the answer in the current batch (prefixed with module name)
|
|
1003
|
+
const answerKey = `${moduleName}_${fieldName}`;
|
|
1004
|
+
if (answers[answerKey] !== undefined) {
|
|
1005
|
+
return answers[answerKey];
|
|
1006
|
+
}
|
|
1007
|
+
// Fall back to collected config
|
|
1008
|
+
return this.collectedConfig[moduleName]?.[fieldName] || match;
|
|
1009
|
+
});
|
|
1010
|
+
// Strip {project-root}/ for cleaner display
|
|
1011
|
+
if (resolved.includes('{project-root}/')) {
|
|
1012
|
+
resolved = resolved.replace('{project-root}/', '');
|
|
1013
|
+
}
|
|
1014
|
+
return resolved;
|
|
1015
|
+
};
|
|
1016
|
+
} else {
|
|
1017
|
+
question.default = defaultValue;
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
// Add choices for select types
|
|
1021
|
+
if (choices) {
|
|
1022
|
+
question.choices = choices;
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
// Add validation for input fields
|
|
1026
|
+
if (questionType === 'input') {
|
|
1027
|
+
question.validate = (input) => {
|
|
1028
|
+
if (!input && item.required) {
|
|
1029
|
+
return 'This field is required';
|
|
1030
|
+
}
|
|
1031
|
+
// Validate against regex pattern if provided
|
|
1032
|
+
if (input && item.regex) {
|
|
1033
|
+
const regex = new RegExp(item.regex);
|
|
1034
|
+
if (!regex.test(input)) {
|
|
1035
|
+
return `Invalid format. Must match pattern: ${item.regex}`;
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
return true;
|
|
1039
|
+
};
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
// Add validation for checkbox (multi-select) fields
|
|
1043
|
+
if (questionType === 'checkbox' && item.required) {
|
|
1044
|
+
question.validate = (answers) => {
|
|
1045
|
+
if (!answers || answers.length === 0) {
|
|
1046
|
+
return 'At least one option must be selected';
|
|
1047
|
+
}
|
|
1048
|
+
return true;
|
|
1049
|
+
};
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
return question;
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
/**
|
|
1056
|
+
* Deep merge two objects
|
|
1057
|
+
* @param {Object} target - Target object
|
|
1058
|
+
* @param {Object} source - Source object
|
|
1059
|
+
*/
|
|
1060
|
+
deepMerge(target, source) {
|
|
1061
|
+
const result = { ...target };
|
|
1062
|
+
|
|
1063
|
+
for (const key in source) {
|
|
1064
|
+
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
|
|
1065
|
+
if (result[key] && typeof result[key] === 'object' && !Array.isArray(result[key])) {
|
|
1066
|
+
result[key] = this.deepMerge(result[key], source[key]);
|
|
1067
|
+
} else {
|
|
1068
|
+
result[key] = source[key];
|
|
1069
|
+
}
|
|
1070
|
+
} else {
|
|
1071
|
+
result[key] = source[key];
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
return result;
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
module.exports = { ConfigCollector };
|