bmad-method 4.37.0 → 4.39.0

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