bmad-method 5.0.0 → 5.0.1

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.
Files changed (329) hide show
  1. package/.github/FUNDING.yaml +15 -0
  2. package/.github/ISSUE_TEMPLATE/bug_report.md +32 -0
  3. package/.github/ISSUE_TEMPLATE/feature_request.md +22 -0
  4. package/.github/workflows/discord.yaml +25 -0
  5. package/.github/workflows/format-check.yaml +42 -0
  6. package/.github/workflows/manual-release.yaml +173 -0
  7. package/.husky/pre-commit +3 -2
  8. package/.vscode/settings.json +67 -74
  9. package/CHANGELOG.md +564 -19
  10. package/CONTRIBUTING.md +168 -5
  11. package/LICENSE +1 -1
  12. package/README.md +146 -218
  13. package/bmad-core/agent-teams/team-all.yaml +14 -0
  14. package/bmad-core/agent-teams/team-fullstack.yaml +18 -0
  15. package/bmad-core/agent-teams/team-ide-minimal.yaml +10 -0
  16. package/bmad-core/agent-teams/team-no-ui.yaml +13 -0
  17. package/bmad-core/agents/analyst.md +81 -0
  18. package/bmad-core/agents/architect.md +83 -0
  19. package/bmad-core/agents/bmad-master.md +107 -0
  20. package/bmad-core/agents/bmad-orchestrator.md +149 -0
  21. package/bmad-core/agents/dev.md +75 -0
  22. package/bmad-core/agents/pm.md +81 -0
  23. package/bmad-core/agents/po.md +76 -0
  24. package/bmad-core/agents/qa.md +88 -0
  25. package/bmad-core/agents/sm.md +62 -0
  26. package/bmad-core/agents/ux-expert.md +66 -0
  27. package/{.bmad-core → bmad-core}/checklists/architect-checklist.md +0 -5
  28. package/{.bmad-core → bmad-core}/checklists/change-checklist.md +2 -2
  29. package/{.bmad-core → bmad-core}/checklists/pm-checklist.md +0 -5
  30. package/{.bmad-core → bmad-core}/checklists/po-master-checklist.md +0 -9
  31. package/{.bmad-core → bmad-core}/checklists/story-dod-checklist.md +0 -7
  32. package/{.bmad-core → bmad-core}/checklists/story-draft-checklist.md +1 -4
  33. package/bmad-core/core-config.yaml +20 -0
  34. package/bmad-core/data/bmad-kb.md +806 -0
  35. package/bmad-core/data/brainstorming-techniques.md +36 -0
  36. package/bmad-core/data/elicitation-methods.md +154 -0
  37. package/bmad-core/data/test-levels-framework.md +146 -0
  38. package/bmad-core/data/test-priorities-matrix.md +172 -0
  39. package/bmad-core/tasks/advanced-elicitation.md +117 -0
  40. package/{.bmad-core → bmad-core}/tasks/correct-course.md +9 -12
  41. package/bmad-core/tasks/create-brownfield-story.md +312 -0
  42. package/{.bmad-core → bmad-core}/tasks/create-deep-research-prompt.md +4 -27
  43. package/bmad-core/tasks/create-next-story.md +112 -0
  44. package/bmad-core/tasks/document-project.md +343 -0
  45. package/bmad-core/tasks/facilitate-brainstorming-session.md +136 -0
  46. package/bmad-core/tasks/generate-ai-frontend-prompt.md +51 -0
  47. package/{.bmad-core → bmad-core}/tasks/index-docs.md +3 -13
  48. package/bmad-core/tasks/kb-mode-interaction.md +75 -0
  49. package/bmad-core/tasks/nfr-assess.md +343 -0
  50. package/bmad-core/tasks/qa-gate.md +159 -0
  51. package/bmad-core/tasks/review-story.md +314 -0
  52. package/bmad-core/tasks/risk-profile.md +353 -0
  53. package/{.bmad-core → bmad-core}/tasks/shard-doc.md +27 -15
  54. package/bmad-core/tasks/test-design.md +174 -0
  55. package/bmad-core/tasks/trace-requirements.md +264 -0
  56. package/bmad-core/tasks/validate-next-story.md +134 -0
  57. package/bmad-core/templates/architecture-tmpl.yaml +650 -0
  58. package/bmad-core/templates/brainstorming-output-tmpl.yaml +156 -0
  59. package/bmad-core/templates/brownfield-architecture-tmpl.yaml +476 -0
  60. package/bmad-core/templates/brownfield-prd-tmpl.yaml +280 -0
  61. package/bmad-core/templates/competitor-analysis-tmpl.yaml +306 -0
  62. package/bmad-core/templates/front-end-architecture-tmpl.yaml +218 -0
  63. package/bmad-core/templates/front-end-spec-tmpl.yaml +349 -0
  64. package/bmad-core/templates/fullstack-architecture-tmpl.yaml +823 -0
  65. package/bmad-core/templates/market-research-tmpl.yaml +252 -0
  66. package/bmad-core/templates/prd-tmpl.yaml +202 -0
  67. package/bmad-core/templates/project-brief-tmpl.yaml +221 -0
  68. package/bmad-core/templates/qa-gate-tmpl.yaml +102 -0
  69. package/bmad-core/templates/story-tmpl.yaml +137 -0
  70. package/bmad-core/workflows/brownfield-fullstack.yaml +297 -0
  71. package/bmad-core/workflows/brownfield-service.yaml +187 -0
  72. package/bmad-core/workflows/brownfield-ui.yaml +197 -0
  73. package/{.bmad-core/workflows/greenfield-fullstack.yml → bmad-core/workflows/greenfield-fullstack.yaml} +140 -77
  74. package/bmad-core/workflows/greenfield-service.yaml +206 -0
  75. package/bmad-core/workflows/greenfield-ui.yaml +235 -0
  76. package/common/tasks/create-doc.md +101 -0
  77. package/{.bmad-core → common}/tasks/execute-checklist.md +2 -13
  78. package/common/utils/bmad-doc-template.md +325 -0
  79. package/common/utils/workflow-management.md +69 -0
  80. package/dist/agents/analyst.txt +2889 -0
  81. package/dist/agents/architect.txt +3552 -0
  82. package/dist/agents/bmad-master.txt +8769 -0
  83. package/dist/agents/bmad-orchestrator.txt +1513 -0
  84. package/dist/agents/dev.txt +414 -0
  85. package/{.bmad-core/web-bundles → dist}/agents/pm.txt +668 -1119
  86. package/{.bmad-core/web-bundles → dist}/agents/po.txt +341 -484
  87. package/dist/agents/qa.txt +1987 -0
  88. package/dist/agents/sm.txt +658 -0
  89. package/dist/agents/ux-expert.txt +694 -0
  90. package/dist/expansion-packs/bmad-2d-phaser-game-dev/agents/game-designer.txt +2371 -0
  91. package/dist/expansion-packs/bmad-2d-phaser-game-dev/agents/game-developer.txt +1620 -0
  92. package/dist/expansion-packs/bmad-2d-phaser-game-dev/agents/game-sm.txt +815 -0
  93. package/dist/expansion-packs/bmad-2d-phaser-game-dev/teams/phaser-2d-nodejs-game-team.txt +10952 -0
  94. package/dist/expansion-packs/bmad-2d-unity-game-dev/agents/game-architect.txt +4012 -0
  95. package/dist/expansion-packs/bmad-2d-unity-game-dev/agents/game-designer.txt +3698 -0
  96. package/dist/expansion-packs/bmad-2d-unity-game-dev/agents/game-developer.txt +450 -0
  97. package/dist/expansion-packs/bmad-2d-unity-game-dev/agents/game-sm.txt +973 -0
  98. package/dist/expansion-packs/bmad-2d-unity-game-dev/teams/unity-2d-game-team.txt +15376 -0
  99. package/dist/expansion-packs/bmad-infrastructure-devops/agents/infra-devops-platform.txt +2075 -0
  100. package/dist/teams/team-all.txt +12682 -0
  101. package/dist/teams/team-fullstack.txt +10421 -0
  102. package/dist/teams/team-ide-minimal.txt +5103 -0
  103. package/dist/teams/team-no-ui.txt +8980 -0
  104. package/docs/GUIDING-PRINCIPLES.md +91 -0
  105. package/docs/core-architecture.md +219 -0
  106. package/docs/enhanced-ide-development-workflow.md +248 -0
  107. package/docs/expansion-packs.md +280 -0
  108. package/docs/how-to-contribute-with-pull-requests.md +158 -0
  109. package/docs/user-guide.md +504 -0
  110. package/docs/versioning-and-releases.md +115 -53
  111. package/docs/versions.md +4 -5
  112. package/docs/working-in-the-brownfield.md +597 -0
  113. package/eslint.config.mjs +119 -0
  114. package/expansion-packs/Complete AI Agent System - Blank Templates & Google Cloud Setup/Complete AI Agent System - Flowchart.svg +102 -0
  115. 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
  116. 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
  117. 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
  118. 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
  119. 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
  120. 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
  121. package/expansion-packs/Complete AI Agent System - Blank Templates & Google Cloud Setup/README.md +109 -0
  122. package/expansion-packs/README.md +2 -112
  123. package/expansion-packs/bmad-2d-phaser-game-dev/agent-teams/phaser-2d-nodejs-game-team.yaml +13 -0
  124. package/expansion-packs/bmad-2d-phaser-game-dev/agents/game-designer.md +71 -0
  125. package/expansion-packs/bmad-2d-phaser-game-dev/agents/game-developer.md +78 -0
  126. package/expansion-packs/bmad-2d-phaser-game-dev/agents/game-sm.md +64 -0
  127. package/expansion-packs/bmad-2d-phaser-game-dev/checklists/game-design-checklist.md +201 -0
  128. package/expansion-packs/bmad-2d-phaser-game-dev/checklists/game-story-dod-checklist.md +160 -0
  129. package/expansion-packs/bmad-2d-phaser-game-dev/config.yaml +8 -0
  130. package/expansion-packs/bmad-2d-phaser-game-dev/data/bmad-kb.md +250 -0
  131. package/expansion-packs/bmad-2d-phaser-game-dev/data/development-guidelines.md +647 -0
  132. package/expansion-packs/bmad-2d-phaser-game-dev/tasks/advanced-elicitation.md +110 -0
  133. package/expansion-packs/bmad-2d-phaser-game-dev/tasks/create-game-story.md +216 -0
  134. package/expansion-packs/bmad-2d-phaser-game-dev/tasks/game-design-brainstorming.md +290 -0
  135. package/expansion-packs/bmad-2d-phaser-game-dev/templates/game-architecture-tmpl.yaml +613 -0
  136. package/expansion-packs/bmad-2d-phaser-game-dev/templates/game-brief-tmpl.yaml +356 -0
  137. package/expansion-packs/bmad-2d-phaser-game-dev/templates/game-design-doc-tmpl.yaml +343 -0
  138. package/expansion-packs/bmad-2d-phaser-game-dev/templates/game-story-tmpl.yaml +253 -0
  139. package/expansion-packs/bmad-2d-phaser-game-dev/templates/level-design-doc-tmpl.yaml +484 -0
  140. package/expansion-packs/bmad-2d-phaser-game-dev/workflows/game-dev-greenfield.yaml +183 -0
  141. package/expansion-packs/bmad-2d-phaser-game-dev/workflows/game-prototype.yaml +175 -0
  142. package/expansion-packs/bmad-2d-unity-game-dev/agent-teams/unity-2d-game-team.yaml +14 -0
  143. package/expansion-packs/bmad-2d-unity-game-dev/agents/game-architect.md +80 -0
  144. package/expansion-packs/bmad-2d-unity-game-dev/agents/game-designer.md +77 -0
  145. package/expansion-packs/bmad-2d-unity-game-dev/agents/game-developer.md +78 -0
  146. package/expansion-packs/bmad-2d-unity-game-dev/agents/game-sm.md +65 -0
  147. package/expansion-packs/bmad-2d-unity-game-dev/checklists/game-architect-checklist.md +391 -0
  148. package/expansion-packs/bmad-2d-unity-game-dev/checklists/game-change-checklist.md +203 -0
  149. package/expansion-packs/bmad-2d-unity-game-dev/checklists/game-design-checklist.md +201 -0
  150. package/expansion-packs/bmad-2d-unity-game-dev/checklists/game-story-dod-checklist.md +124 -0
  151. package/expansion-packs/bmad-2d-unity-game-dev/config.yaml +6 -0
  152. package/expansion-packs/bmad-2d-unity-game-dev/data/bmad-kb.md +769 -0
  153. package/expansion-packs/bmad-2d-unity-game-dev/data/development-guidelines.md +586 -0
  154. package/expansion-packs/bmad-2d-unity-game-dev/tasks/advanced-elicitation.md +110 -0
  155. package/expansion-packs/bmad-2d-unity-game-dev/tasks/correct-course-game.md +141 -0
  156. package/expansion-packs/bmad-2d-unity-game-dev/tasks/create-game-story.md +184 -0
  157. package/expansion-packs/bmad-2d-unity-game-dev/tasks/game-design-brainstorming.md +290 -0
  158. package/expansion-packs/bmad-2d-unity-game-dev/tasks/validate-game-story.md +200 -0
  159. package/expansion-packs/bmad-2d-unity-game-dev/templates/game-architecture-tmpl.yaml +1030 -0
  160. package/expansion-packs/bmad-2d-unity-game-dev/templates/game-brief-tmpl.yaml +356 -0
  161. package/expansion-packs/bmad-2d-unity-game-dev/templates/game-design-doc-tmpl.yaml +705 -0
  162. package/expansion-packs/bmad-2d-unity-game-dev/templates/game-story-tmpl.yaml +256 -0
  163. package/expansion-packs/bmad-2d-unity-game-dev/templates/level-design-doc-tmpl.yaml +484 -0
  164. package/expansion-packs/bmad-2d-unity-game-dev/workflows/game-dev-greenfield.yaml +183 -0
  165. package/expansion-packs/bmad-2d-unity-game-dev/workflows/game-prototype.yaml +175 -0
  166. package/expansion-packs/{infrastructure-devops → bmad-infrastructure-devops}/README.md +9 -9
  167. package/expansion-packs/{infrastructure-devops → bmad-infrastructure-devops}/agents/infra-devops-platform.md +30 -18
  168. package/expansion-packs/{infrastructure-devops → bmad-infrastructure-devops}/checklists/infrastructure-checklist.md +1 -1
  169. package/expansion-packs/bmad-infrastructure-devops/config.yaml +9 -0
  170. package/expansion-packs/bmad-infrastructure-devops/data/bmad-kb.md +305 -0
  171. package/expansion-packs/{infrastructure-devops → bmad-infrastructure-devops}/tasks/review-infrastructure.md +4 -5
  172. package/expansion-packs/{infrastructure-devops → bmad-infrastructure-devops}/tasks/validate-infrastructure.md +4 -5
  173. package/expansion-packs/bmad-infrastructure-devops/templates/infrastructure-architecture-tmpl.yaml +424 -0
  174. package/expansion-packs/bmad-infrastructure-devops/templates/infrastructure-platform-from-arch-tmpl.yaml +629 -0
  175. package/package.json +74 -42
  176. package/prettier.config.mjs +32 -0
  177. package/release_notes.md +30 -0
  178. package/tools/bmad-npx-wrapper.js +13 -15
  179. package/tools/builders/web-builder.js +544 -15
  180. package/tools/bump-all-versions.js +115 -0
  181. package/tools/bump-expansion-version.js +90 -0
  182. package/tools/cli.js +65 -32
  183. package/tools/flattener/aggregate.js +76 -0
  184. package/tools/flattener/binary.js +80 -0
  185. package/tools/flattener/discovery.js +71 -0
  186. package/tools/flattener/files.js +35 -0
  187. package/tools/flattener/ignoreRules.js +176 -0
  188. package/tools/flattener/main.js +573 -0
  189. package/tools/flattener/projectRoot.js +206 -0
  190. package/tools/flattener/prompts.js +44 -0
  191. package/tools/flattener/stats.helpers.js +395 -0
  192. package/tools/flattener/stats.js +80 -0
  193. package/tools/flattener/test-matrix.js +413 -0
  194. package/tools/flattener/xml.js +88 -0
  195. package/tools/installer/README.md +3 -53
  196. package/tools/installer/bin/bmad.js +475 -90
  197. package/tools/installer/config/ide-agent-config.yaml +58 -0
  198. package/tools/installer/config/install.config.yaml +123 -0
  199. package/tools/installer/lib/config-loader.js +208 -40
  200. package/tools/installer/lib/file-manager.js +258 -55
  201. package/tools/installer/lib/ide-base-setup.js +228 -0
  202. package/tools/installer/lib/ide-setup.js +1265 -253
  203. package/tools/installer/lib/installer.js +1651 -310
  204. package/tools/installer/lib/memory-profiler.js +225 -0
  205. package/tools/installer/lib/module-manager.js +114 -0
  206. package/tools/installer/lib/resource-locator.js +308 -0
  207. package/tools/installer/package.json +25 -24
  208. package/tools/lib/dependency-resolver.js +44 -48
  209. package/tools/lib/yaml-utils.js +29 -0
  210. package/tools/md-assets/web-agent-startup-instructions.md +39 -0
  211. package/tools/preview-release-notes.js +66 -0
  212. package/tools/shared/bannerArt.js +105 -0
  213. package/tools/sync-installer-version.js +7 -9
  214. package/tools/update-expansion-version.js +53 -0
  215. package/tools/upgraders/v3-to-v4-upgrader.js +221 -320
  216. package/tools/version-bump.js +42 -27
  217. package/tools/yaml-format.js +57 -44
  218. package/.bmad-core/agent-teams/team-all.yml +0 -16
  219. package/.bmad-core/agent-teams/team-fullstack.yml +0 -26
  220. package/.bmad-core/agent-teams/team-no-ui.yml +0 -15
  221. package/.bmad-core/agents/analyst.md +0 -59
  222. package/.bmad-core/agents/architect.md +0 -66
  223. package/.bmad-core/agents/bmad-master.md +0 -104
  224. package/.bmad-core/agents/bmad-orchestrator.md +0 -81
  225. package/.bmad-core/agents/dev.md +0 -70
  226. package/.bmad-core/agents/pm.md +0 -59
  227. package/.bmad-core/agents/po.md +0 -60
  228. package/.bmad-core/agents/qa.md +0 -52
  229. package/.bmad-core/agents/sm.md +0 -55
  230. package/.bmad-core/agents/ux-expert.md +0 -66
  231. package/.bmad-core/data/bmad-kb.md +0 -47
  232. package/.bmad-core/schemas/agent-team-schema.yml +0 -153
  233. package/.bmad-core/tasks/advanced-elicitation.md +0 -92
  234. package/.bmad-core/tasks/brainstorming-techniques.md +0 -238
  235. package/.bmad-core/tasks/core-dump.md +0 -74
  236. package/.bmad-core/tasks/create-agent.md +0 -202
  237. package/.bmad-core/tasks/create-doc.md +0 -74
  238. package/.bmad-core/tasks/create-expansion-pack.md +0 -425
  239. package/.bmad-core/tasks/create-next-story.md +0 -206
  240. package/.bmad-core/tasks/create-team.md +0 -229
  241. package/.bmad-core/tasks/doc-migration-task.md +0 -143
  242. package/.bmad-core/tasks/generate-ai-frontend-prompt.md +0 -58
  243. package/.bmad-core/templates/agent-tmpl.md +0 -58
  244. package/.bmad-core/templates/architecture-tmpl.md +0 -771
  245. package/.bmad-core/templates/brownfield-architecture-tmpl.md +0 -542
  246. package/.bmad-core/templates/brownfield-prd-tmpl.md +0 -240
  247. package/.bmad-core/templates/competitor-analysis-tmpl.md +0 -289
  248. package/.bmad-core/templates/expansion-pack-plan-tmpl.md +0 -91
  249. package/.bmad-core/templates/front-end-architecture-tmpl.md +0 -173
  250. package/.bmad-core/templates/front-end-spec-tmpl.md +0 -411
  251. package/.bmad-core/templates/fullstack-architecture-tmpl.md +0 -1016
  252. package/.bmad-core/templates/market-research-tmpl.md +0 -261
  253. package/.bmad-core/templates/prd-tmpl.md +0 -200
  254. package/.bmad-core/templates/project-brief-tmpl.md +0 -228
  255. package/.bmad-core/templates/simple-project-prd-tmpl.md +0 -461
  256. package/.bmad-core/templates/story-tmpl.md +0 -61
  257. package/.bmad-core/templates/web-agent-startup-instructions-template.md +0 -39
  258. package/.bmad-core/utils/agent-switcher.ide.md +0 -112
  259. package/.bmad-core/utils/template-format.md +0 -26
  260. package/.bmad-core/utils/workflow-management.md +0 -224
  261. package/.bmad-core/web-bundles/agents/analyst.txt +0 -1684
  262. package/.bmad-core/web-bundles/agents/architect.txt +0 -3584
  263. package/.bmad-core/web-bundles/agents/bmad-master.txt +0 -9491
  264. package/.bmad-core/web-bundles/agents/bmad-orchestrator.txt +0 -1466
  265. package/.bmad-core/web-bundles/agents/dev.txt +0 -316
  266. package/.bmad-core/web-bundles/agents/qa.txt +0 -129
  267. package/.bmad-core/web-bundles/agents/sm.txt +0 -658
  268. package/.bmad-core/web-bundles/agents/ux-expert.txt +0 -1099
  269. package/.bmad-core/web-bundles/teams/team-all.txt +0 -10757
  270. package/.bmad-core/web-bundles/teams/team-fullstack.txt +0 -10109
  271. package/.bmad-core/web-bundles/teams/team-no-ui.txt +0 -8950
  272. package/.bmad-core/workflows/brownfield-fullstack.yml +0 -116
  273. package/.bmad-core/workflows/brownfield-service.yml +0 -117
  274. package/.bmad-core/workflows/brownfield-ui.yml +0 -127
  275. package/.bmad-core/workflows/greenfield-service.yml +0 -143
  276. package/.bmad-core/workflows/greenfield-ui.yml +0 -172
  277. package/.claude/commands/analyst.md +0 -63
  278. package/.claude/commands/architect.md +0 -70
  279. package/.claude/commands/bmad-master.md +0 -108
  280. package/.claude/commands/bmad-orchestrator.md +0 -85
  281. package/.claude/commands/dev.md +0 -74
  282. package/.claude/commands/pm.md +0 -63
  283. package/.claude/commands/po.md +0 -64
  284. package/.claude/commands/qa.md +0 -56
  285. package/.claude/commands/sm.md +0 -59
  286. package/.claude/commands/ux-expert.md +0 -70
  287. package/.cursor/rules/analyst.mdc +0 -77
  288. package/.cursor/rules/architect.mdc +0 -84
  289. package/.cursor/rules/bmad-master.mdc +0 -122
  290. package/.cursor/rules/bmad-orchestrator.mdc +0 -99
  291. package/.cursor/rules/dev.mdc +0 -88
  292. package/.cursor/rules/pm.mdc +0 -77
  293. package/.cursor/rules/po.mdc +0 -78
  294. package/.cursor/rules/qa.mdc +0 -70
  295. package/.cursor/rules/sm.mdc +0 -73
  296. package/.cursor/rules/ux-expert.mdc +0 -84
  297. package/.github/workflows/release.yml +0 -59
  298. package/.releaserc.json +0 -18
  299. package/.roo/.roomodes +0 -95
  300. package/.roo/README.md +0 -38
  301. package/.vscode/extensions.json +0 -6
  302. package/.windsurf/rules/analyst.md +0 -71
  303. package/.windsurf/rules/architect.md +0 -78
  304. package/.windsurf/rules/bmad-master.md +0 -116
  305. package/.windsurf/rules/bmad-orchestrator.md +0 -93
  306. package/.windsurf/rules/dev.md +0 -82
  307. package/.windsurf/rules/pm.md +0 -71
  308. package/.windsurf/rules/po.md +0 -72
  309. package/.windsurf/rules/qa.md +0 -64
  310. package/.windsurf/rules/sm.md +0 -67
  311. package/.windsurf/rules/ux-expert.md +0 -78
  312. package/docs/bmad-workflow-guide.md +0 -161
  313. package/docs/claude-code-guide.md +0 -119
  314. package/docs/cursor-guide.md +0 -127
  315. package/docs/roo-code-guide.md +0 -140
  316. package/docs/sample-output/simple-fullstack-greenfield/prd.md +0 -42
  317. package/docs/windsurf-guide.md +0 -127
  318. package/expansion-packs/infrastructure-devops/manifest.yml +0 -38
  319. package/expansion-packs/infrastructure-devops/templates/infrastructure-architecture-tmpl.md +0 -415
  320. package/expansion-packs/infrastructure-devops/templates/infrastructure-platform-from-arch-tmpl.md +0 -0
  321. package/tools/installer/config/install.config.yml +0 -139
  322. package/tools/installer/package-lock.json +0 -906
  323. package/tools/installer/templates/claude-commands.md +0 -7
  324. package/tools/installer/templates/cursor-rules.md +0 -22
  325. package/tools/installer/templates/windsurf-rules.md +0 -22
  326. package/tools/semantic-release-sync-installer.js +0 -31
  327. /package/{.bmad-core → bmad-core}/data/technical-preferences.md +0 -0
  328. /package/{.bmad-core → bmad-core}/tasks/brownfield-create-epic.md +0 -0
  329. /package/{.bmad-core → bmad-core}/tasks/brownfield-create-story.md +0 -0
