bmad-method 6.0.0-alpha.13 → 6.0.0-alpha.15
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 +36 -0
- package/.github/CODE_OF_CONDUCT.md +128 -0
- package/.github/ISSUE_TEMPLATE/idea_submission.md +1 -1
- package/.github/scripts/discord-helpers.sh +15 -0
- package/.github/workflows/discord.yaml +278 -8
- package/.github/workflows/quality.yaml +19 -0
- package/.markdownlint-cli2.yaml +42 -0
- package/.prettierignore +3 -0
- package/CHANGELOG.md +183 -360
- package/README.md +4 -1
- package/docs/agent-customization-guide.md +2 -2
- package/docs/custom-content-installation.md +245 -0
- package/docs/document-sharding-guide.md +1 -1
- package/docs/index.md +2 -2
- package/docs/installers-bundlers/installers-modules-platforms-reference.md +6 -5
- package/docs/web-bundles-gemini-gpt-guide.md +1 -1
- package/eslint.config.mjs +14 -0
- package/example-custom-content/README.md +8 -0
- package/{custom/src → example-custom-content}/agents/commit-poet/commit-poet.agent.yaml +1 -1
- package/{custom/src → example-custom-content}/agents/toolsmith/toolsmith-sidecar/instructions.md +5 -5
- package/{custom/src → example-custom-content}/agents/toolsmith/toolsmith-sidecar/knowledge/docs.md +1 -1
- package/{custom/src → example-custom-content}/agents/toolsmith/toolsmith-sidecar/knowledge/installers.md +1 -1
- package/{custom/src → example-custom-content}/agents/toolsmith/toolsmith-sidecar/knowledge/modules.md +2 -2
- package/{custom/src → example-custom-content}/agents/toolsmith/toolsmith-sidecar/memories.md +1 -1
- package/{custom/src → example-custom-content}/agents/toolsmith/toolsmith.agent.yaml +18 -17
- package/example-custom-content/module.yaml +4 -0
- package/example-custom-content/workflows/quiz-master/steps/step-01-init.md +168 -0
- package/example-custom-content/workflows/quiz-master/steps/step-02-q1.md +155 -0
- package/example-custom-content/workflows/quiz-master/steps/step-03-q2.md +89 -0
- package/example-custom-content/workflows/quiz-master/steps/step-04-q3.md +36 -0
- package/example-custom-content/workflows/quiz-master/steps/step-05-q4.md +36 -0
- package/example-custom-content/workflows/quiz-master/steps/step-06-q5.md +36 -0
- package/example-custom-content/workflows/quiz-master/steps/step-07-q6.md +36 -0
- package/example-custom-content/workflows/quiz-master/steps/step-08-q7.md +36 -0
- package/example-custom-content/workflows/quiz-master/steps/step-09-q8.md +36 -0
- package/example-custom-content/workflows/quiz-master/steps/step-10-q9.md +36 -0
- package/example-custom-content/workflows/quiz-master/steps/step-11-q10.md +36 -0
- package/example-custom-content/workflows/quiz-master/steps/step-12-results.md +150 -0
- package/example-custom-content/workflows/quiz-master/templates/csv-headers.template +1 -0
- package/example-custom-content/workflows/quiz-master/workflow.md +54 -0
- package/example-custom-content/workflows/wassup/workflow.md +26 -0
- package/example-custom-module/mwm/README.md +9 -0
- package/example-custom-module/mwm/agents/cbt-coach/cbt-coach-sidecar/cognitive-distortions.md +47 -0
- package/example-custom-module/mwm/agents/cbt-coach/cbt-coach-sidecar/thought-records.md +17 -0
- package/example-custom-module/mwm/agents/cbt-coach/cbt-coach.agent.yaml +151 -0
- package/example-custom-module/mwm/agents/crisis-navigator.agent.yaml +138 -0
- package/example-custom-module/mwm/agents/meditation-guide.agent.yaml +138 -0
- package/example-custom-module/mwm/agents/wellness-companion/wellness-companion-sidecar/insights.md +13 -0
- package/example-custom-module/mwm/agents/wellness-companion/wellness-companion-sidecar/instructions.md +30 -0
- package/example-custom-module/mwm/agents/wellness-companion/wellness-companion-sidecar/memories.md +13 -0
- package/example-custom-module/mwm/agents/wellness-companion/wellness-companion-sidecar/patterns.md +17 -0
- package/example-custom-module/mwm/agents/wellness-companion/wellness-companion.agent.yaml +125 -0
- package/example-custom-module/mwm/module.yaml +28 -0
- package/example-custom-module/mwm/workflows/cbt-thought-record/README.md +31 -0
- package/example-custom-module/mwm/workflows/cbt-thought-record/workflow.md +45 -0
- package/example-custom-module/mwm/workflows/crisis-support/README.md +31 -0
- package/example-custom-module/mwm/workflows/crisis-support/workflow.md +45 -0
- package/example-custom-module/mwm/workflows/daily-checkin/README.md +32 -0
- package/example-custom-module/mwm/workflows/daily-checkin/workflow.md +45 -0
- package/example-custom-module/mwm/workflows/guided-meditation/README.md +31 -0
- package/example-custom-module/mwm/workflows/guided-meditation/workflow.md +45 -0
- package/example-custom-module/mwm/workflows/wellness-journal/README.md +31 -0
- package/example-custom-module/mwm/workflows/wellness-journal/workflow.md +45 -0
- package/package.json +9 -4
- package/src/core/_module-installer/installer.js +1 -1
- package/src/core/{_module-installer/install-config.yaml → module.yaml} +5 -1
- package/src/core/resources/excalidraw/library-loader.md +2 -2
- package/src/core/workflows/brainstorming/steps/step-01-session-setup.md +1 -1
- package/src/core/workflows/brainstorming/workflow.md +1 -1
- package/src/core/workflows/party-mode/steps/step-03-graceful-exit.md +0 -1
- package/src/core/workflows/party-mode/workflow.md +2 -3
- package/src/modules/bmb/README.md +1 -1
- package/src/modules/bmb/_module-installer/installer.js +76 -0
- package/src/modules/bmb/agents/bmad-builder.agent.yaml +32 -9
- package/src/modules/bmb/docs/agents/agent-menu-patterns.md +5 -5
- package/src/modules/bmb/docs/agents/expert-agent-architecture.md +20 -20
- package/src/modules/bmb/docs/agents/index.md +1 -1
- package/src/modules/bmb/docs/agents/module-agent-architecture.md +45 -45
- package/src/modules/bmb/docs/agents/simple-agent-architecture.md +7 -3
- package/src/modules/bmb/docs/workflows/architecture.md +1 -1
- package/src/modules/bmb/docs/workflows/templates/step-01-init-continuable-template.md +241 -0
- package/src/modules/bmb/docs/workflows/templates/step-1b-template.md +223 -0
- package/src/modules/bmb/{workflows/create-workflow → docs/workflows}/templates/step-file.md +4 -4
- package/src/modules/bmb/docs/workflows/{step-template.md → templates/step-template.md} +40 -33
- package/src/modules/bmb/docs/workflows/templates/workflow-template.md +104 -0
- package/src/modules/bmb/{workflows/create-workflow → docs/workflows}/templates/workflow.md +1 -1
- package/src/modules/bmb/{_module-installer/install-config.yaml → module.yaml} +4 -9
- package/src/modules/bmb/reference/agents/expert-examples/journal-keeper/README.md +4 -4
- package/src/modules/bmb/reference/agents/expert-examples/journal-keeper/journal-keeper.agent.yaml +8 -8
- package/src/modules/bmb/reference/agents/module-examples/security-engineer.agent.yaml +6 -6
- package/src/modules/bmb/reference/agents/module-examples/trend-analyst.agent.yaml +7 -7
- package/src/modules/bmb/reference/workflows/meal-prep-nutrition/steps/step-01-init.md +2 -3
- package/src/modules/bmb/reference/workflows/meal-prep-nutrition/steps/step-01b-continue.md +10 -40
- package/src/modules/bmb/reference/workflows/meal-prep-nutrition/steps/step-02-profile.md +1 -1
- package/src/modules/bmb/reference/workflows/meal-prep-nutrition/steps/step-03-assessment.md +1 -0
- package/src/modules/bmb/reference/workflows/meal-prep-nutrition/steps/step-04-strategy.md +2 -2
- package/src/modules/bmb/reference/workflows/meal-prep-nutrition/steps/step-05-shopping.md +2 -2
- package/src/modules/bmb/reference/workflows/meal-prep-nutrition/steps/step-06-prep-schedule.md +2 -2
- package/src/modules/bmb/reference/workflows/meal-prep-nutrition/workflow.md +2 -2
- package/src/modules/bmb/workflows/create-agent/data/info-and-installation-guide.md +16 -4
- package/src/modules/bmb/workflows/create-agent/data/reference/agents/expert-examples/journal-keeper/README.md +4 -4
- package/src/modules/bmb/workflows/create-agent/data/reference/agents/expert-examples/journal-keeper/journal-keeper.agent.yaml +7 -7
- package/src/modules/bmb/workflows/create-agent/data/reference/workflows/meal-prep-nutrition/steps/step-05-shopping.md +1 -1
- package/src/modules/bmb/workflows/create-agent/data/validation-complete.md +3 -3
- package/src/modules/bmb/workflows/create-agent/steps/step-01-brainstorm.md +3 -3
- package/src/modules/bmb/workflows/create-agent/steps/step-02-discover.md +3 -3
- package/src/modules/bmb/workflows/create-agent/steps/step-03-persona.md +3 -3
- package/src/modules/bmb/workflows/create-agent/steps/step-04-commands.md +6 -6
- package/src/modules/bmb/workflows/create-agent/steps/step-05-name.md +2 -2
- package/src/modules/bmb/workflows/create-agent/steps/step-06-build.md +3 -3
- package/src/modules/bmb/workflows/create-agent/steps/step-07-validate.md +3 -3
- package/src/modules/bmb/workflows/create-agent/steps/step-08-setup.md +2 -2
- package/src/modules/bmb/workflows/create-agent/steps/step-09-customize.md +3 -3
- package/src/modules/bmb/workflows/create-agent/steps/step-10-build-tools.md +2 -2
- package/src/modules/bmb/workflows/create-agent/steps/step-11-celebrate.md +2 -2
- package/src/modules/bmb/workflows/create-agent/workflow.md +11 -11
- package/src/modules/bmb/workflows/create-module/steps/step-01-init.md +155 -0
- package/src/modules/bmb/workflows/create-module/steps/step-01b-continue.md +169 -0
- package/src/modules/bmb/workflows/create-module/steps/step-02-concept.md +217 -0
- package/src/modules/bmb/workflows/create-module/steps/step-03-components.md +267 -0
- package/src/modules/bmb/workflows/create-module/steps/step-04-structure.md +228 -0
- package/src/modules/bmb/workflows/create-module/steps/step-05-config.md +233 -0
- package/src/modules/bmb/workflows/create-module/steps/step-06-agents.md +296 -0
- package/src/modules/bmb/workflows/create-module/steps/step-07-workflows.md +228 -0
- package/src/modules/bmb/workflows/create-module/steps/step-08-installer.md +186 -0
- package/src/modules/bmb/workflows/create-module/steps/step-09-documentation.md +309 -0
- package/src/modules/bmb/workflows/create-module/steps/step-10-roadmap.md +337 -0
- package/src/modules/bmb/workflows/create-module/steps/step-11-validate.md +335 -0
- package/src/modules/bmb/workflows/create-module/templates/agent.template.md +317 -0
- package/src/modules/bmb/workflows/create-module/templates/installer.template.js +47 -0
- package/src/modules/bmb/workflows/create-module/templates/module-plan.template.md +5 -0
- package/src/modules/bmb/workflows/create-module/templates/module.template.yaml +53 -0
- package/src/modules/bmb/workflows/create-module/templates/workflow-plan-template.md +23 -0
- package/src/modules/bmb/workflows/create-module/validation.md +126 -0
- package/src/modules/bmb/workflows/create-module/workflow.md +55 -0
- package/src/modules/bmb/workflows/create-workflow/steps/step-01-init.md +45 -56
- package/src/modules/bmb/workflows/create-workflow/steps/step-02-gather.md +9 -31
- package/src/modules/bmb/workflows/create-workflow/steps/step-03-tools-configuration.md +250 -0
- package/src/modules/bmb/workflows/create-workflow/steps/step-04-plan-review.md +216 -0
- package/src/modules/bmb/workflows/create-workflow/steps/step-05-output-format-design.md +289 -0
- package/src/modules/bmb/workflows/create-workflow/steps/{step-09-design.md → step-06-design.md} +76 -44
- package/src/modules/bmb/workflows/create-workflow/steps/{step-11-build.md → step-07-build.md} +71 -25
- package/src/modules/bmb/workflows/create-workflow/steps/{step-12-review.md → step-08-review.md} +30 -16
- package/src/modules/bmb/workflows/create-workflow/steps/step-09-complete.md +187 -0
- package/src/modules/bmb/workflows/create-workflow/workflow.md +2 -2
- package/src/modules/bmb/workflows/edit-agent/steps/step-01-discover-intent.md +2 -2
- package/src/modules/bmb/workflows/edit-agent/steps/step-02-analyze-agent.md +14 -14
- package/src/modules/bmb/workflows/edit-agent/steps/step-03-propose-changes.md +4 -4
- package/src/modules/bmb/workflows/edit-agent/steps/step-04-apply-changes.md +2 -2
- package/src/modules/bmb/workflows/edit-agent/steps/step-05-validate.md +4 -4
- package/src/modules/bmb/workflows/edit-agent/workflow.md +1 -1
- package/src/modules/bmb/workflows/edit-workflow/steps/step-01-analyze.md +2 -6
- package/src/modules/bmb/workflows/edit-workflow/steps/step-03-improve.md +2 -2
- package/src/modules/bmb/workflows/edit-workflow/steps/step-04-validate.md +1 -1
- package/src/modules/bmb/workflows/edit-workflow/workflow.md +1 -1
- package/src/modules/bmb/workflows/workflow-compliance-check/steps/step-01-validate-goal.md +2 -2
- package/src/modules/bmb/workflows/workflow-compliance-check/steps/step-02-workflow-validation.md +5 -5
- package/src/modules/bmb/workflows/workflow-compliance-check/steps/step-03-step-validation.md +7 -7
- package/src/modules/bmb/workflows/workflow-compliance-check/steps/step-04-file-validation.md +3 -3
- package/src/modules/bmb/workflows/workflow-compliance-check/steps/step-05-intent-spectrum-validation.md +3 -3
- package/src/modules/bmb/workflows/workflow-compliance-check/steps/step-06-web-subprocess-validation.md +3 -3
- package/src/modules/bmb/workflows/workflow-compliance-check/steps/step-07-holistic-analysis.md +3 -3
- package/src/modules/bmb/workflows/workflow-compliance-check/steps/step-08-generate-report.md +2 -2
- package/src/modules/bmb/workflows/workflow-compliance-check/workflow.md +1 -1
- package/src/modules/bmb/workflows-legacy/edit-module/checklist.md +0 -1
- package/src/modules/bmgd/README.md +2 -1
- package/src/modules/bmgd/workflows/3-technical/game-architecture/instructions.md +8 -8
- package/src/modules/bmm/_module-installer/installer.js +1 -1
- package/src/modules/bmm/_module-installer/platform-specifics/claude-code.js +1 -1
- package/src/modules/bmm/_module-installer/platform-specifics/windsurf.js +1 -1
- package/src/modules/bmm/agents/analyst.agent.yaml +11 -8
- package/src/modules/bmm/agents/architect.agent.yaml +1 -5
- package/src/modules/bmm/agents/pm.agent.yaml +5 -5
- package/src/modules/bmm/docs/README.md +23 -1
- package/src/modules/bmm/docs/agents-guide.md +16 -35
- package/src/modules/bmm/docs/brownfield-guide.md +17 -30
- package/src/modules/bmm/docs/enterprise-agentic-development.md +2 -2
- package/src/modules/bmm/docs/faq.md +6 -39
- package/src/modules/bmm/docs/glossary.md +11 -24
- package/src/modules/bmm/docs/images/README.md +37 -0
- package/src/modules/bmm/docs/images/workflow-method-greenfield.excalidraw +62 -202
- package/src/modules/bmm/docs/images/workflow-method-greenfield.svg +3 -1
- package/src/modules/bmm/docs/quick-spec-flow.md +652 -0
- package/src/modules/bmm/docs/quick-start.md +9 -25
- package/src/modules/bmm/docs/test-architecture.md +6 -6
- package/src/modules/bmm/docs/troubleshooting.md +680 -0
- package/src/modules/bmm/docs/workflow-document-project-reference.md +1 -1
- package/src/modules/bmm/docs/workflows-implementation.md +143 -3
- package/src/modules/bmm/docs/workflows-solutioning.md +2 -2
- package/src/modules/bmm/{_module-installer/install-config.yaml → module.yaml} +1 -1
- package/src/modules/bmm/tasks/daily-standup.xml +85 -0
- package/src/modules/bmm/testarch/knowledge/ci-burn-in.md +1 -1
- package/src/modules/bmm/testarch/knowledge/overview.md +1 -1
- package/src/modules/bmm/workflows/1-analysis/product-brief/steps/step-02-vision.md +2 -2
- package/src/modules/bmm/workflows/1-analysis/product-brief/steps/step-03-users.md +2 -2
- package/src/modules/bmm/workflows/1-analysis/product-brief/steps/step-04-metrics.md +2 -2
- package/src/modules/bmm/workflows/1-analysis/product-brief/steps/step-05-scope.md +2 -2
- package/src/modules/bmm/workflows/1-analysis/product-brief/workflow.md +1 -1
- package/src/modules/bmm/workflows/1-analysis/research/domain-steps/step-01-init.md +8 -8
- package/src/modules/bmm/workflows/1-analysis/research/domain-steps/step-02-domain-analysis.md +18 -18
- package/src/modules/bmm/workflows/1-analysis/research/domain-steps/step-03-competitive-landscape.md +18 -18
- package/src/modules/bmm/workflows/1-analysis/research/domain-steps/step-04-regulatory-focus.md +18 -18
- package/src/modules/bmm/workflows/1-analysis/research/domain-steps/step-05-technical-trends.md +17 -17
- package/src/modules/bmm/workflows/1-analysis/research/domain-steps/step-06-research-synthesis.md +35 -36
- package/src/modules/bmm/workflows/1-analysis/research/market-steps/step-01-init.md +5 -6
- package/src/modules/bmm/workflows/1-analysis/research/market-steps/step-02-customer-behavior.md +20 -19
- package/src/modules/bmm/workflows/1-analysis/research/market-steps/step-02-customer-insights.md +21 -20
- package/src/modules/bmm/workflows/1-analysis/research/market-steps/step-03-customer-pain-points.md +20 -19
- package/src/modules/bmm/workflows/1-analysis/research/market-steps/step-04-customer-decisions.md +21 -20
- package/src/modules/bmm/workflows/1-analysis/research/market-steps/step-05-competitive-analysis.md +16 -15
- package/src/modules/bmm/workflows/1-analysis/research/market-steps/step-06-research-completion.md +36 -37
- package/src/modules/bmm/workflows/1-analysis/research/research.template.md +0 -1
- package/src/modules/bmm/workflows/1-analysis/research/technical-steps/step-01-init.md +8 -8
- package/src/modules/bmm/workflows/1-analysis/research/technical-steps/step-02-technical-overview.md +19 -18
- package/src/modules/bmm/workflows/1-analysis/research/technical-steps/step-03-integration-patterns.md +20 -19
- package/src/modules/bmm/workflows/1-analysis/research/technical-steps/step-04-architectural-patterns.md +21 -20
- package/src/modules/bmm/workflows/1-analysis/research/technical-steps/step-05-implementation-research.md +19 -18
- package/src/modules/bmm/workflows/1-analysis/research/technical-steps/step-06-research-synthesis.md +38 -39
- package/src/modules/bmm/workflows/1-analysis/research/workflow.md +14 -8
- package/src/modules/bmm/workflows/2-plan-workflows/create-ux-design/workflow.md +6 -0
- package/src/modules/bmm/workflows/2-plan-workflows/prd/prd-template.md +7 -0
- package/src/modules/bmm/workflows/2-plan-workflows/prd/steps/step-01-init.md +138 -56
- package/src/modules/bmm/workflows/2-plan-workflows/prd/steps/step-01b-continue.md +93 -51
- package/src/modules/bmm/workflows/2-plan-workflows/prd/steps/step-02-discovery.md +223 -78
- package/src/modules/bmm/workflows/2-plan-workflows/prd/steps/step-03-success.md +20 -2
- package/src/modules/bmm/workflows/2-plan-workflows/prd/steps/step-04-journeys.md +18 -0
- package/src/modules/bmm/workflows/2-plan-workflows/prd/steps/step-05-domain.md +21 -0
- package/src/modules/bmm/workflows/2-plan-workflows/prd/steps/step-06-innovation.md +21 -0
- package/src/modules/bmm/workflows/2-plan-workflows/prd/steps/step-07-project-type.md +21 -0
- package/src/modules/bmm/workflows/2-plan-workflows/prd/steps/step-08-scoping.md +18 -0
- package/src/modules/bmm/workflows/2-plan-workflows/prd/steps/step-09-functional.md +18 -0
- package/src/modules/bmm/workflows/2-plan-workflows/prd/steps/step-10-nonfunctional.md +18 -0
- package/src/modules/bmm/workflows/2-plan-workflows/prd/steps/step-11-complete.md +13 -0
- package/src/modules/bmm/workflows/2-plan-workflows/prd/workflow.md +2 -2
- package/src/modules/bmm/workflows/3-solutioning/architecture/steps/step-03-starter.md +14 -14
- package/src/modules/bmm/workflows/3-solutioning/architecture/steps/step-04-decisions.md +7 -7
- package/src/modules/bmm/workflows/3-solutioning/architecture/workflow.md +2 -1
- package/src/modules/bmm/workflows/3-solutioning/create-epics-and-stories/steps/step-01-validate-prerequisites.md +258 -0
- package/src/modules/bmm/workflows/3-solutioning/create-epics-and-stories/steps/step-02-design-epics.md +232 -0
- package/src/modules/bmm/workflows/3-solutioning/create-epics-and-stories/steps/step-03-create-stories.md +271 -0
- package/src/modules/bmm/workflows/3-solutioning/create-epics-and-stories/steps/step-04-final-validation.md +144 -0
- package/src/modules/bmm/workflows/3-solutioning/create-epics-and-stories/templates/epics-template.md +57 -0
- package/src/modules/bmm/workflows/3-solutioning/create-epics-and-stories/workflow.md +58 -0
- package/src/modules/bmm/workflows/3-solutioning/implementation-readiness/steps/step-01-document-discovery.md +189 -0
- package/src/modules/bmm/workflows/3-solutioning/implementation-readiness/steps/step-02-prd-analysis.md +177 -0
- package/src/modules/bmm/workflows/3-solutioning/implementation-readiness/steps/step-03-epic-coverage-validation.md +178 -0
- package/src/modules/bmm/workflows/3-solutioning/implementation-readiness/steps/step-04-ux-alignment.md +138 -0
- package/src/modules/bmm/workflows/3-solutioning/implementation-readiness/steps/step-05-epic-quality-review.md +251 -0
- package/src/modules/bmm/workflows/3-solutioning/implementation-readiness/steps/step-06-final-assessment.md +132 -0
- package/src/modules/bmm/workflows/3-solutioning/implementation-readiness/templates/readiness-report-template.md +4 -0
- package/src/modules/bmm/workflows/3-solutioning/implementation-readiness/workflow.md +54 -0
- package/src/modules/{bmgd/workflows/4-production → bmm/workflows/4-implementation}/code-review/checklist.md +2 -1
- package/src/modules/bmm/workflows/4-implementation/code-review/instructions.xml +51 -3
- package/src/modules/bmm/workflows/4-implementation/code-review/workflow.yaml +1 -1
- package/src/modules/bmm/workflows/4-implementation/create-story/instructions.xml +32 -2
- package/src/modules/bmm/workflows/4-implementation/retrospective/instructions.md +3 -3
- package/src/modules/bmm/workflows/4-implementation/sprint-planning/instructions.md +19 -21
- package/src/modules/bmm/workflows/4-implementation/sprint-planning/sprint-status-template.yaml +10 -10
- package/src/modules/bmm/workflows/4-implementation/sprint-status/instructions.md +174 -0
- package/src/modules/bmm/workflows/4-implementation/sprint-status/workflow.yaml +35 -0
- package/src/modules/bmm/workflows/bmad-quick-flow/quick-dev/instructions.md +104 -7
- package/src/modules/bmm/workflows/bmad-quick-flow/quick-dev/workflow.yaml +4 -0
- package/src/modules/bmm/workflows/document-project/instructions.md +1 -1
- package/src/modules/bmm/workflows/document-project/workflows/deep-dive-instructions.md +2 -2
- package/src/modules/bmm/workflows/generate-project-context/workflow.md +1 -1
- package/src/modules/bmm/workflows/testarch/atdd/atdd-checklist-template.md +1 -1
- package/src/modules/bmm/workflows/testarch/ci/checklist.md +1 -1
- package/src/modules/bmm/workflows/testarch/ci/github-actions-template.yaml +36 -3
- package/src/modules/bmm/workflows/testarch/ci/gitlab-ci-template.yaml +25 -4
- package/src/modules/bmm/workflows/testarch/ci/instructions.md +2 -2
- package/src/modules/bmm/workflows/testarch/test-review/instructions.md +1 -1
- package/src/modules/bmm/workflows/workflow-status/paths/enterprise-brownfield.yaml +1 -6
- package/src/modules/bmm/workflows/workflow-status/paths/enterprise-greenfield.yaml +1 -6
- package/src/modules/bmm/workflows/workflow-status/paths/method-brownfield.yaml +1 -6
- package/src/modules/bmm/workflows/workflow-status/paths/method-greenfield.yaml +1 -7
- package/src/modules/cis/_module-installer/installer.js +1 -1
- package/tools/cli/README.md +7 -7
- package/tools/cli/commands/build.js +9 -184
- package/tools/cli/commands/install.js +1 -6
- package/tools/cli/installers/lib/core/config-collector.js +80 -12
- package/tools/cli/installers/lib/core/custom-module-cache.js +239 -0
- package/tools/cli/installers/lib/core/detector.js +8 -4
- package/tools/cli/installers/lib/core/installer.js +933 -376
- package/tools/cli/installers/lib/core/manifest-generator.js +265 -41
- package/tools/cli/installers/lib/core/manifest.js +47 -0
- package/tools/cli/installers/lib/core/post-install-sidecar-replacement.js +79 -0
- package/tools/cli/installers/lib/custom/handler.js +396 -0
- package/tools/cli/installers/lib/ide/_base-ide.js +10 -0
- package/tools/cli/installers/lib/ide/auggie.js +19 -7
- package/tools/cli/installers/lib/ide/crush.js +19 -6
- package/tools/cli/installers/lib/ide/cursor.js +29 -13
- package/tools/cli/installers/lib/ide/gemini.js +49 -1
- package/tools/cli/installers/lib/ide/iflow.js +20 -1
- package/tools/cli/installers/lib/ide/kiro-cli.js +327 -0
- package/tools/cli/installers/lib/ide/opencode.js +3 -3
- package/tools/cli/installers/lib/ide/roo.js +120 -184
- package/tools/cli/installers/lib/ide/rovo-dev.js +1 -1
- package/tools/cli/installers/lib/ide/shared/agent-command-generator.js +8 -2
- package/tools/cli/installers/lib/ide/shared/bmad-artifacts.js +34 -19
- package/tools/cli/installers/lib/ide/shared/workflow-command-generator.js +18 -14
- package/tools/cli/installers/lib/ide/templates/agent-command-template.md +1 -1
- package/tools/cli/installers/lib/ide/templates/workflow-commander.md +5 -0
- package/tools/cli/installers/lib/modules/manager.js +535 -56
- package/tools/cli/lib/agent/compiler.js +57 -16
- package/tools/cli/lib/agent/installer.js +129 -28
- package/tools/cli/lib/cli-utils.js +21 -4
- package/tools/cli/lib/config.js +2 -1
- package/tools/cli/lib/ui.js +561 -12
- package/tools/cli/lib/yaml-xml-builder.js +0 -15
- 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/schema/agent.js +149 -89
- package/tools/validate-svg-changes.sh +356 -0
- package/custom/src/agents/commit-poet/installation-guide.md +0 -36
- package/custom/src/agents/toolsmith/installation-guide.md +0 -36
- package/docs/custom-agent-installation.md +0 -183
- package/src/modules/bmb/docs/workflows/workflow-template.md +0 -152
- package/src/modules/bmb/workflows/create-workflow/steps/step-03-tools-overview.md +0 -127
- package/src/modules/bmb/workflows/create-workflow/steps/step-04-core-tools.md +0 -145
- package/src/modules/bmb/workflows/create-workflow/steps/step-05-memory-requirements.md +0 -136
- package/src/modules/bmb/workflows/create-workflow/steps/step-06-external-tools.md +0 -154
- package/src/modules/bmb/workflows/create-workflow/steps/step-07-installation-guidance.md +0 -159
- package/src/modules/bmb/workflows/create-workflow/steps/step-08-tools-summary.md +0 -167
- package/src/modules/bmb/workflows/create-workflow/steps/step-10-plan-review.md +0 -215
- package/src/modules/bmb/workflows/create-workflow/templates/build-summary.md +0 -36
- package/src/modules/bmb/workflows/create-workflow/templates/completion-section.md +0 -39
- package/src/modules/bmb/workflows/create-workflow/templates/content-template.md +0 -21
- package/src/modules/bmb/workflows/create-workflow/templates/design-section.md +0 -53
- package/src/modules/bmb/workflows/create-workflow/templates/project-info.md +0 -18
- package/src/modules/bmb/workflows/create-workflow/templates/requirements-section.md +0 -47
- package/src/modules/bmb/workflows/create-workflow/templates/review-section.md +0 -56
- package/src/modules/bmb/workflows/create-workflow/templates/workflow-plan.md +0 -54
- package/src/modules/bmb/workflows-legacy/create-module/README.md +0 -229
- package/src/modules/bmb/workflows-legacy/create-module/brainstorm-context.md +0 -137
- package/src/modules/bmb/workflows-legacy/create-module/checklist.md +0 -235
- package/src/modules/bmb/workflows-legacy/create-module/installer-templates/install-config.yaml +0 -92
- package/src/modules/bmb/workflows-legacy/create-module/installer-templates/installer.js +0 -231
- package/src/modules/bmb/workflows-legacy/create-module/instructions.md +0 -577
- package/src/modules/bmb/workflows-legacy/create-module/module-structure.md +0 -400
- package/src/modules/bmb/workflows-legacy/create-module/workflow.yaml +0 -52
- package/src/modules/bmm/workflows/3-solutioning/create-epics-and-stories/epics-template.md +0 -80
- package/src/modules/bmm/workflows/3-solutioning/create-epics-and-stories/instructions.md +0 -387
- package/src/modules/bmm/workflows/3-solutioning/create-epics-and-stories/workflow.yaml +0 -53
- package/src/modules/bmm/workflows/3-solutioning/implementation-readiness/checklist.md +0 -169
- package/src/modules/bmm/workflows/3-solutioning/implementation-readiness/instructions.md +0 -332
- package/src/modules/bmm/workflows/3-solutioning/implementation-readiness/template.md +0 -146
- package/src/modules/bmm/workflows/3-solutioning/implementation-readiness/workflow.yaml +0 -64
- package/tools/cli/commands/agent-install.js +0 -409
- package/tools/cli/commands/cleanup.js +0 -141
- /package/{custom/src → example-custom-content}/agents/toolsmith/toolsmith-sidecar/knowledge/bundlers.md +0 -0
- /package/{custom/src → example-custom-content}/agents/toolsmith/toolsmith-sidecar/knowledge/deploy.md +0 -0
- /package/{custom/src → example-custom-content}/agents/toolsmith/toolsmith-sidecar/knowledge/tests.md +0 -0
- /package/src/modules/bmgd/{_module-installer/install-config.yaml → module.yaml} +0 -0
- /package/src/modules/cis/{_module-installer/install-config.yaml → module.yaml} +0 -0
|
@@ -22,6 +22,7 @@ const path = require('node:path');
|
|
|
22
22
|
const fs = require('fs-extra');
|
|
23
23
|
const chalk = require('chalk');
|
|
24
24
|
const ora = require('ora');
|
|
25
|
+
const inquirer = require('inquirer');
|
|
25
26
|
const { Detector } = require('./detector');
|
|
26
27
|
const { Manifest } = require('./manifest');
|
|
27
28
|
const { ModuleManager } = require('../modules/manager');
|
|
@@ -37,6 +38,7 @@ const { AgentPartyGenerator } = require('../../../lib/agent-party-generator');
|
|
|
37
38
|
const { CLIUtils } = require('../../../lib/cli-utils');
|
|
38
39
|
const { ManifestGenerator } = require('./manifest-generator');
|
|
39
40
|
const { IdeConfigManager } = require('./ide-config-manager');
|
|
41
|
+
const { replaceAgentSidecarFolders } = require('./post-install-sidecar-replacement');
|
|
40
42
|
|
|
41
43
|
class Installer {
|
|
42
44
|
constructor() {
|
|
@@ -51,6 +53,7 @@ class Installer {
|
|
|
51
53
|
this.configCollector = new ConfigCollector();
|
|
52
54
|
this.ideConfigManager = new IdeConfigManager();
|
|
53
55
|
this.installedFiles = []; // Track all installed files
|
|
56
|
+
this.ttsInjectedFiles = []; // Track files with TTS injection applied
|
|
54
57
|
}
|
|
55
58
|
|
|
56
59
|
/**
|
|
@@ -127,7 +130,7 @@ class Installer {
|
|
|
127
130
|
*/
|
|
128
131
|
async copyFileWithPlaceholderReplacement(sourcePath, targetPath, bmadFolderName) {
|
|
129
132
|
// List of text file extensions that should have placeholder replacement
|
|
130
|
-
const textExtensions = ['.md', '.yaml', '.yml', '.txt', '.json', '.js', '.ts', '.html', '.css', '.sh', '.bat', '.csv'];
|
|
133
|
+
const textExtensions = ['.md', '.yaml', '.yml', '.txt', '.json', '.js', '.ts', '.html', '.css', '.sh', '.bat', '.csv', '.xml'];
|
|
131
134
|
const ext = path.extname(sourcePath).toLowerCase();
|
|
132
135
|
|
|
133
136
|
// Check if this is a text file that might contain placeholders
|
|
@@ -141,8 +144,13 @@ class Installer {
|
|
|
141
144
|
content = content.replaceAll('{bmad_folder}', bmadFolderName);
|
|
142
145
|
}
|
|
143
146
|
|
|
144
|
-
//
|
|
145
|
-
content
|
|
147
|
+
// Replace escape sequence {*bmad_folder*} with literal {bmad_folder}
|
|
148
|
+
if (content.includes('{*bmad_folder*}')) {
|
|
149
|
+
content = content.replaceAll('{*bmad_folder*}', '{bmad_folder}');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Process AgentVibes injection points (pass targetPath for tracking)
|
|
153
|
+
content = this.processTTSInjectionPoints(content, targetPath);
|
|
146
154
|
|
|
147
155
|
// Write to target with replaced content
|
|
148
156
|
await fs.ensureDir(path.dirname(targetPath));
|
|
@@ -221,10 +229,14 @@ class Installer {
|
|
|
221
229
|
* - src/modules/bmm/agents/*.md (rules sections)
|
|
222
230
|
* - TTS Hook: .claude/hooks/bmad-speak.sh (in AgentVibes repo)
|
|
223
231
|
*/
|
|
224
|
-
processTTSInjectionPoints(content) {
|
|
232
|
+
processTTSInjectionPoints(content, targetPath = null) {
|
|
225
233
|
// Check if AgentVibes is enabled (set during installation configuration)
|
|
226
234
|
const enableAgentVibes = this.enableAgentVibes || false;
|
|
227
235
|
|
|
236
|
+
// Check if content contains any TTS injection markers
|
|
237
|
+
const hasPartyMode = content.includes('<!-- TTS_INJECTION:party-mode -->');
|
|
238
|
+
const hasAgentTTS = content.includes('<!-- TTS_INJECTION:agent-tts -->');
|
|
239
|
+
|
|
228
240
|
if (enableAgentVibes) {
|
|
229
241
|
// Replace party-mode injection marker with actual TTS call
|
|
230
242
|
// Use single quotes to prevent shell expansion of special chars like !
|
|
@@ -248,6 +260,12 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
|
|
248
260
|
IMPORTANT: Use single quotes as shown - do NOT escape special characters like ! or $ inside single quotes
|
|
249
261
|
Run in background (&) to avoid blocking`,
|
|
250
262
|
);
|
|
263
|
+
|
|
264
|
+
// Track files that had TTS injection applied
|
|
265
|
+
if (targetPath && (hasPartyMode || hasAgentTTS)) {
|
|
266
|
+
const injectionType = hasPartyMode ? 'party-mode' : 'agent-tts';
|
|
267
|
+
this.ttsInjectedFiles.push({ path: targetPath, type: injectionType });
|
|
268
|
+
}
|
|
251
269
|
} else {
|
|
252
270
|
// Strip injection markers cleanly when AgentVibes is disabled
|
|
253
271
|
content = content.replaceAll(/<!-- TTS_INJECTION:party-mode -->\n?/g, '');
|
|
@@ -430,6 +448,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
|
|
430
448
|
|
|
431
449
|
// Set bmad folder name on module manager and IDE manager for placeholder replacement
|
|
432
450
|
this.moduleManager.setBmadFolderName(bmadFolderName);
|
|
451
|
+
this.moduleManager.setCoreConfig(moduleConfigs.core || {});
|
|
433
452
|
this.ideManager.setBmadFolderName(bmadFolderName);
|
|
434
453
|
|
|
435
454
|
// Tool selection will be collected after we determine if it's a reinstall/update/new install
|
|
@@ -732,13 +751,81 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
|
|
732
751
|
spinner.text = 'Creating directory structure...';
|
|
733
752
|
await this.createDirectoryStructure(bmadDir);
|
|
734
753
|
|
|
735
|
-
//
|
|
736
|
-
spinner.text = 'Resolving dependencies...';
|
|
754
|
+
// Get project root
|
|
737
755
|
const projectRoot = getProjectRoot();
|
|
738
|
-
|
|
756
|
+
|
|
757
|
+
// Step 1: Install core module first (if requested)
|
|
758
|
+
if (config.installCore) {
|
|
759
|
+
spinner.start('Installing BMAD core...');
|
|
760
|
+
await this.installCoreWithDependencies(bmadDir, { core: {} });
|
|
761
|
+
spinner.succeed('Core installed');
|
|
762
|
+
|
|
763
|
+
// Generate core config file
|
|
764
|
+
await this.generateModuleConfigs(bmadDir, { core: config.coreConfig || {} });
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
// Custom content is already handled in UI before module selection
|
|
768
|
+
let finalCustomContent = config.customContent;
|
|
769
|
+
|
|
770
|
+
// Step 3: Prepare modules list including cached custom modules
|
|
771
|
+
let allModules = [...(config.modules || [])];
|
|
772
|
+
|
|
773
|
+
// During quick update, we might have custom module sources from the manifest
|
|
774
|
+
if (config._customModuleSources) {
|
|
775
|
+
// Add custom modules from stored sources
|
|
776
|
+
for (const [moduleId, customInfo] of config._customModuleSources) {
|
|
777
|
+
if (!allModules.includes(moduleId) && (await fs.pathExists(customInfo.sourcePath))) {
|
|
778
|
+
allModules.push(moduleId);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
// Add cached custom modules
|
|
784
|
+
if (finalCustomContent && finalCustomContent.cachedModules) {
|
|
785
|
+
for (const cachedModule of finalCustomContent.cachedModules) {
|
|
786
|
+
if (!allModules.includes(cachedModule.id)) {
|
|
787
|
+
allModules.push(cachedModule.id);
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// Regular custom content from user input (non-cached)
|
|
793
|
+
if (finalCustomContent && finalCustomContent.selected && finalCustomContent.selectedFiles) {
|
|
794
|
+
// Add custom modules to the installation list
|
|
795
|
+
for (const customFile of finalCustomContent.selectedFiles) {
|
|
796
|
+
const { CustomHandler } = require('../custom/handler');
|
|
797
|
+
const customHandler = new CustomHandler();
|
|
798
|
+
const customInfo = await customHandler.getCustomInfo(customFile, projectDir);
|
|
799
|
+
if (customInfo && customInfo.id) {
|
|
800
|
+
allModules.push(customInfo.id);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// Don't include core again if already installed
|
|
806
|
+
if (config.installCore) {
|
|
807
|
+
allModules = allModules.filter((m) => m !== 'core');
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
const modulesToInstall = allModules;
|
|
739
811
|
|
|
740
812
|
// For dependency resolution, we need to pass the project root
|
|
741
|
-
|
|
813
|
+
// Create a temporary module manager that knows about custom content locations
|
|
814
|
+
const tempModuleManager = new ModuleManager({
|
|
815
|
+
scanProjectForModules: true,
|
|
816
|
+
bmadDir: bmadDir, // Pass bmadDir so we can check cache
|
|
817
|
+
});
|
|
818
|
+
|
|
819
|
+
// Make sure custom modules are discoverable
|
|
820
|
+
if (config.customContent && config.customContent.selected && config.customContent.selectedFiles) {
|
|
821
|
+
// The dependency resolver needs to know about these modules
|
|
822
|
+
// We'll handle custom modules separately in the installation loop
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
const resolution = await this.dependencyResolver.resolve(projectRoot, allModules, {
|
|
826
|
+
verbose: config.verbose,
|
|
827
|
+
moduleManager: tempModuleManager,
|
|
828
|
+
});
|
|
742
829
|
|
|
743
830
|
if (config.verbose) {
|
|
744
831
|
spinner.succeed('Dependencies resolved');
|
|
@@ -746,24 +833,159 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
|
|
746
833
|
spinner.succeed('Dependencies resolved');
|
|
747
834
|
}
|
|
748
835
|
|
|
749
|
-
//
|
|
750
|
-
if (config.installCore || resolution.byModule.core) {
|
|
751
|
-
spinner.start('Installing BMAD core...');
|
|
752
|
-
await this.installCoreWithDependencies(bmadDir, resolution.byModule.core);
|
|
753
|
-
spinner.succeed('Core installed');
|
|
754
|
-
}
|
|
836
|
+
// Core is already installed above, skip if included in resolution
|
|
755
837
|
|
|
756
838
|
// Install modules with their dependencies
|
|
757
|
-
if (
|
|
758
|
-
|
|
839
|
+
if (allModules && allModules.length > 0) {
|
|
840
|
+
const installedModuleNames = new Set();
|
|
841
|
+
|
|
842
|
+
for (const moduleName of allModules) {
|
|
843
|
+
// Skip if already installed
|
|
844
|
+
if (installedModuleNames.has(moduleName)) {
|
|
845
|
+
continue;
|
|
846
|
+
}
|
|
847
|
+
installedModuleNames.add(moduleName);
|
|
848
|
+
|
|
759
849
|
spinner.start(`Installing module: ${moduleName}...`);
|
|
760
|
-
|
|
850
|
+
|
|
851
|
+
// Check if this is a custom module
|
|
852
|
+
let isCustomModule = false;
|
|
853
|
+
let customInfo = null;
|
|
854
|
+
let useCache = false;
|
|
855
|
+
|
|
856
|
+
// First check if we have a cached version
|
|
857
|
+
if (finalCustomContent && finalCustomContent.cachedModules) {
|
|
858
|
+
const cachedModule = finalCustomContent.cachedModules.find((m) => m.id === moduleName);
|
|
859
|
+
if (cachedModule) {
|
|
860
|
+
isCustomModule = true;
|
|
861
|
+
customInfo = {
|
|
862
|
+
id: moduleName,
|
|
863
|
+
path: cachedModule.cachePath,
|
|
864
|
+
config: {},
|
|
865
|
+
};
|
|
866
|
+
useCache = true;
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// Then check if we have custom module sources from the manifest (for quick update)
|
|
871
|
+
if (!isCustomModule && config._customModuleSources && config._customModuleSources.has(moduleName)) {
|
|
872
|
+
customInfo = config._customModuleSources.get(moduleName);
|
|
873
|
+
isCustomModule = true;
|
|
874
|
+
|
|
875
|
+
// Check if this is a cached module (source path starts with _cfg)
|
|
876
|
+
if (customInfo.sourcePath && (customInfo.sourcePath.startsWith('_cfg') || customInfo.sourcePath.includes('_cfg/custom'))) {
|
|
877
|
+
useCache = true;
|
|
878
|
+
// Make sure we have the right path structure
|
|
879
|
+
if (!customInfo.path) {
|
|
880
|
+
customInfo.path = customInfo.sourcePath;
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
// Finally check regular custom content
|
|
886
|
+
if (!isCustomModule && finalCustomContent && finalCustomContent.selected && finalCustomContent.selectedFiles) {
|
|
887
|
+
const { CustomHandler } = require('../custom/handler');
|
|
888
|
+
const customHandler = new CustomHandler();
|
|
889
|
+
for (const customFile of finalCustomContent.selectedFiles) {
|
|
890
|
+
const info = await customHandler.getCustomInfo(customFile, projectDir);
|
|
891
|
+
if (info && info.id === moduleName) {
|
|
892
|
+
isCustomModule = true;
|
|
893
|
+
customInfo = info;
|
|
894
|
+
break;
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
if (isCustomModule && customInfo) {
|
|
900
|
+
// Install custom module using CustomHandler but as a proper module
|
|
901
|
+
const { CustomHandler } = require('../custom/handler');
|
|
902
|
+
const customHandler = new CustomHandler();
|
|
903
|
+
|
|
904
|
+
// Install to module directory instead of custom directory
|
|
905
|
+
const moduleTargetPath = path.join(bmadDir, moduleName);
|
|
906
|
+
await fs.ensureDir(moduleTargetPath);
|
|
907
|
+
|
|
908
|
+
const result = await customHandler.install(
|
|
909
|
+
customInfo.path,
|
|
910
|
+
path.join(bmadDir, 'temp-custom'),
|
|
911
|
+
{ ...config.coreConfig, ...customInfo.config, _bmadDir: bmadDir },
|
|
912
|
+
(filePath) => {
|
|
913
|
+
// Track installed files with correct path
|
|
914
|
+
const relativePath = path.relative(path.join(bmadDir, 'temp-custom'), filePath);
|
|
915
|
+
const finalPath = path.join(moduleTargetPath, relativePath);
|
|
916
|
+
this.installedFiles.push(finalPath);
|
|
917
|
+
},
|
|
918
|
+
);
|
|
919
|
+
|
|
920
|
+
// Move from temp-custom to actual module directory
|
|
921
|
+
const tempCustomPath = path.join(bmadDir, 'temp-custom');
|
|
922
|
+
if (await fs.pathExists(tempCustomPath)) {
|
|
923
|
+
const customDir = path.join(tempCustomPath, 'custom');
|
|
924
|
+
if (await fs.pathExists(customDir)) {
|
|
925
|
+
// Move contents to module directory
|
|
926
|
+
const items = await fs.readdir(customDir);
|
|
927
|
+
for (const item of items) {
|
|
928
|
+
const srcPath = path.join(customDir, item);
|
|
929
|
+
const destPath = path.join(moduleTargetPath, item);
|
|
930
|
+
|
|
931
|
+
// If destination exists, remove it first (or we could merge)
|
|
932
|
+
if (await fs.pathExists(destPath)) {
|
|
933
|
+
await fs.remove(destPath);
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
await fs.move(srcPath, destPath);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
await fs.remove(tempCustomPath);
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
// Create module config
|
|
943
|
+
await this.generateModuleConfigs(bmadDir, { [moduleName]: { ...config.coreConfig, ...customInfo.config } });
|
|
944
|
+
|
|
945
|
+
// Store custom module info for later manifest update
|
|
946
|
+
if (!config._customModulesToTrack) {
|
|
947
|
+
config._customModulesToTrack = [];
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
// For cached modules, use appropriate path handling
|
|
951
|
+
let sourcePath;
|
|
952
|
+
if (useCache) {
|
|
953
|
+
// Check if we have cached modules info (from initial install)
|
|
954
|
+
if (finalCustomContent && finalCustomContent.cachedModules) {
|
|
955
|
+
sourcePath = finalCustomContent.cachedModules.find((m) => m.id === moduleName)?.relativePath;
|
|
956
|
+
} else {
|
|
957
|
+
// During update, the sourcePath is already cache-relative if it starts with _cfg
|
|
958
|
+
sourcePath =
|
|
959
|
+
customInfo.sourcePath && customInfo.sourcePath.startsWith('_cfg')
|
|
960
|
+
? customInfo.sourcePath
|
|
961
|
+
: path.relative(bmadDir, customInfo.path || customInfo.sourcePath);
|
|
962
|
+
}
|
|
963
|
+
} else {
|
|
964
|
+
sourcePath = path.resolve(customInfo.path || customInfo.sourcePath);
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
config._customModulesToTrack.push({
|
|
968
|
+
id: customInfo.id,
|
|
969
|
+
name: customInfo.name,
|
|
970
|
+
sourcePath: sourcePath,
|
|
971
|
+
installDate: new Date().toISOString(),
|
|
972
|
+
});
|
|
973
|
+
} else {
|
|
974
|
+
// Regular module installation
|
|
975
|
+
// Special case for core module
|
|
976
|
+
if (moduleName === 'core') {
|
|
977
|
+
await this.installCoreWithDependencies(bmadDir, resolution.byModule[moduleName]);
|
|
978
|
+
} else {
|
|
979
|
+
await this.installModuleWithDependencies(moduleName, bmadDir, resolution.byModule[moduleName]);
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
|
|
761
983
|
spinner.succeed(`Module installed: ${moduleName}`);
|
|
762
984
|
}
|
|
763
985
|
|
|
764
986
|
// Install partial modules (only dependencies)
|
|
765
987
|
for (const [module, files] of Object.entries(resolution.byModule)) {
|
|
766
|
-
if (!
|
|
988
|
+
if (!allModules.includes(module) && module !== 'core') {
|
|
767
989
|
const totalFiles =
|
|
768
990
|
files.agents.length +
|
|
769
991
|
files.tasks.length +
|
|
@@ -780,6 +1002,72 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
|
|
780
1002
|
}
|
|
781
1003
|
}
|
|
782
1004
|
|
|
1005
|
+
// Install custom content if provided AND selected
|
|
1006
|
+
// Process custom content that wasn't installed as modules
|
|
1007
|
+
// This is now handled in the module installation loop above
|
|
1008
|
+
// This section is kept for backward compatibility with any custom content
|
|
1009
|
+
// that doesn't have a module structure
|
|
1010
|
+
const remainingCustomContent = [];
|
|
1011
|
+
if (
|
|
1012
|
+
config.customContent &&
|
|
1013
|
+
config.customContent.hasCustomContent &&
|
|
1014
|
+
config.customContent.customPath &&
|
|
1015
|
+
config.customContent.selected &&
|
|
1016
|
+
config.customContent.selectedFiles
|
|
1017
|
+
) {
|
|
1018
|
+
// Filter out custom modules that were already installed
|
|
1019
|
+
for (const customFile of config.customContent.selectedFiles) {
|
|
1020
|
+
const { CustomHandler } = require('../custom/handler');
|
|
1021
|
+
const customHandler = new CustomHandler();
|
|
1022
|
+
const customInfo = await customHandler.getCustomInfo(customFile, projectDir);
|
|
1023
|
+
|
|
1024
|
+
// Skip if this was installed as a module
|
|
1025
|
+
if (!customInfo || !customInfo.id || !allModules.includes(customInfo.id)) {
|
|
1026
|
+
remainingCustomContent.push(customFile);
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
if (remainingCustomContent.length > 0) {
|
|
1032
|
+
spinner.start('Installing remaining custom content...');
|
|
1033
|
+
const { CustomHandler } = require('../custom/handler');
|
|
1034
|
+
const customHandler = new CustomHandler();
|
|
1035
|
+
|
|
1036
|
+
// Use the remaining files
|
|
1037
|
+
const customFiles = remainingCustomContent;
|
|
1038
|
+
|
|
1039
|
+
if (customFiles.length > 0) {
|
|
1040
|
+
console.log(chalk.cyan(`\n Found ${customFiles.length} custom content file(s):`));
|
|
1041
|
+
for (const customFile of customFiles) {
|
|
1042
|
+
const customInfo = await customHandler.getCustomInfo(customFile, projectDir);
|
|
1043
|
+
if (customInfo) {
|
|
1044
|
+
console.log(chalk.dim(` • ${customInfo.name} (${customInfo.relativePath})`));
|
|
1045
|
+
|
|
1046
|
+
// Install the custom content
|
|
1047
|
+
const result = await customHandler.install(
|
|
1048
|
+
customInfo.path,
|
|
1049
|
+
bmadDir,
|
|
1050
|
+
{ ...config.coreConfig, ...customInfo.config },
|
|
1051
|
+
(filePath) => {
|
|
1052
|
+
// Track installed files
|
|
1053
|
+
this.installedFiles.push(filePath);
|
|
1054
|
+
},
|
|
1055
|
+
);
|
|
1056
|
+
|
|
1057
|
+
if (result.errors.length > 0) {
|
|
1058
|
+
console.log(chalk.yellow(` ⚠️ ${result.errors.length} error(s) occurred`));
|
|
1059
|
+
for (const error of result.errors) {
|
|
1060
|
+
console.log(chalk.dim(` - ${error}`));
|
|
1061
|
+
}
|
|
1062
|
+
} else {
|
|
1063
|
+
console.log(chalk.green(` ✓ Installed ${result.agentsInstalled} agents, ${result.workflowsInstalled} workflows`));
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
spinner.succeed('Custom content installed');
|
|
1069
|
+
}
|
|
1070
|
+
|
|
783
1071
|
// Generate clean config.yaml files for each installed module
|
|
784
1072
|
spinner.start('Generating module configurations...');
|
|
785
1073
|
await this.generateModuleConfigs(bmadDir, moduleConfigs);
|
|
@@ -802,14 +1090,37 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
|
|
802
1090
|
spinner.start('Generating workflow and agent manifests...');
|
|
803
1091
|
const manifestGen = new ManifestGenerator();
|
|
804
1092
|
|
|
805
|
-
//
|
|
806
|
-
|
|
1093
|
+
// For quick update, we need ALL installed modules in the manifest
|
|
1094
|
+
// Not just the ones being updated
|
|
1095
|
+
const allModulesForManifest = config._quickUpdate
|
|
1096
|
+
? config._existingModules || allModules || []
|
|
1097
|
+
: config._preserveModules
|
|
1098
|
+
? [...allModules, ...config._preserveModules]
|
|
1099
|
+
: allModules || [];
|
|
807
1100
|
|
|
808
|
-
|
|
1101
|
+
// For regular installs (including when called from quick update), use what we have
|
|
1102
|
+
let modulesForCsvPreserve;
|
|
1103
|
+
if (config._quickUpdate) {
|
|
1104
|
+
// Quick update - use existing modules or fall back to modules being updated
|
|
1105
|
+
modulesForCsvPreserve = config._existingModules || allModules || [];
|
|
1106
|
+
} else {
|
|
1107
|
+
// Regular install - use the modules we're installing plus any preserved ones
|
|
1108
|
+
modulesForCsvPreserve = config._preserveModules ? [...allModules, ...config._preserveModules] : allModules;
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
const manifestStats = await manifestGen.generateManifests(bmadDir, allModulesForManifest, this.installedFiles, {
|
|
809
1112
|
ides: config.ides || [],
|
|
810
|
-
preservedModules:
|
|
1113
|
+
preservedModules: modulesForCsvPreserve, // Scan these from installed bmad/ dir
|
|
811
1114
|
});
|
|
812
1115
|
|
|
1116
|
+
// Add custom modules to manifest (now that it exists)
|
|
1117
|
+
if (config._customModulesToTrack && config._customModulesToTrack.length > 0) {
|
|
1118
|
+
spinner.text = 'Storing custom module sources...';
|
|
1119
|
+
for (const customModule of config._customModulesToTrack) {
|
|
1120
|
+
await this.manifest.addCustomModule(bmadDir, customModule);
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
|
|
813
1124
|
spinner.succeed(
|
|
814
1125
|
`Manifests generated: ${manifestStats.workflows} workflows, ${manifestStats.agents} agents, ${manifestStats.tasks} tasks, ${manifestStats.tools} tools, ${manifestStats.files} files`,
|
|
815
1126
|
);
|
|
@@ -894,6 +1205,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
|
|
894
1205
|
await this.moduleManager.runModuleInstaller('core', bmadDir, {
|
|
895
1206
|
installedIDEs: config.ides || [],
|
|
896
1207
|
moduleConfig: moduleConfigs.core || {},
|
|
1208
|
+
coreConfig: moduleConfigs.core || {},
|
|
897
1209
|
logger: {
|
|
898
1210
|
log: (msg) => console.log(msg),
|
|
899
1211
|
error: (msg) => console.error(msg),
|
|
@@ -911,6 +1223,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
|
|
911
1223
|
await this.moduleManager.runModuleInstaller(moduleName, bmadDir, {
|
|
912
1224
|
installedIDEs: config.ides || [],
|
|
913
1225
|
moduleConfig: moduleConfigs[moduleName] || {},
|
|
1226
|
+
coreConfig: moduleConfigs.core || {},
|
|
914
1227
|
logger: {
|
|
915
1228
|
log: (msg) => console.log(msg),
|
|
916
1229
|
error: (msg) => console.error(msg),
|
|
@@ -1008,6 +1321,20 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
|
|
1008
1321
|
}
|
|
1009
1322
|
}
|
|
1010
1323
|
|
|
1324
|
+
// Replace {agent_sidecar_folder} placeholders in all agent files
|
|
1325
|
+
console.log(chalk.dim('\n Configuring agent sidecar folders...'));
|
|
1326
|
+
const sidecarResults = await replaceAgentSidecarFolders(bmadDir);
|
|
1327
|
+
|
|
1328
|
+
if (sidecarResults.filesReplaced > 0) {
|
|
1329
|
+
console.log(
|
|
1330
|
+
chalk.green(
|
|
1331
|
+
` ✓ Updated ${sidecarResults.filesReplaced} agent file(s) with ${sidecarResults.totalReplacements} sidecar reference(s)`,
|
|
1332
|
+
),
|
|
1333
|
+
);
|
|
1334
|
+
} else {
|
|
1335
|
+
console.log(chalk.dim(' No agent sidecar references found'));
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1011
1338
|
// Display completion message
|
|
1012
1339
|
const { UI } = require('../../../lib/ui');
|
|
1013
1340
|
const ui = new UI();
|
|
@@ -1016,25 +1343,10 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
|
|
1016
1343
|
modules: config.modules,
|
|
1017
1344
|
ides: config.ides,
|
|
1018
1345
|
customFiles: customFiles.length > 0 ? customFiles : undefined,
|
|
1346
|
+
ttsInjectedFiles: this.enableAgentVibes && this.ttsInjectedFiles.length > 0 ? this.ttsInjectedFiles : undefined,
|
|
1347
|
+
agentVibesEnabled: this.enableAgentVibes || false,
|
|
1019
1348
|
});
|
|
1020
1349
|
|
|
1021
|
-
// Offer cleanup for legacy files (only for updates, not fresh installs, and only if not skipped)
|
|
1022
|
-
if (!config.skipCleanup && config._isUpdate) {
|
|
1023
|
-
try {
|
|
1024
|
-
const cleanupResult = await this.performCleanup(bmadDir, false);
|
|
1025
|
-
if (cleanupResult.deleted > 0) {
|
|
1026
|
-
console.log(chalk.green(`\n✓ Cleaned up ${cleanupResult.deleted} legacy file${cleanupResult.deleted > 1 ? 's' : ''}`));
|
|
1027
|
-
}
|
|
1028
|
-
if (cleanupResult.retained > 0) {
|
|
1029
|
-
console.log(chalk.dim(`Run 'bmad cleanup' anytime to manage retained files`));
|
|
1030
|
-
}
|
|
1031
|
-
} catch (cleanupError) {
|
|
1032
|
-
// Don't fail the installation for cleanup errors
|
|
1033
|
-
console.log(chalk.yellow(`\n⚠️ Cleanup warning: ${cleanupError.message}`));
|
|
1034
|
-
console.log(chalk.dim('Run "bmad cleanup" to manually clean up legacy files'));
|
|
1035
|
-
}
|
|
1036
|
-
}
|
|
1037
|
-
|
|
1038
1350
|
return {
|
|
1039
1351
|
success: true,
|
|
1040
1352
|
path: bmadDir,
|
|
@@ -1071,6 +1383,30 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
|
|
1071
1383
|
const currentVersion = existingInstall.version;
|
|
1072
1384
|
const newVersion = require(path.join(getProjectRoot(), 'package.json')).version;
|
|
1073
1385
|
|
|
1386
|
+
// Check for custom modules with missing sources before update
|
|
1387
|
+
const customModuleSources = new Map();
|
|
1388
|
+
if (existingInstall.customModules) {
|
|
1389
|
+
for (const customModule of existingInstall.customModules) {
|
|
1390
|
+
customModuleSources.set(customModule.id, customModule);
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
if (customModuleSources.size > 0) {
|
|
1395
|
+
spinner.stop();
|
|
1396
|
+
console.log(chalk.yellow('\nChecking custom module sources before update...'));
|
|
1397
|
+
|
|
1398
|
+
const projectRoot = getProjectRoot();
|
|
1399
|
+
await this.handleMissingCustomSources(
|
|
1400
|
+
customModuleSources,
|
|
1401
|
+
bmadDir,
|
|
1402
|
+
projectRoot,
|
|
1403
|
+
'update',
|
|
1404
|
+
existingInstall.modules.map((m) => m.id),
|
|
1405
|
+
);
|
|
1406
|
+
|
|
1407
|
+
spinner.start('Preparing update...');
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1074
1410
|
if (config.dryRun) {
|
|
1075
1411
|
spinner.stop();
|
|
1076
1412
|
console.log(chalk.cyan('\n🔍 Update Preview (Dry Run)\n'));
|
|
@@ -1521,22 +1857,86 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
|
|
1521
1857
|
|
|
1522
1858
|
// Build YAML + customize to .md
|
|
1523
1859
|
const customizeExists = await fs.pathExists(customizePath);
|
|
1524
|
-
|
|
1860
|
+
let xmlContent = await this.xmlHandler.buildFromYaml(yamlPath, customizeExists ? customizePath : null, {
|
|
1525
1861
|
includeMetadata: true,
|
|
1526
1862
|
});
|
|
1527
1863
|
|
|
1528
1864
|
// DO NOT replace {project-root} - LLMs understand this placeholder at runtime
|
|
1529
1865
|
// const processedContent = xmlContent.replaceAll('{project-root}', projectDir);
|
|
1530
1866
|
|
|
1867
|
+
// Replace {bmad_folder} with actual folder name
|
|
1868
|
+
xmlContent = xmlContent.replaceAll('{bmad_folder}', this.bmadFolderName || 'bmad');
|
|
1869
|
+
|
|
1870
|
+
// Replace {agent_sidecar_folder} if configured
|
|
1871
|
+
const coreConfig = this.configCollector.collectedConfig.core || {};
|
|
1872
|
+
if (coreConfig.agent_sidecar_folder && xmlContent.includes('{agent_sidecar_folder}')) {
|
|
1873
|
+
xmlContent = xmlContent.replaceAll('{agent_sidecar_folder}', coreConfig.agent_sidecar_folder);
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
// Process TTS injection points (pass targetPath for tracking)
|
|
1877
|
+
xmlContent = this.processTTSInjectionPoints(xmlContent, mdPath);
|
|
1878
|
+
|
|
1879
|
+
// Check if agent has sidecar and copy it
|
|
1880
|
+
let agentYamlContent = null;
|
|
1881
|
+
let hasSidecar = false;
|
|
1882
|
+
|
|
1883
|
+
try {
|
|
1884
|
+
agentYamlContent = await fs.readFile(yamlPath, 'utf8');
|
|
1885
|
+
const yamlLib = require('yaml');
|
|
1886
|
+
const agentYaml = yamlLib.parse(agentYamlContent);
|
|
1887
|
+
hasSidecar = agentYaml?.agent?.metadata?.hasSidecar === true;
|
|
1888
|
+
} catch {
|
|
1889
|
+
// Continue without sidecar processing
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1531
1892
|
// Write the built .md file to bmad/{module}/agents/ with POSIX-compliant final newline
|
|
1532
1893
|
const content = xmlContent.endsWith('\n') ? xmlContent : xmlContent + '\n';
|
|
1533
1894
|
await fs.writeFile(mdPath, content, 'utf8');
|
|
1534
1895
|
this.installedFiles.push(mdPath);
|
|
1535
1896
|
|
|
1897
|
+
// Copy sidecar files if agent has hasSidecar flag
|
|
1898
|
+
if (hasSidecar) {
|
|
1899
|
+
const { copyAgentSidecarFiles } = require('../../../lib/agent/installer');
|
|
1900
|
+
|
|
1901
|
+
// Get agent sidecar folder from core config
|
|
1902
|
+
const coreConfigPath = path.join(bmadDir, 'bmb', 'config.yaml');
|
|
1903
|
+
let agentSidecarFolder;
|
|
1904
|
+
|
|
1905
|
+
if (await fs.pathExists(coreConfigPath)) {
|
|
1906
|
+
const yamlLib = require('yaml');
|
|
1907
|
+
const coreConfigContent = await fs.readFile(coreConfigPath, 'utf8');
|
|
1908
|
+
const coreConfig = yamlLib.parse(coreConfigContent);
|
|
1909
|
+
agentSidecarFolder = coreConfig.agent_sidecar_folder || agentSidecarFolder;
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1912
|
+
// Resolve path variables
|
|
1913
|
+
const resolvedSidecarFolder = agentSidecarFolder
|
|
1914
|
+
.replaceAll('{project-root}', projectDir)
|
|
1915
|
+
.replaceAll('{bmad_folder}', this.bmadFolderName || 'bmad');
|
|
1916
|
+
|
|
1917
|
+
// Create sidecar directory for this agent
|
|
1918
|
+
const agentSidecarDir = path.join(resolvedSidecarFolder, agentName);
|
|
1919
|
+
await fs.ensureDir(agentSidecarDir);
|
|
1920
|
+
|
|
1921
|
+
// Find and copy sidecar folder from source module
|
|
1922
|
+
const sourceModulePath = getSourcePath(`modules/${moduleName}`);
|
|
1923
|
+
const sourceAgentPath = path.join(sourceModulePath, 'agents');
|
|
1924
|
+
|
|
1925
|
+
// Copy sidecar files (preserve existing, add new)
|
|
1926
|
+
const sidecarResult = copyAgentSidecarFiles(sourceAgentPath, agentSidecarDir, yamlPath);
|
|
1927
|
+
|
|
1928
|
+
if (sidecarResult.copied.length > 0) {
|
|
1929
|
+
console.log(chalk.dim(` Copied ${sidecarResult.copied.length} new sidecar file(s) to: ${agentSidecarDir}`));
|
|
1930
|
+
}
|
|
1931
|
+
if (sidecarResult.preserved.length > 0) {
|
|
1932
|
+
console.log(chalk.dim(` Preserved ${sidecarResult.preserved.length} existing sidecar file(s)`));
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1536
1936
|
// Remove the source YAML file - we can regenerate from installer source if needed
|
|
1537
1937
|
await fs.remove(yamlPath);
|
|
1538
1938
|
|
|
1539
|
-
console.log(chalk.dim(` Built agent: ${agentName}.md`));
|
|
1939
|
+
console.log(chalk.dim(` Built agent: ${agentName}.md${hasSidecar ? ' (with sidecar)' : ''}`));
|
|
1540
1940
|
}
|
|
1541
1941
|
// Handle legacy .md agents - inject activation if needed
|
|
1542
1942
|
else if (agentFile.endsWith('.md')) {
|
|
@@ -1623,13 +2023,16 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
|
|
1623
2023
|
}
|
|
1624
2024
|
|
|
1625
2025
|
// Build YAML to XML .md
|
|
1626
|
-
|
|
2026
|
+
let xmlContent = await this.xmlHandler.buildFromYaml(sourceYamlPath, customizeExists ? customizePath : null, {
|
|
1627
2027
|
includeMetadata: true,
|
|
1628
2028
|
});
|
|
1629
2029
|
|
|
1630
2030
|
// DO NOT replace {project-root} - LLMs understand this placeholder at runtime
|
|
1631
2031
|
// const processedContent = xmlContent.replaceAll('{project-root}', projectDir);
|
|
1632
2032
|
|
|
2033
|
+
// Process TTS injection points (pass targetPath for tracking)
|
|
2034
|
+
xmlContent = this.processTTSInjectionPoints(xmlContent, targetMdPath);
|
|
2035
|
+
|
|
1633
2036
|
// Write the built .md file with POSIX-compliant final newline
|
|
1634
2037
|
const content = xmlContent.endsWith('\n') ? xmlContent : xmlContent + '\n';
|
|
1635
2038
|
await fs.writeFile(targetMdPath, content, 'utf8');
|
|
@@ -1717,13 +2120,31 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
|
|
1717
2120
|
}
|
|
1718
2121
|
|
|
1719
2122
|
// Build YAML + customize to .md
|
|
1720
|
-
|
|
2123
|
+
let xmlContent = await this.xmlHandler.buildFromYaml(sourceYamlPath, customizeExists ? customizePath : null, {
|
|
1721
2124
|
includeMetadata: true,
|
|
1722
2125
|
});
|
|
1723
2126
|
|
|
1724
2127
|
// DO NOT replace {project-root} - LLMs understand this placeholder at runtime
|
|
1725
2128
|
// const processedContent = xmlContent.replaceAll('{project-root}', projectDir);
|
|
1726
2129
|
|
|
2130
|
+
// Replace {agent_sidecar_folder} if configured
|
|
2131
|
+
const coreConfigPath = path.join(bmadDir, 'bmb', 'config.yaml');
|
|
2132
|
+
let agentSidecarFolder = null;
|
|
2133
|
+
|
|
2134
|
+
if (await fs.pathExists(coreConfigPath)) {
|
|
2135
|
+
const yamlLib = require('yaml');
|
|
2136
|
+
const coreConfigContent = await fs.readFile(coreConfigPath, 'utf8');
|
|
2137
|
+
const coreConfig = yamlLib.parse(coreConfigContent);
|
|
2138
|
+
agentSidecarFolder = coreConfig.agent_sidecar_folder;
|
|
2139
|
+
}
|
|
2140
|
+
|
|
2141
|
+
if (agentSidecarFolder && xmlContent.includes('{agent_sidecar_folder}')) {
|
|
2142
|
+
xmlContent = xmlContent.replaceAll('{agent_sidecar_folder}', agentSidecarFolder);
|
|
2143
|
+
}
|
|
2144
|
+
|
|
2145
|
+
// Process TTS injection points (pass targetPath for tracking)
|
|
2146
|
+
xmlContent = this.processTTSInjectionPoints(xmlContent, targetMdPath);
|
|
2147
|
+
|
|
1727
2148
|
// Write the rebuilt .md file with POSIX-compliant final newline
|
|
1728
2149
|
const content = xmlContent.endsWith('\n') ? xmlContent : xmlContent + '\n';
|
|
1729
2150
|
await fs.writeFile(targetMdPath, content, 'utf8');
|
|
@@ -1757,6 +2178,24 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
|
|
1757
2178
|
throw new Error(`BMAD not installed at ${bmadDir}`);
|
|
1758
2179
|
}
|
|
1759
2180
|
|
|
2181
|
+
// Check for custom modules with missing sources
|
|
2182
|
+
const manifest = await this.manifest.read(bmadDir);
|
|
2183
|
+
if (manifest && manifest.customModules && manifest.customModules.length > 0) {
|
|
2184
|
+
spinner.stop();
|
|
2185
|
+
console.log(chalk.yellow('\nChecking custom module sources before compilation...'));
|
|
2186
|
+
|
|
2187
|
+
const customModuleSources = new Map();
|
|
2188
|
+
for (const customModule of manifest.customModules) {
|
|
2189
|
+
customModuleSources.set(customModule.id, customModule);
|
|
2190
|
+
}
|
|
2191
|
+
|
|
2192
|
+
const projectRoot = getProjectRoot();
|
|
2193
|
+
const installedModules = manifest.modules || [];
|
|
2194
|
+
await this.handleMissingCustomSources(customModuleSources, bmadDir, projectRoot, 'compile-agents', installedModules);
|
|
2195
|
+
|
|
2196
|
+
spinner.start('Rebuilding agent files...');
|
|
2197
|
+
}
|
|
2198
|
+
|
|
1760
2199
|
let agentCount = 0;
|
|
1761
2200
|
let taskCount = 0;
|
|
1762
2201
|
|
|
@@ -1901,17 +2340,245 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
|
|
1901
2340
|
const existingInstall = await this.detector.detect(bmadDir);
|
|
1902
2341
|
const installedModules = existingInstall.modules.map((m) => m.id);
|
|
1903
2342
|
const configuredIdes = existingInstall.ides || [];
|
|
2343
|
+
const projectRoot = path.dirname(bmadDir);
|
|
2344
|
+
|
|
2345
|
+
// Get custom module sources from manifest
|
|
2346
|
+
const customModuleSources = new Map();
|
|
2347
|
+
if (existingInstall.customModules) {
|
|
2348
|
+
for (const customModule of existingInstall.customModules) {
|
|
2349
|
+
// Ensure we have an absolute sourcePath
|
|
2350
|
+
let absoluteSourcePath = customModule.sourcePath;
|
|
2351
|
+
|
|
2352
|
+
// Check if sourcePath is a cache-relative path (starts with _cfg/)
|
|
2353
|
+
if (absoluteSourcePath && absoluteSourcePath.startsWith('_cfg')) {
|
|
2354
|
+
// Convert cache-relative path to absolute path
|
|
2355
|
+
absoluteSourcePath = path.join(bmadDir, absoluteSourcePath);
|
|
2356
|
+
}
|
|
2357
|
+
// If no sourcePath but we have relativePath, convert it
|
|
2358
|
+
else if (!absoluteSourcePath && customModule.relativePath) {
|
|
2359
|
+
// relativePath is relative to the project root (parent of bmad dir)
|
|
2360
|
+
absoluteSourcePath = path.resolve(projectRoot, customModule.relativePath);
|
|
2361
|
+
}
|
|
2362
|
+
// Ensure sourcePath is absolute for anything else
|
|
2363
|
+
else if (absoluteSourcePath && !path.isAbsolute(absoluteSourcePath)) {
|
|
2364
|
+
absoluteSourcePath = path.resolve(absoluteSourcePath);
|
|
2365
|
+
}
|
|
2366
|
+
|
|
2367
|
+
// Update the custom module object with the absolute path
|
|
2368
|
+
const updatedModule = {
|
|
2369
|
+
...customModule,
|
|
2370
|
+
sourcePath: absoluteSourcePath,
|
|
2371
|
+
};
|
|
2372
|
+
|
|
2373
|
+
customModuleSources.set(customModule.id, updatedModule);
|
|
2374
|
+
}
|
|
2375
|
+
}
|
|
1904
2376
|
|
|
1905
2377
|
// Load saved IDE configurations
|
|
1906
2378
|
const savedIdeConfigs = await this.ideConfigManager.loadAllIdeConfigs(bmadDir);
|
|
1907
2379
|
|
|
1908
2380
|
// Get available modules (what we have source for)
|
|
1909
|
-
const
|
|
1910
|
-
const
|
|
2381
|
+
const availableModulesData = await this.moduleManager.listAvailable();
|
|
2382
|
+
const availableModules = [...availableModulesData.modules, ...availableModulesData.customModules];
|
|
2383
|
+
|
|
2384
|
+
// Add custom modules from manifest if their sources exist
|
|
2385
|
+
for (const [moduleId, customModule] of customModuleSources) {
|
|
2386
|
+
// Use the absolute sourcePath
|
|
2387
|
+
const sourcePath = customModule.sourcePath;
|
|
2388
|
+
|
|
2389
|
+
// Check if source exists at the recorded path
|
|
2390
|
+
if (
|
|
2391
|
+
sourcePath &&
|
|
2392
|
+
(await fs.pathExists(sourcePath)) && // Add to available modules if not already there
|
|
2393
|
+
!availableModules.some((m) => m.id === moduleId)
|
|
2394
|
+
) {
|
|
2395
|
+
availableModules.push({
|
|
2396
|
+
id: moduleId,
|
|
2397
|
+
name: customModule.name || moduleId,
|
|
2398
|
+
path: sourcePath,
|
|
2399
|
+
isCustom: true,
|
|
2400
|
+
fromManifest: true,
|
|
2401
|
+
});
|
|
2402
|
+
}
|
|
2403
|
+
}
|
|
2404
|
+
|
|
2405
|
+
// Check for untracked custom modules (installed but not in manifest)
|
|
2406
|
+
const untrackedCustomModules = [];
|
|
2407
|
+
for (const installedModule of installedModules) {
|
|
2408
|
+
// Skip standard modules and core
|
|
2409
|
+
const standardModuleIds = ['bmb', 'bmgd', 'bmm', 'cis', 'core'];
|
|
2410
|
+
if (standardModuleIds.includes(installedModule)) {
|
|
2411
|
+
continue;
|
|
2412
|
+
}
|
|
2413
|
+
|
|
2414
|
+
// Check if this installed module is not tracked in customModules
|
|
2415
|
+
if (!customModuleSources.has(installedModule)) {
|
|
2416
|
+
const modulePath = path.join(bmadDir, installedModule);
|
|
2417
|
+
if (await fs.pathExists(modulePath)) {
|
|
2418
|
+
untrackedCustomModules.push({
|
|
2419
|
+
id: installedModule,
|
|
2420
|
+
name: installedModule, // We don't have the original name
|
|
2421
|
+
path: modulePath,
|
|
2422
|
+
untracked: true,
|
|
2423
|
+
});
|
|
2424
|
+
}
|
|
2425
|
+
}
|
|
2426
|
+
}
|
|
2427
|
+
|
|
2428
|
+
// If we found untracked custom modules, offer to track them
|
|
2429
|
+
if (untrackedCustomModules.length > 0) {
|
|
2430
|
+
spinner.stop();
|
|
2431
|
+
console.log(chalk.yellow(`\n⚠️ Found ${untrackedCustomModules.length} custom module(s) not tracked in manifest:`));
|
|
2432
|
+
|
|
2433
|
+
for (const untracked of untrackedCustomModules) {
|
|
2434
|
+
console.log(chalk.dim(` • ${untracked.id} (installed at ${path.relative(projectRoot, untracked.path)})`));
|
|
2435
|
+
}
|
|
2436
|
+
|
|
2437
|
+
const { trackModules } = await inquirer.prompt([
|
|
2438
|
+
{
|
|
2439
|
+
type: 'confirm',
|
|
2440
|
+
name: 'trackModules',
|
|
2441
|
+
message: chalk.cyan('Would you like to scan for their source locations?'),
|
|
2442
|
+
default: true,
|
|
2443
|
+
},
|
|
2444
|
+
]);
|
|
2445
|
+
|
|
2446
|
+
if (trackModules) {
|
|
2447
|
+
const { scanDirectory } = await inquirer.prompt([
|
|
2448
|
+
{
|
|
2449
|
+
type: 'input',
|
|
2450
|
+
name: 'scanDirectory',
|
|
2451
|
+
message: 'Enter directory to scan for custom module sources (or leave blank to skip):',
|
|
2452
|
+
default: projectRoot,
|
|
2453
|
+
validate: async (input) => {
|
|
2454
|
+
if (input && input.trim() !== '') {
|
|
2455
|
+
const expandedPath = path.resolve(input.trim());
|
|
2456
|
+
if (!(await fs.pathExists(expandedPath))) {
|
|
2457
|
+
return 'Directory does not exist';
|
|
2458
|
+
}
|
|
2459
|
+
const stats = await fs.stat(expandedPath);
|
|
2460
|
+
if (!stats.isDirectory()) {
|
|
2461
|
+
return 'Path must be a directory';
|
|
2462
|
+
}
|
|
2463
|
+
}
|
|
2464
|
+
return true;
|
|
2465
|
+
},
|
|
2466
|
+
},
|
|
2467
|
+
]);
|
|
2468
|
+
|
|
2469
|
+
if (scanDirectory && scanDirectory.trim() !== '') {
|
|
2470
|
+
console.log(chalk.dim('\nScanning for custom module sources...'));
|
|
2471
|
+
|
|
2472
|
+
// Scan for all module.yaml files
|
|
2473
|
+
const allModulePaths = await this.moduleManager.findModulesInProject(scanDirectory);
|
|
2474
|
+
const { ModuleManager } = require('../modules/manager');
|
|
2475
|
+
const mm = new ModuleManager({ scanProjectForModules: true });
|
|
2476
|
+
|
|
2477
|
+
for (const untracked of untrackedCustomModules) {
|
|
2478
|
+
let foundSource = null;
|
|
2479
|
+
|
|
2480
|
+
// Try to find by module ID
|
|
2481
|
+
for (const modulePath of allModulePaths) {
|
|
2482
|
+
try {
|
|
2483
|
+
const moduleInfo = await mm.getModuleInfo(modulePath);
|
|
2484
|
+
if (moduleInfo && moduleInfo.id === untracked.id) {
|
|
2485
|
+
foundSource = {
|
|
2486
|
+
path: modulePath,
|
|
2487
|
+
info: moduleInfo,
|
|
2488
|
+
};
|
|
2489
|
+
break;
|
|
2490
|
+
}
|
|
2491
|
+
} catch {
|
|
2492
|
+
// Continue searching
|
|
2493
|
+
}
|
|
2494
|
+
}
|
|
2495
|
+
|
|
2496
|
+
if (foundSource) {
|
|
2497
|
+
console.log(chalk.green(` ✓ Found source for ${untracked.id}: ${path.relative(projectRoot, foundSource.path)}`));
|
|
2498
|
+
|
|
2499
|
+
// Add to manifest
|
|
2500
|
+
await this.manifest.addCustomModule(bmadDir, {
|
|
2501
|
+
id: untracked.id,
|
|
2502
|
+
name: foundSource.info.name || untracked.name,
|
|
2503
|
+
sourcePath: path.resolve(foundSource.path),
|
|
2504
|
+
installDate: new Date().toISOString(),
|
|
2505
|
+
tracked: true,
|
|
2506
|
+
});
|
|
2507
|
+
|
|
2508
|
+
// Add to customModuleSources for processing
|
|
2509
|
+
customModuleSources.set(untracked.id, {
|
|
2510
|
+
id: untracked.id,
|
|
2511
|
+
name: foundSource.info.name || untracked.name,
|
|
2512
|
+
sourcePath: path.resolve(foundSource.path),
|
|
2513
|
+
});
|
|
2514
|
+
} else {
|
|
2515
|
+
console.log(chalk.yellow(` ⚠ Could not find source for ${untracked.id}`));
|
|
2516
|
+
}
|
|
2517
|
+
}
|
|
2518
|
+
}
|
|
2519
|
+
}
|
|
2520
|
+
|
|
2521
|
+
console.log(chalk.dim('\nUntracked custom modules will remain installed but cannot be updated without their source.'));
|
|
2522
|
+
spinner.start('Preparing update...');
|
|
2523
|
+
}
|
|
2524
|
+
|
|
2525
|
+
// Handle missing custom module sources using shared method
|
|
2526
|
+
const customModuleResult = await this.handleMissingCustomSources(
|
|
2527
|
+
customModuleSources,
|
|
2528
|
+
bmadDir,
|
|
2529
|
+
projectRoot,
|
|
2530
|
+
'update',
|
|
2531
|
+
installedModules,
|
|
2532
|
+
);
|
|
2533
|
+
|
|
2534
|
+
// Handle both old return format (array) and new format (object)
|
|
2535
|
+
let validCustomModules = [];
|
|
2536
|
+
let keptModulesWithoutSources = [];
|
|
2537
|
+
|
|
2538
|
+
if (Array.isArray(customModuleResult)) {
|
|
2539
|
+
// Old format - just an array
|
|
2540
|
+
validCustomModules = customModuleResult;
|
|
2541
|
+
} else if (customModuleResult && typeof customModuleResult === 'object') {
|
|
2542
|
+
// New format - object with two arrays
|
|
2543
|
+
validCustomModules = customModuleResult.validCustomModules || [];
|
|
2544
|
+
keptModulesWithoutSources = customModuleResult.keptModulesWithoutSources || [];
|
|
2545
|
+
}
|
|
2546
|
+
|
|
2547
|
+
const customModulesFromManifest = validCustomModules.map((m) => ({
|
|
2548
|
+
...m,
|
|
2549
|
+
isCustom: true,
|
|
2550
|
+
hasUpdate: true,
|
|
2551
|
+
}));
|
|
2552
|
+
|
|
2553
|
+
// Add untracked modules to the update list but mark them as untrackable
|
|
2554
|
+
for (const untracked of untrackedCustomModules) {
|
|
2555
|
+
if (!customModuleSources.has(untracked.id)) {
|
|
2556
|
+
customModulesFromManifest.push({
|
|
2557
|
+
...untracked,
|
|
2558
|
+
isCustom: true,
|
|
2559
|
+
hasUpdate: false, // Can't update without source
|
|
2560
|
+
untracked: true,
|
|
2561
|
+
});
|
|
2562
|
+
}
|
|
2563
|
+
}
|
|
2564
|
+
|
|
2565
|
+
const allAvailableModules = [...availableModules, ...customModulesFromManifest];
|
|
2566
|
+
const availableModuleIds = new Set(allAvailableModules.map((m) => m.id));
|
|
2567
|
+
|
|
2568
|
+
// Core module is special - never include it in update flow
|
|
2569
|
+
const nonCoreInstalledModules = installedModules.filter((id) => id !== 'core');
|
|
1911
2570
|
|
|
1912
2571
|
// Only update modules that are BOTH installed AND available (we have source for)
|
|
1913
|
-
const modulesToUpdate =
|
|
1914
|
-
const skippedModules =
|
|
2572
|
+
const modulesToUpdate = nonCoreInstalledModules.filter((id) => availableModuleIds.has(id));
|
|
2573
|
+
const skippedModules = nonCoreInstalledModules.filter((id) => !availableModuleIds.has(id));
|
|
2574
|
+
|
|
2575
|
+
// Add custom modules that were kept without sources to the skipped modules
|
|
2576
|
+
// This ensures their agents are preserved in the manifest
|
|
2577
|
+
for (const keptModule of keptModulesWithoutSources) {
|
|
2578
|
+
if (!skippedModules.includes(keptModule)) {
|
|
2579
|
+
skippedModules.push(keptModule);
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
1915
2582
|
|
|
1916
2583
|
spinner.succeed(`Found ${modulesToUpdate.length} module(s) to update and ${configuredIdes.length} configured tool(s)`);
|
|
1917
2584
|
|
|
@@ -1976,6 +2643,8 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
|
|
1976
2643
|
_quickUpdate: true, // Flag to skip certain prompts
|
|
1977
2644
|
_preserveModules: skippedModules, // Preserve these in manifest even though we didn't update them
|
|
1978
2645
|
_savedIdeConfigs: savedIdeConfigs, // Pass saved IDE configs to installer
|
|
2646
|
+
_customModuleSources: customModuleSources, // Pass custom module sources for updates
|
|
2647
|
+
_existingModules: installedModules, // Pass all installed modules for manifest generation
|
|
1979
2648
|
};
|
|
1980
2649
|
|
|
1981
2650
|
// Call the standard install method
|
|
@@ -2183,8 +2852,9 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
|
|
2183
2852
|
const installedFilesMap = new Map();
|
|
2184
2853
|
for (const fileEntry of existingFilesManifest) {
|
|
2185
2854
|
if (fileEntry.path) {
|
|
2186
|
-
//
|
|
2187
|
-
//
|
|
2855
|
+
// Paths are relative to bmadDir. Legacy manifests incorrectly prefixed 'bmad/' -
|
|
2856
|
+
// strip it if present. This is safe because no real path inside bmadDir would
|
|
2857
|
+
// start with 'bmad/' (you'd never have .bmad/bmad/... as an actual structure).
|
|
2188
2858
|
const relativePath = fileEntry.path.startsWith('bmad/') ? fileEntry.path.slice(5) : fileEntry.path;
|
|
2189
2859
|
const absolutePath = path.join(bmadDir, relativePath);
|
|
2190
2860
|
installedFilesMap.set(path.normalize(absolutePath), {
|
|
@@ -2504,8 +3174,10 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
|
|
2504
3174
|
agentType = parts.slice(-2).join('-'); // Take last 2 parts as type
|
|
2505
3175
|
}
|
|
2506
3176
|
|
|
2507
|
-
// Create target directory
|
|
2508
|
-
const agentTargetDir =
|
|
3177
|
+
// Create target directory - use relative path if agent is in a subdirectory
|
|
3178
|
+
const agentTargetDir = agent.relativePath
|
|
3179
|
+
? path.join(customAgentsDir, agent.relativePath)
|
|
3180
|
+
: path.join(customAgentsDir, finalAgentName);
|
|
2509
3181
|
await fs.ensureDir(agentTargetDir);
|
|
2510
3182
|
|
|
2511
3183
|
// Calculate paths
|
|
@@ -2519,6 +3191,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
|
|
2519
3191
|
agentConfig.defaults || {},
|
|
2520
3192
|
finalAgentName,
|
|
2521
3193
|
relativePath,
|
|
3194
|
+
{ config: config.coreConfig },
|
|
2522
3195
|
);
|
|
2523
3196
|
|
|
2524
3197
|
// Write compiled agent
|
|
@@ -2534,10 +3207,26 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
|
|
2534
3207
|
await fs.copy(agent.yamlFile, backupYamlPath);
|
|
2535
3208
|
}
|
|
2536
3209
|
|
|
2537
|
-
// Copy sidecar files
|
|
2538
|
-
if (
|
|
2539
|
-
const {
|
|
2540
|
-
|
|
3210
|
+
// Copy sidecar files for agents with hasSidecar flag
|
|
3211
|
+
if (agentConfig.hasSidecar === true && agent.type === 'expert') {
|
|
3212
|
+
const { copyAgentSidecarFiles } = require('../../../lib/agent/installer');
|
|
3213
|
+
|
|
3214
|
+
// Get agent sidecar folder from config or use default
|
|
3215
|
+
const agentSidecarFolder = config.coreConfig?.agent_sidecar_folder;
|
|
3216
|
+
|
|
3217
|
+
// Resolve path variables
|
|
3218
|
+
const resolvedSidecarFolder = agentSidecarFolder.replaceAll('{project-root}', projectDir).replaceAll('{bmad_folder}', bmadDir);
|
|
3219
|
+
|
|
3220
|
+
// Create sidecar directory for this agent
|
|
3221
|
+
const agentSidecarDir = path.join(resolvedSidecarFolder, finalAgentName);
|
|
3222
|
+
await fs.ensureDir(agentSidecarDir);
|
|
3223
|
+
|
|
3224
|
+
// Copy sidecar files (preserve existing, add new)
|
|
3225
|
+
const sidecarResult = copyAgentSidecarFiles(agent.path, agentSidecarDir, agent.yamlFile);
|
|
3226
|
+
|
|
3227
|
+
if (sidecarResult.copied.length > 0 || sidecarResult.preserved.length > 0) {
|
|
3228
|
+
console.log(chalk.dim(` Sidecar: ${sidecarResult.copied.length} new, ${sidecarResult.preserved.length} preserved`));
|
|
3229
|
+
}
|
|
2541
3230
|
}
|
|
2542
3231
|
|
|
2543
3232
|
// Update manifest CSV
|
|
@@ -2597,359 +3286,227 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
|
|
2597
3286
|
}
|
|
2598
3287
|
|
|
2599
3288
|
/**
|
|
2600
|
-
*
|
|
2601
|
-
* @param {
|
|
2602
|
-
* @
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
documentation: [],
|
|
2608
|
-
deprecated_task: [],
|
|
2609
|
-
unknown: [],
|
|
2610
|
-
};
|
|
2611
|
-
|
|
2612
|
-
try {
|
|
2613
|
-
// Load files manifest to understand what should exist
|
|
2614
|
-
const manifestPath = path.join(bmadDir, 'files-manifest.csv');
|
|
2615
|
-
const manifestFiles = new Set();
|
|
2616
|
-
|
|
2617
|
-
if (await fs.pathExists(manifestPath)) {
|
|
2618
|
-
const manifestContent = await fs.readFile(manifestPath, 'utf8');
|
|
2619
|
-
const lines = manifestContent.split('\n').slice(1); // Skip header
|
|
2620
|
-
for (const line of lines) {
|
|
2621
|
-
if (line.trim()) {
|
|
2622
|
-
const relativePath = line.split(',')[0];
|
|
2623
|
-
if (relativePath) {
|
|
2624
|
-
manifestFiles.add(relativePath);
|
|
2625
|
-
}
|
|
2626
|
-
}
|
|
2627
|
-
}
|
|
2628
|
-
}
|
|
2629
|
-
|
|
2630
|
-
// Scan all files recursively
|
|
2631
|
-
const allFiles = await this.getAllFiles(bmadDir);
|
|
2632
|
-
|
|
2633
|
-
for (const filePath of allFiles) {
|
|
2634
|
-
const relativePath = path.relative(bmadDir, filePath);
|
|
2635
|
-
|
|
2636
|
-
// Skip expected files
|
|
2637
|
-
if (this.isExpectedFile(relativePath, manifestFiles)) {
|
|
2638
|
-
continue;
|
|
2639
|
-
}
|
|
2640
|
-
|
|
2641
|
-
// Categorize legacy files
|
|
2642
|
-
if (relativePath.endsWith('.bak')) {
|
|
2643
|
-
legacyFiles.backup.push({
|
|
2644
|
-
path: filePath,
|
|
2645
|
-
relativePath: relativePath,
|
|
2646
|
-
size: (await fs.stat(filePath)).size,
|
|
2647
|
-
mtime: (await fs.stat(filePath)).mtime,
|
|
2648
|
-
});
|
|
2649
|
-
} else if (this.isDocumentationFile(relativePath)) {
|
|
2650
|
-
legacyFiles.documentation.push({
|
|
2651
|
-
path: filePath,
|
|
2652
|
-
relativePath: relativePath,
|
|
2653
|
-
size: (await fs.stat(filePath)).size,
|
|
2654
|
-
mtime: (await fs.stat(filePath)).mtime,
|
|
2655
|
-
});
|
|
2656
|
-
} else if (this.isDeprecatedTaskFile(relativePath)) {
|
|
2657
|
-
const suggestedAlternative = this.suggestAlternative(relativePath);
|
|
2658
|
-
legacyFiles.deprecated_task.push({
|
|
2659
|
-
path: filePath,
|
|
2660
|
-
relativePath: relativePath,
|
|
2661
|
-
size: (await fs.stat(filePath)).size,
|
|
2662
|
-
mtime: (await fs.stat(filePath)).mtime,
|
|
2663
|
-
suggestedAlternative,
|
|
2664
|
-
});
|
|
2665
|
-
} else {
|
|
2666
|
-
legacyFiles.unknown.push({
|
|
2667
|
-
path: filePath,
|
|
2668
|
-
relativePath: relativePath,
|
|
2669
|
-
size: (await fs.stat(filePath)).size,
|
|
2670
|
-
mtime: (await fs.stat(filePath)).mtime,
|
|
2671
|
-
});
|
|
2672
|
-
}
|
|
2673
|
-
}
|
|
2674
|
-
} catch (error) {
|
|
2675
|
-
console.warn(`Warning: Could not scan for legacy files: ${error.message}`);
|
|
2676
|
-
}
|
|
2677
|
-
|
|
2678
|
-
return legacyFiles;
|
|
2679
|
-
}
|
|
2680
|
-
|
|
2681
|
-
/**
|
|
2682
|
-
* Get all files in directory recursively
|
|
2683
|
-
* @param {string} dir - Directory to scan
|
|
2684
|
-
* @returns {Array} Array of file paths
|
|
3289
|
+
* Handle missing custom module sources interactively
|
|
3290
|
+
* @param {Map} customModuleSources - Map of custom module ID to info
|
|
3291
|
+
* @param {string} bmadDir - BMAD directory
|
|
3292
|
+
* @param {string} projectRoot - Project root directory
|
|
3293
|
+
* @param {string} operation - Current operation ('update', 'compile', etc.)
|
|
3294
|
+
* @param {Array} installedModules - Array of installed module IDs (will be modified)
|
|
3295
|
+
* @returns {Object} Object with validCustomModules array and keptModulesWithoutSources array
|
|
2685
3296
|
*/
|
|
2686
|
-
async
|
|
2687
|
-
const
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
3297
|
+
async handleMissingCustomSources(customModuleSources, bmadDir, projectRoot, operation, installedModules) {
|
|
3298
|
+
const validCustomModules = [];
|
|
3299
|
+
const keptModulesWithoutSources = []; // Track modules kept without sources
|
|
3300
|
+
const customModulesWithMissingSources = [];
|
|
3301
|
+
|
|
3302
|
+
// Check which sources exist
|
|
3303
|
+
for (const [moduleId, customInfo] of customModuleSources) {
|
|
3304
|
+
if (await fs.pathExists(customInfo.sourcePath)) {
|
|
3305
|
+
validCustomModules.push({
|
|
3306
|
+
id: moduleId,
|
|
3307
|
+
name: customInfo.name,
|
|
3308
|
+
path: customInfo.sourcePath,
|
|
3309
|
+
info: customInfo,
|
|
3310
|
+
});
|
|
3311
|
+
} else {
|
|
3312
|
+
customModulesWithMissingSources.push({
|
|
3313
|
+
id: moduleId,
|
|
3314
|
+
name: customInfo.name,
|
|
3315
|
+
sourcePath: customInfo.sourcePath,
|
|
3316
|
+
relativePath: customInfo.relativePath,
|
|
3317
|
+
info: customInfo,
|
|
3318
|
+
});
|
|
2704
3319
|
}
|
|
2705
3320
|
}
|
|
2706
3321
|
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
/**
|
|
2712
|
-
* Check if file is expected in installation
|
|
2713
|
-
* @param {string} relativePath - Relative path from BMAD dir
|
|
2714
|
-
* @param {Set} manifestFiles - Files from manifest
|
|
2715
|
-
* @returns {boolean} True if expected file
|
|
2716
|
-
*/
|
|
2717
|
-
isExpectedFile(relativePath, manifestFiles) {
|
|
2718
|
-
// Core files in manifest
|
|
2719
|
-
if (manifestFiles.has(relativePath)) {
|
|
2720
|
-
return true;
|
|
2721
|
-
}
|
|
2722
|
-
|
|
2723
|
-
// Configuration files
|
|
2724
|
-
if (relativePath.startsWith('_cfg/') || relativePath === 'config.yaml') {
|
|
2725
|
-
return true;
|
|
3322
|
+
// If no missing sources, return immediately
|
|
3323
|
+
if (customModulesWithMissingSources.length === 0) {
|
|
3324
|
+
return validCustomModules;
|
|
2726
3325
|
}
|
|
2727
3326
|
|
|
2728
|
-
//
|
|
2729
|
-
|
|
2730
|
-
|
|
3327
|
+
// Stop any spinner for interactive prompts
|
|
3328
|
+
const currentSpinner = ora();
|
|
3329
|
+
if (currentSpinner.isSpinning) {
|
|
3330
|
+
currentSpinner.stop();
|
|
2731
3331
|
}
|
|
2732
3332
|
|
|
2733
|
-
|
|
2734
|
-
if (relativePath === 'manifest.csv' || relativePath === 'files-manifest.csv') {
|
|
2735
|
-
return true;
|
|
2736
|
-
}
|
|
3333
|
+
console.log(chalk.yellow(`\n⚠️ Found ${customModulesWithMissingSources.length} custom module(s) with missing sources:`));
|
|
2737
3334
|
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
}
|
|
3335
|
+
const inquirer = require('inquirer');
|
|
3336
|
+
let keptCount = 0;
|
|
3337
|
+
let updatedCount = 0;
|
|
3338
|
+
let removedCount = 0;
|
|
2743
3339
|
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
3340
|
+
for (const missing of customModulesWithMissingSources) {
|
|
3341
|
+
console.log(chalk.dim(` • ${missing.name} (${missing.id})`));
|
|
3342
|
+
console.log(chalk.dim(` Original source: ${missing.relativePath}`));
|
|
3343
|
+
console.log(chalk.dim(` Full path: ${missing.sourcePath}`));
|
|
2747
3344
|
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
3345
|
+
const choices = [
|
|
3346
|
+
{
|
|
3347
|
+
name: 'Keep installed (will not be processed)',
|
|
3348
|
+
value: 'keep',
|
|
3349
|
+
short: 'Keep',
|
|
3350
|
+
},
|
|
3351
|
+
{
|
|
3352
|
+
name: 'Specify new source location',
|
|
3353
|
+
value: 'update',
|
|
3354
|
+
short: 'Update',
|
|
3355
|
+
},
|
|
3356
|
+
];
|
|
3357
|
+
|
|
3358
|
+
// Only add remove option if not just compiling agents
|
|
3359
|
+
if (operation !== 'compile-agents') {
|
|
3360
|
+
choices.push({
|
|
3361
|
+
name: '⚠️ REMOVE module completely (destructive!)',
|
|
3362
|
+
value: 'remove',
|
|
3363
|
+
short: 'Remove',
|
|
3364
|
+
});
|
|
2756
3365
|
}
|
|
2757
|
-
}
|
|
2758
|
-
|
|
2759
|
-
// Special case for core module resources
|
|
2760
|
-
if (relativePath.startsWith('core/resources/')) {
|
|
2761
|
-
return true;
|
|
2762
|
-
}
|
|
2763
|
-
|
|
2764
|
-
// Special case for docs directory
|
|
2765
|
-
if (relativePath.startsWith('docs/')) {
|
|
2766
|
-
return true;
|
|
2767
|
-
}
|
|
2768
|
-
|
|
2769
|
-
return false;
|
|
2770
|
-
}
|
|
2771
3366
|
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
return docExtensions.some((ext) => relativePath.endsWith(ext)) || docPatterns.some((pattern) => relativePath.includes(pattern));
|
|
2782
|
-
}
|
|
2783
|
-
|
|
2784
|
-
/**
|
|
2785
|
-
* Check if file is deprecated task file
|
|
2786
|
-
* @param {string} relativePath - Relative path
|
|
2787
|
-
* @returns {boolean} True if deprecated
|
|
2788
|
-
*/
|
|
2789
|
-
isDeprecatedTaskFile(relativePath) {
|
|
2790
|
-
// Known deprecated files
|
|
2791
|
-
const deprecatedFiles = ['adv-elicit-methods.csv', 'game-resources.json', 'ux-workflow.json'];
|
|
3367
|
+
const { action } = await inquirer.prompt([
|
|
3368
|
+
{
|
|
3369
|
+
type: 'list',
|
|
3370
|
+
name: 'action',
|
|
3371
|
+
message: `How would you like to handle "${missing.name}"?`,
|
|
3372
|
+
choices,
|
|
3373
|
+
},
|
|
3374
|
+
]);
|
|
2792
3375
|
|
|
2793
|
-
|
|
2794
|
-
|
|
3376
|
+
switch (action) {
|
|
3377
|
+
case 'update': {
|
|
3378
|
+
const { newSourcePath } = await inquirer.prompt([
|
|
3379
|
+
{
|
|
3380
|
+
type: 'input',
|
|
3381
|
+
name: 'newSourcePath',
|
|
3382
|
+
message: 'Enter the new path to the custom module:',
|
|
3383
|
+
default: missing.sourcePath,
|
|
3384
|
+
validate: async (input) => {
|
|
3385
|
+
if (!input || input.trim() === '') {
|
|
3386
|
+
return 'Please enter a path';
|
|
3387
|
+
}
|
|
3388
|
+
const expandedPath = path.resolve(input.trim());
|
|
3389
|
+
if (!(await fs.pathExists(expandedPath))) {
|
|
3390
|
+
return 'Path does not exist';
|
|
3391
|
+
}
|
|
3392
|
+
// Check if it looks like a valid module
|
|
3393
|
+
const moduleYamlPath = path.join(expandedPath, 'module.yaml');
|
|
3394
|
+
const agentsPath = path.join(expandedPath, 'agents');
|
|
3395
|
+
const workflowsPath = path.join(expandedPath, 'workflows');
|
|
2795
3396
|
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
'adv-elicit-methods.csv': 'Use the new structured workflows in src/modules/',
|
|
2804
|
-
'game-resources.json': 'Resources are now integrated into modules',
|
|
2805
|
-
'ux-workflow.json': 'UX workflows are now in src/modules/bmm/workflows/',
|
|
2806
|
-
};
|
|
3397
|
+
if (!(await fs.pathExists(moduleYamlPath)) && !(await fs.pathExists(agentsPath)) && !(await fs.pathExists(workflowsPath))) {
|
|
3398
|
+
return 'Path does not appear to contain a valid custom module';
|
|
3399
|
+
}
|
|
3400
|
+
return true;
|
|
3401
|
+
},
|
|
3402
|
+
},
|
|
3403
|
+
]);
|
|
2807
3404
|
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
3405
|
+
// Update the source in manifest
|
|
3406
|
+
const resolvedPath = path.resolve(newSourcePath.trim());
|
|
3407
|
+
missing.info.sourcePath = resolvedPath;
|
|
3408
|
+
// Remove relativePath - we only store absolute sourcePath now
|
|
3409
|
+
delete missing.info.relativePath;
|
|
3410
|
+
await this.manifest.addCustomModule(bmadDir, missing.info);
|
|
3411
|
+
|
|
3412
|
+
validCustomModules.push({
|
|
3413
|
+
id: moduleId,
|
|
3414
|
+
name: missing.name,
|
|
3415
|
+
path: resolvedPath,
|
|
3416
|
+
info: missing.info,
|
|
3417
|
+
});
|
|
2813
3418
|
|
|
2814
|
-
|
|
2815
|
-
|
|
3419
|
+
updatedCount++;
|
|
3420
|
+
console.log(chalk.green(`✓ Updated source location`));
|
|
2816
3421
|
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
async performCleanup(bmadDir, skipInteractive = false) {
|
|
2824
|
-
const inquirer = require('inquirer');
|
|
2825
|
-
const yaml = require('js-yaml');
|
|
3422
|
+
break;
|
|
3423
|
+
}
|
|
3424
|
+
case 'remove': {
|
|
3425
|
+
// Extra confirmation for destructive remove
|
|
3426
|
+
console.log(chalk.red.bold(`\n⚠️ WARNING: This will PERMANENTLY DELETE "${missing.name}" and all its files!`));
|
|
3427
|
+
console.log(chalk.red(` Module location: ${path.join(bmadDir, moduleId)}`));
|
|
2826
3428
|
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
3429
|
+
const { confirm } = await inquirer.prompt([
|
|
3430
|
+
{
|
|
3431
|
+
type: 'confirm',
|
|
3432
|
+
name: 'confirm',
|
|
3433
|
+
message: chalk.red.bold('Are you absolutely sure you want to delete this module?'),
|
|
3434
|
+
default: false,
|
|
3435
|
+
},
|
|
3436
|
+
]);
|
|
2830
3437
|
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
3438
|
+
if (confirm) {
|
|
3439
|
+
const { typedConfirm } = await inquirer.prompt([
|
|
3440
|
+
{
|
|
3441
|
+
type: 'input',
|
|
3442
|
+
name: 'typedConfirm',
|
|
3443
|
+
message: chalk.red.bold('Type "DELETE" to confirm permanent deletion:'),
|
|
3444
|
+
validate: (input) => {
|
|
3445
|
+
if (input !== 'DELETE') {
|
|
3446
|
+
return chalk.red('You must type "DELETE" exactly to proceed');
|
|
3447
|
+
}
|
|
3448
|
+
return true;
|
|
3449
|
+
},
|
|
3450
|
+
},
|
|
3451
|
+
]);
|
|
3452
|
+
|
|
3453
|
+
if (typedConfirm === 'DELETE') {
|
|
3454
|
+
// Remove the module from filesystem and manifest
|
|
3455
|
+
const modulePath = path.join(bmadDir, moduleId);
|
|
3456
|
+
if (await fs.pathExists(modulePath)) {
|
|
3457
|
+
const fsExtra = require('fs-extra');
|
|
3458
|
+
await fsExtra.remove(modulePath);
|
|
3459
|
+
console.log(chalk.yellow(` ✓ Deleted module directory: ${path.relative(projectRoot, modulePath)}`));
|
|
3460
|
+
}
|
|
2835
3461
|
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
3462
|
+
await this.manifest.removeModule(bmadDir, moduleId);
|
|
3463
|
+
await this.manifest.removeCustomModule(bmadDir, moduleId);
|
|
3464
|
+
console.log(chalk.yellow(` ✓ Removed from manifest`));
|
|
2839
3465
|
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
3466
|
+
// Also remove from installedModules list
|
|
3467
|
+
if (installedModules && installedModules.includes(moduleId)) {
|
|
3468
|
+
const index = installedModules.indexOf(moduleId);
|
|
3469
|
+
if (index !== -1) {
|
|
3470
|
+
installedModules.splice(index, 1);
|
|
3471
|
+
}
|
|
3472
|
+
}
|
|
2843
3473
|
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
3474
|
+
removedCount++;
|
|
3475
|
+
console.log(chalk.red.bold(`✓ "${missing.name}" has been permanently removed`));
|
|
3476
|
+
} else {
|
|
3477
|
+
console.log(chalk.dim(' Removal cancelled - module will be kept'));
|
|
3478
|
+
keptCount++;
|
|
3479
|
+
}
|
|
3480
|
+
} else {
|
|
3481
|
+
console.log(chalk.dim(' Removal cancelled - module will be kept'));
|
|
3482
|
+
keptCount++;
|
|
3483
|
+
}
|
|
2847
3484
|
|
|
2848
|
-
|
|
2849
|
-
// Auto-delete all non-retained files
|
|
2850
|
-
for (const file of allLegacyFiles) {
|
|
2851
|
-
if (!retentionData.retainedFiles.includes(file.relativePath)) {
|
|
2852
|
-
filesToDelete.push(file);
|
|
3485
|
+
break;
|
|
2853
3486
|
}
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
console.log(chalk.dim('The following obsolete files were found:\n'));
|
|
3487
|
+
case 'keep': {
|
|
3488
|
+
keptCount++;
|
|
3489
|
+
keptModulesWithoutSources.push(moduleId);
|
|
3490
|
+
console.log(chalk.dim(` Module will be kept as-is`));
|
|
2859
3491
|
|
|
2860
|
-
|
|
2861
|
-
const categories = [];
|
|
2862
|
-
if (legacyFiles.backup.length > 0) {
|
|
2863
|
-
categories.push({ name: 'Backup Files (.bak)', files: legacyFiles.backup });
|
|
2864
|
-
}
|
|
2865
|
-
if (legacyFiles.documentation.length > 0) {
|
|
2866
|
-
categories.push({ name: 'Documentation', files: legacyFiles.documentation });
|
|
2867
|
-
}
|
|
2868
|
-
if (legacyFiles.deprecated_task.length > 0) {
|
|
2869
|
-
categories.push({ name: 'Deprecated Task Files', files: legacyFiles.deprecated_task });
|
|
2870
|
-
}
|
|
2871
|
-
if (legacyFiles.unknown.length > 0) {
|
|
2872
|
-
categories.push({ name: 'Unknown Files', files: legacyFiles.unknown });
|
|
2873
|
-
}
|
|
2874
|
-
|
|
2875
|
-
for (const category of categories) {
|
|
2876
|
-
console.log(chalk.yellow(`${category.name}:`));
|
|
2877
|
-
for (const file of category.files) {
|
|
2878
|
-
const size = (file.size / 1024).toFixed(1);
|
|
2879
|
-
const date = file.mtime.toLocaleDateString();
|
|
2880
|
-
let line = ` - ${file.relativePath} (${size}KB, ${date})`;
|
|
2881
|
-
if (file.suggestedAlternative) {
|
|
2882
|
-
line += chalk.dim(` → ${file.suggestedAlternative}`);
|
|
2883
|
-
}
|
|
2884
|
-
console.log(chalk.dim(line));
|
|
3492
|
+
break;
|
|
2885
3493
|
}
|
|
2886
|
-
|
|
3494
|
+
// No default
|
|
2887
3495
|
}
|
|
2888
|
-
|
|
2889
|
-
const prompt = await inquirer.prompt([
|
|
2890
|
-
{
|
|
2891
|
-
type: 'confirm',
|
|
2892
|
-
name: 'proceed',
|
|
2893
|
-
message: 'Would you like to review these files for cleanup?',
|
|
2894
|
-
default: true,
|
|
2895
|
-
},
|
|
2896
|
-
]);
|
|
2897
|
-
|
|
2898
|
-
if (!prompt.proceed) {
|
|
2899
|
-
return { deleted: 0, retained: allLegacyFiles.length, message: 'Cleanup cancelled by user' };
|
|
2900
|
-
}
|
|
2901
|
-
|
|
2902
|
-
// Show selection interface
|
|
2903
|
-
const selectionPrompt = await inquirer.prompt([
|
|
2904
|
-
{
|
|
2905
|
-
type: 'checkbox',
|
|
2906
|
-
name: 'filesToDelete',
|
|
2907
|
-
message: 'Select files to delete (use SPACEBAR to select, ENTER to continue):',
|
|
2908
|
-
choices: allLegacyFiles.map((file) => {
|
|
2909
|
-
const isRetained = retentionData.retainedFiles.includes(file.relativePath);
|
|
2910
|
-
const description = `${file.relativePath} (${(file.size / 1024).toFixed(1)}KB)`;
|
|
2911
|
-
return {
|
|
2912
|
-
name: description,
|
|
2913
|
-
value: file,
|
|
2914
|
-
checked: !isRetained && !file.relativePath.includes('.bak'),
|
|
2915
|
-
};
|
|
2916
|
-
}),
|
|
2917
|
-
pageSize: Math.min(allLegacyFiles.length, 15),
|
|
2918
|
-
},
|
|
2919
|
-
]);
|
|
2920
|
-
|
|
2921
|
-
filesToDelete.push(...selectionPrompt.filesToDelete);
|
|
2922
3496
|
}
|
|
2923
3497
|
|
|
2924
|
-
//
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
console.warn(`Warning: Could not delete ${file.relativePath}: ${error.message}`);
|
|
2931
|
-
}
|
|
3498
|
+
// Show summary
|
|
3499
|
+
if (keptCount > 0 || updatedCount > 0 || removedCount > 0) {
|
|
3500
|
+
console.log(chalk.dim(`\nSummary for custom modules with missing sources:`));
|
|
3501
|
+
if (keptCount > 0) console.log(chalk.dim(` • ${keptCount} module(s) kept as-is`));
|
|
3502
|
+
if (updatedCount > 0) console.log(chalk.dim(` • ${updatedCount} module(s) updated with new sources`));
|
|
3503
|
+
if (removedCount > 0) console.log(chalk.red(` • ${removedCount} module(s) permanently deleted`));
|
|
2932
3504
|
}
|
|
2933
3505
|
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
const newlyRetained = allLegacyFiles.filter((f) => !filesToDelete.includes(f)).map((f) => f.relativePath);
|
|
2939
|
-
|
|
2940
|
-
retentionData.retainedFiles = [...new Set([...retentionData.retainedFiles, ...newlyRetained])];
|
|
2941
|
-
retentionData.history.push({
|
|
2942
|
-
date: new Date().toISOString(),
|
|
2943
|
-
deleted: deletedCount,
|
|
2944
|
-
retained: retainedCount,
|
|
2945
|
-
files: filesToDelete.map((f) => f.relativePath),
|
|
2946
|
-
});
|
|
2947
|
-
|
|
2948
|
-
// Save retention data
|
|
2949
|
-
await fs.ensureDir(path.dirname(retentionPath));
|
|
2950
|
-
await fs.writeFile(retentionPath, yaml.dump(retentionData), 'utf8');
|
|
2951
|
-
|
|
2952
|
-
return { deleted: deletedCount, retained: retainedCount };
|
|
3506
|
+
return {
|
|
3507
|
+
validCustomModules,
|
|
3508
|
+
keptModulesWithoutSources,
|
|
3509
|
+
};
|
|
2953
3510
|
}
|
|
2954
3511
|
}
|
|
2955
3512
|
|