bmad-method 5.0.0 → 5.1.0
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/.github/FUNDING.yaml +15 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +32 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +22 -0
- package/.github/workflows/discord.yaml +25 -0
- package/.github/workflows/format-check.yaml +42 -0
- package/.github/workflows/manual-release.yaml +173 -0
- package/.husky/pre-commit +3 -2
- package/.vscode/settings.json +67 -74
- package/CHANGELOG.md +564 -19
- package/CONTRIBUTING.md +168 -5
- package/LICENSE +1 -1
- package/README.md +146 -218
- package/bmad-core/agent-teams/team-all.yaml +14 -0
- package/bmad-core/agent-teams/team-fullstack.yaml +18 -0
- package/bmad-core/agent-teams/team-ide-minimal.yaml +10 -0
- package/bmad-core/agent-teams/team-no-ui.yaml +13 -0
- package/bmad-core/agents/analyst.md +81 -0
- package/bmad-core/agents/architect.md +83 -0
- package/bmad-core/agents/bmad-master.md +107 -0
- package/bmad-core/agents/bmad-orchestrator.md +149 -0
- package/bmad-core/agents/dev.md +75 -0
- package/bmad-core/agents/pm.md +81 -0
- package/bmad-core/agents/po.md +76 -0
- package/bmad-core/agents/qa.md +88 -0
- package/bmad-core/agents/sm.md +62 -0
- package/bmad-core/agents/ux-expert.md +66 -0
- package/{.bmad-core → bmad-core}/checklists/architect-checklist.md +0 -5
- package/{.bmad-core → bmad-core}/checklists/change-checklist.md +2 -2
- package/{.bmad-core → bmad-core}/checklists/pm-checklist.md +0 -5
- package/{.bmad-core → bmad-core}/checklists/po-master-checklist.md +0 -9
- package/{.bmad-core → bmad-core}/checklists/story-dod-checklist.md +0 -7
- package/{.bmad-core → bmad-core}/checklists/story-draft-checklist.md +1 -4
- package/bmad-core/core-config.yaml +20 -0
- package/bmad-core/data/bmad-kb.md +806 -0
- package/bmad-core/data/brainstorming-techniques.md +36 -0
- package/bmad-core/data/elicitation-methods.md +154 -0
- package/bmad-core/data/test-levels-framework.md +146 -0
- package/bmad-core/data/test-priorities-matrix.md +172 -0
- package/bmad-core/tasks/advanced-elicitation.md +117 -0
- package/{.bmad-core → bmad-core}/tasks/correct-course.md +9 -12
- package/bmad-core/tasks/create-brownfield-story.md +312 -0
- package/{.bmad-core → bmad-core}/tasks/create-deep-research-prompt.md +4 -27
- package/bmad-core/tasks/create-next-story.md +112 -0
- package/bmad-core/tasks/document-project.md +343 -0
- package/bmad-core/tasks/facilitate-brainstorming-session.md +136 -0
- package/bmad-core/tasks/generate-ai-frontend-prompt.md +51 -0
- package/{.bmad-core → bmad-core}/tasks/index-docs.md +3 -13
- package/bmad-core/tasks/kb-mode-interaction.md +75 -0
- package/bmad-core/tasks/nfr-assess.md +343 -0
- package/bmad-core/tasks/qa-gate.md +159 -0
- package/bmad-core/tasks/review-story.md +314 -0
- package/bmad-core/tasks/risk-profile.md +353 -0
- package/{.bmad-core → bmad-core}/tasks/shard-doc.md +27 -15
- package/bmad-core/tasks/test-design.md +174 -0
- package/bmad-core/tasks/trace-requirements.md +264 -0
- package/bmad-core/tasks/validate-next-story.md +134 -0
- package/bmad-core/templates/architecture-tmpl.yaml +650 -0
- package/bmad-core/templates/brainstorming-output-tmpl.yaml +156 -0
- package/bmad-core/templates/brownfield-architecture-tmpl.yaml +476 -0
- package/bmad-core/templates/brownfield-prd-tmpl.yaml +280 -0
- package/bmad-core/templates/competitor-analysis-tmpl.yaml +306 -0
- package/bmad-core/templates/front-end-architecture-tmpl.yaml +218 -0
- package/bmad-core/templates/front-end-spec-tmpl.yaml +349 -0
- package/bmad-core/templates/fullstack-architecture-tmpl.yaml +823 -0
- package/bmad-core/templates/market-research-tmpl.yaml +252 -0
- package/bmad-core/templates/prd-tmpl.yaml +202 -0
- package/bmad-core/templates/project-brief-tmpl.yaml +221 -0
- package/bmad-core/templates/qa-gate-tmpl.yaml +102 -0
- package/bmad-core/templates/story-tmpl.yaml +137 -0
- package/bmad-core/workflows/brownfield-fullstack.yaml +297 -0
- package/bmad-core/workflows/brownfield-service.yaml +187 -0
- package/bmad-core/workflows/brownfield-ui.yaml +197 -0
- package/{.bmad-core/workflows/greenfield-fullstack.yml → bmad-core/workflows/greenfield-fullstack.yaml} +140 -77
- package/bmad-core/workflows/greenfield-service.yaml +206 -0
- package/bmad-core/workflows/greenfield-ui.yaml +235 -0
- package/common/tasks/create-doc.md +101 -0
- package/{.bmad-core → common}/tasks/execute-checklist.md +2 -13
- package/common/utils/bmad-doc-template.md +325 -0
- package/common/utils/workflow-management.md +69 -0
- package/dist/agents/analyst.txt +2889 -0
- package/dist/agents/architect.txt +3552 -0
- package/dist/agents/bmad-master.txt +8769 -0
- package/dist/agents/bmad-orchestrator.txt +1513 -0
- package/dist/agents/dev.txt +414 -0
- package/{.bmad-core/web-bundles → dist}/agents/pm.txt +668 -1119
- package/{.bmad-core/web-bundles → dist}/agents/po.txt +341 -484
- package/dist/agents/qa.txt +1987 -0
- package/dist/agents/sm.txt +658 -0
- package/dist/agents/ux-expert.txt +694 -0
- package/dist/expansion-packs/bmad-2d-phaser-game-dev/agents/game-designer.txt +2371 -0
- package/dist/expansion-packs/bmad-2d-phaser-game-dev/agents/game-developer.txt +1620 -0
- package/dist/expansion-packs/bmad-2d-phaser-game-dev/agents/game-sm.txt +815 -0
- package/dist/expansion-packs/bmad-2d-phaser-game-dev/teams/phaser-2d-nodejs-game-team.txt +10952 -0
- package/dist/expansion-packs/bmad-2d-unity-game-dev/agents/game-architect.txt +4012 -0
- package/dist/expansion-packs/bmad-2d-unity-game-dev/agents/game-designer.txt +3698 -0
- package/dist/expansion-packs/bmad-2d-unity-game-dev/agents/game-developer.txt +450 -0
- package/dist/expansion-packs/bmad-2d-unity-game-dev/agents/game-sm.txt +973 -0
- package/dist/expansion-packs/bmad-2d-unity-game-dev/teams/unity-2d-game-team.txt +15376 -0
- package/dist/expansion-packs/bmad-infrastructure-devops/agents/infra-devops-platform.txt +2075 -0
- package/dist/teams/team-all.txt +12682 -0
- package/dist/teams/team-fullstack.txt +10421 -0
- package/dist/teams/team-ide-minimal.txt +5103 -0
- package/dist/teams/team-no-ui.txt +8980 -0
- package/docs/GUIDING-PRINCIPLES.md +91 -0
- package/docs/core-architecture.md +219 -0
- package/docs/enhanced-ide-development-workflow.md +248 -0
- package/docs/expansion-packs.md +280 -0
- package/docs/how-to-contribute-with-pull-requests.md +158 -0
- package/docs/user-guide.md +504 -0
- package/docs/versioning-and-releases.md +8 -16
- package/docs/versions.md +4 -5
- package/docs/working-in-the-brownfield.md +597 -0
- package/eslint.config.mjs +119 -0
- package/expansion-packs/Complete AI Agent System - Blank Templates & Google Cloud Setup/Complete AI Agent System - Flowchart.svg +102 -0
- package/expansion-packs/Complete AI Agent System - Blank Templates & Google Cloud Setup/PART 1 - Google Cloud Vertex AI Setup Documentation/1.1 Google Cloud Project Setup/1.1.1 - Initial Project Configuration - bash copy.txt +13 -0
- package/expansion-packs/Complete AI Agent System - Blank Templates & Google Cloud Setup/PART 1 - Google Cloud Vertex AI Setup Documentation/1.1 Google Cloud Project Setup/1.1.1 - Initial Project Configuration - bash.txt +13 -0
- package/expansion-packs/Complete AI Agent System - Blank Templates & Google Cloud Setup/PART 1 - Google Cloud Vertex AI Setup Documentation/1.2 Agent Development Kit Installation/1.2.2 - Basic Project Structure - txt.txt +25 -0
- package/expansion-packs/Complete AI Agent System - Blank Templates & Google Cloud Setup/PART 1 - Google Cloud Vertex AI Setup Documentation/1.3 Core Configuration Files/1.3.1 - settings.py +34 -0
- package/expansion-packs/Complete AI Agent System - Blank Templates & Google Cloud Setup/PART 1 - Google Cloud Vertex AI Setup Documentation/1.3 Core Configuration Files/1.3.2 - main.py - Base Application.py +70 -0
- package/expansion-packs/Complete AI Agent System - Blank Templates & Google Cloud Setup/PART 1 - Google Cloud Vertex AI Setup Documentation/1.4 Deployment Configuration/1.4.2 - cloudbuild.yaml +26 -0
- package/expansion-packs/Complete AI Agent System - Blank Templates & Google Cloud Setup/README.md +109 -0
- package/expansion-packs/README.md +2 -112
- package/expansion-packs/bmad-2d-phaser-game-dev/agent-teams/phaser-2d-nodejs-game-team.yaml +13 -0
- package/expansion-packs/bmad-2d-phaser-game-dev/agents/game-designer.md +71 -0
- package/expansion-packs/bmad-2d-phaser-game-dev/agents/game-developer.md +78 -0
- package/expansion-packs/bmad-2d-phaser-game-dev/agents/game-sm.md +64 -0
- package/expansion-packs/bmad-2d-phaser-game-dev/checklists/game-design-checklist.md +201 -0
- package/expansion-packs/bmad-2d-phaser-game-dev/checklists/game-story-dod-checklist.md +160 -0
- package/expansion-packs/bmad-2d-phaser-game-dev/config.yaml +8 -0
- package/expansion-packs/bmad-2d-phaser-game-dev/data/bmad-kb.md +250 -0
- package/expansion-packs/bmad-2d-phaser-game-dev/data/development-guidelines.md +647 -0
- package/expansion-packs/bmad-2d-phaser-game-dev/tasks/advanced-elicitation.md +110 -0
- package/expansion-packs/bmad-2d-phaser-game-dev/tasks/create-game-story.md +216 -0
- package/expansion-packs/bmad-2d-phaser-game-dev/tasks/game-design-brainstorming.md +290 -0
- package/expansion-packs/bmad-2d-phaser-game-dev/templates/game-architecture-tmpl.yaml +613 -0
- package/expansion-packs/bmad-2d-phaser-game-dev/templates/game-brief-tmpl.yaml +356 -0
- package/expansion-packs/bmad-2d-phaser-game-dev/templates/game-design-doc-tmpl.yaml +343 -0
- package/expansion-packs/bmad-2d-phaser-game-dev/templates/game-story-tmpl.yaml +253 -0
- package/expansion-packs/bmad-2d-phaser-game-dev/templates/level-design-doc-tmpl.yaml +484 -0
- package/expansion-packs/bmad-2d-phaser-game-dev/workflows/game-dev-greenfield.yaml +183 -0
- package/expansion-packs/bmad-2d-phaser-game-dev/workflows/game-prototype.yaml +175 -0
- package/expansion-packs/bmad-2d-unity-game-dev/agent-teams/unity-2d-game-team.yaml +14 -0
- package/expansion-packs/bmad-2d-unity-game-dev/agents/game-architect.md +80 -0
- package/expansion-packs/bmad-2d-unity-game-dev/agents/game-designer.md +77 -0
- package/expansion-packs/bmad-2d-unity-game-dev/agents/game-developer.md +78 -0
- package/expansion-packs/bmad-2d-unity-game-dev/agents/game-sm.md +65 -0
- package/expansion-packs/bmad-2d-unity-game-dev/checklists/game-architect-checklist.md +391 -0
- package/expansion-packs/bmad-2d-unity-game-dev/checklists/game-change-checklist.md +203 -0
- package/expansion-packs/bmad-2d-unity-game-dev/checklists/game-design-checklist.md +201 -0
- package/expansion-packs/bmad-2d-unity-game-dev/checklists/game-story-dod-checklist.md +124 -0
- package/expansion-packs/bmad-2d-unity-game-dev/config.yaml +6 -0
- package/expansion-packs/bmad-2d-unity-game-dev/data/bmad-kb.md +769 -0
- package/expansion-packs/bmad-2d-unity-game-dev/data/development-guidelines.md +586 -0
- package/expansion-packs/bmad-2d-unity-game-dev/tasks/advanced-elicitation.md +110 -0
- package/expansion-packs/bmad-2d-unity-game-dev/tasks/correct-course-game.md +141 -0
- package/expansion-packs/bmad-2d-unity-game-dev/tasks/create-game-story.md +184 -0
- package/expansion-packs/bmad-2d-unity-game-dev/tasks/game-design-brainstorming.md +290 -0
- package/expansion-packs/bmad-2d-unity-game-dev/tasks/validate-game-story.md +200 -0
- package/expansion-packs/bmad-2d-unity-game-dev/templates/game-architecture-tmpl.yaml +1030 -0
- package/expansion-packs/bmad-2d-unity-game-dev/templates/game-brief-tmpl.yaml +356 -0
- package/expansion-packs/bmad-2d-unity-game-dev/templates/game-design-doc-tmpl.yaml +705 -0
- package/expansion-packs/bmad-2d-unity-game-dev/templates/game-story-tmpl.yaml +256 -0
- package/expansion-packs/bmad-2d-unity-game-dev/templates/level-design-doc-tmpl.yaml +484 -0
- package/expansion-packs/bmad-2d-unity-game-dev/workflows/game-dev-greenfield.yaml +183 -0
- package/expansion-packs/bmad-2d-unity-game-dev/workflows/game-prototype.yaml +175 -0
- package/expansion-packs/{infrastructure-devops → bmad-infrastructure-devops}/README.md +9 -9
- package/expansion-packs/{infrastructure-devops → bmad-infrastructure-devops}/agents/infra-devops-platform.md +30 -18
- package/expansion-packs/{infrastructure-devops → bmad-infrastructure-devops}/checklists/infrastructure-checklist.md +1 -1
- package/expansion-packs/bmad-infrastructure-devops/config.yaml +9 -0
- package/expansion-packs/bmad-infrastructure-devops/data/bmad-kb.md +305 -0
- package/expansion-packs/{infrastructure-devops → bmad-infrastructure-devops}/tasks/review-infrastructure.md +4 -5
- package/expansion-packs/{infrastructure-devops → bmad-infrastructure-devops}/tasks/validate-infrastructure.md +4 -5
- package/expansion-packs/bmad-infrastructure-devops/templates/infrastructure-architecture-tmpl.yaml +424 -0
- package/expansion-packs/bmad-infrastructure-devops/templates/infrastructure-platform-from-arch-tmpl.yaml +629 -0
- package/package.json +74 -42
- package/prettier.config.mjs +32 -0
- package/release_notes.md +25 -0
- package/tools/bmad-npx-wrapper.js +13 -15
- package/tools/builders/web-builder.js +544 -15
- package/tools/bump-all-versions.js +115 -0
- package/tools/bump-expansion-version.js +90 -0
- package/tools/cli.js +65 -32
- package/tools/flattener/aggregate.js +76 -0
- package/tools/flattener/binary.js +80 -0
- package/tools/flattener/discovery.js +71 -0
- package/tools/flattener/files.js +35 -0
- package/tools/flattener/ignoreRules.js +176 -0
- package/tools/flattener/main.js +573 -0
- package/tools/flattener/projectRoot.js +206 -0
- package/tools/flattener/prompts.js +44 -0
- package/tools/flattener/stats.helpers.js +395 -0
- package/tools/flattener/stats.js +80 -0
- package/tools/flattener/test-matrix.js +413 -0
- package/tools/flattener/xml.js +88 -0
- package/tools/installer/README.md +3 -53
- package/tools/installer/bin/bmad.js +475 -90
- package/tools/installer/config/ide-agent-config.yaml +58 -0
- package/tools/installer/config/install.config.yaml +123 -0
- package/tools/installer/lib/config-loader.js +208 -40
- package/tools/installer/lib/file-manager.js +258 -55
- package/tools/installer/lib/ide-base-setup.js +228 -0
- package/tools/installer/lib/ide-setup.js +1265 -253
- package/tools/installer/lib/installer.js +1651 -310
- package/tools/installer/lib/memory-profiler.js +225 -0
- package/tools/installer/lib/module-manager.js +114 -0
- package/tools/installer/lib/resource-locator.js +308 -0
- package/tools/installer/package.json +25 -24
- package/tools/lib/dependency-resolver.js +44 -48
- package/tools/lib/yaml-utils.js +29 -0
- package/tools/md-assets/web-agent-startup-instructions.md +39 -0
- package/tools/preview-release-notes.js +66 -0
- package/tools/shared/bannerArt.js +105 -0
- package/tools/sync-installer-version.js +7 -9
- package/tools/update-expansion-version.js +53 -0
- package/tools/upgraders/v3-to-v4-upgrader.js +221 -320
- package/tools/version-bump.js +42 -27
- package/tools/yaml-format.js +57 -44
- package/.bmad-core/agent-teams/team-all.yml +0 -16
- package/.bmad-core/agent-teams/team-fullstack.yml +0 -26
- package/.bmad-core/agent-teams/team-no-ui.yml +0 -15
- package/.bmad-core/agents/analyst.md +0 -59
- package/.bmad-core/agents/architect.md +0 -66
- package/.bmad-core/agents/bmad-master.md +0 -104
- package/.bmad-core/agents/bmad-orchestrator.md +0 -81
- package/.bmad-core/agents/dev.md +0 -70
- package/.bmad-core/agents/pm.md +0 -59
- package/.bmad-core/agents/po.md +0 -60
- package/.bmad-core/agents/qa.md +0 -52
- package/.bmad-core/agents/sm.md +0 -55
- package/.bmad-core/agents/ux-expert.md +0 -66
- package/.bmad-core/data/bmad-kb.md +0 -47
- package/.bmad-core/schemas/agent-team-schema.yml +0 -153
- package/.bmad-core/tasks/advanced-elicitation.md +0 -92
- package/.bmad-core/tasks/brainstorming-techniques.md +0 -238
- package/.bmad-core/tasks/core-dump.md +0 -74
- package/.bmad-core/tasks/create-agent.md +0 -202
- package/.bmad-core/tasks/create-doc.md +0 -74
- package/.bmad-core/tasks/create-expansion-pack.md +0 -425
- package/.bmad-core/tasks/create-next-story.md +0 -206
- package/.bmad-core/tasks/create-team.md +0 -229
- package/.bmad-core/tasks/doc-migration-task.md +0 -143
- package/.bmad-core/tasks/generate-ai-frontend-prompt.md +0 -58
- package/.bmad-core/templates/agent-tmpl.md +0 -58
- package/.bmad-core/templates/architecture-tmpl.md +0 -771
- package/.bmad-core/templates/brownfield-architecture-tmpl.md +0 -542
- package/.bmad-core/templates/brownfield-prd-tmpl.md +0 -240
- package/.bmad-core/templates/competitor-analysis-tmpl.md +0 -289
- package/.bmad-core/templates/expansion-pack-plan-tmpl.md +0 -91
- package/.bmad-core/templates/front-end-architecture-tmpl.md +0 -173
- package/.bmad-core/templates/front-end-spec-tmpl.md +0 -411
- package/.bmad-core/templates/fullstack-architecture-tmpl.md +0 -1016
- package/.bmad-core/templates/market-research-tmpl.md +0 -261
- package/.bmad-core/templates/prd-tmpl.md +0 -200
- package/.bmad-core/templates/project-brief-tmpl.md +0 -228
- package/.bmad-core/templates/simple-project-prd-tmpl.md +0 -461
- package/.bmad-core/templates/story-tmpl.md +0 -61
- package/.bmad-core/templates/web-agent-startup-instructions-template.md +0 -39
- package/.bmad-core/utils/agent-switcher.ide.md +0 -112
- package/.bmad-core/utils/template-format.md +0 -26
- package/.bmad-core/utils/workflow-management.md +0 -224
- package/.bmad-core/web-bundles/agents/analyst.txt +0 -1684
- package/.bmad-core/web-bundles/agents/architect.txt +0 -3584
- package/.bmad-core/web-bundles/agents/bmad-master.txt +0 -9491
- package/.bmad-core/web-bundles/agents/bmad-orchestrator.txt +0 -1466
- package/.bmad-core/web-bundles/agents/dev.txt +0 -316
- package/.bmad-core/web-bundles/agents/qa.txt +0 -129
- package/.bmad-core/web-bundles/agents/sm.txt +0 -658
- package/.bmad-core/web-bundles/agents/ux-expert.txt +0 -1099
- package/.bmad-core/web-bundles/teams/team-all.txt +0 -10757
- package/.bmad-core/web-bundles/teams/team-fullstack.txt +0 -10109
- package/.bmad-core/web-bundles/teams/team-no-ui.txt +0 -8950
- package/.bmad-core/workflows/brownfield-fullstack.yml +0 -116
- package/.bmad-core/workflows/brownfield-service.yml +0 -117
- package/.bmad-core/workflows/brownfield-ui.yml +0 -127
- package/.bmad-core/workflows/greenfield-service.yml +0 -143
- package/.bmad-core/workflows/greenfield-ui.yml +0 -172
- package/.claude/commands/analyst.md +0 -63
- package/.claude/commands/architect.md +0 -70
- package/.claude/commands/bmad-master.md +0 -108
- package/.claude/commands/bmad-orchestrator.md +0 -85
- package/.claude/commands/dev.md +0 -74
- package/.claude/commands/pm.md +0 -63
- package/.claude/commands/po.md +0 -64
- package/.claude/commands/qa.md +0 -56
- package/.claude/commands/sm.md +0 -59
- package/.claude/commands/ux-expert.md +0 -70
- package/.cursor/rules/analyst.mdc +0 -77
- package/.cursor/rules/architect.mdc +0 -84
- package/.cursor/rules/bmad-master.mdc +0 -122
- package/.cursor/rules/bmad-orchestrator.mdc +0 -99
- package/.cursor/rules/dev.mdc +0 -88
- package/.cursor/rules/pm.mdc +0 -77
- package/.cursor/rules/po.mdc +0 -78
- package/.cursor/rules/qa.mdc +0 -70
- package/.cursor/rules/sm.mdc +0 -73
- package/.cursor/rules/ux-expert.mdc +0 -84
- package/.github/workflows/release.yml +0 -59
- package/.releaserc.json +0 -18
- package/.roo/.roomodes +0 -95
- package/.roo/README.md +0 -38
- package/.vscode/extensions.json +0 -6
- package/.windsurf/rules/analyst.md +0 -71
- package/.windsurf/rules/architect.md +0 -78
- package/.windsurf/rules/bmad-master.md +0 -116
- package/.windsurf/rules/bmad-orchestrator.md +0 -93
- package/.windsurf/rules/dev.md +0 -82
- package/.windsurf/rules/pm.md +0 -71
- package/.windsurf/rules/po.md +0 -72
- package/.windsurf/rules/qa.md +0 -64
- package/.windsurf/rules/sm.md +0 -67
- package/.windsurf/rules/ux-expert.md +0 -78
- package/docs/bmad-workflow-guide.md +0 -161
- package/docs/claude-code-guide.md +0 -119
- package/docs/cursor-guide.md +0 -127
- package/docs/roo-code-guide.md +0 -140
- package/docs/sample-output/simple-fullstack-greenfield/prd.md +0 -42
- package/docs/windsurf-guide.md +0 -127
- package/expansion-packs/infrastructure-devops/manifest.yml +0 -38
- package/expansion-packs/infrastructure-devops/templates/infrastructure-architecture-tmpl.md +0 -415
- package/expansion-packs/infrastructure-devops/templates/infrastructure-platform-from-arch-tmpl.md +0 -0
- package/tools/installer/config/install.config.yml +0 -139
- package/tools/installer/package-lock.json +0 -906
- package/tools/installer/templates/claude-commands.md +0 -7
- package/tools/installer/templates/cursor-rules.md +0 -22
- package/tools/installer/templates/windsurf-rules.md +0 -22
- package/tools/semantic-release-sync-installer.js +0 -31
- /package/{.bmad-core → bmad-core}/data/technical-preferences.md +0 -0
- /package/{.bmad-core → bmad-core}/tasks/brownfield-create-epic.md +0 -0
- /package/{.bmad-core → bmad-core}/tasks/brownfield-create-story.md +0 -0
|
@@ -1,40 +1,54 @@
|
|
|
1
|
-
const path = require(
|
|
2
|
-
const
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
if (!chalk) {
|
|
12
|
-
chalk = (await import("chalk")).default;
|
|
13
|
-
ora = (await import("ora")).default;
|
|
14
|
-
inquirer = (await import("inquirer")).default;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
1
|
+
const path = require('node:path');
|
|
2
|
+
const fs = require('fs-extra');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
const ora = require('ora');
|
|
5
|
+
const inquirer = require('inquirer');
|
|
6
|
+
const fileManager = require('./file-manager');
|
|
7
|
+
const configLoader = require('./config-loader');
|
|
8
|
+
const ideSetup = require('./ide-setup');
|
|
9
|
+
const { extractYamlFromAgent } = require('../../lib/yaml-utils');
|
|
10
|
+
const resourceLocator = require('./resource-locator');
|
|
17
11
|
|
|
18
12
|
class Installer {
|
|
13
|
+
async getCoreVersion() {
|
|
14
|
+
try {
|
|
15
|
+
// Always use package.json version
|
|
16
|
+
const packagePath = path.join(__dirname, '..', '..', '..', 'package.json');
|
|
17
|
+
const packageJson = require(packagePath);
|
|
18
|
+
return packageJson.version;
|
|
19
|
+
} catch {
|
|
20
|
+
console.warn("Could not read version from package.json, using 'unknown'");
|
|
21
|
+
return 'unknown';
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
19
25
|
async install(config) {
|
|
20
|
-
|
|
21
|
-
await initializeModules();
|
|
22
|
-
|
|
23
|
-
const spinner = ora("Analyzing installation directory...").start();
|
|
26
|
+
const spinner = ora('Analyzing installation directory...').start();
|
|
24
27
|
|
|
25
28
|
try {
|
|
26
|
-
//
|
|
27
|
-
|
|
29
|
+
// Store the original CWD where npx was executed
|
|
30
|
+
const originalCwd = process.env.INIT_CWD || process.env.PWD || process.cwd();
|
|
31
|
+
|
|
32
|
+
// Resolve installation directory relative to where the user ran the command
|
|
33
|
+
let installDir = path.isAbsolute(config.directory)
|
|
34
|
+
? config.directory
|
|
35
|
+
: path.resolve(originalCwd, config.directory);
|
|
36
|
+
|
|
28
37
|
if (path.basename(installDir) === '.bmad-core') {
|
|
29
38
|
// If user points directly to .bmad-core, treat its parent as the project root
|
|
30
39
|
installDir = path.dirname(installDir);
|
|
31
40
|
}
|
|
32
41
|
|
|
42
|
+
// Log resolved path for clarity
|
|
43
|
+
if (!path.isAbsolute(config.directory)) {
|
|
44
|
+
spinner.text = `Resolving "${config.directory}" to: ${installDir}`;
|
|
45
|
+
}
|
|
46
|
+
|
|
33
47
|
// Check if directory exists and handle non-existent directories
|
|
34
48
|
if (!(await fileManager.pathExists(installDir))) {
|
|
35
49
|
spinner.stop();
|
|
36
|
-
console.log(
|
|
37
|
-
|
|
50
|
+
console.log(`\nThe directory ${installDir} does not exist.`);
|
|
51
|
+
|
|
38
52
|
const { action } = await inquirer.prompt([
|
|
39
53
|
{
|
|
40
54
|
type: 'list',
|
|
@@ -43,51 +57,72 @@ class Installer {
|
|
|
43
57
|
choices: [
|
|
44
58
|
{
|
|
45
59
|
name: 'Create the directory and continue',
|
|
46
|
-
value: 'create'
|
|
60
|
+
value: 'create',
|
|
47
61
|
},
|
|
48
62
|
{
|
|
49
63
|
name: 'Choose a different directory',
|
|
50
|
-
value: 'change'
|
|
64
|
+
value: 'change',
|
|
51
65
|
},
|
|
52
66
|
{
|
|
53
67
|
name: 'Cancel installation',
|
|
54
|
-
value: 'cancel'
|
|
55
|
-
}
|
|
56
|
-
]
|
|
57
|
-
}
|
|
68
|
+
value: 'cancel',
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
},
|
|
58
72
|
]);
|
|
59
73
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
74
|
+
switch (action) {
|
|
75
|
+
case 'cancel': {
|
|
76
|
+
console.log('Installation cancelled.');
|
|
77
|
+
process.exit(0);
|
|
78
|
+
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
case 'change': {
|
|
82
|
+
const { newDirectory } = await inquirer.prompt([
|
|
83
|
+
{
|
|
84
|
+
type: 'input',
|
|
85
|
+
name: 'newDirectory',
|
|
86
|
+
message: 'Enter the new directory path:',
|
|
87
|
+
validate: (input) => {
|
|
88
|
+
if (!input.trim()) {
|
|
89
|
+
return 'Please enter a valid directory path';
|
|
90
|
+
}
|
|
91
|
+
return true;
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
]);
|
|
95
|
+
// Preserve the original CWD for the recursive call
|
|
96
|
+
config.directory = newDirectory;
|
|
97
|
+
return await this.install(config); // Recursive call with new directory
|
|
98
|
+
}
|
|
99
|
+
case 'create': {
|
|
100
|
+
try {
|
|
101
|
+
await fileManager.ensureDirectory(installDir);
|
|
102
|
+
console.log(`✓ Created directory: ${installDir}`);
|
|
103
|
+
} catch (error) {
|
|
104
|
+
console.error(`Failed to create directory: ${error.message}`);
|
|
105
|
+
console.error('You may need to check permissions or use a different path.');
|
|
106
|
+
process.exit(1);
|
|
75
107
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
return await this.install(config); // Recursive call with new directory
|
|
79
|
-
} else if (action === 'create') {
|
|
80
|
-
try {
|
|
81
|
-
await fileManager.ensureDirectory(installDir);
|
|
82
|
-
console.log(chalk.green(`✓ Created directory: ${installDir}`));
|
|
83
|
-
} catch (error) {
|
|
84
|
-
console.error(chalk.red(`Failed to create directory: ${error.message}`));
|
|
85
|
-
console.error(chalk.yellow('You may need to check permissions or use a different path.'));
|
|
86
|
-
process.exit(1);
|
|
108
|
+
|
|
109
|
+
break;
|
|
87
110
|
}
|
|
111
|
+
// No default
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
spinner.start('Analyzing installation directory...');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// If this is an update request from early detection, handle it directly
|
|
118
|
+
if (config.installType === 'update') {
|
|
119
|
+
const state = await this.detectInstallationState(installDir);
|
|
120
|
+
if (state.type === 'v4_existing') {
|
|
121
|
+
return await this.performUpdate(config, installDir, state.manifest, spinner);
|
|
122
|
+
} else {
|
|
123
|
+
spinner.fail('No existing v4 installation found to update');
|
|
124
|
+
throw new Error('No existing v4 installation found');
|
|
88
125
|
}
|
|
89
|
-
|
|
90
|
-
spinner.start("Analyzing installation directory...");
|
|
91
126
|
}
|
|
92
127
|
|
|
93
128
|
// Detect current state
|
|
@@ -95,49 +130,42 @@ class Installer {
|
|
|
95
130
|
|
|
96
131
|
// Handle different states
|
|
97
132
|
switch (state.type) {
|
|
98
|
-
case
|
|
133
|
+
case 'clean': {
|
|
99
134
|
return await this.performFreshInstall(config, installDir, spinner);
|
|
135
|
+
}
|
|
100
136
|
|
|
101
|
-
case
|
|
102
|
-
return await this.handleExistingV4Installation(
|
|
103
|
-
|
|
104
|
-
installDir,
|
|
105
|
-
state,
|
|
106
|
-
spinner
|
|
107
|
-
);
|
|
137
|
+
case 'v4_existing': {
|
|
138
|
+
return await this.handleExistingV4Installation(config, installDir, state, spinner);
|
|
139
|
+
}
|
|
108
140
|
|
|
109
|
-
case
|
|
110
|
-
return await this.handleV3Installation(
|
|
111
|
-
|
|
112
|
-
installDir,
|
|
113
|
-
state,
|
|
114
|
-
spinner
|
|
115
|
-
);
|
|
141
|
+
case 'v3_existing': {
|
|
142
|
+
return await this.handleV3Installation(config, installDir, state, spinner);
|
|
143
|
+
}
|
|
116
144
|
|
|
117
|
-
case
|
|
118
|
-
return await this.handleUnknownInstallation(
|
|
119
|
-
|
|
120
|
-
installDir,
|
|
121
|
-
state,
|
|
122
|
-
spinner
|
|
123
|
-
);
|
|
145
|
+
case 'unknown_existing': {
|
|
146
|
+
return await this.handleUnknownInstallation(config, installDir, state, spinner);
|
|
147
|
+
}
|
|
124
148
|
}
|
|
125
149
|
} catch (error) {
|
|
126
|
-
|
|
150
|
+
// Check if modules were initialized
|
|
151
|
+
if (spinner) {
|
|
152
|
+
spinner.fail('Installation failed');
|
|
153
|
+
} else {
|
|
154
|
+
console.error('Installation failed:', error.message);
|
|
155
|
+
}
|
|
127
156
|
throw error;
|
|
128
157
|
}
|
|
129
158
|
}
|
|
130
159
|
|
|
131
160
|
async detectInstallationState(installDir) {
|
|
132
|
-
// Ensure modules are initialized
|
|
133
|
-
await initializeModules();
|
|
134
161
|
const state = {
|
|
135
|
-
type:
|
|
162
|
+
type: 'clean',
|
|
136
163
|
hasV4Manifest: false,
|
|
137
164
|
hasV3Structure: false,
|
|
138
165
|
hasBmadCore: false,
|
|
139
166
|
hasOtherFiles: false,
|
|
140
167
|
manifest: null,
|
|
168
|
+
expansionPacks: {},
|
|
141
169
|
};
|
|
142
170
|
|
|
143
171
|
// Check if directory exists
|
|
@@ -146,11 +174,11 @@ class Installer {
|
|
|
146
174
|
}
|
|
147
175
|
|
|
148
176
|
// Check for V4 installation (has .bmad-core with manifest)
|
|
149
|
-
const bmadCorePath = path.join(installDir,
|
|
150
|
-
const manifestPath = path.join(bmadCorePath,
|
|
177
|
+
const bmadCorePath = path.join(installDir, '.bmad-core');
|
|
178
|
+
const manifestPath = path.join(bmadCorePath, 'install-manifest.yaml');
|
|
151
179
|
|
|
152
180
|
if (await fileManager.pathExists(manifestPath)) {
|
|
153
|
-
state.type =
|
|
181
|
+
state.type = 'v4_existing';
|
|
154
182
|
state.hasV4Manifest = true;
|
|
155
183
|
state.hasBmadCore = true;
|
|
156
184
|
state.manifest = await fileManager.readManifest(installDir);
|
|
@@ -158,107 +186,221 @@ class Installer {
|
|
|
158
186
|
}
|
|
159
187
|
|
|
160
188
|
// Check for V3 installation (has bmad-agent directory)
|
|
161
|
-
const bmadAgentPath = path.join(installDir,
|
|
189
|
+
const bmadAgentPath = path.join(installDir, 'bmad-agent');
|
|
162
190
|
if (await fileManager.pathExists(bmadAgentPath)) {
|
|
163
|
-
state.type =
|
|
191
|
+
state.type = 'v3_existing';
|
|
164
192
|
state.hasV3Structure = true;
|
|
165
193
|
return state;
|
|
166
194
|
}
|
|
167
195
|
|
|
168
196
|
// Check for .bmad-core without manifest (broken V4 or manual copy)
|
|
169
197
|
if (await fileManager.pathExists(bmadCorePath)) {
|
|
170
|
-
state.type =
|
|
198
|
+
state.type = 'unknown_existing';
|
|
171
199
|
state.hasBmadCore = true;
|
|
172
200
|
return state;
|
|
173
201
|
}
|
|
174
202
|
|
|
175
203
|
// Check if directory has other files
|
|
176
|
-
const
|
|
177
|
-
const files = glob.sync("**/*", {
|
|
204
|
+
const files = await resourceLocator.findFiles('**/*', {
|
|
178
205
|
cwd: installDir,
|
|
179
206
|
nodir: true,
|
|
180
|
-
ignore: [
|
|
207
|
+
ignore: ['**/.git/**', '**/node_modules/**'],
|
|
181
208
|
});
|
|
182
209
|
|
|
183
210
|
if (files.length > 0) {
|
|
184
|
-
// Directory has other files, but no
|
|
211
|
+
// Directory has other files, but no BMad installation.
|
|
185
212
|
// Treat as clean install but record that it isn't empty.
|
|
186
213
|
state.hasOtherFiles = true;
|
|
187
214
|
}
|
|
188
215
|
|
|
216
|
+
// Check for expansion packs (folders starting with .)
|
|
217
|
+
const expansionPacks = await this.detectExpansionPacks(installDir);
|
|
218
|
+
state.expansionPacks = expansionPacks;
|
|
219
|
+
|
|
189
220
|
return state; // clean install
|
|
190
221
|
}
|
|
191
222
|
|
|
192
|
-
async performFreshInstall(config, installDir, spinner) {
|
|
193
|
-
|
|
194
|
-
await initializeModules();
|
|
195
|
-
spinner.text = "Installing BMAD Method...";
|
|
223
|
+
async performFreshInstall(config, installDir, spinner, options = {}) {
|
|
224
|
+
spinner.text = 'Installing BMad Method...';
|
|
196
225
|
|
|
197
226
|
let files = [];
|
|
198
227
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
228
|
+
switch (config.installType) {
|
|
229
|
+
case 'full': {
|
|
230
|
+
// Full installation - copy entire .bmad-core folder as a subdirectory
|
|
231
|
+
spinner.text = 'Copying complete .bmad-core folder...';
|
|
232
|
+
const sourceDir = resourceLocator.getBmadCorePath();
|
|
233
|
+
const bmadCoreDestDir = path.join(installDir, '.bmad-core');
|
|
234
|
+
await fileManager.copyDirectoryWithRootReplacement(
|
|
235
|
+
sourceDir,
|
|
236
|
+
bmadCoreDestDir,
|
|
237
|
+
'.bmad-core',
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
// Copy common/ items to .bmad-core
|
|
241
|
+
spinner.text = 'Copying common utilities...';
|
|
242
|
+
await this.copyCommonItems(installDir, '.bmad-core', spinner);
|
|
243
|
+
|
|
244
|
+
// Copy documentation files from docs/ to .bmad-core
|
|
245
|
+
spinner.text = 'Copying documentation files...';
|
|
246
|
+
await this.copyDocsItems(installDir, '.bmad-core', spinner);
|
|
247
|
+
|
|
248
|
+
// Get list of all files for manifest
|
|
249
|
+
const foundFiles = await resourceLocator.findFiles('**/*', {
|
|
210
250
|
cwd: bmadCoreDestDir,
|
|
211
251
|
nodir: true,
|
|
212
|
-
ignore: [
|
|
213
|
-
})
|
|
214
|
-
.map((file) => path.join(
|
|
215
|
-
} else if (config.installType === "single-agent") {
|
|
216
|
-
// Single agent installation
|
|
217
|
-
spinner.text = `Installing ${config.agent} agent...`;
|
|
218
|
-
|
|
219
|
-
// Copy agent file
|
|
220
|
-
const agentPath = configLoader.getAgentPath(config.agent);
|
|
221
|
-
const destAgentPath = path.join(
|
|
222
|
-
installDir,
|
|
223
|
-
"agents",
|
|
224
|
-
`${config.agent}.md`
|
|
225
|
-
);
|
|
226
|
-
await fileManager.copyFile(agentPath, destAgentPath);
|
|
227
|
-
files.push(`agents/${config.agent}.md`);
|
|
252
|
+
ignore: ['**/.git/**', '**/node_modules/**'],
|
|
253
|
+
});
|
|
254
|
+
files = foundFiles.map((file) => path.join('.bmad-core', file));
|
|
228
255
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
case 'single-agent': {
|
|
259
|
+
// Single agent installation
|
|
260
|
+
spinner.text = `Installing ${config.agent} agent...`;
|
|
261
|
+
|
|
262
|
+
// Copy agent file with {root} replacement
|
|
263
|
+
const agentPath = configLoader.getAgentPath(config.agent);
|
|
264
|
+
const destinationAgentPath = path.join(
|
|
265
|
+
installDir,
|
|
266
|
+
'.bmad-core',
|
|
267
|
+
'agents',
|
|
268
|
+
`${config.agent}.md`,
|
|
269
|
+
);
|
|
270
|
+
await fileManager.copyFileWithRootReplacement(
|
|
271
|
+
agentPath,
|
|
272
|
+
destinationAgentPath,
|
|
273
|
+
'.bmad-core',
|
|
274
|
+
);
|
|
275
|
+
files.push(`.bmad-core/agents/${config.agent}.md`);
|
|
276
|
+
|
|
277
|
+
// Copy dependencies
|
|
278
|
+
const { all: dependencies } = await resourceLocator.getAgentDependencies(config.agent);
|
|
279
|
+
const sourceBase = resourceLocator.getBmadCorePath();
|
|
280
|
+
|
|
281
|
+
for (const dep of dependencies) {
|
|
282
|
+
spinner.text = `Copying dependency: ${dep}`;
|
|
283
|
+
|
|
284
|
+
if (dep.includes('*')) {
|
|
285
|
+
// Handle glob patterns with {root} replacement
|
|
286
|
+
const copiedFiles = await fileManager.copyGlobPattern(
|
|
287
|
+
dep.replace('.bmad-core/', ''),
|
|
288
|
+
sourceBase,
|
|
289
|
+
path.join(installDir, '.bmad-core'),
|
|
290
|
+
'.bmad-core',
|
|
291
|
+
);
|
|
292
|
+
files.push(...copiedFiles.map((f) => `.bmad-core/${f}`));
|
|
293
|
+
} else {
|
|
294
|
+
// Handle single files with {root} replacement if needed
|
|
295
|
+
const sourcePath = path.join(sourceBase, dep.replace('.bmad-core/', ''));
|
|
296
|
+
const destinationPath = path.join(installDir, dep);
|
|
297
|
+
|
|
298
|
+
const needsRootReplacement =
|
|
299
|
+
dep.endsWith('.md') || dep.endsWith('.yaml') || dep.endsWith('.yml');
|
|
300
|
+
let success = false;
|
|
301
|
+
|
|
302
|
+
success = await (needsRootReplacement
|
|
303
|
+
? fileManager.copyFileWithRootReplacement(sourcePath, destinationPath, '.bmad-core')
|
|
304
|
+
: fileManager.copyFile(sourcePath, destinationPath));
|
|
305
|
+
|
|
306
|
+
if (success) {
|
|
307
|
+
files.push(dep);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
234
311
|
|
|
235
|
-
|
|
236
|
-
spinner.text =
|
|
312
|
+
// Copy common/ items to .bmad-core
|
|
313
|
+
spinner.text = 'Copying common utilities...';
|
|
314
|
+
const commonFiles = await this.copyCommonItems(installDir, '.bmad-core', spinner);
|
|
315
|
+
files.push(...commonFiles);
|
|
237
316
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
sourceBase,
|
|
243
|
-
installDir
|
|
244
|
-
);
|
|
245
|
-
files.push(...copiedFiles);
|
|
246
|
-
} else {
|
|
247
|
-
// Handle single files
|
|
248
|
-
const sourcePath = path.join(
|
|
249
|
-
sourceBase,
|
|
250
|
-
dep.replace(".bmad-core/", "")
|
|
251
|
-
);
|
|
252
|
-
const destPath = path.join(
|
|
253
|
-
installDir,
|
|
254
|
-
dep.replace(".bmad-core/", "")
|
|
255
|
-
);
|
|
317
|
+
// Copy documentation files from docs/ to .bmad-core
|
|
318
|
+
spinner.text = 'Copying documentation files...';
|
|
319
|
+
const documentFiles = await this.copyDocsItems(installDir, '.bmad-core', spinner);
|
|
320
|
+
files.push(...documentFiles);
|
|
256
321
|
|
|
257
|
-
|
|
258
|
-
|
|
322
|
+
break;
|
|
323
|
+
}
|
|
324
|
+
case 'team': {
|
|
325
|
+
// Team installation
|
|
326
|
+
spinner.text = `Installing ${config.team} team...`;
|
|
327
|
+
|
|
328
|
+
// Get team dependencies
|
|
329
|
+
const teamDependencies = await configLoader.getTeamDependencies(config.team);
|
|
330
|
+
const sourceBase = resourceLocator.getBmadCorePath();
|
|
331
|
+
|
|
332
|
+
// Install all team dependencies
|
|
333
|
+
for (const dep of teamDependencies) {
|
|
334
|
+
spinner.text = `Copying team dependency: ${dep}`;
|
|
335
|
+
|
|
336
|
+
if (dep.includes('*')) {
|
|
337
|
+
// Handle glob patterns with {root} replacement
|
|
338
|
+
const copiedFiles = await fileManager.copyGlobPattern(
|
|
339
|
+
dep.replace('.bmad-core/', ''),
|
|
340
|
+
sourceBase,
|
|
341
|
+
path.join(installDir, '.bmad-core'),
|
|
342
|
+
'.bmad-core',
|
|
343
|
+
);
|
|
344
|
+
files.push(...copiedFiles.map((f) => `.bmad-core/${f}`));
|
|
345
|
+
} else {
|
|
346
|
+
// Handle single files with {root} replacement if needed
|
|
347
|
+
const sourcePath = path.join(sourceBase, dep.replace('.bmad-core/', ''));
|
|
348
|
+
const destinationPath = path.join(installDir, dep);
|
|
349
|
+
|
|
350
|
+
const needsRootReplacement =
|
|
351
|
+
dep.endsWith('.md') || dep.endsWith('.yaml') || dep.endsWith('.yml');
|
|
352
|
+
let success = false;
|
|
353
|
+
|
|
354
|
+
success = await (needsRootReplacement
|
|
355
|
+
? fileManager.copyFileWithRootReplacement(sourcePath, destinationPath, '.bmad-core')
|
|
356
|
+
: fileManager.copyFile(sourcePath, destinationPath));
|
|
357
|
+
|
|
358
|
+
if (success) {
|
|
359
|
+
files.push(dep);
|
|
360
|
+
}
|
|
259
361
|
}
|
|
260
362
|
}
|
|
363
|
+
|
|
364
|
+
// Copy common/ items to .bmad-core
|
|
365
|
+
spinner.text = 'Copying common utilities...';
|
|
366
|
+
const commonFiles = await this.copyCommonItems(installDir, '.bmad-core', spinner);
|
|
367
|
+
files.push(...commonFiles);
|
|
368
|
+
|
|
369
|
+
// Copy documentation files from docs/ to .bmad-core
|
|
370
|
+
spinner.text = 'Copying documentation files...';
|
|
371
|
+
const documentFiles = await this.copyDocsItems(installDir, '.bmad-core', spinner);
|
|
372
|
+
files.push(...documentFiles);
|
|
373
|
+
|
|
374
|
+
break;
|
|
261
375
|
}
|
|
376
|
+
case 'expansion-only': {
|
|
377
|
+
// Expansion-only installation - DO NOT create .bmad-core
|
|
378
|
+
// Only install expansion packs
|
|
379
|
+
spinner.text = 'Installing expansion packs only...';
|
|
380
|
+
|
|
381
|
+
break;
|
|
382
|
+
}
|
|
383
|
+
// No default
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Install expansion packs if requested
|
|
387
|
+
const expansionFiles = await this.installExpansionPacks(
|
|
388
|
+
installDir,
|
|
389
|
+
config.expansionPacks,
|
|
390
|
+
spinner,
|
|
391
|
+
config,
|
|
392
|
+
);
|
|
393
|
+
files.push(...expansionFiles);
|
|
394
|
+
|
|
395
|
+
// Install web bundles if requested
|
|
396
|
+
if (config.includeWebBundles && config.webBundlesDirectory) {
|
|
397
|
+
spinner.text = 'Installing web bundles...';
|
|
398
|
+
// Resolve web bundles directory using the same logic as the main installation directory
|
|
399
|
+
const originalCwd = process.env.INIT_CWD || process.env.PWD || process.cwd();
|
|
400
|
+
let resolvedWebBundlesDir = path.isAbsolute(config.webBundlesDirectory)
|
|
401
|
+
? config.webBundlesDirectory
|
|
402
|
+
: path.resolve(originalCwd, config.webBundlesDirectory);
|
|
403
|
+
await this.installWebBundles(resolvedWebBundlesDir, config, spinner);
|
|
262
404
|
}
|
|
263
405
|
|
|
264
406
|
// Set up IDE integration if requested
|
|
@@ -266,362 +408,1539 @@ class Installer {
|
|
|
266
408
|
if (ides.length > 0) {
|
|
267
409
|
for (const ide of ides) {
|
|
268
410
|
spinner.text = `Setting up ${ide} integration...`;
|
|
269
|
-
|
|
411
|
+
const preConfiguredSettings = ide === 'github-copilot' ? config.githubCopilotConfig : null;
|
|
412
|
+
await ideSetup.setup(ide, installDir, config.agent, spinner, preConfiguredSettings);
|
|
270
413
|
}
|
|
271
414
|
}
|
|
272
415
|
|
|
273
|
-
//
|
|
274
|
-
|
|
275
|
-
|
|
416
|
+
// Modify core-config.yaml if sharding preferences were provided
|
|
417
|
+
if (
|
|
418
|
+
config.installType !== 'expansion-only' &&
|
|
419
|
+
(config.prdSharded !== undefined || config.architectureSharded !== undefined)
|
|
420
|
+
) {
|
|
421
|
+
spinner.text = 'Configuring document sharding settings...';
|
|
422
|
+
await fileManager.modifyCoreConfig(installDir, config);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Create manifest (skip for expansion-only installations)
|
|
426
|
+
if (config.installType !== 'expansion-only') {
|
|
427
|
+
spinner.text = 'Creating installation manifest...';
|
|
428
|
+
await fileManager.createManifest(installDir, config, files);
|
|
429
|
+
}
|
|
276
430
|
|
|
277
|
-
spinner.succeed(
|
|
278
|
-
this.showSuccessMessage(config, installDir);
|
|
431
|
+
spinner.succeed('Installation complete!');
|
|
432
|
+
this.showSuccessMessage(config, installDir, options);
|
|
279
433
|
}
|
|
280
434
|
|
|
281
435
|
async handleExistingV4Installation(config, installDir, state, spinner) {
|
|
282
|
-
// Ensure modules are initialized
|
|
283
|
-
await initializeModules();
|
|
284
436
|
spinner.stop();
|
|
285
437
|
|
|
286
|
-
|
|
438
|
+
const currentVersion = state.manifest.version;
|
|
439
|
+
const newVersion = await this.getCoreVersion();
|
|
440
|
+
const versionCompare = this.compareVersions(currentVersion, newVersion);
|
|
441
|
+
|
|
442
|
+
console.log(chalk.yellow('\n🔍 Found existing BMad v4 installation'));
|
|
287
443
|
console.log(` Directory: ${installDir}`);
|
|
288
|
-
console.log(`
|
|
289
|
-
console.log(
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
444
|
+
console.log(` Current version: ${currentVersion}`);
|
|
445
|
+
console.log(` Available version: ${newVersion}`);
|
|
446
|
+
console.log(` Installed: ${new Date(state.manifest.installed_at).toLocaleDateString()}`);
|
|
447
|
+
|
|
448
|
+
// Check file integrity
|
|
449
|
+
spinner.start('Checking installation integrity...');
|
|
450
|
+
const integrity = await fileManager.checkFileIntegrity(installDir, state.manifest);
|
|
451
|
+
spinner.stop();
|
|
452
|
+
|
|
453
|
+
const hasMissingFiles = integrity.missing.length > 0;
|
|
454
|
+
const hasModifiedFiles = integrity.modified.length > 0;
|
|
455
|
+
const hasIntegrityIssues = hasMissingFiles || hasModifiedFiles;
|
|
456
|
+
|
|
457
|
+
if (hasIntegrityIssues) {
|
|
458
|
+
console.log(chalk.red('\n⚠️ Installation issues detected:'));
|
|
459
|
+
if (hasMissingFiles) {
|
|
460
|
+
console.log(chalk.red(` Missing files: ${integrity.missing.length}`));
|
|
461
|
+
if (integrity.missing.length <= 5) {
|
|
462
|
+
for (const file of integrity.missing) console.log(chalk.dim(` - ${file}`));
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
if (hasModifiedFiles) {
|
|
466
|
+
console.log(chalk.yellow(` Modified files: ${integrity.modified.length}`));
|
|
467
|
+
if (integrity.modified.length <= 5) {
|
|
468
|
+
for (const file of integrity.modified) console.log(chalk.dim(` - ${file}`));
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Show existing expansion packs
|
|
474
|
+
if (Object.keys(state.expansionPacks).length > 0) {
|
|
475
|
+
console.log(chalk.cyan('\n📦 Installed expansion packs:'));
|
|
476
|
+
for (const [packId, packInfo] of Object.entries(state.expansionPacks)) {
|
|
477
|
+
if (packInfo.hasManifest && packInfo.manifest) {
|
|
478
|
+
console.log(` - ${packId} (v${packInfo.manifest.version || 'unknown'})`);
|
|
479
|
+
} else {
|
|
480
|
+
console.log(` - ${packId} (no manifest)`);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
let choices = [];
|
|
486
|
+
|
|
487
|
+
if (versionCompare < 0) {
|
|
488
|
+
console.log(chalk.cyan('\n⬆️ Upgrade available for BMad core'));
|
|
489
|
+
choices.push({
|
|
490
|
+
name: `Upgrade BMad core (v${currentVersion} → v${newVersion})`,
|
|
491
|
+
value: 'upgrade',
|
|
492
|
+
});
|
|
493
|
+
} else if (versionCompare === 0) {
|
|
494
|
+
if (hasIntegrityIssues) {
|
|
495
|
+
// Offer repair option when files are missing or modified
|
|
496
|
+
choices.push({
|
|
497
|
+
name: 'Repair installation (restore missing/modified files)',
|
|
498
|
+
value: 'repair',
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
console.log(chalk.yellow('\n⚠️ Same version already installed'));
|
|
502
|
+
choices.push({
|
|
503
|
+
name: `Force reinstall BMad core (v${currentVersion} - reinstall)`,
|
|
504
|
+
value: 'reinstall',
|
|
505
|
+
});
|
|
506
|
+
} else {
|
|
507
|
+
console.log(chalk.yellow('\n⬇️ Installed version is newer than available'));
|
|
508
|
+
choices.push({
|
|
509
|
+
name: `Downgrade BMad core (v${currentVersion} → v${newVersion})`,
|
|
510
|
+
value: 'reinstall',
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
choices.push(
|
|
515
|
+
{ name: 'Add/update expansion packs only', value: 'expansions' },
|
|
516
|
+
{ name: 'Cancel', value: 'cancel' },
|
|
293
517
|
);
|
|
294
518
|
|
|
295
519
|
const { action } = await inquirer.prompt([
|
|
296
520
|
{
|
|
297
|
-
type:
|
|
298
|
-
name:
|
|
299
|
-
message:
|
|
300
|
-
choices:
|
|
301
|
-
{ name: "Update existing installation", value: "update" },
|
|
302
|
-
{ name: "Reinstall (overwrite)", value: "reinstall" },
|
|
303
|
-
{ name: "Cancel", value: "cancel" },
|
|
304
|
-
],
|
|
521
|
+
type: 'list',
|
|
522
|
+
name: 'action',
|
|
523
|
+
message: 'What would you like to do?',
|
|
524
|
+
choices: choices,
|
|
305
525
|
},
|
|
306
526
|
]);
|
|
307
527
|
|
|
308
528
|
switch (action) {
|
|
309
|
-
case
|
|
529
|
+
case 'upgrade': {
|
|
310
530
|
return await this.performUpdate(config, installDir, state.manifest, spinner);
|
|
311
|
-
|
|
531
|
+
}
|
|
532
|
+
case 'repair': {
|
|
533
|
+
// For repair, restore missing/modified files while backing up modified ones
|
|
534
|
+
return await this.performRepair(config, installDir, state.manifest, integrity, spinner);
|
|
535
|
+
}
|
|
536
|
+
case 'reinstall': {
|
|
537
|
+
// For reinstall, don't check for modifications - just overwrite
|
|
312
538
|
return await this.performReinstall(config, installDir, spinner);
|
|
313
|
-
|
|
314
|
-
|
|
539
|
+
}
|
|
540
|
+
case 'expansions': {
|
|
541
|
+
// Ask which expansion packs to install
|
|
542
|
+
const availableExpansionPacks = await resourceLocator.getExpansionPacks();
|
|
543
|
+
|
|
544
|
+
if (availableExpansionPacks.length === 0) {
|
|
545
|
+
console.log(chalk.yellow('No expansion packs available.'));
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
const { selectedPacks } = await inquirer.prompt([
|
|
550
|
+
{
|
|
551
|
+
type: 'checkbox',
|
|
552
|
+
name: 'selectedPacks',
|
|
553
|
+
message: 'Select expansion packs to install/update:',
|
|
554
|
+
choices: availableExpansionPacks.map((pack) => ({
|
|
555
|
+
name: `${pack.name} (v${pack.version}) .${pack.id}`,
|
|
556
|
+
value: pack.id,
|
|
557
|
+
checked: state.expansionPacks[pack.id] !== undefined,
|
|
558
|
+
})),
|
|
559
|
+
},
|
|
560
|
+
]);
|
|
561
|
+
|
|
562
|
+
if (selectedPacks.length === 0) {
|
|
563
|
+
console.log(chalk.yellow('No expansion packs selected.'));
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
spinner.start('Installing expansion packs...');
|
|
568
|
+
const expansionFiles = await this.installExpansionPacks(
|
|
569
|
+
installDir,
|
|
570
|
+
selectedPacks,
|
|
571
|
+
spinner,
|
|
572
|
+
{ ides: config.ides || [] },
|
|
573
|
+
);
|
|
574
|
+
spinner.succeed('Expansion packs installed successfully!');
|
|
575
|
+
|
|
576
|
+
console.log(chalk.green('\n✓ Installation complete!'));
|
|
577
|
+
console.log(chalk.green(`✓ Expansion packs installed/updated:`));
|
|
578
|
+
for (const packId of selectedPacks) {
|
|
579
|
+
console.log(chalk.green(` - ${packId} → .${packId}/`));
|
|
580
|
+
}
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
case 'cancel': {
|
|
584
|
+
console.log('Installation cancelled.');
|
|
315
585
|
return;
|
|
586
|
+
}
|
|
316
587
|
}
|
|
317
588
|
}
|
|
318
589
|
|
|
319
590
|
async handleV3Installation(config, installDir, state, spinner) {
|
|
320
|
-
// Ensure modules are initialized
|
|
321
|
-
await initializeModules();
|
|
322
591
|
spinner.stop();
|
|
323
592
|
|
|
324
|
-
console.log(
|
|
325
|
-
chalk.yellow("\n🔍 Found BMAD v3 installation (bmad-agent/ directory)")
|
|
326
|
-
);
|
|
593
|
+
console.log(chalk.yellow('\n🔍 Found BMad v3 installation (bmad-agent/ directory)'));
|
|
327
594
|
console.log(` Directory: ${installDir}`);
|
|
328
595
|
|
|
329
596
|
const { action } = await inquirer.prompt([
|
|
330
597
|
{
|
|
331
|
-
type:
|
|
332
|
-
name:
|
|
333
|
-
message:
|
|
598
|
+
type: 'list',
|
|
599
|
+
name: 'action',
|
|
600
|
+
message: 'What would you like to do?',
|
|
334
601
|
choices: [
|
|
335
|
-
{ name:
|
|
336
|
-
{ name:
|
|
337
|
-
{ name:
|
|
602
|
+
{ name: 'Upgrade from v3 to v4 (recommended)', value: 'upgrade' },
|
|
603
|
+
{ name: 'Install v4 alongside v3', value: 'alongside' },
|
|
604
|
+
{ name: 'Cancel', value: 'cancel' },
|
|
338
605
|
],
|
|
339
606
|
},
|
|
340
607
|
]);
|
|
341
608
|
|
|
342
609
|
switch (action) {
|
|
343
|
-
case
|
|
344
|
-
console.log(chalk.cyan(
|
|
345
|
-
const V3ToV4Upgrader = require(
|
|
610
|
+
case 'upgrade': {
|
|
611
|
+
console.log(chalk.cyan('\n📦 Starting v3 to v4 upgrade process...'));
|
|
612
|
+
const V3ToV4Upgrader = require('../../upgraders/v3-to-v4-upgrader');
|
|
346
613
|
const upgrader = new V3ToV4Upgrader();
|
|
347
|
-
return await upgrader.upgrade({
|
|
614
|
+
return await upgrader.upgrade({
|
|
615
|
+
projectPath: installDir,
|
|
616
|
+
ides: config.ides || [], // Pass IDE selections from initial config
|
|
617
|
+
});
|
|
348
618
|
}
|
|
349
|
-
case
|
|
619
|
+
case 'alongside': {
|
|
350
620
|
return await this.performFreshInstall(config, installDir, spinner);
|
|
351
|
-
|
|
352
|
-
|
|
621
|
+
}
|
|
622
|
+
case 'cancel': {
|
|
623
|
+
console.log('Installation cancelled.');
|
|
353
624
|
return;
|
|
625
|
+
}
|
|
354
626
|
}
|
|
355
627
|
}
|
|
356
628
|
|
|
357
629
|
async handleUnknownInstallation(config, installDir, state, spinner) {
|
|
358
|
-
// Ensure modules are initialized
|
|
359
|
-
await initializeModules();
|
|
360
630
|
spinner.stop();
|
|
361
631
|
|
|
362
|
-
console.log(chalk.yellow(
|
|
632
|
+
console.log(chalk.yellow('\n⚠️ Directory contains existing files'));
|
|
363
633
|
console.log(` Directory: ${installDir}`);
|
|
364
634
|
|
|
365
635
|
if (state.hasBmadCore) {
|
|
366
|
-
console.log(
|
|
636
|
+
console.log(' Found: .bmad-core directory (but no manifest)');
|
|
367
637
|
}
|
|
368
638
|
if (state.hasOtherFiles) {
|
|
369
|
-
console.log(
|
|
639
|
+
console.log(' Found: Other files in directory');
|
|
370
640
|
}
|
|
371
641
|
|
|
372
642
|
const { action } = await inquirer.prompt([
|
|
373
643
|
{
|
|
374
|
-
type:
|
|
375
|
-
name:
|
|
376
|
-
message:
|
|
644
|
+
type: 'list',
|
|
645
|
+
name: 'action',
|
|
646
|
+
message: 'What would you like to do?',
|
|
377
647
|
choices: [
|
|
378
|
-
{ name:
|
|
379
|
-
{ name:
|
|
380
|
-
{ name:
|
|
648
|
+
{ name: 'Install anyway (may overwrite files)', value: 'force' },
|
|
649
|
+
{ name: 'Choose different directory', value: 'different' },
|
|
650
|
+
{ name: 'Cancel', value: 'cancel' },
|
|
381
651
|
],
|
|
382
652
|
},
|
|
383
653
|
]);
|
|
384
654
|
|
|
385
655
|
switch (action) {
|
|
386
|
-
case
|
|
656
|
+
case 'force': {
|
|
387
657
|
return await this.performFreshInstall(config, installDir, spinner);
|
|
388
|
-
|
|
658
|
+
}
|
|
659
|
+
case 'different': {
|
|
389
660
|
const { newDir } = await inquirer.prompt([
|
|
390
661
|
{
|
|
391
|
-
type:
|
|
392
|
-
name:
|
|
393
|
-
message:
|
|
394
|
-
default: path.join(path.dirname(installDir),
|
|
662
|
+
type: 'input',
|
|
663
|
+
name: 'newDir',
|
|
664
|
+
message: 'Enter new installation directory:',
|
|
665
|
+
default: path.join(path.dirname(installDir), 'bmad-project'),
|
|
395
666
|
},
|
|
396
667
|
]);
|
|
397
668
|
config.directory = newDir;
|
|
398
669
|
return await this.install(config);
|
|
399
670
|
}
|
|
400
|
-
case
|
|
401
|
-
console.log(
|
|
671
|
+
case 'cancel': {
|
|
672
|
+
console.log('Installation cancelled.');
|
|
402
673
|
return;
|
|
674
|
+
}
|
|
403
675
|
}
|
|
404
676
|
}
|
|
405
677
|
|
|
406
678
|
async performUpdate(newConfig, installDir, manifest, spinner) {
|
|
407
|
-
spinner.start(
|
|
679
|
+
spinner.start('Checking for updates...');
|
|
408
680
|
|
|
409
681
|
try {
|
|
410
|
-
//
|
|
411
|
-
|
|
412
|
-
const
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
682
|
+
// Get current and new versions
|
|
683
|
+
const currentVersion = manifest.version;
|
|
684
|
+
const newVersion = await this.getCoreVersion();
|
|
685
|
+
const versionCompare = this.compareVersions(currentVersion, newVersion);
|
|
686
|
+
|
|
687
|
+
// Only check for modified files if it's an actual version upgrade
|
|
688
|
+
let modifiedFiles = [];
|
|
689
|
+
if (versionCompare !== 0) {
|
|
690
|
+
spinner.text = 'Checking for modified files...';
|
|
691
|
+
modifiedFiles = await fileManager.checkModifiedFiles(installDir, manifest);
|
|
692
|
+
}
|
|
416
693
|
|
|
417
694
|
if (modifiedFiles.length > 0) {
|
|
418
|
-
spinner.warn(
|
|
419
|
-
console.log(chalk.yellow(
|
|
695
|
+
spinner.warn('Found modified files');
|
|
696
|
+
console.log(chalk.yellow('\nThe following files have been modified:'));
|
|
420
697
|
for (const file of modifiedFiles) {
|
|
421
698
|
console.log(` - ${file}`);
|
|
422
699
|
}
|
|
423
700
|
|
|
424
701
|
const { action } = await inquirer.prompt([
|
|
425
702
|
{
|
|
426
|
-
type:
|
|
427
|
-
name:
|
|
428
|
-
message:
|
|
703
|
+
type: 'list',
|
|
704
|
+
name: 'action',
|
|
705
|
+
message: 'How would you like to proceed?',
|
|
429
706
|
choices: [
|
|
430
|
-
{ name:
|
|
431
|
-
{ name:
|
|
432
|
-
{ name:
|
|
707
|
+
{ name: 'Backup and overwrite modified files', value: 'backup' },
|
|
708
|
+
{ name: 'Skip modified files', value: 'skip' },
|
|
709
|
+
{ name: 'Cancel update', value: 'cancel' },
|
|
433
710
|
],
|
|
434
711
|
},
|
|
435
712
|
]);
|
|
436
713
|
|
|
437
|
-
if (action ===
|
|
438
|
-
console.log(
|
|
714
|
+
if (action === 'cancel') {
|
|
715
|
+
console.log('Update cancelled.');
|
|
439
716
|
return;
|
|
440
717
|
}
|
|
441
718
|
|
|
442
|
-
if (action ===
|
|
443
|
-
spinner.start(
|
|
719
|
+
if (action === 'backup') {
|
|
720
|
+
spinner.start('Backing up modified files...');
|
|
444
721
|
for (const file of modifiedFiles) {
|
|
445
722
|
const filePath = path.join(installDir, file);
|
|
446
723
|
const backupPath = await fileManager.backupFile(filePath);
|
|
447
|
-
console.log(
|
|
448
|
-
chalk.dim(` Backed up: ${file} → ${path.basename(backupPath)}`)
|
|
449
|
-
);
|
|
724
|
+
console.log(chalk.dim(` Backed up: ${file} → ${path.basename(backupPath)}`));
|
|
450
725
|
}
|
|
451
726
|
}
|
|
452
727
|
}
|
|
453
728
|
|
|
454
729
|
// Perform update by re-running installation
|
|
455
|
-
spinner.text =
|
|
730
|
+
spinner.text = versionCompare === 0 ? 'Reinstalling files...' : 'Updating files...';
|
|
456
731
|
const config = {
|
|
457
732
|
installType: manifest.install_type,
|
|
458
733
|
agent: manifest.agent,
|
|
459
734
|
directory: installDir,
|
|
460
|
-
|
|
735
|
+
ides: newConfig?.ides || manifest.ides_setup || [],
|
|
461
736
|
};
|
|
462
737
|
|
|
463
|
-
await this.performFreshInstall(config, installDir, spinner);
|
|
738
|
+
await this.performFreshInstall(config, installDir, spinner, { isUpdate: true });
|
|
739
|
+
|
|
740
|
+
// Clean up .yml files that now have .yaml counterparts
|
|
741
|
+
spinner.text = 'Cleaning up legacy .yml files...';
|
|
742
|
+
await this.cleanupLegacyYmlFiles(installDir, spinner);
|
|
743
|
+
} catch (error) {
|
|
744
|
+
spinner.fail('Update failed');
|
|
745
|
+
throw error;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
async performRepair(config, installDir, manifest, integrity, spinner) {
|
|
750
|
+
spinner.start('Preparing to repair installation...');
|
|
751
|
+
|
|
752
|
+
try {
|
|
753
|
+
// Back up modified files
|
|
754
|
+
if (integrity.modified.length > 0) {
|
|
755
|
+
spinner.text = 'Backing up modified files...';
|
|
756
|
+
for (const file of integrity.modified) {
|
|
757
|
+
const filePath = path.join(installDir, file);
|
|
758
|
+
if (await fileManager.pathExists(filePath)) {
|
|
759
|
+
const backupPath = await fileManager.backupFile(filePath);
|
|
760
|
+
console.log(chalk.dim(` Backed up: ${file} → ${path.basename(backupPath)}`));
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
// Restore missing and modified files
|
|
766
|
+
spinner.text = 'Restoring files...';
|
|
767
|
+
const sourceBase = resourceLocator.getBmadCorePath();
|
|
768
|
+
const filesToRestore = [...integrity.missing, ...integrity.modified];
|
|
769
|
+
|
|
770
|
+
for (const file of filesToRestore) {
|
|
771
|
+
// Skip the manifest file itself
|
|
772
|
+
if (file.endsWith('install-manifest.yaml')) continue;
|
|
773
|
+
|
|
774
|
+
const relativePath = file.replace('.bmad-core/', '');
|
|
775
|
+
const destinationPath = path.join(installDir, file);
|
|
776
|
+
|
|
777
|
+
// Check if this is a common/ file that needs special processing
|
|
778
|
+
const commonBase = path.dirname(path.dirname(path.dirname(path.dirname(__filename))));
|
|
779
|
+
const commonSourcePath = path.join(commonBase, 'common', relativePath);
|
|
780
|
+
|
|
781
|
+
if (await fileManager.pathExists(commonSourcePath)) {
|
|
782
|
+
// This is a common/ file - needs template processing
|
|
783
|
+
const fs = require('node:fs').promises;
|
|
784
|
+
const content = await fs.readFile(commonSourcePath, 'utf8');
|
|
785
|
+
const updatedContent = content.replaceAll('{root}', '.bmad-core');
|
|
786
|
+
await fileManager.ensureDirectory(path.dirname(destinationPath));
|
|
787
|
+
await fs.writeFile(destinationPath, updatedContent, 'utf8');
|
|
788
|
+
spinner.text = `Restored: ${file}`;
|
|
789
|
+
} else {
|
|
790
|
+
// Regular file from bmad-core
|
|
791
|
+
const sourcePath = path.join(sourceBase, relativePath);
|
|
792
|
+
if (await fileManager.pathExists(sourcePath)) {
|
|
793
|
+
await fileManager.copyFile(sourcePath, destinationPath);
|
|
794
|
+
spinner.text = `Restored: ${file}`;
|
|
795
|
+
|
|
796
|
+
// If this is a .yaml file, check for and remove corresponding .yml file
|
|
797
|
+
if (file.endsWith('.yaml')) {
|
|
798
|
+
const ymlFile = file.replace(/\.yaml$/, '.yml');
|
|
799
|
+
const ymlPath = path.join(installDir, ymlFile);
|
|
800
|
+
if (await fileManager.pathExists(ymlPath)) {
|
|
801
|
+
const fs = require('node:fs').promises;
|
|
802
|
+
await fs.unlink(ymlPath);
|
|
803
|
+
console.log(chalk.dim(` Removed legacy: ${ymlFile} (replaced by ${file})`));
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
} else {
|
|
807
|
+
console.warn(chalk.yellow(` Warning: Source file not found: ${file}`));
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
// Clean up .yml files that now have .yaml counterparts
|
|
813
|
+
spinner.text = 'Cleaning up legacy .yml files...';
|
|
814
|
+
await this.cleanupLegacyYmlFiles(installDir, spinner);
|
|
815
|
+
|
|
816
|
+
spinner.succeed('Repair completed successfully!');
|
|
817
|
+
|
|
818
|
+
// Show summary
|
|
819
|
+
console.log(chalk.green('\n✓ Installation repaired!'));
|
|
820
|
+
if (integrity.missing.length > 0) {
|
|
821
|
+
console.log(chalk.green(` Restored ${integrity.missing.length} missing files`));
|
|
822
|
+
}
|
|
823
|
+
if (integrity.modified.length > 0) {
|
|
824
|
+
console.log(
|
|
825
|
+
chalk.green(` Restored ${integrity.modified.length} modified files (backups created)`),
|
|
826
|
+
);
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
// Warning for Cursor custom modes if agents were repaired
|
|
830
|
+
const ides = manifest.ides_setup || [];
|
|
831
|
+
if (ides.includes('cursor')) {
|
|
832
|
+
console.log(chalk.yellow.bold('\n⚠️ IMPORTANT: Cursor Custom Modes Update Required'));
|
|
833
|
+
console.log(
|
|
834
|
+
chalk.yellow(
|
|
835
|
+
'Since agent files have been repaired, you need to update any custom agent modes configured in the Cursor custom agent GUI per the Cursor docs.',
|
|
836
|
+
),
|
|
837
|
+
);
|
|
838
|
+
}
|
|
464
839
|
} catch (error) {
|
|
465
|
-
spinner.fail(
|
|
840
|
+
spinner.fail('Repair failed');
|
|
466
841
|
throw error;
|
|
467
842
|
}
|
|
468
843
|
}
|
|
469
844
|
|
|
470
845
|
async performReinstall(config, installDir, spinner) {
|
|
471
|
-
spinner.start(
|
|
846
|
+
spinner.start('Preparing to reinstall BMad Method...');
|
|
472
847
|
|
|
473
848
|
// Remove existing .bmad-core
|
|
474
|
-
const bmadCorePath = path.join(installDir,
|
|
849
|
+
const bmadCorePath = path.join(installDir, '.bmad-core');
|
|
475
850
|
if (await fileManager.pathExists(bmadCorePath)) {
|
|
851
|
+
spinner.text = 'Removing existing installation...';
|
|
476
852
|
await fileManager.removeDirectory(bmadCorePath);
|
|
477
853
|
}
|
|
478
854
|
|
|
479
|
-
|
|
855
|
+
spinner.text = 'Installing fresh copy...';
|
|
856
|
+
const result = await this.performFreshInstall(config, installDir, spinner, { isUpdate: true });
|
|
857
|
+
|
|
858
|
+
// Clean up .yml files that now have .yaml counterparts
|
|
859
|
+
spinner.text = 'Cleaning up legacy .yml files...';
|
|
860
|
+
await this.cleanupLegacyYmlFiles(installDir, spinner);
|
|
861
|
+
|
|
862
|
+
return result;
|
|
480
863
|
}
|
|
481
864
|
|
|
482
|
-
showSuccessMessage(config, installDir) {
|
|
483
|
-
console.log(chalk.green(
|
|
865
|
+
showSuccessMessage(config, installDir, options = {}) {
|
|
866
|
+
console.log(chalk.green('\n✓ BMad Method installed successfully!\n'));
|
|
484
867
|
|
|
485
868
|
const ides = config.ides || (config.ide ? [config.ide] : []);
|
|
486
869
|
if (ides.length > 0) {
|
|
487
870
|
for (const ide of ides) {
|
|
488
871
|
const ideConfig = configLoader.getIdeConfiguration(ide);
|
|
489
872
|
if (ideConfig?.instructions) {
|
|
490
|
-
console.log(
|
|
491
|
-
chalk.bold(`To use BMAD agents in ${ideConfig.name}:`)
|
|
492
|
-
);
|
|
873
|
+
console.log(chalk.bold(`To use BMad agents in ${ideConfig.name}:`));
|
|
493
874
|
console.log(ideConfig.instructions);
|
|
494
875
|
}
|
|
495
876
|
}
|
|
496
877
|
} else {
|
|
497
|
-
console.log(chalk.yellow(
|
|
878
|
+
console.log(chalk.yellow('No IDE configuration was set up.'));
|
|
879
|
+
console.log('You can manually configure your IDE using the agent files in:', installDir);
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
// Information about installation components
|
|
883
|
+
console.log(chalk.bold('\n🎯 Installation Summary:'));
|
|
884
|
+
if (config.installType !== 'expansion-only') {
|
|
885
|
+
console.log(chalk.green('✓ .bmad-core framework installed with all agents and workflows'));
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
if (config.expansionPacks && config.expansionPacks.length > 0) {
|
|
889
|
+
console.log(chalk.green(`✓ Expansion packs installed:`));
|
|
890
|
+
for (const packId of config.expansionPacks) {
|
|
891
|
+
console.log(chalk.green(` - ${packId} → .${packId}/`));
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
if (config.includeWebBundles && config.webBundlesDirectory) {
|
|
896
|
+
const bundleInfo = this.getWebBundleInfo(config);
|
|
897
|
+
// Resolve the web bundles directory for display
|
|
898
|
+
const originalCwd = process.env.INIT_CWD || process.env.PWD || process.cwd();
|
|
899
|
+
const resolvedWebBundlesDir = path.isAbsolute(config.webBundlesDirectory)
|
|
900
|
+
? config.webBundlesDirectory
|
|
901
|
+
: path.resolve(originalCwd, config.webBundlesDirectory);
|
|
498
902
|
console.log(
|
|
499
|
-
|
|
500
|
-
installDir
|
|
903
|
+
chalk.green(`✓ Web bundles (${bundleInfo}) installed to: ${resolvedWebBundlesDir}`),
|
|
501
904
|
);
|
|
502
905
|
}
|
|
503
906
|
|
|
504
|
-
// Information about installation components
|
|
505
|
-
console.log(chalk.bold("\n🎯 Installation Summary:"));
|
|
506
|
-
console.log(chalk.green("✓ .bmad-core framework installed with all agents and workflows"));
|
|
507
|
-
|
|
508
907
|
if (ides.length > 0) {
|
|
509
|
-
const ideNames = ides
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
908
|
+
const ideNames = ides
|
|
909
|
+
.map((ide) => {
|
|
910
|
+
const ideConfig = configLoader.getIdeConfiguration(ide);
|
|
911
|
+
return ideConfig?.name || ide;
|
|
912
|
+
})
|
|
913
|
+
.join(', ');
|
|
513
914
|
console.log(chalk.green(`✓ IDE rules and configurations set up for: ${ideNames}`));
|
|
514
915
|
}
|
|
515
916
|
|
|
516
917
|
// Information about web bundles
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
918
|
+
if (!config.includeWebBundles) {
|
|
919
|
+
console.log(chalk.bold('\n📦 Web Bundles Available:'));
|
|
920
|
+
console.log('Pre-built web bundles are available and can be added later:');
|
|
921
|
+
console.log(chalk.cyan(' Run the installer again to add them to your project'));
|
|
922
|
+
console.log('These bundles work independently and can be shared, moved, or used');
|
|
923
|
+
console.log('in other projects as standalone files.');
|
|
924
|
+
}
|
|
522
925
|
|
|
523
|
-
if (config.installType ===
|
|
524
|
-
console.log(
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
926
|
+
if (config.installType === 'single-agent') {
|
|
927
|
+
console.log(chalk.dim('\nNeed other agents? Run: npx bmad-method install --agent=<name>'));
|
|
928
|
+
console.log(chalk.dim('Need everything? Run: npx bmad-method install --full'));
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
// Warning for Cursor custom modes if agents were updated
|
|
932
|
+
if (options.isUpdate && ides.includes('cursor')) {
|
|
933
|
+
console.log(chalk.yellow.bold('\n⚠️ IMPORTANT: Cursor Custom Modes Update Required'));
|
|
529
934
|
console.log(
|
|
530
|
-
chalk.
|
|
935
|
+
chalk.yellow(
|
|
936
|
+
'Since agents have been updated, you need to update any custom agent modes configured in the Cursor custom agent GUI per the Cursor docs.',
|
|
937
|
+
),
|
|
531
938
|
);
|
|
532
939
|
}
|
|
940
|
+
|
|
941
|
+
// Important notice to read the user guide
|
|
942
|
+
console.log(
|
|
943
|
+
chalk.red.bold(
|
|
944
|
+
'\n📖 IMPORTANT: Please read the user guide at docs/user-guide.md (also installed at .bmad-core/user-guide.md)',
|
|
945
|
+
),
|
|
946
|
+
);
|
|
947
|
+
console.log(
|
|
948
|
+
chalk.red(
|
|
949
|
+
'This guide contains essential information about the BMad workflow and how to use the agents effectively.',
|
|
950
|
+
),
|
|
951
|
+
);
|
|
533
952
|
}
|
|
534
953
|
|
|
535
954
|
// Legacy method for backward compatibility
|
|
536
955
|
async update() {
|
|
537
|
-
// Initialize ES modules
|
|
538
|
-
await initializeModules();
|
|
539
956
|
console.log(chalk.yellow('The "update" command is deprecated.'));
|
|
540
957
|
console.log(
|
|
541
|
-
'Please use "install" instead - it will detect and offer to update existing installations.'
|
|
958
|
+
'Please use "install" instead - it will detect and offer to update existing installations.',
|
|
542
959
|
);
|
|
543
960
|
|
|
544
961
|
const installDir = await this.findInstallation();
|
|
545
962
|
if (installDir) {
|
|
546
963
|
const config = {
|
|
547
|
-
installType:
|
|
964
|
+
installType: 'full',
|
|
548
965
|
directory: path.dirname(installDir),
|
|
549
966
|
ide: null,
|
|
550
967
|
};
|
|
551
968
|
return await this.install(config);
|
|
552
969
|
}
|
|
553
|
-
console.log(chalk.red(
|
|
970
|
+
console.log(chalk.red('No BMad installation found.'));
|
|
554
971
|
}
|
|
555
972
|
|
|
556
973
|
async listAgents() {
|
|
557
|
-
|
|
558
|
-
await initializeModules();
|
|
559
|
-
const agents = await configLoader.getAvailableAgents();
|
|
974
|
+
const agents = await resourceLocator.getAvailableAgents();
|
|
560
975
|
|
|
561
|
-
console.log(chalk.bold(
|
|
976
|
+
console.log(chalk.bold('\nAvailable BMad Agents:\n'));
|
|
562
977
|
|
|
563
978
|
for (const agent of agents) {
|
|
564
979
|
console.log(chalk.cyan(` ${agent.id.padEnd(20)}`), agent.description);
|
|
565
980
|
}
|
|
566
981
|
|
|
567
|
-
console.log(
|
|
568
|
-
|
|
569
|
-
|
|
982
|
+
console.log(chalk.dim('\nInstall with: npx bmad-method install --agent=<id>\n'));
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
async listExpansionPacks() {
|
|
986
|
+
const expansionPacks = await resourceLocator.getExpansionPacks();
|
|
987
|
+
|
|
988
|
+
console.log(chalk.bold('\nAvailable BMad Expansion Packs:\n'));
|
|
989
|
+
|
|
990
|
+
if (expansionPacks.length === 0) {
|
|
991
|
+
console.log(chalk.yellow('No expansion packs found.'));
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
for (const pack of expansionPacks) {
|
|
996
|
+
console.log(chalk.cyan(` ${pack.id.padEnd(20)}`), `${pack.name} v${pack.version}`);
|
|
997
|
+
console.log(chalk.dim(` ${' '.repeat(22)}${pack.description}`));
|
|
998
|
+
if (pack.author && pack.author !== 'Unknown') {
|
|
999
|
+
console.log(chalk.dim(` ${' '.repeat(22)}by ${pack.author}`));
|
|
1000
|
+
}
|
|
1001
|
+
console.log();
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
console.log(chalk.dim('Install with: npx bmad-method install --full --expansion-packs <id>\n'));
|
|
570
1005
|
}
|
|
571
1006
|
|
|
572
1007
|
async showStatus() {
|
|
573
|
-
// Initialize ES modules
|
|
574
|
-
await initializeModules();
|
|
575
1008
|
const installDir = await this.findInstallation();
|
|
576
1009
|
|
|
577
1010
|
if (!installDir) {
|
|
578
|
-
console.log(
|
|
579
|
-
chalk.yellow("No BMAD installation found in current directory tree")
|
|
580
|
-
);
|
|
1011
|
+
console.log(chalk.yellow('No BMad installation found in current directory tree'));
|
|
581
1012
|
return;
|
|
582
1013
|
}
|
|
583
1014
|
|
|
584
1015
|
const manifest = await fileManager.readManifest(installDir);
|
|
585
1016
|
|
|
586
1017
|
if (!manifest) {
|
|
587
|
-
console.log(chalk.red(
|
|
1018
|
+
console.log(chalk.red('Invalid installation - manifest not found'));
|
|
588
1019
|
return;
|
|
589
1020
|
}
|
|
590
1021
|
|
|
591
|
-
console.log(chalk.bold(
|
|
1022
|
+
console.log(chalk.bold('\nBMad Installation Status:\n'));
|
|
592
1023
|
console.log(` Directory: ${installDir}`);
|
|
593
1024
|
console.log(` Version: ${manifest.version}`);
|
|
594
|
-
console.log(
|
|
595
|
-
` Installed: ${new Date(
|
|
596
|
-
manifest.installed_at
|
|
597
|
-
).toLocaleDateString()}`
|
|
598
|
-
);
|
|
1025
|
+
console.log(` Installed: ${new Date(manifest.installed_at).toLocaleDateString()}`);
|
|
599
1026
|
console.log(` Type: ${manifest.install_type}`);
|
|
600
1027
|
|
|
601
1028
|
if (manifest.agent) {
|
|
602
1029
|
console.log(` Agent: ${manifest.agent}`);
|
|
603
1030
|
}
|
|
604
1031
|
|
|
605
|
-
if (manifest.
|
|
606
|
-
console.log(` IDE Setup: ${manifest.
|
|
1032
|
+
if (manifest.ides_setup && manifest.ides_setup.length > 0) {
|
|
1033
|
+
console.log(` IDE Setup: ${manifest.ides_setup.join(', ')}`);
|
|
607
1034
|
}
|
|
608
1035
|
|
|
609
1036
|
console.log(` Total Files: ${manifest.files.length}`);
|
|
610
1037
|
|
|
611
1038
|
// Check for modifications
|
|
612
|
-
const modifiedFiles = await fileManager.checkModifiedFiles(
|
|
613
|
-
installDir,
|
|
614
|
-
manifest
|
|
615
|
-
);
|
|
1039
|
+
const modifiedFiles = await fileManager.checkModifiedFiles(installDir, manifest);
|
|
616
1040
|
if (modifiedFiles.length > 0) {
|
|
617
1041
|
console.log(chalk.yellow(` Modified Files: ${modifiedFiles.length}`));
|
|
618
1042
|
}
|
|
619
1043
|
|
|
620
|
-
console.log(
|
|
1044
|
+
console.log('');
|
|
621
1045
|
}
|
|
622
1046
|
|
|
623
1047
|
async getAvailableAgents() {
|
|
624
|
-
return
|
|
1048
|
+
return resourceLocator.getAvailableAgents();
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
async getAvailableExpansionPacks() {
|
|
1052
|
+
return resourceLocator.getExpansionPacks();
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
async getAvailableTeams() {
|
|
1056
|
+
return configLoader.getAvailableTeams();
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
async installExpansionPacks(installDir, selectedPacks, spinner, config = {}) {
|
|
1060
|
+
if (!selectedPacks || selectedPacks.length === 0) {
|
|
1061
|
+
return [];
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
const installedFiles = [];
|
|
1065
|
+
|
|
1066
|
+
for (const packId of selectedPacks) {
|
|
1067
|
+
spinner.text = `Installing expansion pack: ${packId}...`;
|
|
1068
|
+
|
|
1069
|
+
try {
|
|
1070
|
+
const expansionPacks = await resourceLocator.getExpansionPacks();
|
|
1071
|
+
const pack = expansionPacks.find((p) => p.id === packId);
|
|
1072
|
+
|
|
1073
|
+
if (!pack) {
|
|
1074
|
+
console.warn(`Expansion pack ${packId} not found, skipping...`);
|
|
1075
|
+
continue;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
// Check if expansion pack already exists
|
|
1079
|
+
let expansionDotFolder = path.join(installDir, `.${packId}`);
|
|
1080
|
+
const existingManifestPath = path.join(expansionDotFolder, 'install-manifest.yaml');
|
|
1081
|
+
|
|
1082
|
+
if (await fileManager.pathExists(existingManifestPath)) {
|
|
1083
|
+
spinner.stop();
|
|
1084
|
+
const existingManifest = await fileManager.readExpansionPackManifest(installDir, packId);
|
|
1085
|
+
|
|
1086
|
+
console.log(chalk.yellow(`\n🔍 Found existing ${pack.name} installation`));
|
|
1087
|
+
console.log(` Current version: ${existingManifest.version || 'unknown'}`);
|
|
1088
|
+
console.log(` New version: ${pack.version}`);
|
|
1089
|
+
|
|
1090
|
+
// Check integrity of existing expansion pack
|
|
1091
|
+
const packIntegrity = await fileManager.checkFileIntegrity(installDir, existingManifest);
|
|
1092
|
+
const hasPackIntegrityIssues =
|
|
1093
|
+
packIntegrity.missing.length > 0 || packIntegrity.modified.length > 0;
|
|
1094
|
+
|
|
1095
|
+
if (hasPackIntegrityIssues) {
|
|
1096
|
+
console.log(chalk.red(' ⚠️ Installation issues detected:'));
|
|
1097
|
+
if (packIntegrity.missing.length > 0) {
|
|
1098
|
+
console.log(chalk.red(` Missing files: ${packIntegrity.missing.length}`));
|
|
1099
|
+
}
|
|
1100
|
+
if (packIntegrity.modified.length > 0) {
|
|
1101
|
+
console.log(chalk.yellow(` Modified files: ${packIntegrity.modified.length}`));
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
const versionCompare = this.compareVersions(
|
|
1106
|
+
existingManifest.version || '0.0.0',
|
|
1107
|
+
pack.version,
|
|
1108
|
+
);
|
|
1109
|
+
|
|
1110
|
+
if (versionCompare === 0) {
|
|
1111
|
+
console.log(chalk.yellow(' ⚠️ Same version already installed'));
|
|
1112
|
+
|
|
1113
|
+
const choices = [];
|
|
1114
|
+
if (hasPackIntegrityIssues) {
|
|
1115
|
+
choices.push({ name: 'Repair (restore missing/modified files)', value: 'repair' });
|
|
1116
|
+
}
|
|
1117
|
+
choices.push(
|
|
1118
|
+
{ name: 'Force reinstall (overwrite)', value: 'overwrite' },
|
|
1119
|
+
{ name: 'Skip this expansion pack', value: 'skip' },
|
|
1120
|
+
{ name: 'Cancel installation', value: 'cancel' },
|
|
1121
|
+
);
|
|
1122
|
+
|
|
1123
|
+
const { action } = await inquirer.prompt([
|
|
1124
|
+
{
|
|
1125
|
+
type: 'list',
|
|
1126
|
+
name: 'action',
|
|
1127
|
+
message: `${pack.name} v${pack.version} is already installed. What would you like to do?`,
|
|
1128
|
+
choices: choices,
|
|
1129
|
+
},
|
|
1130
|
+
]);
|
|
1131
|
+
|
|
1132
|
+
switch (action) {
|
|
1133
|
+
case 'skip': {
|
|
1134
|
+
spinner.start();
|
|
1135
|
+
continue;
|
|
1136
|
+
|
|
1137
|
+
break;
|
|
1138
|
+
}
|
|
1139
|
+
case 'cancel': {
|
|
1140
|
+
console.log('Installation cancelled.');
|
|
1141
|
+
process.exit(0);
|
|
1142
|
+
|
|
1143
|
+
break;
|
|
1144
|
+
}
|
|
1145
|
+
case 'repair': {
|
|
1146
|
+
// Repair the expansion pack
|
|
1147
|
+
await this.repairExpansionPack(installDir, packId, pack, packIntegrity, spinner);
|
|
1148
|
+
continue;
|
|
1149
|
+
|
|
1150
|
+
break;
|
|
1151
|
+
}
|
|
1152
|
+
// No default
|
|
1153
|
+
}
|
|
1154
|
+
} else if (versionCompare < 0) {
|
|
1155
|
+
console.log(chalk.cyan(' ⬆️ Upgrade available'));
|
|
1156
|
+
|
|
1157
|
+
const { proceed } = await inquirer.prompt([
|
|
1158
|
+
{
|
|
1159
|
+
type: 'confirm',
|
|
1160
|
+
name: 'proceed',
|
|
1161
|
+
message: `Upgrade ${pack.name} from v${existingManifest.version} to v${pack.version}?`,
|
|
1162
|
+
default: true,
|
|
1163
|
+
},
|
|
1164
|
+
]);
|
|
1165
|
+
|
|
1166
|
+
if (!proceed) {
|
|
1167
|
+
spinner.start();
|
|
1168
|
+
continue;
|
|
1169
|
+
}
|
|
1170
|
+
} else {
|
|
1171
|
+
console.log(chalk.yellow(' ⬇️ Installed version is newer than available version'));
|
|
1172
|
+
|
|
1173
|
+
const { action } = await inquirer.prompt([
|
|
1174
|
+
{
|
|
1175
|
+
type: 'list',
|
|
1176
|
+
name: 'action',
|
|
1177
|
+
message: 'What would you like to do?',
|
|
1178
|
+
choices: [
|
|
1179
|
+
{ name: 'Keep current version', value: 'skip' },
|
|
1180
|
+
{ name: 'Downgrade to available version', value: 'downgrade' },
|
|
1181
|
+
{ name: 'Cancel installation', value: 'cancel' },
|
|
1182
|
+
],
|
|
1183
|
+
},
|
|
1184
|
+
]);
|
|
1185
|
+
|
|
1186
|
+
if (action === 'skip') {
|
|
1187
|
+
spinner.start();
|
|
1188
|
+
continue;
|
|
1189
|
+
} else if (action === 'cancel') {
|
|
1190
|
+
console.log('Installation cancelled.');
|
|
1191
|
+
process.exit(0);
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
// If we get here, we're proceeding with installation
|
|
1196
|
+
spinner.start(`Removing old ${pack.name} installation...`);
|
|
1197
|
+
await fileManager.removeDirectory(expansionDotFolder);
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
const expansionPackDir = pack.path;
|
|
1201
|
+
|
|
1202
|
+
// Ensure dedicated dot folder exists for this expansion pack
|
|
1203
|
+
expansionDotFolder = path.join(installDir, `.${packId}`);
|
|
1204
|
+
await fileManager.ensureDirectory(expansionDotFolder);
|
|
1205
|
+
|
|
1206
|
+
// Define the folders to copy from expansion packs
|
|
1207
|
+
const foldersToSync = [
|
|
1208
|
+
'agents',
|
|
1209
|
+
'agent-teams',
|
|
1210
|
+
'templates',
|
|
1211
|
+
'tasks',
|
|
1212
|
+
'checklists',
|
|
1213
|
+
'workflows',
|
|
1214
|
+
'data',
|
|
1215
|
+
'utils',
|
|
1216
|
+
'schemas',
|
|
1217
|
+
];
|
|
1218
|
+
|
|
1219
|
+
// Copy each folder if it exists
|
|
1220
|
+
for (const folder of foldersToSync) {
|
|
1221
|
+
const sourceFolder = path.join(expansionPackDir, folder);
|
|
1222
|
+
|
|
1223
|
+
// Check if folder exists in expansion pack
|
|
1224
|
+
if (await fileManager.pathExists(sourceFolder)) {
|
|
1225
|
+
// Get all files in this folder
|
|
1226
|
+
const files = await resourceLocator.findFiles('**/*', {
|
|
1227
|
+
cwd: sourceFolder,
|
|
1228
|
+
nodir: true,
|
|
1229
|
+
});
|
|
1230
|
+
|
|
1231
|
+
// Copy each file to the expansion pack's dot folder with {root} replacement
|
|
1232
|
+
for (const file of files) {
|
|
1233
|
+
const sourcePath = path.join(sourceFolder, file);
|
|
1234
|
+
const destinationPath = path.join(expansionDotFolder, folder, file);
|
|
1235
|
+
|
|
1236
|
+
const needsRootReplacement =
|
|
1237
|
+
file.endsWith('.md') || file.endsWith('.yaml') || file.endsWith('.yml');
|
|
1238
|
+
let success = false;
|
|
1239
|
+
|
|
1240
|
+
success = await (needsRootReplacement
|
|
1241
|
+
? fileManager.copyFileWithRootReplacement(sourcePath, destinationPath, `.${packId}`)
|
|
1242
|
+
: fileManager.copyFile(sourcePath, destinationPath));
|
|
1243
|
+
|
|
1244
|
+
if (success) {
|
|
1245
|
+
installedFiles.push(path.join(`.${packId}`, folder, file));
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
// Copy config.yaml with {root} replacement
|
|
1252
|
+
const configPath = path.join(expansionPackDir, 'config.yaml');
|
|
1253
|
+
if (await fileManager.pathExists(configPath)) {
|
|
1254
|
+
const configDestinationPath = path.join(expansionDotFolder, 'config.yaml');
|
|
1255
|
+
if (
|
|
1256
|
+
await fileManager.copyFileWithRootReplacement(
|
|
1257
|
+
configPath,
|
|
1258
|
+
configDestinationPath,
|
|
1259
|
+
`.${packId}`,
|
|
1260
|
+
)
|
|
1261
|
+
) {
|
|
1262
|
+
installedFiles.push(path.join(`.${packId}`, 'config.yaml'));
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
// Copy README if it exists with {root} replacement
|
|
1267
|
+
const readmePath = path.join(expansionPackDir, 'README.md');
|
|
1268
|
+
if (await fileManager.pathExists(readmePath)) {
|
|
1269
|
+
const readmeDestinationPath = path.join(expansionDotFolder, 'README.md');
|
|
1270
|
+
if (
|
|
1271
|
+
await fileManager.copyFileWithRootReplacement(
|
|
1272
|
+
readmePath,
|
|
1273
|
+
readmeDestinationPath,
|
|
1274
|
+
`.${packId}`,
|
|
1275
|
+
)
|
|
1276
|
+
) {
|
|
1277
|
+
installedFiles.push(path.join(`.${packId}`, 'README.md'));
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
// Copy common/ items to expansion pack folder
|
|
1282
|
+
spinner.text = `Copying common utilities to ${packId}...`;
|
|
1283
|
+
await this.copyCommonItems(installDir, `.${packId}`, spinner);
|
|
1284
|
+
|
|
1285
|
+
// Check and resolve core dependencies
|
|
1286
|
+
await this.resolveExpansionPackCoreDependencies(
|
|
1287
|
+
installDir,
|
|
1288
|
+
expansionDotFolder,
|
|
1289
|
+
packId,
|
|
1290
|
+
pack,
|
|
1291
|
+
spinner,
|
|
1292
|
+
);
|
|
1293
|
+
|
|
1294
|
+
// Check and resolve core agents referenced by teams
|
|
1295
|
+
await this.resolveExpansionPackCoreAgents(installDir, expansionDotFolder, packId, spinner);
|
|
1296
|
+
|
|
1297
|
+
// Create manifest for this expansion pack
|
|
1298
|
+
spinner.text = `Creating manifest for ${packId}...`;
|
|
1299
|
+
const expansionConfig = {
|
|
1300
|
+
installType: 'expansion-pack',
|
|
1301
|
+
expansionPackId: packId,
|
|
1302
|
+
expansionPackName: pack.name,
|
|
1303
|
+
expansionPackVersion: pack.version,
|
|
1304
|
+
ides: config.ides || [], // Use ides_setup instead of ide_setup
|
|
1305
|
+
};
|
|
1306
|
+
|
|
1307
|
+
// Get all files installed in this expansion pack
|
|
1308
|
+
const foundFiles = await resourceLocator.findFiles('**/*', {
|
|
1309
|
+
cwd: expansionDotFolder,
|
|
1310
|
+
nodir: true,
|
|
1311
|
+
});
|
|
1312
|
+
const expansionPackFiles = foundFiles.map((f) => path.join(`.${packId}`, f));
|
|
1313
|
+
|
|
1314
|
+
await fileManager.createExpansionPackManifest(
|
|
1315
|
+
installDir,
|
|
1316
|
+
packId,
|
|
1317
|
+
expansionConfig,
|
|
1318
|
+
expansionPackFiles,
|
|
1319
|
+
);
|
|
1320
|
+
|
|
1321
|
+
console.log(chalk.green(`✓ Installed expansion pack: ${pack.name} to ${`.${packId}`}`));
|
|
1322
|
+
} catch (error) {
|
|
1323
|
+
console.error(`Failed to install expansion pack ${packId}: ${error.message}`);
|
|
1324
|
+
console.error(`Stack trace: ${error.stack}`);
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
return installedFiles;
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
async resolveExpansionPackCoreDependencies(
|
|
1332
|
+
installDir,
|
|
1333
|
+
expansionDotFolder,
|
|
1334
|
+
packId,
|
|
1335
|
+
pack,
|
|
1336
|
+
spinner,
|
|
1337
|
+
) {
|
|
1338
|
+
const yaml = require('js-yaml');
|
|
1339
|
+
const fs = require('node:fs').promises;
|
|
1340
|
+
|
|
1341
|
+
// Find all agent files in the expansion pack
|
|
1342
|
+
const agentFiles = await resourceLocator.findFiles('agents/*.md', {
|
|
1343
|
+
cwd: expansionDotFolder,
|
|
1344
|
+
});
|
|
1345
|
+
|
|
1346
|
+
for (const agentFile of agentFiles) {
|
|
1347
|
+
const agentPath = path.join(expansionDotFolder, agentFile);
|
|
1348
|
+
const agentContent = await fs.readFile(agentPath, 'utf8');
|
|
1349
|
+
|
|
1350
|
+
// Extract YAML frontmatter to check dependencies
|
|
1351
|
+
const yamlContent = extractYamlFromAgent(agentContent);
|
|
1352
|
+
if (yamlContent) {
|
|
1353
|
+
try {
|
|
1354
|
+
const agentConfig = yaml.load(yamlContent);
|
|
1355
|
+
const dependencies = agentConfig.dependencies || {};
|
|
1356
|
+
|
|
1357
|
+
// Check for core dependencies (those that don't exist in the expansion pack)
|
|
1358
|
+
for (const depType of [
|
|
1359
|
+
'tasks',
|
|
1360
|
+
'templates',
|
|
1361
|
+
'checklists',
|
|
1362
|
+
'workflows',
|
|
1363
|
+
'utils',
|
|
1364
|
+
'data',
|
|
1365
|
+
]) {
|
|
1366
|
+
const deps = dependencies[depType] || [];
|
|
1367
|
+
|
|
1368
|
+
for (const dep of deps) {
|
|
1369
|
+
const depFileName =
|
|
1370
|
+
dep.endsWith('.md') || dep.endsWith('.yaml')
|
|
1371
|
+
? dep
|
|
1372
|
+
: depType === 'templates'
|
|
1373
|
+
? `${dep}.yaml`
|
|
1374
|
+
: `${dep}.md`;
|
|
1375
|
+
const expansionDepPath = path.join(expansionDotFolder, depType, depFileName);
|
|
1376
|
+
|
|
1377
|
+
// Check if dependency exists in expansion pack dot folder
|
|
1378
|
+
if (!(await fileManager.pathExists(expansionDepPath))) {
|
|
1379
|
+
// Try to find it in expansion pack source
|
|
1380
|
+
const sourceDepPath = path.join(pack.path, depType, depFileName);
|
|
1381
|
+
|
|
1382
|
+
if (await fileManager.pathExists(sourceDepPath)) {
|
|
1383
|
+
// Copy from expansion pack source
|
|
1384
|
+
spinner.text = `Copying ${packId} dependency ${dep}...`;
|
|
1385
|
+
const destinationPath = path.join(expansionDotFolder, depType, depFileName);
|
|
1386
|
+
await fileManager.copyFileWithRootReplacement(
|
|
1387
|
+
sourceDepPath,
|
|
1388
|
+
destinationPath,
|
|
1389
|
+
`.${packId}`,
|
|
1390
|
+
);
|
|
1391
|
+
console.log(chalk.dim(` Added ${packId} dependency: ${depType}/${depFileName}`));
|
|
1392
|
+
} else {
|
|
1393
|
+
// Try to find it in core
|
|
1394
|
+
const coreDepPath = path.join(
|
|
1395
|
+
resourceLocator.getBmadCorePath(),
|
|
1396
|
+
depType,
|
|
1397
|
+
depFileName,
|
|
1398
|
+
);
|
|
1399
|
+
|
|
1400
|
+
if (await fileManager.pathExists(coreDepPath)) {
|
|
1401
|
+
spinner.text = `Copying core dependency ${dep} for ${packId}...`;
|
|
1402
|
+
|
|
1403
|
+
// Copy from core to expansion pack dot folder with {root} replacement
|
|
1404
|
+
const destinationPath = path.join(expansionDotFolder, depType, depFileName);
|
|
1405
|
+
await fileManager.copyFileWithRootReplacement(
|
|
1406
|
+
coreDepPath,
|
|
1407
|
+
destinationPath,
|
|
1408
|
+
`.${packId}`,
|
|
1409
|
+
);
|
|
1410
|
+
|
|
1411
|
+
console.log(chalk.dim(` Added core dependency: ${depType}/${depFileName}`));
|
|
1412
|
+
} else {
|
|
1413
|
+
console.warn(
|
|
1414
|
+
chalk.yellow(
|
|
1415
|
+
` Warning: Dependency ${depType}/${dep} not found in core or expansion pack`,
|
|
1416
|
+
),
|
|
1417
|
+
);
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
} catch (error) {
|
|
1424
|
+
console.warn(` Warning: Could not parse agent dependencies: ${error.message}`);
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
async resolveExpansionPackCoreAgents(installDir, expansionDotFolder, packId, spinner) {
|
|
1431
|
+
const yaml = require('js-yaml');
|
|
1432
|
+
const fs = require('node:fs').promises;
|
|
1433
|
+
|
|
1434
|
+
// Find all team files in the expansion pack
|
|
1435
|
+
const teamFiles = await resourceLocator.findFiles('agent-teams/*.yaml', {
|
|
1436
|
+
cwd: expansionDotFolder,
|
|
1437
|
+
});
|
|
1438
|
+
|
|
1439
|
+
// Also get existing agents in the expansion pack
|
|
1440
|
+
const existingAgents = new Set();
|
|
1441
|
+
const agentFiles = await resourceLocator.findFiles('agents/*.md', {
|
|
1442
|
+
cwd: expansionDotFolder,
|
|
1443
|
+
});
|
|
1444
|
+
for (const agentFile of agentFiles) {
|
|
1445
|
+
const agentName = path.basename(agentFile, '.md');
|
|
1446
|
+
existingAgents.add(agentName);
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
// Process each team file
|
|
1450
|
+
for (const teamFile of teamFiles) {
|
|
1451
|
+
const teamPath = path.join(expansionDotFolder, teamFile);
|
|
1452
|
+
const teamContent = await fs.readFile(teamPath, 'utf8');
|
|
1453
|
+
|
|
1454
|
+
try {
|
|
1455
|
+
const teamConfig = yaml.load(teamContent);
|
|
1456
|
+
const agents = teamConfig.agents || [];
|
|
1457
|
+
|
|
1458
|
+
// Add bmad-orchestrator if not present (required for all teams)
|
|
1459
|
+
if (!agents.includes('bmad-orchestrator')) {
|
|
1460
|
+
agents.unshift('bmad-orchestrator');
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
// Check each agent in the team
|
|
1464
|
+
for (const agentId of agents) {
|
|
1465
|
+
if (!existingAgents.has(agentId)) {
|
|
1466
|
+
// Agent not in expansion pack, try to get from core
|
|
1467
|
+
const coreAgentPath = path.join(
|
|
1468
|
+
resourceLocator.getBmadCorePath(),
|
|
1469
|
+
'agents',
|
|
1470
|
+
`${agentId}.md`,
|
|
1471
|
+
);
|
|
1472
|
+
|
|
1473
|
+
if (await fileManager.pathExists(coreAgentPath)) {
|
|
1474
|
+
spinner.text = `Copying core agent ${agentId} for ${packId}...`;
|
|
1475
|
+
|
|
1476
|
+
// Copy agent file with {root} replacement
|
|
1477
|
+
const destinationPath = path.join(expansionDotFolder, 'agents', `${agentId}.md`);
|
|
1478
|
+
await fileManager.copyFileWithRootReplacement(
|
|
1479
|
+
coreAgentPath,
|
|
1480
|
+
destinationPath,
|
|
1481
|
+
`.${packId}`,
|
|
1482
|
+
);
|
|
1483
|
+
existingAgents.add(agentId);
|
|
1484
|
+
|
|
1485
|
+
console.log(chalk.dim(` Added core agent: ${agentId}`));
|
|
1486
|
+
|
|
1487
|
+
// Now resolve this agent's dependencies too
|
|
1488
|
+
const agentContent = await fs.readFile(coreAgentPath, 'utf8');
|
|
1489
|
+
const yamlContent = extractYamlFromAgent(agentContent, true);
|
|
1490
|
+
|
|
1491
|
+
if (yamlContent) {
|
|
1492
|
+
try {
|
|
1493
|
+
const agentConfig = yaml.load(yamlContent);
|
|
1494
|
+
const dependencies = agentConfig.dependencies || {};
|
|
1495
|
+
|
|
1496
|
+
// Copy all dependencies for this agent
|
|
1497
|
+
for (const depType of [
|
|
1498
|
+
'tasks',
|
|
1499
|
+
'templates',
|
|
1500
|
+
'checklists',
|
|
1501
|
+
'workflows',
|
|
1502
|
+
'utils',
|
|
1503
|
+
'data',
|
|
1504
|
+
]) {
|
|
1505
|
+
const deps = dependencies[depType] || [];
|
|
1506
|
+
|
|
1507
|
+
for (const dep of deps) {
|
|
1508
|
+
const depFileName =
|
|
1509
|
+
dep.endsWith('.md') || dep.endsWith('.yaml')
|
|
1510
|
+
? dep
|
|
1511
|
+
: depType === 'templates'
|
|
1512
|
+
? `${dep}.yaml`
|
|
1513
|
+
: `${dep}.md`;
|
|
1514
|
+
const expansionDepPath = path.join(expansionDotFolder, depType, depFileName);
|
|
1515
|
+
|
|
1516
|
+
// Check if dependency exists in expansion pack
|
|
1517
|
+
if (!(await fileManager.pathExists(expansionDepPath))) {
|
|
1518
|
+
// Try to find it in core
|
|
1519
|
+
const coreDepPath = path.join(
|
|
1520
|
+
resourceLocator.getBmadCorePath(),
|
|
1521
|
+
depType,
|
|
1522
|
+
depFileName,
|
|
1523
|
+
);
|
|
1524
|
+
|
|
1525
|
+
if (await fileManager.pathExists(coreDepPath)) {
|
|
1526
|
+
const destinationDepPath = path.join(
|
|
1527
|
+
expansionDotFolder,
|
|
1528
|
+
depType,
|
|
1529
|
+
depFileName,
|
|
1530
|
+
);
|
|
1531
|
+
await fileManager.copyFileWithRootReplacement(
|
|
1532
|
+
coreDepPath,
|
|
1533
|
+
destinationDepPath,
|
|
1534
|
+
`.${packId}`,
|
|
1535
|
+
);
|
|
1536
|
+
console.log(
|
|
1537
|
+
chalk.dim(` Added agent dependency: ${depType}/${depFileName}`),
|
|
1538
|
+
);
|
|
1539
|
+
} else {
|
|
1540
|
+
// Try common folder
|
|
1541
|
+
const sourceBase = path.dirname(
|
|
1542
|
+
path.dirname(path.dirname(path.dirname(__filename))),
|
|
1543
|
+
); // Go up to project root
|
|
1544
|
+
const commonDepPath = path.join(
|
|
1545
|
+
sourceBase,
|
|
1546
|
+
'common',
|
|
1547
|
+
depType,
|
|
1548
|
+
depFileName,
|
|
1549
|
+
);
|
|
1550
|
+
if (await fileManager.pathExists(commonDepPath)) {
|
|
1551
|
+
const destinationDepPath = path.join(
|
|
1552
|
+
expansionDotFolder,
|
|
1553
|
+
depType,
|
|
1554
|
+
depFileName,
|
|
1555
|
+
);
|
|
1556
|
+
await fileManager.copyFile(commonDepPath, destinationDepPath);
|
|
1557
|
+
console.log(
|
|
1558
|
+
chalk.dim(
|
|
1559
|
+
` Added agent dependency from common: ${depType}/${depFileName}`,
|
|
1560
|
+
),
|
|
1561
|
+
);
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
} catch (error) {
|
|
1568
|
+
console.warn(
|
|
1569
|
+
` Warning: Could not parse agent ${agentId} dependencies: ${error.message}`,
|
|
1570
|
+
);
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
} else {
|
|
1574
|
+
console.warn(
|
|
1575
|
+
chalk.yellow(
|
|
1576
|
+
` Warning: Core agent ${agentId} not found for team ${path.basename(teamFile, '.yaml')}`,
|
|
1577
|
+
),
|
|
1578
|
+
);
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
} catch (error) {
|
|
1583
|
+
console.warn(` Warning: Could not parse team file ${teamFile}: ${error.message}`);
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
getWebBundleInfo(config) {
|
|
1589
|
+
const webBundleType = config.webBundleType || 'all';
|
|
1590
|
+
|
|
1591
|
+
switch (webBundleType) {
|
|
1592
|
+
case 'all': {
|
|
1593
|
+
return 'all bundles';
|
|
1594
|
+
}
|
|
1595
|
+
case 'agents': {
|
|
1596
|
+
return 'individual agents only';
|
|
1597
|
+
}
|
|
1598
|
+
case 'teams': {
|
|
1599
|
+
return config.selectedWebBundleTeams
|
|
1600
|
+
? `teams: ${config.selectedWebBundleTeams.join(', ')}`
|
|
1601
|
+
: 'selected teams';
|
|
1602
|
+
}
|
|
1603
|
+
case 'custom': {
|
|
1604
|
+
const parts = [];
|
|
1605
|
+
if (config.selectedWebBundleTeams && config.selectedWebBundleTeams.length > 0) {
|
|
1606
|
+
parts.push(`teams: ${config.selectedWebBundleTeams.join(', ')}`);
|
|
1607
|
+
}
|
|
1608
|
+
if (config.includeIndividualAgents) {
|
|
1609
|
+
parts.push('individual agents');
|
|
1610
|
+
}
|
|
1611
|
+
return parts.length > 0 ? parts.join(' + ') : 'custom selection';
|
|
1612
|
+
}
|
|
1613
|
+
default: {
|
|
1614
|
+
return 'selected bundles';
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
async installWebBundles(webBundlesDirectory, config, spinner) {
|
|
1620
|
+
try {
|
|
1621
|
+
// Find the dist directory in the BMad installation
|
|
1622
|
+
const distDir = configLoader.getDistPath();
|
|
1623
|
+
|
|
1624
|
+
if (!(await fileManager.pathExists(distDir))) {
|
|
1625
|
+
console.warn('Web bundles not found. Run "npm run build" to generate them.');
|
|
1626
|
+
return;
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
// Ensure web bundles directory exists
|
|
1630
|
+
await fileManager.ensureDirectory(webBundlesDirectory);
|
|
1631
|
+
|
|
1632
|
+
const webBundleType = config.webBundleType || 'all';
|
|
1633
|
+
|
|
1634
|
+
if (webBundleType === 'all') {
|
|
1635
|
+
// Copy the entire dist directory structure
|
|
1636
|
+
await fileManager.copyDirectory(distDir, webBundlesDirectory);
|
|
1637
|
+
console.log(chalk.green(`✓ Installed all web bundles to: ${webBundlesDirectory}`));
|
|
1638
|
+
} else {
|
|
1639
|
+
let copiedCount = 0;
|
|
1640
|
+
|
|
1641
|
+
// Copy specific selections based on type
|
|
1642
|
+
if (
|
|
1643
|
+
webBundleType === 'agents' ||
|
|
1644
|
+
(webBundleType === 'custom' && config.includeIndividualAgents)
|
|
1645
|
+
) {
|
|
1646
|
+
const agentsSource = path.join(distDir, 'agents');
|
|
1647
|
+
const agentsTarget = path.join(webBundlesDirectory, 'agents');
|
|
1648
|
+
if (await fileManager.pathExists(agentsSource)) {
|
|
1649
|
+
await fileManager.copyDirectory(agentsSource, agentsTarget);
|
|
1650
|
+
console.log(chalk.green(`✓ Copied individual agent bundles`));
|
|
1651
|
+
copiedCount += 10; // Approximate count for agents
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
if (
|
|
1656
|
+
(webBundleType === 'teams' || webBundleType === 'custom') &&
|
|
1657
|
+
config.selectedWebBundleTeams &&
|
|
1658
|
+
config.selectedWebBundleTeams.length > 0
|
|
1659
|
+
) {
|
|
1660
|
+
const teamsSource = path.join(distDir, 'teams');
|
|
1661
|
+
const teamsTarget = path.join(webBundlesDirectory, 'teams');
|
|
1662
|
+
await fileManager.ensureDirectory(teamsTarget);
|
|
1663
|
+
|
|
1664
|
+
for (const teamId of config.selectedWebBundleTeams) {
|
|
1665
|
+
const teamFile = `${teamId}.txt`;
|
|
1666
|
+
const sourcePath = path.join(teamsSource, teamFile);
|
|
1667
|
+
const targetPath = path.join(teamsTarget, teamFile);
|
|
1668
|
+
|
|
1669
|
+
if (await fileManager.pathExists(sourcePath)) {
|
|
1670
|
+
await fileManager.copyFile(sourcePath, targetPath);
|
|
1671
|
+
copiedCount++;
|
|
1672
|
+
console.log(chalk.green(`✓ Copied team bundle: ${teamId}`));
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
// Always copy expansion packs if they exist
|
|
1678
|
+
const expansionSource = path.join(distDir, 'expansion-packs');
|
|
1679
|
+
const expansionTarget = path.join(webBundlesDirectory, 'expansion-packs');
|
|
1680
|
+
if (await fileManager.pathExists(expansionSource)) {
|
|
1681
|
+
await fileManager.copyDirectory(expansionSource, expansionTarget);
|
|
1682
|
+
console.log(chalk.green(`✓ Copied expansion pack bundles`));
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
console.log(
|
|
1686
|
+
chalk.green(`✓ Installed ${copiedCount} selected web bundles to: ${webBundlesDirectory}`),
|
|
1687
|
+
);
|
|
1688
|
+
}
|
|
1689
|
+
} catch (error) {
|
|
1690
|
+
console.error(`Failed to install web bundles: ${error.message}`);
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
async copyCommonItems(installDir, targetSubdir, spinner) {
|
|
1695
|
+
const fs = require('node:fs').promises;
|
|
1696
|
+
const sourceBase = path.dirname(path.dirname(path.dirname(path.dirname(__filename)))); // Go up to project root
|
|
1697
|
+
const commonPath = path.join(sourceBase, 'common');
|
|
1698
|
+
const targetPath = path.join(installDir, targetSubdir);
|
|
1699
|
+
const copiedFiles = [];
|
|
1700
|
+
|
|
1701
|
+
// Check if common/ exists
|
|
1702
|
+
if (!(await fileManager.pathExists(commonPath))) {
|
|
1703
|
+
console.warn('Warning: common/ folder not found');
|
|
1704
|
+
return copiedFiles;
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
// Copy all items from common/ to target
|
|
1708
|
+
const commonItems = await resourceLocator.findFiles('**/*', {
|
|
1709
|
+
cwd: commonPath,
|
|
1710
|
+
nodir: true,
|
|
1711
|
+
});
|
|
1712
|
+
|
|
1713
|
+
for (const item of commonItems) {
|
|
1714
|
+
const sourcePath = path.join(commonPath, item);
|
|
1715
|
+
const destinationPath = path.join(targetPath, item);
|
|
1716
|
+
|
|
1717
|
+
// Read the file content
|
|
1718
|
+
const content = await fs.readFile(sourcePath, 'utf8');
|
|
1719
|
+
|
|
1720
|
+
// Replace {root} with the target subdirectory
|
|
1721
|
+
const updatedContent = content.replaceAll('{root}', targetSubdir);
|
|
1722
|
+
|
|
1723
|
+
// Ensure directory exists
|
|
1724
|
+
await fileManager.ensureDirectory(path.dirname(destinationPath));
|
|
1725
|
+
|
|
1726
|
+
// Write the updated content
|
|
1727
|
+
await fs.writeFile(destinationPath, updatedContent, 'utf8');
|
|
1728
|
+
copiedFiles.push(path.join(targetSubdir, item));
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
console.log(chalk.dim(` Added ${commonItems.length} common utilities`));
|
|
1732
|
+
return copiedFiles;
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
async copyDocsItems(installDir, targetSubdir, spinner) {
|
|
1736
|
+
const fs = require('node:fs').promises;
|
|
1737
|
+
const sourceBase = path.dirname(path.dirname(path.dirname(path.dirname(__filename)))); // Go up to project root
|
|
1738
|
+
const docsPath = path.join(sourceBase, 'docs');
|
|
1739
|
+
const targetPath = path.join(installDir, targetSubdir);
|
|
1740
|
+
const copiedFiles = [];
|
|
1741
|
+
|
|
1742
|
+
// Specific documentation files to copy
|
|
1743
|
+
const documentFiles = [
|
|
1744
|
+
'enhanced-ide-development-workflow.md',
|
|
1745
|
+
'user-guide.md',
|
|
1746
|
+
'working-in-the-brownfield.md',
|
|
1747
|
+
];
|
|
1748
|
+
|
|
1749
|
+
// Check if docs/ exists
|
|
1750
|
+
if (!(await fileManager.pathExists(docsPath))) {
|
|
1751
|
+
console.warn('Warning: docs/ folder not found');
|
|
1752
|
+
return copiedFiles;
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
// Copy specific documentation files from docs/ to target
|
|
1756
|
+
for (const documentFile of documentFiles) {
|
|
1757
|
+
const sourcePath = path.join(docsPath, documentFile);
|
|
1758
|
+
const destinationPath = path.join(targetPath, documentFile);
|
|
1759
|
+
|
|
1760
|
+
// Check if the source file exists
|
|
1761
|
+
if (await fileManager.pathExists(sourcePath)) {
|
|
1762
|
+
// Read the file content
|
|
1763
|
+
const content = await fs.readFile(sourcePath, 'utf8');
|
|
1764
|
+
|
|
1765
|
+
// Replace {root} with the target subdirectory
|
|
1766
|
+
const updatedContent = content.replaceAll('{root}', targetSubdir);
|
|
1767
|
+
|
|
1768
|
+
// Ensure directory exists
|
|
1769
|
+
await fileManager.ensureDirectory(path.dirname(destinationPath));
|
|
1770
|
+
|
|
1771
|
+
// Write the updated content
|
|
1772
|
+
await fs.writeFile(destinationPath, updatedContent, 'utf8');
|
|
1773
|
+
copiedFiles.push(path.join(targetSubdir, documentFile));
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1777
|
+
if (copiedFiles.length > 0) {
|
|
1778
|
+
console.log(chalk.dim(` Added ${copiedFiles.length} documentation files`));
|
|
1779
|
+
}
|
|
1780
|
+
return copiedFiles;
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
async detectExpansionPacks(installDir) {
|
|
1784
|
+
const expansionPacks = {};
|
|
1785
|
+
const glob = require('glob');
|
|
1786
|
+
|
|
1787
|
+
// Find all dot folders that might be expansion packs
|
|
1788
|
+
const dotFolders = glob.sync('.*', {
|
|
1789
|
+
cwd: installDir,
|
|
1790
|
+
ignore: ['.git', '.git/**', '.bmad-core', '.bmad-core/**'],
|
|
1791
|
+
});
|
|
1792
|
+
|
|
1793
|
+
for (const folder of dotFolders) {
|
|
1794
|
+
const folderPath = path.join(installDir, folder);
|
|
1795
|
+
const stats = await fileManager.pathExists(folderPath);
|
|
1796
|
+
|
|
1797
|
+
if (stats) {
|
|
1798
|
+
// Check if it has a manifest
|
|
1799
|
+
const manifestPath = path.join(folderPath, 'install-manifest.yaml');
|
|
1800
|
+
if (await fileManager.pathExists(manifestPath)) {
|
|
1801
|
+
const manifest = await fileManager.readExpansionPackManifest(installDir, folder.slice(1));
|
|
1802
|
+
if (manifest) {
|
|
1803
|
+
expansionPacks[folder.slice(1)] = {
|
|
1804
|
+
path: folderPath,
|
|
1805
|
+
manifest: manifest,
|
|
1806
|
+
hasManifest: true,
|
|
1807
|
+
};
|
|
1808
|
+
}
|
|
1809
|
+
} else {
|
|
1810
|
+
// Check if it has a config.yaml (expansion pack without manifest)
|
|
1811
|
+
const configPath = path.join(folderPath, 'config.yaml');
|
|
1812
|
+
if (await fileManager.pathExists(configPath)) {
|
|
1813
|
+
expansionPacks[folder.slice(1)] = {
|
|
1814
|
+
path: folderPath,
|
|
1815
|
+
manifest: null,
|
|
1816
|
+
hasManifest: false,
|
|
1817
|
+
};
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1823
|
+
return expansionPacks;
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
async repairExpansionPack(installDir, packId, pack, integrity, spinner) {
|
|
1827
|
+
spinner.start(`Repairing ${pack.name}...`);
|
|
1828
|
+
|
|
1829
|
+
try {
|
|
1830
|
+
const expansionDotFolder = path.join(installDir, `.${packId}`);
|
|
1831
|
+
|
|
1832
|
+
// Back up modified files
|
|
1833
|
+
if (integrity.modified.length > 0) {
|
|
1834
|
+
spinner.text = 'Backing up modified files...';
|
|
1835
|
+
for (const file of integrity.modified) {
|
|
1836
|
+
const filePath = path.join(installDir, file);
|
|
1837
|
+
if (await fileManager.pathExists(filePath)) {
|
|
1838
|
+
const backupPath = await fileManager.backupFile(filePath);
|
|
1839
|
+
console.log(chalk.dim(` Backed up: ${file} → ${path.basename(backupPath)}`));
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
// Restore missing and modified files
|
|
1845
|
+
spinner.text = 'Restoring files...';
|
|
1846
|
+
const filesToRestore = [...integrity.missing, ...integrity.modified];
|
|
1847
|
+
|
|
1848
|
+
for (const file of filesToRestore) {
|
|
1849
|
+
// Skip the manifest file itself
|
|
1850
|
+
if (file.endsWith('install-manifest.yaml')) continue;
|
|
1851
|
+
|
|
1852
|
+
const relativePath = file.replace(`.${packId}/`, '');
|
|
1853
|
+
const sourcePath = path.join(pack.path, relativePath);
|
|
1854
|
+
const destinationPath = path.join(installDir, file);
|
|
1855
|
+
|
|
1856
|
+
// Check if this is a common/ file that needs special processing
|
|
1857
|
+
const commonBase = path.dirname(path.dirname(path.dirname(path.dirname(__filename))));
|
|
1858
|
+
const commonSourcePath = path.join(commonBase, 'common', relativePath);
|
|
1859
|
+
|
|
1860
|
+
if (await fileManager.pathExists(commonSourcePath)) {
|
|
1861
|
+
// This is a common/ file - needs template processing
|
|
1862
|
+
const fs = require('node:fs').promises;
|
|
1863
|
+
const content = await fs.readFile(commonSourcePath, 'utf8');
|
|
1864
|
+
const updatedContent = content.replaceAll('{root}', `.${packId}`);
|
|
1865
|
+
await fileManager.ensureDirectory(path.dirname(destinationPath));
|
|
1866
|
+
await fs.writeFile(destinationPath, updatedContent, 'utf8');
|
|
1867
|
+
spinner.text = `Restored: ${file}`;
|
|
1868
|
+
} else if (await fileManager.pathExists(sourcePath)) {
|
|
1869
|
+
// Regular file from expansion pack
|
|
1870
|
+
await fileManager.copyFile(sourcePath, destinationPath);
|
|
1871
|
+
spinner.text = `Restored: ${file}`;
|
|
1872
|
+
} else {
|
|
1873
|
+
console.warn(chalk.yellow(` Warning: Source file not found: ${file}`));
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1877
|
+
spinner.succeed(`${pack.name} repaired successfully!`);
|
|
1878
|
+
|
|
1879
|
+
// Show summary
|
|
1880
|
+
console.log(chalk.green(`\n✓ ${pack.name} repaired!`));
|
|
1881
|
+
if (integrity.missing.length > 0) {
|
|
1882
|
+
console.log(chalk.green(` Restored ${integrity.missing.length} missing files`));
|
|
1883
|
+
}
|
|
1884
|
+
if (integrity.modified.length > 0) {
|
|
1885
|
+
console.log(
|
|
1886
|
+
chalk.green(` Restored ${integrity.modified.length} modified files (backups created)`),
|
|
1887
|
+
);
|
|
1888
|
+
}
|
|
1889
|
+
} catch (error) {
|
|
1890
|
+
if (spinner) spinner.fail(`Failed to repair ${pack.name}`);
|
|
1891
|
+
console.error(`Error: ${error.message}`);
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1895
|
+
compareVersions(v1, v2) {
|
|
1896
|
+
// Simple semver comparison
|
|
1897
|
+
const parts1 = v1.split('.').map(Number);
|
|
1898
|
+
const parts2 = v2.split('.').map(Number);
|
|
1899
|
+
|
|
1900
|
+
for (let index = 0; index < 3; index++) {
|
|
1901
|
+
const part1 = parts1[index] || 0;
|
|
1902
|
+
const part2 = parts2[index] || 0;
|
|
1903
|
+
|
|
1904
|
+
if (part1 > part2) return 1;
|
|
1905
|
+
if (part1 < part2) return -1;
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1908
|
+
return 0;
|
|
1909
|
+
}
|
|
1910
|
+
|
|
1911
|
+
async cleanupLegacyYmlFiles(installDir, spinner) {
|
|
1912
|
+
const glob = require('glob');
|
|
1913
|
+
const fs = require('node:fs').promises;
|
|
1914
|
+
|
|
1915
|
+
try {
|
|
1916
|
+
// Find all .yml files in the installation directory
|
|
1917
|
+
const ymlFiles = glob.sync('**/*.yml', {
|
|
1918
|
+
cwd: installDir,
|
|
1919
|
+
ignore: ['**/node_modules/**', '**/.git/**'],
|
|
1920
|
+
});
|
|
1921
|
+
|
|
1922
|
+
let deletedCount = 0;
|
|
1923
|
+
|
|
1924
|
+
for (const ymlFile of ymlFiles) {
|
|
1925
|
+
// Check if corresponding .yaml file exists
|
|
1926
|
+
const yamlFile = ymlFile.replace(/\.yml$/, '.yaml');
|
|
1927
|
+
const ymlPath = path.join(installDir, ymlFile);
|
|
1928
|
+
const yamlPath = path.join(installDir, yamlFile);
|
|
1929
|
+
|
|
1930
|
+
if (await fileManager.pathExists(yamlPath)) {
|
|
1931
|
+
// .yaml counterpart exists, delete the .yml file
|
|
1932
|
+
await fs.unlink(ymlPath);
|
|
1933
|
+
deletedCount++;
|
|
1934
|
+
console.log(chalk.dim(` Removed legacy: ${ymlFile} (replaced by ${yamlFile})`));
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
|
|
1938
|
+
if (deletedCount > 0) {
|
|
1939
|
+
console.log(chalk.green(`✓ Cleaned up ${deletedCount} legacy .yml files`));
|
|
1940
|
+
}
|
|
1941
|
+
} catch (error) {
|
|
1942
|
+
console.warn(`Warning: Could not cleanup legacy .yml files: ${error.message}`);
|
|
1943
|
+
}
|
|
625
1944
|
}
|
|
626
1945
|
|
|
627
1946
|
async findInstallation() {
|
|
@@ -629,26 +1948,48 @@ class Installer {
|
|
|
629
1948
|
let currentDir = process.cwd();
|
|
630
1949
|
|
|
631
1950
|
while (currentDir !== path.dirname(currentDir)) {
|
|
632
|
-
const bmadDir = path.join(currentDir,
|
|
633
|
-
const manifestPath = path.join(bmadDir,
|
|
1951
|
+
const bmadDir = path.join(currentDir, '.bmad-core');
|
|
1952
|
+
const manifestPath = path.join(bmadDir, 'install-manifest.yaml');
|
|
634
1953
|
|
|
635
1954
|
if (await fileManager.pathExists(manifestPath)) {
|
|
636
|
-
return
|
|
1955
|
+
return currentDir; // Return parent directory, not .bmad-core itself
|
|
637
1956
|
}
|
|
638
1957
|
|
|
639
1958
|
currentDir = path.dirname(currentDir);
|
|
640
1959
|
}
|
|
641
1960
|
|
|
642
1961
|
// Also check if we're inside a .bmad-core directory
|
|
643
|
-
if (path.basename(process.cwd()) ===
|
|
644
|
-
const manifestPath = path.join(process.cwd(),
|
|
1962
|
+
if (path.basename(process.cwd()) === '.bmad-core') {
|
|
1963
|
+
const manifestPath = path.join(process.cwd(), 'install-manifest.yaml');
|
|
645
1964
|
if (await fileManager.pathExists(manifestPath)) {
|
|
646
|
-
return process.cwd();
|
|
1965
|
+
return path.dirname(process.cwd()); // Return parent directory
|
|
647
1966
|
}
|
|
648
1967
|
}
|
|
649
1968
|
|
|
650
1969
|
return null;
|
|
651
1970
|
}
|
|
1971
|
+
|
|
1972
|
+
async flatten(options) {
|
|
1973
|
+
const { spawn } = require('node:child_process');
|
|
1974
|
+
const flattenerPath = path.join(__dirname, '..', '..', 'flattener', 'main.js');
|
|
1975
|
+
|
|
1976
|
+
const arguments_ = [];
|
|
1977
|
+
if (options.input) {
|
|
1978
|
+
arguments_.push('--input', options.input);
|
|
1979
|
+
}
|
|
1980
|
+
if (options.output) {
|
|
1981
|
+
arguments_.push('--output', options.output);
|
|
1982
|
+
}
|
|
1983
|
+
|
|
1984
|
+
const child = spawn('node', [flattenerPath, ...arguments_], {
|
|
1985
|
+
stdio: 'inherit',
|
|
1986
|
+
cwd: process.cwd(),
|
|
1987
|
+
});
|
|
1988
|
+
|
|
1989
|
+
child.on('exit', (code) => {
|
|
1990
|
+
process.exit(code);
|
|
1991
|
+
});
|
|
1992
|
+
}
|
|
652
1993
|
}
|
|
653
1994
|
|
|
654
1995
|
module.exports = new Installer();
|