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,1362 @@
|
|
|
1
|
+
const path = require('node:path');
|
|
2
|
+
const fs = require('fs-extra');
|
|
3
|
+
const yaml = require('yaml');
|
|
4
|
+
const chalk = require('chalk');
|
|
5
|
+
const ora = require('ora');
|
|
6
|
+
const { XmlHandler } = require('../../../lib/xml-handler');
|
|
7
|
+
const { getProjectRoot, getSourcePath, getModulePath } = require('../../../lib/project-root');
|
|
8
|
+
const { filterCustomizationData } = require('../../../lib/agent/compiler');
|
|
9
|
+
const { ExternalModuleManager } = require('./external-manager');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Manages the installation, updating, and removal of BMAD modules.
|
|
13
|
+
* Handles module discovery, dependency resolution, configuration processing,
|
|
14
|
+
* and agent file management including XML activation block injection.
|
|
15
|
+
*
|
|
16
|
+
* @class ModuleManager
|
|
17
|
+
* @requires fs-extra
|
|
18
|
+
* @requires yaml
|
|
19
|
+
* @requires chalk
|
|
20
|
+
* @requires XmlHandler
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* const manager = new ModuleManager();
|
|
24
|
+
* const modules = await manager.listAvailable();
|
|
25
|
+
* await manager.install('core-module', '/path/to/bmad');
|
|
26
|
+
*/
|
|
27
|
+
class ModuleManager {
|
|
28
|
+
constructor(options = {}) {
|
|
29
|
+
this.xmlHandler = new XmlHandler();
|
|
30
|
+
this.bmadFolderName = 'bmad'; // Default, can be overridden
|
|
31
|
+
this.customModulePaths = new Map(); // Initialize custom module paths
|
|
32
|
+
this.externalModuleManager = new ExternalModuleManager(); // For external official modules
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Set the bmad folder name for placeholder replacement
|
|
37
|
+
* @param {string} bmadFolderName - The bmad folder name
|
|
38
|
+
*/
|
|
39
|
+
setBmadFolderName(bmadFolderName) {
|
|
40
|
+
this.bmadFolderName = bmadFolderName;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Set the core configuration for access during module installation
|
|
45
|
+
* @param {Object} coreConfig - Core configuration object
|
|
46
|
+
*/
|
|
47
|
+
setCoreConfig(coreConfig) {
|
|
48
|
+
this.coreConfig = coreConfig;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Set custom module paths for priority lookup
|
|
53
|
+
* @param {Map<string, string>} customModulePaths - Map of module ID to source path
|
|
54
|
+
*/
|
|
55
|
+
setCustomModulePaths(customModulePaths) {
|
|
56
|
+
this.customModulePaths = customModulePaths;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Copy a file to the target location
|
|
61
|
+
* @param {string} sourcePath - Source file path
|
|
62
|
+
* @param {string} targetPath - Target file path
|
|
63
|
+
* @param {boolean} overwrite - Whether to overwrite existing files (default: true)
|
|
64
|
+
*/
|
|
65
|
+
async copyFileWithPlaceholderReplacement(sourcePath, targetPath, overwrite = true) {
|
|
66
|
+
await fs.copy(sourcePath, targetPath, { overwrite });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Copy a directory recursively
|
|
71
|
+
* @param {string} sourceDir - Source directory path
|
|
72
|
+
* @param {string} targetDir - Target directory path
|
|
73
|
+
* @param {boolean} overwrite - Whether to overwrite existing files (default: true)
|
|
74
|
+
*/
|
|
75
|
+
async copyDirectoryWithPlaceholderReplacement(sourceDir, targetDir, overwrite = true) {
|
|
76
|
+
await fs.ensureDir(targetDir);
|
|
77
|
+
const entries = await fs.readdir(sourceDir, { withFileTypes: true });
|
|
78
|
+
|
|
79
|
+
for (const entry of entries) {
|
|
80
|
+
const sourcePath = path.join(sourceDir, entry.name);
|
|
81
|
+
const targetPath = path.join(targetDir, entry.name);
|
|
82
|
+
|
|
83
|
+
if (entry.isDirectory()) {
|
|
84
|
+
await this.copyDirectoryWithPlaceholderReplacement(sourcePath, targetPath, overwrite);
|
|
85
|
+
} else {
|
|
86
|
+
await this.copyFileWithPlaceholderReplacement(sourcePath, targetPath, overwrite);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Copy sidecar directory to _bmad/_memory location with update-safe handling
|
|
93
|
+
* @param {string} sourceSidecarPath - Source sidecar directory path
|
|
94
|
+
* @param {string} agentName - Name of the agent (for naming)
|
|
95
|
+
* @param {string} bmadMemoryPath - This should ALWAYS be _bmad/_memory
|
|
96
|
+
* @param {boolean} isUpdate - Whether this is an update (default: false)
|
|
97
|
+
* @param {string} bmadDir - BMAD installation directory
|
|
98
|
+
* @param {Object} installer - Installer instance for file tracking
|
|
99
|
+
*/
|
|
100
|
+
async copySidecarToMemory(sourceSidecarPath, agentName, bmadMemoryPath, isUpdate = false, bmadDir = null, installer = null) {
|
|
101
|
+
const crypto = require('node:crypto');
|
|
102
|
+
const sidecarTargetDir = path.join(bmadMemoryPath, `${agentName}-sidecar`);
|
|
103
|
+
|
|
104
|
+
// Ensure target directory exists
|
|
105
|
+
await fs.ensureDir(bmadMemoryPath);
|
|
106
|
+
await fs.ensureDir(sidecarTargetDir);
|
|
107
|
+
|
|
108
|
+
// Get existing files manifest for update checking
|
|
109
|
+
let existingFilesManifest = [];
|
|
110
|
+
if (isUpdate && installer) {
|
|
111
|
+
existingFilesManifest = await installer.readFilesManifest(bmadDir);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Build map of existing sidecar files with their hashes
|
|
115
|
+
const existingSidecarFiles = new Map();
|
|
116
|
+
for (const fileEntry of existingFilesManifest) {
|
|
117
|
+
if (fileEntry.path && fileEntry.path.includes(`${agentName}-sidecar/`)) {
|
|
118
|
+
existingSidecarFiles.set(fileEntry.path, fileEntry.hash);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Get all files in source sidecar
|
|
123
|
+
const sourceFiles = await this.getFileList(sourceSidecarPath);
|
|
124
|
+
|
|
125
|
+
for (const file of sourceFiles) {
|
|
126
|
+
const sourceFilePath = path.join(sourceSidecarPath, file);
|
|
127
|
+
const targetFilePath = path.join(sidecarTargetDir, file);
|
|
128
|
+
|
|
129
|
+
// Calculate current source file hash
|
|
130
|
+
const sourceHash = crypto
|
|
131
|
+
.createHash('sha256')
|
|
132
|
+
.update(await fs.readFile(sourceFilePath))
|
|
133
|
+
.digest('hex');
|
|
134
|
+
|
|
135
|
+
// Path relative to bmad directory
|
|
136
|
+
const relativeToBmad = path.join('_memory', `${agentName}-sidecar`, file);
|
|
137
|
+
|
|
138
|
+
if (isUpdate && (await fs.pathExists(targetFilePath))) {
|
|
139
|
+
// Calculate current target file hash
|
|
140
|
+
const currentTargetHash = crypto
|
|
141
|
+
.createHash('sha256')
|
|
142
|
+
.update(await fs.readFile(targetFilePath))
|
|
143
|
+
.digest('hex');
|
|
144
|
+
|
|
145
|
+
// Get the last known hash from files-manifest
|
|
146
|
+
const lastKnownHash = existingSidecarFiles.get(relativeToBmad);
|
|
147
|
+
|
|
148
|
+
if (lastKnownHash) {
|
|
149
|
+
// We have a record of this file
|
|
150
|
+
if (currentTargetHash === lastKnownHash) {
|
|
151
|
+
// File hasn't been modified by user, safe to update
|
|
152
|
+
await this.copyFileWithPlaceholderReplacement(sourceFilePath, targetFilePath, true);
|
|
153
|
+
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
|
|
154
|
+
console.log(chalk.dim(` Updated sidecar file: ${relativeToBmad}`));
|
|
155
|
+
}
|
|
156
|
+
} else {
|
|
157
|
+
// User has modified the file, preserve it
|
|
158
|
+
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
|
|
159
|
+
console.log(chalk.dim(` Preserving user-modified file: ${relativeToBmad}`));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
} else {
|
|
163
|
+
// First time seeing this file in manifest, copy it
|
|
164
|
+
await this.copyFileWithPlaceholderReplacement(sourceFilePath, targetFilePath, true);
|
|
165
|
+
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
|
|
166
|
+
console.log(chalk.dim(` Added new sidecar file: ${relativeToBmad}`));
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
} else {
|
|
170
|
+
// New installation
|
|
171
|
+
await this.copyFileWithPlaceholderReplacement(sourceFilePath, targetFilePath, true);
|
|
172
|
+
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
|
|
173
|
+
console.log(chalk.dim(` Copied sidecar file: ${relativeToBmad}`));
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Track the file in the installer's file tracking system
|
|
178
|
+
if (installer && installer.installedFiles) {
|
|
179
|
+
installer.installedFiles.add(targetFilePath);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Return list of files that were processed
|
|
184
|
+
const processedFiles = sourceFiles.map((file) => path.join('_memory', `${agentName}-sidecar`, file));
|
|
185
|
+
return processedFiles;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* List all available modules (excluding core which is always installed)
|
|
190
|
+
* bmm is the only built-in module, directly under src/bmm
|
|
191
|
+
* All other modules come from external-official-modules.yaml
|
|
192
|
+
* @returns {Object} Object with modules array and customModules array
|
|
193
|
+
*/
|
|
194
|
+
async listAvailable() {
|
|
195
|
+
const modules = [];
|
|
196
|
+
const customModules = [];
|
|
197
|
+
|
|
198
|
+
// Add built-in bmm module (directly under src/bmm)
|
|
199
|
+
const bmmPath = getSourcePath('bmm');
|
|
200
|
+
if (await fs.pathExists(bmmPath)) {
|
|
201
|
+
const bmmInfo = await this.getModuleInfo(bmmPath, 'bmm', 'src/bmm');
|
|
202
|
+
if (bmmInfo) {
|
|
203
|
+
modules.push(bmmInfo);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Check for cached custom modules in _config/custom/
|
|
208
|
+
if (this.bmadDir) {
|
|
209
|
+
const customCacheDir = path.join(this.bmadDir, '_config', 'custom');
|
|
210
|
+
if (await fs.pathExists(customCacheDir)) {
|
|
211
|
+
const cacheEntries = await fs.readdir(customCacheDir, { withFileTypes: true });
|
|
212
|
+
for (const entry of cacheEntries) {
|
|
213
|
+
if (entry.isDirectory()) {
|
|
214
|
+
const cachePath = path.join(customCacheDir, entry.name);
|
|
215
|
+
const moduleInfo = await this.getModuleInfo(cachePath, entry.name, '_config/custom');
|
|
216
|
+
if (moduleInfo && !modules.some((m) => m.id === moduleInfo.id) && !customModules.some((m) => m.id === moduleInfo.id)) {
|
|
217
|
+
moduleInfo.isCustom = true;
|
|
218
|
+
moduleInfo.fromCache = true;
|
|
219
|
+
customModules.push(moduleInfo);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return { modules, customModules };
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Get module information from a module path
|
|
231
|
+
* @param {string} modulePath - Path to the module directory
|
|
232
|
+
* @param {string} defaultName - Default name for the module
|
|
233
|
+
* @param {string} sourceDescription - Description of where the module was found
|
|
234
|
+
* @returns {Object|null} Module info or null if not a valid module
|
|
235
|
+
*/
|
|
236
|
+
async getModuleInfo(modulePath, defaultName, sourceDescription) {
|
|
237
|
+
// Check for module structure (module.yaml OR custom.yaml)
|
|
238
|
+
const moduleConfigPath = path.join(modulePath, 'module.yaml');
|
|
239
|
+
const installerConfigPath = path.join(modulePath, '_module-installer', 'module.yaml');
|
|
240
|
+
const customConfigPath = path.join(modulePath, '_module-installer', 'custom.yaml');
|
|
241
|
+
const rootCustomConfigPath = path.join(modulePath, 'custom.yaml');
|
|
242
|
+
let configPath = null;
|
|
243
|
+
|
|
244
|
+
if (await fs.pathExists(moduleConfigPath)) {
|
|
245
|
+
configPath = moduleConfigPath;
|
|
246
|
+
} else if (await fs.pathExists(installerConfigPath)) {
|
|
247
|
+
configPath = installerConfigPath;
|
|
248
|
+
} else if (await fs.pathExists(customConfigPath)) {
|
|
249
|
+
configPath = customConfigPath;
|
|
250
|
+
} else if (await fs.pathExists(rootCustomConfigPath)) {
|
|
251
|
+
configPath = rootCustomConfigPath;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Skip if this doesn't look like a module
|
|
255
|
+
if (!configPath) {
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Mark as custom if it's using custom.yaml OR if it's outside src/bmm or src/core
|
|
260
|
+
const isCustomSource = sourceDescription !== 'src/bmm' && sourceDescription !== 'src/core' && sourceDescription !== 'src/modules';
|
|
261
|
+
const moduleInfo = {
|
|
262
|
+
id: defaultName,
|
|
263
|
+
path: modulePath,
|
|
264
|
+
name: defaultName
|
|
265
|
+
.split('-')
|
|
266
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
267
|
+
.join(' '),
|
|
268
|
+
description: 'BMAD Module',
|
|
269
|
+
version: '5.0.0',
|
|
270
|
+
source: sourceDescription,
|
|
271
|
+
isCustom: configPath === customConfigPath || configPath === rootCustomConfigPath || isCustomSource,
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
// Read module config for metadata
|
|
275
|
+
try {
|
|
276
|
+
const configContent = await fs.readFile(configPath, 'utf8');
|
|
277
|
+
const config = yaml.parse(configContent);
|
|
278
|
+
|
|
279
|
+
// Use the code property as the id if available
|
|
280
|
+
if (config.code) {
|
|
281
|
+
moduleInfo.id = config.code;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
moduleInfo.name = config.name || moduleInfo.name;
|
|
285
|
+
moduleInfo.description = config.description || moduleInfo.description;
|
|
286
|
+
moduleInfo.version = config.version || moduleInfo.version;
|
|
287
|
+
moduleInfo.dependencies = config.dependencies || [];
|
|
288
|
+
moduleInfo.defaultSelected = config.default_selected === undefined ? false : config.default_selected;
|
|
289
|
+
} catch (error) {
|
|
290
|
+
console.warn(`Failed to read config for ${defaultName}:`, error.message);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return moduleInfo;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Find the source path for a module by searching all possible locations
|
|
298
|
+
* @param {string} moduleCode - Code of the module to find (from module.yaml)
|
|
299
|
+
* @returns {string|null} Path to the module source or null if not found
|
|
300
|
+
*/
|
|
301
|
+
async findModuleSource(moduleCode) {
|
|
302
|
+
const projectRoot = getProjectRoot();
|
|
303
|
+
|
|
304
|
+
// First check custom module paths if they exist
|
|
305
|
+
if (this.customModulePaths && this.customModulePaths.has(moduleCode)) {
|
|
306
|
+
return this.customModulePaths.get(moduleCode);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Check for built-in bmm module (directly under src/bmm)
|
|
310
|
+
if (moduleCode === 'bmm') {
|
|
311
|
+
const bmmPath = getSourcePath('bmm');
|
|
312
|
+
if (await fs.pathExists(bmmPath)) {
|
|
313
|
+
return bmmPath;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Check external official modules
|
|
318
|
+
const externalSource = await this.findExternalModuleSource(moduleCode);
|
|
319
|
+
if (externalSource) {
|
|
320
|
+
return externalSource;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Check if a module is an external official module
|
|
328
|
+
* @param {string} moduleCode - Code of the module to check
|
|
329
|
+
* @returns {boolean} True if the module is external
|
|
330
|
+
*/
|
|
331
|
+
async isExternalModule(moduleCode) {
|
|
332
|
+
return await this.externalModuleManager.hasModule(moduleCode);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Get the cache directory for external modules
|
|
337
|
+
* @returns {string} Path to the external modules cache directory
|
|
338
|
+
*/
|
|
339
|
+
getExternalCacheDir() {
|
|
340
|
+
const os = require('node:os');
|
|
341
|
+
const cacheDir = path.join(os.homedir(), '.bmad', 'cache', 'external-modules');
|
|
342
|
+
return cacheDir;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Clone an external module repository to cache
|
|
347
|
+
* @param {string} moduleCode - Code of the external module
|
|
348
|
+
* @returns {string} Path to the cloned repository
|
|
349
|
+
*/
|
|
350
|
+
async cloneExternalModule(moduleCode) {
|
|
351
|
+
const { execSync } = require('node:child_process');
|
|
352
|
+
const moduleInfo = await this.externalModuleManager.getModuleByCode(moduleCode);
|
|
353
|
+
|
|
354
|
+
if (!moduleInfo) {
|
|
355
|
+
throw new Error(`External module '${moduleCode}' not found in external-official-modules.yaml`);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const cacheDir = this.getExternalCacheDir();
|
|
359
|
+
const moduleCacheDir = path.join(cacheDir, moduleCode);
|
|
360
|
+
|
|
361
|
+
// Create cache directory if it doesn't exist
|
|
362
|
+
await fs.ensureDir(cacheDir);
|
|
363
|
+
|
|
364
|
+
// Track if we need to install dependencies
|
|
365
|
+
let needsDependencyInstall = false;
|
|
366
|
+
let wasNewClone = false;
|
|
367
|
+
|
|
368
|
+
// Check if already cloned
|
|
369
|
+
if (await fs.pathExists(moduleCacheDir)) {
|
|
370
|
+
// Try to update if it's a git repo
|
|
371
|
+
const fetchSpinner = ora(`Fetching ${moduleInfo.name}...`).start();
|
|
372
|
+
try {
|
|
373
|
+
const currentRef = execSync('git rev-parse HEAD', { cwd: moduleCacheDir, stdio: 'pipe' }).toString().trim();
|
|
374
|
+
execSync('git fetch --depth 1', { cwd: moduleCacheDir, stdio: 'pipe' });
|
|
375
|
+
execSync('git checkout -f', { cwd: moduleCacheDir, stdio: 'pipe' });
|
|
376
|
+
execSync('git pull --ff-only', { cwd: moduleCacheDir, stdio: 'pipe' });
|
|
377
|
+
const newRef = execSync('git rev-parse HEAD', { cwd: moduleCacheDir, stdio: 'pipe' }).toString().trim();
|
|
378
|
+
|
|
379
|
+
fetchSpinner.succeed(`Fetched ${moduleInfo.name}`);
|
|
380
|
+
// Force dependency install if we got new code
|
|
381
|
+
if (currentRef !== newRef) {
|
|
382
|
+
needsDependencyInstall = true;
|
|
383
|
+
}
|
|
384
|
+
} catch {
|
|
385
|
+
fetchSpinner.warn(`Fetch failed, re-downloading ${moduleInfo.name}`);
|
|
386
|
+
// If update fails, remove and re-clone
|
|
387
|
+
await fs.remove(moduleCacheDir);
|
|
388
|
+
wasNewClone = true;
|
|
389
|
+
}
|
|
390
|
+
} else {
|
|
391
|
+
wasNewClone = true;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Clone if not exists or was removed
|
|
395
|
+
if (wasNewClone) {
|
|
396
|
+
const fetchSpinner = ora(`Fetching ${moduleInfo.name}...`).start();
|
|
397
|
+
try {
|
|
398
|
+
execSync(`git clone --depth 1 "${moduleInfo.url}" "${moduleCacheDir}"`, {
|
|
399
|
+
stdio: 'pipe',
|
|
400
|
+
});
|
|
401
|
+
fetchSpinner.succeed(`Fetched ${moduleInfo.name}`);
|
|
402
|
+
} catch (error) {
|
|
403
|
+
fetchSpinner.fail(`Failed to fetch ${moduleInfo.name}`);
|
|
404
|
+
throw new Error(`Failed to clone external module '${moduleCode}': ${error.message}`);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Install dependencies if package.json exists
|
|
409
|
+
const packageJsonPath = path.join(moduleCacheDir, 'package.json');
|
|
410
|
+
const nodeModulesPath = path.join(moduleCacheDir, 'node_modules');
|
|
411
|
+
if (await fs.pathExists(packageJsonPath)) {
|
|
412
|
+
// Install if node_modules doesn't exist, or if package.json is newer (dependencies changed)
|
|
413
|
+
const nodeModulesMissing = !(await fs.pathExists(nodeModulesPath));
|
|
414
|
+
|
|
415
|
+
// Force install if we updated or cloned new
|
|
416
|
+
if (needsDependencyInstall || wasNewClone || nodeModulesMissing) {
|
|
417
|
+
const installSpinner = ora(`Installing dependencies for ${moduleInfo.name}...`).start();
|
|
418
|
+
try {
|
|
419
|
+
execSync('npm install --production --no-audit --no-fund --prefer-offline --no-progress', {
|
|
420
|
+
cwd: moduleCacheDir,
|
|
421
|
+
stdio: 'pipe',
|
|
422
|
+
timeout: 120_000, // 2 minute timeout
|
|
423
|
+
});
|
|
424
|
+
installSpinner.succeed(`Installed dependencies for ${moduleInfo.name}`);
|
|
425
|
+
} catch (error) {
|
|
426
|
+
installSpinner.warn(`Failed to install dependencies for ${moduleInfo.name}`);
|
|
427
|
+
console.warn(chalk.yellow(` Warning: ${error.message}`));
|
|
428
|
+
}
|
|
429
|
+
} else {
|
|
430
|
+
// Check if package.json is newer than node_modules
|
|
431
|
+
let packageJsonNewer = false;
|
|
432
|
+
try {
|
|
433
|
+
const packageStats = await fs.stat(packageJsonPath);
|
|
434
|
+
const nodeModulesStats = await fs.stat(nodeModulesPath);
|
|
435
|
+
packageJsonNewer = packageStats.mtime > nodeModulesStats.mtime;
|
|
436
|
+
} catch {
|
|
437
|
+
// If stat fails, assume we need to install
|
|
438
|
+
packageJsonNewer = true;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
if (packageJsonNewer) {
|
|
442
|
+
const installSpinner = ora(`Installing dependencies for ${moduleInfo.name}...`).start();
|
|
443
|
+
try {
|
|
444
|
+
execSync('npm install --production --no-audit --no-fund --prefer-offline --no-progress', {
|
|
445
|
+
cwd: moduleCacheDir,
|
|
446
|
+
stdio: 'pipe',
|
|
447
|
+
timeout: 120_000, // 2 minute timeout
|
|
448
|
+
});
|
|
449
|
+
installSpinner.succeed(`Installed dependencies for ${moduleInfo.name}`);
|
|
450
|
+
} catch (error) {
|
|
451
|
+
installSpinner.warn(`Failed to install dependencies for ${moduleInfo.name}`);
|
|
452
|
+
console.warn(chalk.yellow(` Warning: ${error.message}`));
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
return moduleCacheDir;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Find the source path for an external module
|
|
463
|
+
* @param {string} moduleCode - Code of the external module
|
|
464
|
+
* @returns {string|null} Path to the module source or null if not found
|
|
465
|
+
*/
|
|
466
|
+
async findExternalModuleSource(moduleCode) {
|
|
467
|
+
const moduleInfo = await this.externalModuleManager.getModuleByCode(moduleCode);
|
|
468
|
+
|
|
469
|
+
if (!moduleInfo) {
|
|
470
|
+
return null;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Clone the external module repo
|
|
474
|
+
const cloneDir = await this.cloneExternalModule(moduleCode);
|
|
475
|
+
|
|
476
|
+
// The module-definition specifies the path to module.yaml relative to repo root
|
|
477
|
+
// We need to return the directory containing module.yaml
|
|
478
|
+
const moduleDefinitionPath = moduleInfo.moduleDefinition; // e.g., 'src/module.yaml'
|
|
479
|
+
const moduleDir = path.dirname(path.join(cloneDir, moduleDefinitionPath));
|
|
480
|
+
|
|
481
|
+
return moduleDir;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Install a module
|
|
486
|
+
* @param {string} moduleName - Code of the module to install (from module.yaml)
|
|
487
|
+
* @param {string} bmadDir - Target bmad directory
|
|
488
|
+
* @param {Function} fileTrackingCallback - Optional callback to track installed files
|
|
489
|
+
* @param {Object} options - Additional installation options
|
|
490
|
+
* @param {Array<string>} options.installedIDEs - Array of IDE codes that were installed
|
|
491
|
+
* @param {Object} options.moduleConfig - Module configuration from config collector
|
|
492
|
+
* @param {Object} options.logger - Logger instance for output
|
|
493
|
+
*/
|
|
494
|
+
async install(moduleName, bmadDir, fileTrackingCallback = null, options = {}) {
|
|
495
|
+
const sourcePath = await this.findModuleSource(moduleName);
|
|
496
|
+
const targetPath = path.join(bmadDir, moduleName);
|
|
497
|
+
|
|
498
|
+
// Check if source module exists
|
|
499
|
+
if (!sourcePath) {
|
|
500
|
+
// Provide a more user-friendly error message
|
|
501
|
+
throw new Error(
|
|
502
|
+
`Source for module '${moduleName}' is not available. It will be retained but cannot be updated without its source files.`,
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// Check if this is a custom module and read its custom.yaml values
|
|
507
|
+
let customConfig = null;
|
|
508
|
+
const rootCustomConfigPath = path.join(sourcePath, 'custom.yaml');
|
|
509
|
+
const moduleInstallerCustomPath = path.join(sourcePath, '_module-installer', 'custom.yaml');
|
|
510
|
+
|
|
511
|
+
if (await fs.pathExists(rootCustomConfigPath)) {
|
|
512
|
+
try {
|
|
513
|
+
const customContent = await fs.readFile(rootCustomConfigPath, 'utf8');
|
|
514
|
+
customConfig = yaml.parse(customContent);
|
|
515
|
+
} catch (error) {
|
|
516
|
+
console.warn(chalk.yellow(`Warning: Failed to read custom.yaml for ${moduleName}:`, error.message));
|
|
517
|
+
}
|
|
518
|
+
} else if (await fs.pathExists(moduleInstallerCustomPath)) {
|
|
519
|
+
try {
|
|
520
|
+
const customContent = await fs.readFile(moduleInstallerCustomPath, 'utf8');
|
|
521
|
+
customConfig = yaml.parse(customContent);
|
|
522
|
+
} catch (error) {
|
|
523
|
+
console.warn(chalk.yellow(`Warning: Failed to read custom.yaml for ${moduleName}:`, error.message));
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// If this is a custom module, merge its values into the module config
|
|
528
|
+
if (customConfig) {
|
|
529
|
+
options.moduleConfig = { ...options.moduleConfig, ...customConfig };
|
|
530
|
+
if (options.logger) {
|
|
531
|
+
options.logger.log(chalk.cyan(` Merged custom configuration for ${moduleName}`));
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// Check if already installed
|
|
536
|
+
if (await fs.pathExists(targetPath)) {
|
|
537
|
+
await fs.remove(targetPath);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// Vendor cross-module workflows BEFORE copying
|
|
541
|
+
// This reads source agent.yaml files and copies referenced workflows
|
|
542
|
+
await this.vendorCrossModuleWorkflows(sourcePath, targetPath, moduleName);
|
|
543
|
+
|
|
544
|
+
// Copy module files with filtering
|
|
545
|
+
await this.copyModuleWithFiltering(sourcePath, targetPath, fileTrackingCallback, options.moduleConfig);
|
|
546
|
+
|
|
547
|
+
// Compile any .agent.yaml files to .md format
|
|
548
|
+
await this.compileModuleAgents(sourcePath, targetPath, moduleName, bmadDir, options.installer);
|
|
549
|
+
|
|
550
|
+
// Process agent files to inject activation block
|
|
551
|
+
await this.processAgentFiles(targetPath, moduleName);
|
|
552
|
+
|
|
553
|
+
// Call module-specific installer if it exists (unless explicitly skipped)
|
|
554
|
+
if (!options.skipModuleInstaller) {
|
|
555
|
+
await this.runModuleInstaller(moduleName, bmadDir, options);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
return {
|
|
559
|
+
success: true,
|
|
560
|
+
module: moduleName,
|
|
561
|
+
path: targetPath,
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* Update an existing module
|
|
567
|
+
* @param {string} moduleName - Name of the module to update
|
|
568
|
+
* @param {string} bmadDir - Target bmad directory
|
|
569
|
+
* @param {boolean} force - Force update (overwrite modifications)
|
|
570
|
+
*/
|
|
571
|
+
async update(moduleName, bmadDir, force = false) {
|
|
572
|
+
const sourcePath = await this.findModuleSource(moduleName);
|
|
573
|
+
const targetPath = path.join(bmadDir, moduleName);
|
|
574
|
+
|
|
575
|
+
// Check if source module exists
|
|
576
|
+
if (!sourcePath) {
|
|
577
|
+
throw new Error(`Module '${moduleName}' not found in any source location`);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// Check if module is installed
|
|
581
|
+
if (!(await fs.pathExists(targetPath))) {
|
|
582
|
+
throw new Error(`Module '${moduleName}' is not installed`);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
if (force) {
|
|
586
|
+
// Force update - remove and reinstall
|
|
587
|
+
await fs.remove(targetPath);
|
|
588
|
+
return await this.install(moduleName, bmadDir);
|
|
589
|
+
} else {
|
|
590
|
+
// Selective update - preserve user modifications
|
|
591
|
+
await this.syncModule(sourcePath, targetPath);
|
|
592
|
+
|
|
593
|
+
// Recompile agents (#1133)
|
|
594
|
+
await this.compileModuleAgents(sourcePath, targetPath, moduleName, bmadDir, options.installer);
|
|
595
|
+
await this.processAgentFiles(targetPath, moduleName);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
return {
|
|
599
|
+
success: true,
|
|
600
|
+
module: moduleName,
|
|
601
|
+
path: targetPath,
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* Remove a module
|
|
607
|
+
* @param {string} moduleName - Name of the module to remove
|
|
608
|
+
* @param {string} bmadDir - Target bmad directory
|
|
609
|
+
*/
|
|
610
|
+
async remove(moduleName, bmadDir) {
|
|
611
|
+
const targetPath = path.join(bmadDir, moduleName);
|
|
612
|
+
|
|
613
|
+
if (!(await fs.pathExists(targetPath))) {
|
|
614
|
+
throw new Error(`Module '${moduleName}' is not installed`);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
await fs.remove(targetPath);
|
|
618
|
+
|
|
619
|
+
return {
|
|
620
|
+
success: true,
|
|
621
|
+
module: moduleName,
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
* Check if a module is installed
|
|
627
|
+
* @param {string} moduleName - Name of the module
|
|
628
|
+
* @param {string} bmadDir - Target bmad directory
|
|
629
|
+
* @returns {boolean} True if module is installed
|
|
630
|
+
*/
|
|
631
|
+
async isInstalled(moduleName, bmadDir) {
|
|
632
|
+
const targetPath = path.join(bmadDir, moduleName);
|
|
633
|
+
return await fs.pathExists(targetPath);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
/**
|
|
637
|
+
* Get installed module info
|
|
638
|
+
* @param {string} moduleName - Name of the module
|
|
639
|
+
* @param {string} bmadDir - Target bmad directory
|
|
640
|
+
* @returns {Object|null} Module info or null if not installed
|
|
641
|
+
*/
|
|
642
|
+
async getInstalledInfo(moduleName, bmadDir) {
|
|
643
|
+
const targetPath = path.join(bmadDir, moduleName);
|
|
644
|
+
|
|
645
|
+
if (!(await fs.pathExists(targetPath))) {
|
|
646
|
+
return null;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
const configPath = path.join(targetPath, 'config.yaml');
|
|
650
|
+
const moduleInfo = {
|
|
651
|
+
id: moduleName,
|
|
652
|
+
path: targetPath,
|
|
653
|
+
installed: true,
|
|
654
|
+
};
|
|
655
|
+
|
|
656
|
+
if (await fs.pathExists(configPath)) {
|
|
657
|
+
try {
|
|
658
|
+
const configContent = await fs.readFile(configPath, 'utf8');
|
|
659
|
+
const config = yaml.parse(configContent);
|
|
660
|
+
Object.assign(moduleInfo, config);
|
|
661
|
+
} catch (error) {
|
|
662
|
+
console.warn(`Failed to read installed module config:`, error.message);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
return moduleInfo;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/**
|
|
670
|
+
* Copy module with filtering for localskip agents and conditional content
|
|
671
|
+
* @param {string} sourcePath - Source module path
|
|
672
|
+
* @param {string} targetPath - Target module path
|
|
673
|
+
* @param {Function} fileTrackingCallback - Optional callback to track installed files
|
|
674
|
+
* @param {Object} moduleConfig - Module configuration with conditional flags
|
|
675
|
+
*/
|
|
676
|
+
async copyModuleWithFiltering(sourcePath, targetPath, fileTrackingCallback = null, moduleConfig = {}) {
|
|
677
|
+
// Get all files in source
|
|
678
|
+
const sourceFiles = await this.getFileList(sourcePath);
|
|
679
|
+
|
|
680
|
+
for (const file of sourceFiles) {
|
|
681
|
+
// Skip sub-modules directory - these are IDE-specific and handled separately
|
|
682
|
+
if (file.startsWith('sub-modules/')) {
|
|
683
|
+
continue;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// Only skip sidecar directories - they are handled separately during agent compilation
|
|
687
|
+
// But still allow other files in agent directories
|
|
688
|
+
const isInAgentDirectory = file.startsWith('agents/');
|
|
689
|
+
const isInSidecarDirectory = path
|
|
690
|
+
.dirname(file)
|
|
691
|
+
.split('/')
|
|
692
|
+
.some((dir) => dir.toLowerCase().endsWith('-sidecar'));
|
|
693
|
+
|
|
694
|
+
if (isInSidecarDirectory) {
|
|
695
|
+
continue;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// Skip _module-installer directory - it's only needed at install time
|
|
699
|
+
if (file.startsWith('_module-installer/') || file === 'module.yaml') {
|
|
700
|
+
continue;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// Skip config.yaml templates - we'll generate clean ones with actual values
|
|
704
|
+
if (file === 'config.yaml' || file.endsWith('/config.yaml')) {
|
|
705
|
+
continue;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
// Skip .agent.yaml files - they will be compiled separately
|
|
709
|
+
if (file.endsWith('.agent.yaml')) {
|
|
710
|
+
continue;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
const sourceFile = path.join(sourcePath, file);
|
|
714
|
+
const targetFile = path.join(targetPath, file);
|
|
715
|
+
|
|
716
|
+
// Check if this is an agent file
|
|
717
|
+
if (file.startsWith('agents/') && file.endsWith('.md')) {
|
|
718
|
+
// Read the file to check for localskip
|
|
719
|
+
const content = await fs.readFile(sourceFile, 'utf8');
|
|
720
|
+
|
|
721
|
+
// Check for localskip="true" in the agent tag
|
|
722
|
+
const agentMatch = content.match(/<agent[^>]*\slocalskip="true"[^>]*>/);
|
|
723
|
+
if (agentMatch) {
|
|
724
|
+
console.log(chalk.dim(` Skipping web-only agent: ${path.basename(file)}`));
|
|
725
|
+
continue; // Skip this agent
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
// Check if this is a workflow.yaml file
|
|
730
|
+
if (file.endsWith('workflow.yaml')) {
|
|
731
|
+
await fs.ensureDir(path.dirname(targetFile));
|
|
732
|
+
await this.copyWorkflowYamlStripped(sourceFile, targetFile);
|
|
733
|
+
} else {
|
|
734
|
+
// Copy the file with placeholder replacement
|
|
735
|
+
await this.copyFileWithPlaceholderReplacement(sourceFile, targetFile);
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
// Track the file if callback provided
|
|
739
|
+
if (fileTrackingCallback) {
|
|
740
|
+
fileTrackingCallback(targetFile);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
/**
|
|
746
|
+
* Copy workflow.yaml file with web_bundle section stripped
|
|
747
|
+
* Preserves comments, formatting, and line breaks
|
|
748
|
+
* @param {string} sourceFile - Source workflow.yaml file path
|
|
749
|
+
* @param {string} targetFile - Target workflow.yaml file path
|
|
750
|
+
*/
|
|
751
|
+
async copyWorkflowYamlStripped(sourceFile, targetFile) {
|
|
752
|
+
// Read the source YAML file
|
|
753
|
+
let yamlContent = await fs.readFile(sourceFile, 'utf8');
|
|
754
|
+
|
|
755
|
+
// IMPORTANT: Replace escape sequence and placeholder BEFORE parsing YAML
|
|
756
|
+
// Otherwise parsing will fail on the placeholder
|
|
757
|
+
yamlContent = yamlContent.replaceAll('_bmad', '_bmad');
|
|
758
|
+
yamlContent = yamlContent.replaceAll('_bmad', this.bmadFolderName);
|
|
759
|
+
|
|
760
|
+
try {
|
|
761
|
+
// First check if web_bundle exists by parsing
|
|
762
|
+
const workflowConfig = yaml.parse(yamlContent);
|
|
763
|
+
|
|
764
|
+
if (workflowConfig.web_bundle === undefined) {
|
|
765
|
+
// No web_bundle section, just write (placeholders already replaced above)
|
|
766
|
+
await fs.writeFile(targetFile, yamlContent, 'utf8');
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
// Remove web_bundle section using regex to preserve formatting
|
|
771
|
+
// Match the web_bundle key and all its content (including nested items)
|
|
772
|
+
// This handles both web_bundle: false and web_bundle: {...}
|
|
773
|
+
|
|
774
|
+
// Find the line that starts web_bundle
|
|
775
|
+
const lines = yamlContent.split('\n');
|
|
776
|
+
let startIdx = -1;
|
|
777
|
+
let endIdx = -1;
|
|
778
|
+
let baseIndent = 0;
|
|
779
|
+
|
|
780
|
+
// Find the start of web_bundle section
|
|
781
|
+
for (const [i, line] of lines.entries()) {
|
|
782
|
+
const match = line.match(/^(\s*)web_bundle:/);
|
|
783
|
+
if (match) {
|
|
784
|
+
startIdx = i;
|
|
785
|
+
baseIndent = match[1].length;
|
|
786
|
+
break;
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
if (startIdx === -1) {
|
|
791
|
+
// web_bundle not found in text (shouldn't happen), copy as-is
|
|
792
|
+
await fs.writeFile(targetFile, yamlContent, 'utf8');
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
// Find the end of web_bundle section
|
|
797
|
+
// It ends when we find a line with same or less indentation that's not empty/comment
|
|
798
|
+
endIdx = startIdx;
|
|
799
|
+
for (let i = startIdx + 1; i < lines.length; i++) {
|
|
800
|
+
const line = lines[i];
|
|
801
|
+
|
|
802
|
+
// Skip empty lines and comments
|
|
803
|
+
if (line.trim() === '' || line.trim().startsWith('#')) {
|
|
804
|
+
continue;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
// Check indentation
|
|
808
|
+
const indent = line.match(/^(\s*)/)[1].length;
|
|
809
|
+
if (indent <= baseIndent) {
|
|
810
|
+
// Found next section at same or lower indentation
|
|
811
|
+
endIdx = i - 1;
|
|
812
|
+
break;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
// If we didn't find an end, it goes to end of file
|
|
817
|
+
if (endIdx === startIdx) {
|
|
818
|
+
endIdx = lines.length - 1;
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
// Remove the web_bundle section (including the line before if it's just a blank line)
|
|
822
|
+
const newLines = [...lines.slice(0, startIdx), ...lines.slice(endIdx + 1)];
|
|
823
|
+
|
|
824
|
+
// Clean up any double blank lines that might result
|
|
825
|
+
const strippedYaml = newLines.join('\n').replaceAll(/\n\n\n+/g, '\n\n');
|
|
826
|
+
|
|
827
|
+
// Placeholders already replaced at the beginning of this function
|
|
828
|
+
await fs.writeFile(targetFile, strippedYaml, 'utf8');
|
|
829
|
+
} catch {
|
|
830
|
+
// If anything fails, just copy the file as-is
|
|
831
|
+
console.warn(chalk.yellow(` Warning: Could not process ${path.basename(sourceFile)}, copying as-is`));
|
|
832
|
+
await fs.copy(sourceFile, targetFile, { overwrite: true });
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
/**
|
|
837
|
+
* Compile .agent.yaml files to .md format in modules
|
|
838
|
+
* @param {string} sourcePath - Source module path
|
|
839
|
+
* @param {string} targetPath - Target module path
|
|
840
|
+
* @param {string} moduleName - Module name
|
|
841
|
+
* @param {string} bmadDir - BMAD installation directory
|
|
842
|
+
* @param {Object} installer - Installer instance for file tracking
|
|
843
|
+
*/
|
|
844
|
+
async compileModuleAgents(sourcePath, targetPath, moduleName, bmadDir, installer = null) {
|
|
845
|
+
const sourceAgentsPath = path.join(sourcePath, 'agents');
|
|
846
|
+
const targetAgentsPath = path.join(targetPath, 'agents');
|
|
847
|
+
const cfgAgentsDir = path.join(bmadDir, '_config', 'agents');
|
|
848
|
+
|
|
849
|
+
// Check if agents directory exists in source
|
|
850
|
+
if (!(await fs.pathExists(sourceAgentsPath))) {
|
|
851
|
+
return; // No agents to compile
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
// Get all agent YAML files recursively
|
|
855
|
+
const agentFiles = await this.findAgentFiles(sourceAgentsPath);
|
|
856
|
+
|
|
857
|
+
for (const agentFile of agentFiles) {
|
|
858
|
+
if (!agentFile.endsWith('.agent.yaml')) continue;
|
|
859
|
+
|
|
860
|
+
const relativePath = path.relative(sourceAgentsPath, agentFile);
|
|
861
|
+
const targetDir = path.join(targetAgentsPath, path.dirname(relativePath));
|
|
862
|
+
|
|
863
|
+
await fs.ensureDir(targetDir);
|
|
864
|
+
|
|
865
|
+
const agentName = path.basename(agentFile, '.agent.yaml');
|
|
866
|
+
const sourceYamlPath = agentFile;
|
|
867
|
+
const targetMdPath = path.join(targetDir, `${agentName}.md`);
|
|
868
|
+
const customizePath = path.join(cfgAgentsDir, `${moduleName}-${agentName}.customize.yaml`);
|
|
869
|
+
|
|
870
|
+
// Read and compile the YAML
|
|
871
|
+
try {
|
|
872
|
+
const yamlContent = await fs.readFile(sourceYamlPath, 'utf8');
|
|
873
|
+
const { compileAgent } = require('../../../lib/agent/compiler');
|
|
874
|
+
|
|
875
|
+
// Create customize template if it doesn't exist
|
|
876
|
+
if (!(await fs.pathExists(customizePath))) {
|
|
877
|
+
const { getSourcePath } = require('../../../lib/project-root');
|
|
878
|
+
const genericTemplatePath = getSourcePath('utility', 'agent-components', 'agent.customize.template.yaml');
|
|
879
|
+
if (await fs.pathExists(genericTemplatePath)) {
|
|
880
|
+
await this.copyFileWithPlaceholderReplacement(genericTemplatePath, customizePath);
|
|
881
|
+
// Only show customize creation in verbose mode
|
|
882
|
+
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
|
|
883
|
+
console.log(chalk.dim(` Created customize: ${moduleName}-${agentName}.customize.yaml`));
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
// Store original hash for modification detection
|
|
887
|
+
const crypto = require('node:crypto');
|
|
888
|
+
const customizeContent = await fs.readFile(customizePath, 'utf8');
|
|
889
|
+
const originalHash = crypto.createHash('sha256').update(customizeContent).digest('hex');
|
|
890
|
+
|
|
891
|
+
// Store in main manifest
|
|
892
|
+
const manifestPath = path.join(bmadDir, '_config', 'manifest.yaml');
|
|
893
|
+
let manifestData = {};
|
|
894
|
+
if (await fs.pathExists(manifestPath)) {
|
|
895
|
+
const manifestContent = await fs.readFile(manifestPath, 'utf8');
|
|
896
|
+
const yaml = require('yaml');
|
|
897
|
+
manifestData = yaml.parse(manifestContent);
|
|
898
|
+
}
|
|
899
|
+
if (!manifestData.agentCustomizations) {
|
|
900
|
+
manifestData.agentCustomizations = {};
|
|
901
|
+
}
|
|
902
|
+
manifestData.agentCustomizations[path.relative(bmadDir, customizePath)] = originalHash;
|
|
903
|
+
|
|
904
|
+
// Write back to manifest
|
|
905
|
+
const yaml = require('yaml');
|
|
906
|
+
// Clean the manifest data to remove any non-serializable values
|
|
907
|
+
const cleanManifestData = structuredClone(manifestData);
|
|
908
|
+
|
|
909
|
+
const updatedContent = yaml.stringify(cleanManifestData, {
|
|
910
|
+
indent: 2,
|
|
911
|
+
lineWidth: 0,
|
|
912
|
+
});
|
|
913
|
+
await fs.writeFile(manifestPath, updatedContent, 'utf8');
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
// Check for customizations and build answers object
|
|
918
|
+
let customizedFields = [];
|
|
919
|
+
let answers = {};
|
|
920
|
+
if (await fs.pathExists(customizePath)) {
|
|
921
|
+
const customizeContent = await fs.readFile(customizePath, 'utf8');
|
|
922
|
+
const customizeData = yaml.parse(customizeContent);
|
|
923
|
+
customizedFields = customizeData.customized_fields || [];
|
|
924
|
+
|
|
925
|
+
// Build answers object from customizations
|
|
926
|
+
if (customizeData.persona) {
|
|
927
|
+
answers.persona = customizeData.persona;
|
|
928
|
+
}
|
|
929
|
+
if (customizeData.agent?.metadata) {
|
|
930
|
+
const filteredMetadata = filterCustomizationData(customizeData.agent.metadata);
|
|
931
|
+
if (Object.keys(filteredMetadata).length > 0) {
|
|
932
|
+
Object.assign(answers, { metadata: filteredMetadata });
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
if (customizeData.critical_actions && customizeData.critical_actions.length > 0) {
|
|
936
|
+
answers.critical_actions = customizeData.critical_actions;
|
|
937
|
+
}
|
|
938
|
+
if (customizeData.memories && customizeData.memories.length > 0) {
|
|
939
|
+
answers.memories = customizeData.memories;
|
|
940
|
+
}
|
|
941
|
+
if (customizeData.menu && customizeData.menu.length > 0) {
|
|
942
|
+
answers.menu = customizeData.menu;
|
|
943
|
+
}
|
|
944
|
+
if (customizeData.prompts && customizeData.prompts.length > 0) {
|
|
945
|
+
answers.prompts = customizeData.prompts;
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
// Check if agent has sidecar
|
|
950
|
+
let hasSidecar = false;
|
|
951
|
+
try {
|
|
952
|
+
const agentYaml = yaml.parse(yamlContent);
|
|
953
|
+
hasSidecar = agentYaml?.agent?.metadata?.hasSidecar === true;
|
|
954
|
+
} catch {
|
|
955
|
+
// Continue without sidecar processing
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
// Compile with customizations if any
|
|
959
|
+
const { xml } = await compileAgent(yamlContent, answers, agentName, relativePath, { config: this.coreConfig || {} });
|
|
960
|
+
|
|
961
|
+
// Write the compiled agent
|
|
962
|
+
await fs.writeFile(targetMdPath, xml, 'utf8');
|
|
963
|
+
|
|
964
|
+
// Handle sidecar copying if present
|
|
965
|
+
if (hasSidecar) {
|
|
966
|
+
// Get the agent's directory to look for sidecar
|
|
967
|
+
const agentDir = path.dirname(agentFile);
|
|
968
|
+
const sidecarDirName = `${agentName}-sidecar`;
|
|
969
|
+
const sourceSidecarPath = path.join(agentDir, sidecarDirName);
|
|
970
|
+
|
|
971
|
+
// Check if sidecar directory exists
|
|
972
|
+
if (await fs.pathExists(sourceSidecarPath)) {
|
|
973
|
+
// Memory is always in _bmad/_memory
|
|
974
|
+
const bmadMemoryPath = path.join(bmadDir, '_memory');
|
|
975
|
+
|
|
976
|
+
// Determine if this is an update (by checking if agent already exists)
|
|
977
|
+
const isUpdate = await fs.pathExists(targetMdPath);
|
|
978
|
+
|
|
979
|
+
// Copy sidecar to memory location with update-safe handling
|
|
980
|
+
const copiedFiles = await this.copySidecarToMemory(sourceSidecarPath, agentName, bmadMemoryPath, isUpdate, bmadDir, installer);
|
|
981
|
+
|
|
982
|
+
if (process.env.BMAD_VERBOSE_INSTALL === 'true' && copiedFiles.length > 0) {
|
|
983
|
+
console.log(chalk.dim(` Sidecar files processed: ${copiedFiles.length} files`));
|
|
984
|
+
}
|
|
985
|
+
} else if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
|
|
986
|
+
console.log(chalk.yellow(` Warning: Agent marked as having sidecar but ${sidecarDirName} directory not found`));
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
// Copy any non-sidecar files from agent directory (e.g., foo.md)
|
|
991
|
+
const agentDir = path.dirname(agentFile);
|
|
992
|
+
const agentEntries = await fs.readdir(agentDir, { withFileTypes: true });
|
|
993
|
+
|
|
994
|
+
for (const entry of agentEntries) {
|
|
995
|
+
if (entry.isFile() && !entry.name.endsWith('.agent.yaml') && !entry.name.endsWith('.md')) {
|
|
996
|
+
// Copy additional files (like foo.md) to the agent target directory
|
|
997
|
+
const sourceFile = path.join(agentDir, entry.name);
|
|
998
|
+
const targetFile = path.join(targetDir, entry.name);
|
|
999
|
+
await this.copyFileWithPlaceholderReplacement(sourceFile, targetFile);
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
// Only show compilation details in verbose mode
|
|
1004
|
+
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
|
|
1005
|
+
console.log(
|
|
1006
|
+
chalk.dim(
|
|
1007
|
+
` Compiled agent: ${agentName} -> ${path.relative(targetPath, targetMdPath)}${hasSidecar ? ' (with sidecar)' : ''}`,
|
|
1008
|
+
),
|
|
1009
|
+
);
|
|
1010
|
+
}
|
|
1011
|
+
} catch (error) {
|
|
1012
|
+
console.warn(chalk.yellow(` Failed to compile agent ${agentName}:`, error.message));
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
/**
|
|
1018
|
+
* Find all .agent.yaml files recursively in a directory
|
|
1019
|
+
* @param {string} dir - Directory to search
|
|
1020
|
+
* @returns {Array} List of .agent.yaml file paths
|
|
1021
|
+
*/
|
|
1022
|
+
async findAgentFiles(dir) {
|
|
1023
|
+
const agentFiles = [];
|
|
1024
|
+
|
|
1025
|
+
async function searchDirectory(searchDir) {
|
|
1026
|
+
const entries = await fs.readdir(searchDir, { withFileTypes: true });
|
|
1027
|
+
|
|
1028
|
+
for (const entry of entries) {
|
|
1029
|
+
const fullPath = path.join(searchDir, entry.name);
|
|
1030
|
+
|
|
1031
|
+
if (entry.isFile() && entry.name.endsWith('.agent.yaml')) {
|
|
1032
|
+
agentFiles.push(fullPath);
|
|
1033
|
+
} else if (entry.isDirectory()) {
|
|
1034
|
+
await searchDirectory(fullPath);
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
await searchDirectory(dir);
|
|
1040
|
+
return agentFiles;
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
/**
|
|
1044
|
+
* Process agent files to inject activation block
|
|
1045
|
+
* @param {string} modulePath - Path to installed module
|
|
1046
|
+
* @param {string} moduleName - Module name
|
|
1047
|
+
*/
|
|
1048
|
+
async processAgentFiles(modulePath, moduleName) {
|
|
1049
|
+
// const agentsPath = path.join(modulePath, 'agents');
|
|
1050
|
+
// // Check if agents directory exists
|
|
1051
|
+
// if (!(await fs.pathExists(agentsPath))) {
|
|
1052
|
+
// return; // No agents to process
|
|
1053
|
+
// }
|
|
1054
|
+
// // Get all agent MD files recursively
|
|
1055
|
+
// const agentFiles = await this.findAgentMdFiles(agentsPath);
|
|
1056
|
+
// for (const agentFile of agentFiles) {
|
|
1057
|
+
// if (!agentFile.endsWith('.md')) continue;
|
|
1058
|
+
// let content = await fs.readFile(agentFile, 'utf8');
|
|
1059
|
+
// // Check if content has agent XML and no activation block
|
|
1060
|
+
// if (content.includes('<agent') && !content.includes('<activation')) {
|
|
1061
|
+
// // Inject the activation block using XML handler
|
|
1062
|
+
// content = this.xmlHandler.injectActivationSimple(content);
|
|
1063
|
+
// await fs.writeFile(agentFile, content, 'utf8');
|
|
1064
|
+
// }
|
|
1065
|
+
// }
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
/**
|
|
1069
|
+
* Find all .md agent files recursively in a directory
|
|
1070
|
+
* @param {string} dir - Directory to search
|
|
1071
|
+
* @returns {Array} List of .md agent file paths
|
|
1072
|
+
*/
|
|
1073
|
+
async findAgentMdFiles(dir) {
|
|
1074
|
+
const agentFiles = [];
|
|
1075
|
+
|
|
1076
|
+
async function searchDirectory(searchDir) {
|
|
1077
|
+
const entries = await fs.readdir(searchDir, { withFileTypes: true });
|
|
1078
|
+
|
|
1079
|
+
for (const entry of entries) {
|
|
1080
|
+
const fullPath = path.join(searchDir, entry.name);
|
|
1081
|
+
|
|
1082
|
+
if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
1083
|
+
agentFiles.push(fullPath);
|
|
1084
|
+
} else if (entry.isDirectory()) {
|
|
1085
|
+
await searchDirectory(fullPath);
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
await searchDirectory(dir);
|
|
1091
|
+
return agentFiles;
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
/**
|
|
1095
|
+
* Vendor cross-module workflows referenced in agent files
|
|
1096
|
+
* Scans SOURCE agent.yaml files for workflow-install and copies workflows to destination
|
|
1097
|
+
* @param {string} sourcePath - Source module path
|
|
1098
|
+
* @param {string} targetPath - Target module path (destination)
|
|
1099
|
+
* @param {string} moduleName - Module name being installed
|
|
1100
|
+
*/
|
|
1101
|
+
async vendorCrossModuleWorkflows(sourcePath, targetPath, moduleName) {
|
|
1102
|
+
const sourceAgentsPath = path.join(sourcePath, 'agents');
|
|
1103
|
+
|
|
1104
|
+
// Check if source agents directory exists
|
|
1105
|
+
if (!(await fs.pathExists(sourceAgentsPath))) {
|
|
1106
|
+
return; // No agents to process
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
// Get all agent YAML files from source
|
|
1110
|
+
const agentFiles = await fs.readdir(sourceAgentsPath);
|
|
1111
|
+
const yamlFiles = agentFiles.filter((f) => f.endsWith('.agent.yaml') || f.endsWith('.yaml'));
|
|
1112
|
+
|
|
1113
|
+
if (yamlFiles.length === 0) {
|
|
1114
|
+
return; // No YAML agent files
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
let workflowsVendored = false;
|
|
1118
|
+
|
|
1119
|
+
for (const agentFile of yamlFiles) {
|
|
1120
|
+
const agentPath = path.join(sourceAgentsPath, agentFile);
|
|
1121
|
+
const agentYaml = yaml.parse(await fs.readFile(agentPath, 'utf8'));
|
|
1122
|
+
|
|
1123
|
+
// Check if agent has menu items with workflow-install
|
|
1124
|
+
const menuItems = agentYaml?.agent?.menu || [];
|
|
1125
|
+
const workflowInstallItems = menuItems.filter((item) => item['workflow-install']);
|
|
1126
|
+
|
|
1127
|
+
if (workflowInstallItems.length === 0) {
|
|
1128
|
+
continue; // No workflow-install in this agent
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
if (!workflowsVendored) {
|
|
1132
|
+
console.log(chalk.cyan(`\n Vendoring cross-module workflows for ${moduleName}...`));
|
|
1133
|
+
workflowsVendored = true;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
console.log(chalk.dim(` Processing: ${agentFile}`));
|
|
1137
|
+
|
|
1138
|
+
for (const item of workflowInstallItems) {
|
|
1139
|
+
const sourceWorkflowPath = item.workflow; // Where to copy FROM
|
|
1140
|
+
const installWorkflowPath = item['workflow-install']; // Where to copy TO
|
|
1141
|
+
|
|
1142
|
+
// Parse SOURCE workflow path
|
|
1143
|
+
// Handle both _bmad placeholder and hardcoded 'bmad'
|
|
1144
|
+
// Example: {project-root}/_bmad/bmm/workflows/4-implementation/create-story/workflow.yaml
|
|
1145
|
+
// Or: {project-root}/bmad/bmm/workflows/4-implementation/create-story/workflow.yaml
|
|
1146
|
+
const sourceMatch = sourceWorkflowPath.match(/\{project-root\}\/(?:_bmad)\/([^/]+)\/workflows\/(.+)/);
|
|
1147
|
+
if (!sourceMatch) {
|
|
1148
|
+
console.warn(chalk.yellow(` Could not parse workflow path: ${sourceWorkflowPath}`));
|
|
1149
|
+
continue;
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
const [, sourceModule, sourceWorkflowSubPath] = sourceMatch;
|
|
1153
|
+
|
|
1154
|
+
// Parse INSTALL workflow path
|
|
1155
|
+
// Handle_bmad
|
|
1156
|
+
// Example: {project-root}/_bmad/bmgd/workflows/4-production/create-story/workflow.yaml
|
|
1157
|
+
const installMatch = installWorkflowPath.match(/\{project-root\}\/(_bmad)\/([^/]+)\/workflows\/(.+)/);
|
|
1158
|
+
if (!installMatch) {
|
|
1159
|
+
console.warn(chalk.yellow(` Could not parse workflow-install path: ${installWorkflowPath}`));
|
|
1160
|
+
continue;
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
const installWorkflowSubPath = installMatch[2];
|
|
1164
|
+
|
|
1165
|
+
const sourceModulePath = getModulePath(sourceModule);
|
|
1166
|
+
const actualSourceWorkflowPath = path.join(sourceModulePath, 'workflows', sourceWorkflowSubPath.replace(/\/workflow\.yaml$/, ''));
|
|
1167
|
+
|
|
1168
|
+
const actualDestWorkflowPath = path.join(targetPath, 'workflows', installWorkflowSubPath.replace(/\/workflow\.yaml$/, ''));
|
|
1169
|
+
|
|
1170
|
+
// Check if source workflow exists
|
|
1171
|
+
if (!(await fs.pathExists(actualSourceWorkflowPath))) {
|
|
1172
|
+
console.warn(chalk.yellow(` Source workflow not found: ${actualSourceWorkflowPath}`));
|
|
1173
|
+
continue;
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
// Copy the entire workflow folder
|
|
1177
|
+
console.log(
|
|
1178
|
+
chalk.dim(
|
|
1179
|
+
` Vendoring: ${sourceModule}/workflows/${sourceWorkflowSubPath.replace(/\/workflow\.yaml$/, '')} → ${moduleName}/workflows/${installWorkflowSubPath.replace(/\/workflow\.yaml$/, '')}`,
|
|
1180
|
+
),
|
|
1181
|
+
);
|
|
1182
|
+
|
|
1183
|
+
await fs.ensureDir(path.dirname(actualDestWorkflowPath));
|
|
1184
|
+
// Copy the workflow directory recursively with placeholder replacement
|
|
1185
|
+
await this.copyDirectoryWithPlaceholderReplacement(actualSourceWorkflowPath, actualDestWorkflowPath);
|
|
1186
|
+
|
|
1187
|
+
// Update the workflow.yaml config_source reference
|
|
1188
|
+
const workflowYamlPath = path.join(actualDestWorkflowPath, 'workflow.yaml');
|
|
1189
|
+
if (await fs.pathExists(workflowYamlPath)) {
|
|
1190
|
+
await this.updateWorkflowConfigSource(workflowYamlPath, moduleName);
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
if (workflowsVendored) {
|
|
1196
|
+
console.log(chalk.green(` ✓ Workflow vendoring complete\n`));
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
/**
|
|
1201
|
+
* Update workflow.yaml config_source to point to new module
|
|
1202
|
+
* @param {string} workflowYamlPath - Path to workflow.yaml file
|
|
1203
|
+
* @param {string} newModuleName - New module name to reference
|
|
1204
|
+
*/
|
|
1205
|
+
async updateWorkflowConfigSource(workflowYamlPath, newModuleName) {
|
|
1206
|
+
let yamlContent = await fs.readFile(workflowYamlPath, 'utf8');
|
|
1207
|
+
|
|
1208
|
+
// Replace config_source: "{project-root}/_bmad/OLD_MODULE/config.yaml"
|
|
1209
|
+
// with config_source: "{project-root}/_bmad/NEW_MODULE/config.yaml"
|
|
1210
|
+
// Note: At this point _bmad has already been replaced with actual folder name
|
|
1211
|
+
const configSourcePattern = /config_source:\s*["']?\{project-root\}\/[^/]+\/[^/]+\/config\.yaml["']?/g;
|
|
1212
|
+
const newConfigSource = `config_source: "{project-root}/${this.bmadFolderName}/${newModuleName}/config.yaml"`;
|
|
1213
|
+
|
|
1214
|
+
const updatedYaml = yamlContent.replaceAll(configSourcePattern, newConfigSource);
|
|
1215
|
+
|
|
1216
|
+
if (updatedYaml !== yamlContent) {
|
|
1217
|
+
await fs.writeFile(workflowYamlPath, updatedYaml, 'utf8');
|
|
1218
|
+
console.log(chalk.dim(` Updated config_source to: ${this.bmadFolderName}/${newModuleName}/config.yaml`));
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
/**
|
|
1223
|
+
* Run module-specific installer if it exists
|
|
1224
|
+
* @param {string} moduleName - Name of the module
|
|
1225
|
+
* @param {string} bmadDir - Target bmad directory
|
|
1226
|
+
* @param {Object} options - Installation options
|
|
1227
|
+
*/
|
|
1228
|
+
async runModuleInstaller(moduleName, bmadDir, options = {}) {
|
|
1229
|
+
// Special handling for core module - it's in src/core not src/modules
|
|
1230
|
+
let sourcePath;
|
|
1231
|
+
if (moduleName === 'core') {
|
|
1232
|
+
sourcePath = getSourcePath('core');
|
|
1233
|
+
} else {
|
|
1234
|
+
sourcePath = await this.findModuleSource(moduleName);
|
|
1235
|
+
if (!sourcePath) {
|
|
1236
|
+
// No source found, skip module installer
|
|
1237
|
+
return;
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
const installerPath = path.join(sourcePath, '_module-installer', 'installer.js');
|
|
1242
|
+
|
|
1243
|
+
// Check if module has a custom installer
|
|
1244
|
+
if (!(await fs.pathExists(installerPath))) {
|
|
1245
|
+
return; // No custom installer
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
try {
|
|
1249
|
+
// Load the module installer
|
|
1250
|
+
const moduleInstaller = require(installerPath);
|
|
1251
|
+
|
|
1252
|
+
if (typeof moduleInstaller.install === 'function') {
|
|
1253
|
+
// Get project root (parent of bmad directory)
|
|
1254
|
+
const projectRoot = path.dirname(bmadDir);
|
|
1255
|
+
|
|
1256
|
+
// Prepare logger (use console if not provided)
|
|
1257
|
+
const logger = options.logger || {
|
|
1258
|
+
log: console.log,
|
|
1259
|
+
error: console.error,
|
|
1260
|
+
warn: console.warn,
|
|
1261
|
+
};
|
|
1262
|
+
|
|
1263
|
+
// Call the module installer
|
|
1264
|
+
const result = await moduleInstaller.install({
|
|
1265
|
+
projectRoot,
|
|
1266
|
+
config: options.moduleConfig || {},
|
|
1267
|
+
coreConfig: options.coreConfig || {},
|
|
1268
|
+
installedIDEs: options.installedIDEs || [],
|
|
1269
|
+
logger,
|
|
1270
|
+
});
|
|
1271
|
+
|
|
1272
|
+
if (!result) {
|
|
1273
|
+
console.warn(chalk.yellow(`Module installer for ${moduleName} returned false`));
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
} catch (error) {
|
|
1277
|
+
console.error(chalk.red(`Error running module installer for ${moduleName}: ${error.message}`));
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
/**
|
|
1282
|
+
* Private: Process module configuration
|
|
1283
|
+
* @param {string} modulePath - Path to installed module
|
|
1284
|
+
* @param {string} moduleName - Module name
|
|
1285
|
+
*/
|
|
1286
|
+
async processModuleConfig(modulePath, moduleName) {
|
|
1287
|
+
const configPath = path.join(modulePath, 'config.yaml');
|
|
1288
|
+
|
|
1289
|
+
if (await fs.pathExists(configPath)) {
|
|
1290
|
+
try {
|
|
1291
|
+
let configContent = await fs.readFile(configPath, 'utf8');
|
|
1292
|
+
|
|
1293
|
+
// Replace path placeholders
|
|
1294
|
+
configContent = configContent.replaceAll('{project-root}', `bmad/${moduleName}`);
|
|
1295
|
+
configContent = configContent.replaceAll('{module}', moduleName);
|
|
1296
|
+
|
|
1297
|
+
await fs.writeFile(configPath, configContent, 'utf8');
|
|
1298
|
+
} catch (error) {
|
|
1299
|
+
console.warn(`Failed to process module config:`, error.message);
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
/**
|
|
1305
|
+
* Private: Sync module files (preserving user modifications)
|
|
1306
|
+
* @param {string} sourcePath - Source module path
|
|
1307
|
+
* @param {string} targetPath - Target module path
|
|
1308
|
+
*/
|
|
1309
|
+
async syncModule(sourcePath, targetPath) {
|
|
1310
|
+
// Get list of all source files
|
|
1311
|
+
const sourceFiles = await this.getFileList(sourcePath);
|
|
1312
|
+
|
|
1313
|
+
for (const file of sourceFiles) {
|
|
1314
|
+
const sourceFile = path.join(sourcePath, file);
|
|
1315
|
+
const targetFile = path.join(targetPath, file);
|
|
1316
|
+
|
|
1317
|
+
// Check if target file exists and has been modified
|
|
1318
|
+
if (await fs.pathExists(targetFile)) {
|
|
1319
|
+
const sourceStats = await fs.stat(sourceFile);
|
|
1320
|
+
const targetStats = await fs.stat(targetFile);
|
|
1321
|
+
|
|
1322
|
+
// Skip if target is newer (user modified)
|
|
1323
|
+
if (targetStats.mtime > sourceStats.mtime) {
|
|
1324
|
+
continue;
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
// Copy file with placeholder replacement
|
|
1329
|
+
await this.copyFileWithPlaceholderReplacement(sourceFile, targetFile);
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
/**
|
|
1334
|
+
* Private: Get list of all files in a directory
|
|
1335
|
+
* @param {string} dir - Directory path
|
|
1336
|
+
* @param {string} baseDir - Base directory for relative paths
|
|
1337
|
+
* @returns {Array} List of relative file paths
|
|
1338
|
+
*/
|
|
1339
|
+
async getFileList(dir, baseDir = dir) {
|
|
1340
|
+
const files = [];
|
|
1341
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
1342
|
+
|
|
1343
|
+
for (const entry of entries) {
|
|
1344
|
+
const fullPath = path.join(dir, entry.name);
|
|
1345
|
+
|
|
1346
|
+
if (entry.isDirectory()) {
|
|
1347
|
+
// Skip _module-installer directories
|
|
1348
|
+
if (entry.name === '_module-installer') {
|
|
1349
|
+
continue;
|
|
1350
|
+
}
|
|
1351
|
+
const subFiles = await this.getFileList(fullPath, baseDir);
|
|
1352
|
+
files.push(...subFiles);
|
|
1353
|
+
} else {
|
|
1354
|
+
files.push(path.relative(baseDir, fullPath));
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
return files;
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
module.exports = { ModuleManager };
|