@@ -1,40 +1,54 @@
1
- const path = require("node:path");
2
- const fileManager = require("./file-manager");
3
- const configLoader = require("./config-loader");
4
- const ideSetup = require("./ide-setup");
5
-
6
- // Dynamic imports for ES modules
7
- let chalk, ora, inquirer;
8
-
9
- // Initialize ES modules
10
- async function initializeModules() {
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
- // Initialize ES modules
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
- // Resolve installation directory
27
- let installDir = path.resolve(config.directory);
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(chalk.yellow(`\nThe directory ${chalk.bold(installDir)} does not exist.`));
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
- if (action === 'cancel') {
61
- console.log(chalk.red('Installation cancelled.'));
62
- process.exit(0);
63
- } else if (action === 'change') {
64
- const { newDirectory } = await inquirer.prompt([
65
- {
66
- type: 'input',
67
- name: 'newDirectory',
68
- message: 'Enter the new directory path:',
69
- validate: (input) => {
70
- if (!input.trim()) {
71
- return 'Please enter a valid directory path';
72
- }
73
- return true;
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
- config.directory = newDirectory;
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 "clean":
133
+ case 'clean': {
99
134
  return await this.performFreshInstall(config, installDir, spinner);
135
+ }
100
136
 
101
- case "v4_existing":
102
- return await this.handleExistingV4Installation(
103
- config,
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 "v3_existing":
110
- return await this.handleV3Installation(
111
- config,
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 "unknown_existing":
118
- return await this.handleUnknownInstallation(
119
- config,
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
- spinner.fail("Installation failed");
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: "clean",
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, ".bmad-core");
150
- const manifestPath = path.join(bmadCorePath, "install-manifest.yml");
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 = "v4_existing";
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, "bmad-agent");
189
+ const bmadAgentPath = path.join(installDir, 'bmad-agent');
162
190
  if (await fileManager.pathExists(bmadAgentPath)) {
163
- state.type = "v3_existing";
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 = "unknown_existing";
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 glob = require("glob");
177
- const files = glob.sync("**/*", {
204
+ const files = await resourceLocator.findFiles('**/*', {
178
205
  cwd: installDir,
179
206
  nodir: true,
180
- ignore: ["**/.git/**", "**/node_modules/**"],
207
+ ignore: ['**/.git/**', '**/node_modules/**'],
181
208
  });
182
209
 
183
210
  if (files.length > 0) {
184
- // Directory has other files, but no BMAD installation.
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
- // Ensure modules are initialized
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
- if (config.installType === "full") {
200
- // Full installation - copy entire .bmad-core folder as a subdirectory
201
- spinner.text = "Copying complete .bmad-core folder...";
202
- const sourceDir = configLoader.getBmadCorePath();
203
- const bmadCoreDestDir = path.join(installDir, ".bmad-core");
204
- await fileManager.copyDirectory(sourceDir, bmadCoreDestDir);
205
-
206
- // Get list of all files for manifest
207
- const glob = require("glob");
208
- files = glob
209
- .sync("**/*", {
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: ["**/.git/**", "**/node_modules/**"],
213
- })
214
- .map((file) => path.join(".bmad-core", file));
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
- // Copy dependencies
230
- const dependencies = await configLoader.getAgentDependencies(
231
- config.agent
232
- );
233
- const sourceBase = configLoader.getBmadCorePath();
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
- for (const dep of dependencies) {
236
- spinner.text = `Copying dependency: ${dep}`;
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
- if (dep.includes("*")) {
239
- // Handle glob patterns
240
- const copiedFiles = await fileManager.copyGlobPattern(
241
- dep.replace(".bmad-core/", ""),
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
- if (await fileManager.copyFile(sourcePath, destPath)) {
258
- files.push(dep.replace(".bmad-core/", ""));
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
- await ideSetup.setup(ide, installDir, config.agent);
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
- // Create manifest
274
- spinner.text = "Creating installation manifest...";
275
- await fileManager.createManifest(installDir, config, files);
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("Installation complete!");
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
- console.log(chalk.yellow("\n🔍 Found existing BMAD v4 installation"));
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(` Version: ${state.manifest.version}`);
289
- console.log(
290
- ` Installed: ${new Date(
291
- state.manifest.installed_at
292
- ).toLocaleDateString()}`
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: "list",
298
- name: "action",
299
- message: "What would you like to do?",
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 "update":
529
+ case 'upgrade': {
310
530
  return await this.performUpdate(config, installDir, state.manifest, spinner);
311
- case "reinstall":
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
- case "cancel":
314
- console.log("Installation cancelled.");
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: "list",
332
- name: "action",
333
- message: "What would you like to do?",
598
+ type: 'list',
599
+ name: 'action',
600
+ message: 'What would you like to do?',
334
601
  choices: [
335
- { name: "Upgrade from v3 to v4 (recommended)", value: "upgrade" },
336
- { name: "Install v4 alongside v3", value: "alongside" },
337
- { name: "Cancel", value: "cancel" },
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 "upgrade": {
344
- console.log(chalk.cyan("\n📦 Starting v3 to v4 upgrade process..."));
345
- const V3ToV4Upgrader = require("../../upgraders/v3-to-v4-upgrader");
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({ projectPath: installDir });
614
+ return await upgrader.upgrade({
615
+ projectPath: installDir,
616
+ ides: config.ides || [], // Pass IDE selections from initial config
617
+ });
348
618
  }
349
- case "alongside":
619
+ case 'alongside': {
350
620
  return await this.performFreshInstall(config, installDir, spinner);
351
- case "cancel":
352
- console.log("Installation cancelled.");
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("\n⚠️ Directory contains existing files"));
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(" Found: .bmad-core directory (but no manifest)");
636
+ console.log(' Found: .bmad-core directory (but no manifest)');
367
637
  }
368
638
  if (state.hasOtherFiles) {
369
- console.log(" Found: Other files in directory");
639
+ console.log(' Found: Other files in directory');
370
640
  }
371
641
 
372
642
  const { action } = await inquirer.prompt([
373
643
  {
374
- type: "list",
375
- name: "action",
376
- message: "What would you like to do?",
644
+ type: 'list',
645
+ name: 'action',
646
+ message: 'What would you like to do?',
377
647
  choices: [
378
- { name: "Install anyway (may overwrite files)", value: "force" },
379
- { name: "Choose different directory", value: "different" },
380
- { name: "Cancel", value: "cancel" },
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 "force":
656
+ case 'force': {
387
657
  return await this.performFreshInstall(config, installDir, spinner);
388
- case "different": {
658
+ }
659
+ case 'different': {
389
660
  const { newDir } = await inquirer.prompt([
390
661
  {
391
- type: "input",
392
- name: "newDir",
393
- message: "Enter new installation directory:",
394
- default: path.join(path.dirname(installDir), "bmad-project"),
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 "cancel":
401
- console.log("Installation cancelled.");
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("Checking for updates...");
679
+ spinner.start('Checking for updates...');
408
680
 
409
681
  try {
410
- // Check for modified files
411
- spinner.text = "Checking for modified files...";
412
- const modifiedFiles = await fileManager.checkModifiedFiles(
413
- installDir,
414
- manifest
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("Found modified files");
419
- console.log(chalk.yellow("\nThe following files have been modified:"));
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: "list",
427
- name: "action",
428
- message: "How would you like to proceed?",
703
+ type: 'list',
704
+ name: 'action',
705
+ message: 'How would you like to proceed?',
429
706
  choices: [
430
- { name: "Backup and overwrite modified files", value: "backup" },
431
- { name: "Skip modified files", value: "skip" },
432
- { name: "Cancel update", value: "cancel" },
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 === "cancel") {
438
- console.log("Update cancelled.");
714
+ if (action === 'cancel') {
715
+ console.log('Update cancelled.');
439
716
  return;
440
717
  }
441
718
 
442
- if (action === "backup") {
443
- spinner.start("Backing up modified files...");
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 = "Updating files...";
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
- ide: newConfig.ide || manifest.ide_setup, // Use new IDE choice if provided
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("Update failed");
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("Reinstalling BMAD Method...");
846
+ spinner.start('Preparing to reinstall BMad Method...');
472
847
 
473
848
  // Remove existing .bmad-core
474
- const bmadCorePath = path.join(installDir, ".bmad-core");
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
- return await this.performFreshInstall(config, installDir, spinner);
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("\n✓ BMAD Method installed successfully!\n"));
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("No IDE configuration was set up."));
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
- "You can manually configure your IDE using the agent files in:",
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.map(ide => {
510
- const ideConfig = configLoader.getIdeConfiguration(ide);
511
- return ideConfig?.name || ide;
512
- }).join(", ");
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
- console.log(chalk.bold("\n📦 Web Bundles Available:"));
518
- console.log("Self-contained web bundles have been included in your installation:");
519
- console.log(chalk.cyan(` ${installDir}/.bmad-core/web-bundles/`));
520
- console.log("These bundles work independently without this installation and can be");
521
- console.log("shared, moved, or used in other projects as standalone files.");
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 === "single-agent") {
524
- console.log(
525
- chalk.dim(
526
- "\nNeed other agents? Run: npx bmad-method install --agent=<name>"
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.dim("Need everything? Run: npx bmad-method install --full")
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: "full",
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("No BMAD installation found."));
970
+ console.log(chalk.red('No BMad installation found.'));
554
971
  }
555
972
 
556
973
  async listAgents() {
557
- // Initialize ES modules
558
- await initializeModules();
559
- const agents = await configLoader.getAvailableAgents();
974
+ const agents = await resourceLocator.getAvailableAgents();
560
975
 
561
- console.log(chalk.bold("\nAvailable BMAD Agents:\n"));
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
- chalk.dim("\nInstall with: npx bmad-method install --agent=<id>\n")
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("Invalid installation - manifest not found"));
1018
+ console.log(chalk.red('Invalid installation - manifest not found'));
588
1019
  return;
589
1020
  }
590
1021
 
591
- console.log(chalk.bold("\nBMAD Installation Status:\n"));
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.ide_setup) {
606
- console.log(` IDE Setup: ${manifest.ide_setup}`);
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 configLoader.getAvailableAgents();
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, ".bmad-core");
633
- const manifestPath = path.join(bmadDir, "install-manifest.yml");
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 bmadDir;
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()) === ".bmad-core") {
644
- const manifestPath = path.join(process.cwd(), "install-manifest.yml");
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();