bmad-method 6.0.0-alpha.9 → 6.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (931) hide show
  1. package/.augment/code_review_guidelines.yaml +271 -0
  2. package/.claude/skills/bmad-os-changelog-social/SKILL.md +178 -0
  3. package/.claude/skills/bmad-os-changelog-social/examples/discord-example.md +53 -0
  4. package/.claude/skills/bmad-os-changelog-social/examples/linkedin-example.md +49 -0
  5. package/.claude/skills/bmad-os-changelog-social/examples/twitter-example.md +55 -0
  6. package/.claude/skills/bmad-os-diataxis-style-fix/SKILL.md +7 -0
  7. package/.claude/skills/bmad-os-diataxis-style-fix/prompts/instructions.md +229 -0
  8. package/.claude/skills/bmad-os-draft-changelog/SKILL.md +7 -0
  9. package/.claude/skills/bmad-os-draft-changelog/prompts/instructions.md +82 -0
  10. package/.claude/skills/bmad-os-gh-triage/README.md +14 -0
  11. package/.claude/skills/bmad-os-gh-triage/SKILL.md +12 -0
  12. package/.claude/skills/bmad-os-gh-triage/prompts/agent-prompt.md +60 -0
  13. package/.claude/skills/bmad-os-gh-triage/prompts/instructions.md +74 -0
  14. package/.claude/skills/bmad-os-release-module/README.md +24 -0
  15. package/.claude/skills/bmad-os-release-module/SKILL.md +7 -0
  16. package/.claude/skills/bmad-os-release-module/prompts/instructions.md +53 -0
  17. package/.coderabbit.yaml +85 -0
  18. package/.github/CODE_OF_CONDUCT.md +128 -0
  19. package/.github/ISSUE_TEMPLATE/bug-report.yaml +124 -0
  20. package/.github/ISSUE_TEMPLATE/config.yaml +5 -2
  21. package/.github/ISSUE_TEMPLATE/documentation.yaml +55 -0
  22. package/.github/ISSUE_TEMPLATE/feature-request.md +22 -0
  23. package/.github/ISSUE_TEMPLATE/issue.md +32 -0
  24. package/.github/PULL_REQUEST_TEMPLATE.md +13 -0
  25. package/.github/scripts/discord-helpers.sh +34 -0
  26. package/.github/workflows/coderabbit-review.yaml +22 -0
  27. package/.github/workflows/discord.yaml +82 -8
  28. package/.github/workflows/docs.yaml +64 -0
  29. package/.github/workflows/quality.yaml +40 -2
  30. package/.husky/pre-commit +13 -0
  31. package/.markdownlint-cli2.yaml +41 -0
  32. package/.prettierignore +7 -0
  33. package/.vscode/settings.json +4 -3
  34. package/CHANGELOG.md +1074 -674
  35. package/CNAME +1 -0
  36. package/CONTRIBUTING.md +97 -189
  37. package/CONTRIBUTORS.md +32 -0
  38. package/LICENSE +7 -3
  39. package/README.md +51 -396
  40. package/SECURITY.md +85 -0
  41. package/TRADEMARK.md +55 -0
  42. package/Wordmark.png +0 -0
  43. package/banner-bmad-method.png +0 -0
  44. package/docs/404.md +9 -0
  45. package/docs/_STYLE_GUIDE.md +370 -0
  46. package/docs/explanation/advanced-elicitation.md +49 -0
  47. package/docs/explanation/adversarial-review.md +59 -0
  48. package/docs/explanation/brainstorming.md +33 -0
  49. package/docs/explanation/established-projects-faq.md +50 -0
  50. package/docs/explanation/party-mode.md +59 -0
  51. package/docs/explanation/preventing-agent-conflicts.md +112 -0
  52. package/docs/explanation/project-context.md +157 -0
  53. package/docs/explanation/quick-flow.md +73 -0
  54. package/docs/explanation/why-solutioning-matters.md +77 -0
  55. package/docs/how-to/customize-bmad.md +172 -0
  56. package/docs/how-to/established-projects.md +105 -0
  57. package/docs/how-to/get-answers-about-bmad.md +103 -0
  58. package/docs/how-to/install-bmad.md +88 -0
  59. package/docs/how-to/non-interactive-installation.md +171 -0
  60. package/docs/how-to/project-context.md +136 -0
  61. package/docs/how-to/quick-fixes.md +123 -0
  62. package/docs/how-to/shard-large-documents.md +78 -0
  63. package/docs/how-to/upgrade-to-v6.md +97 -0
  64. package/docs/index.md +33 -206
  65. package/docs/reference/agents.md +28 -0
  66. package/docs/reference/commands.md +131 -0
  67. package/docs/reference/modules.md +76 -0
  68. package/docs/reference/testing.md +106 -0
  69. package/docs/reference/workflow-map.md +122 -0
  70. package/docs/tutorials/getting-started.md +219 -0
  71. package/eslint.config.mjs +26 -16
  72. package/package.json +35 -28
  73. package/src/bmm/agents/analyst.agent.yaml +43 -0
  74. package/src/bmm/agents/architect.agent.yaml +29 -0
  75. package/src/bmm/agents/dev.agent.yaml +38 -0
  76. package/src/bmm/agents/pm.agent.yaml +44 -0
  77. package/src/bmm/agents/qa.agent.yaml +58 -0
  78. package/src/bmm/agents/quick-flow-solo-dev.agent.yaml +32 -0
  79. package/src/bmm/agents/sm.agent.yaml +37 -0
  80. package/src/bmm/agents/tech-writer/tech-writer-sidecar/documentation-standards.md +224 -0
  81. package/src/bmm/agents/tech-writer/tech-writer.agent.yaml +46 -0
  82. package/src/bmm/agents/ux-designer.agent.yaml +27 -0
  83. package/src/bmm/data/project-context-template.md +26 -0
  84. package/src/bmm/module-help.csv +31 -0
  85. package/src/bmm/module.yaml +50 -0
  86. package/src/bmm/teams/default-party.csv +20 -0
  87. package/src/bmm/workflows/1-analysis/create-product-brief/product-brief.template.md +10 -0
  88. package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-01-init.md +177 -0
  89. package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-01b-continue.md +161 -0
  90. package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-02-vision.md +199 -0
  91. package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-03-users.md +202 -0
  92. package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-04-metrics.md +205 -0
  93. package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-05-scope.md +219 -0
  94. package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-06-complete.md +162 -0
  95. package/src/bmm/workflows/1-analysis/create-product-brief/workflow.md +57 -0
  96. package/src/bmm/workflows/1-analysis/research/domain-steps/step-01-init.md +137 -0
  97. package/src/bmm/workflows/1-analysis/research/domain-steps/step-02-domain-analysis.md +229 -0
  98. package/src/bmm/workflows/1-analysis/research/domain-steps/step-03-competitive-landscape.md +238 -0
  99. package/src/bmm/workflows/1-analysis/research/domain-steps/step-04-regulatory-focus.md +206 -0
  100. package/src/bmm/workflows/1-analysis/research/domain-steps/step-05-technical-trends.md +234 -0
  101. package/src/bmm/workflows/1-analysis/research/domain-steps/step-06-research-synthesis.md +443 -0
  102. package/src/bmm/workflows/1-analysis/research/market-steps/step-01-init.md +182 -0
  103. package/src/bmm/workflows/1-analysis/research/market-steps/step-02-customer-behavior.md +237 -0
  104. package/src/bmm/workflows/1-analysis/research/market-steps/step-03-customer-pain-points.md +249 -0
  105. package/src/bmm/workflows/1-analysis/research/market-steps/step-04-customer-decisions.md +259 -0
  106. package/src/bmm/workflows/1-analysis/research/market-steps/step-05-competitive-analysis.md +177 -0
  107. package/src/bmm/workflows/1-analysis/research/market-steps/step-06-research-completion.md +475 -0
  108. package/src/bmm/workflows/1-analysis/research/research.template.md +29 -0
  109. package/src/bmm/workflows/1-analysis/research/technical-steps/step-01-init.md +137 -0
  110. package/src/bmm/workflows/1-analysis/research/technical-steps/step-02-technical-overview.md +239 -0
  111. package/src/bmm/workflows/1-analysis/research/technical-steps/step-03-integration-patterns.md +248 -0
  112. package/src/bmm/workflows/1-analysis/research/technical-steps/step-04-architectural-patterns.md +202 -0
  113. package/src/bmm/workflows/1-analysis/research/technical-steps/step-05-implementation-research.md +233 -0
  114. package/src/bmm/workflows/1-analysis/research/technical-steps/step-06-research-synthesis.md +486 -0
  115. package/src/bmm/workflows/1-analysis/research/workflow-domain-research.md +54 -0
  116. package/src/bmm/workflows/1-analysis/research/workflow-market-research.md +54 -0
  117. package/src/bmm/workflows/1-analysis/research/workflow-technical-research.md +54 -0
  118. package/src/bmm/workflows/2-plan-workflows/create-prd/data/domain-complexity.csv +15 -0
  119. package/src/bmm/workflows/2-plan-workflows/create-prd/data/prd-purpose.md +197 -0
  120. package/src/bmm/workflows/2-plan-workflows/create-prd/data/project-types.csv +11 -0
  121. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-01-init.md +191 -0
  122. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-01b-continue.md +153 -0
  123. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-02-discovery.md +224 -0
  124. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-02b-vision.md +154 -0
  125. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-02c-executive-summary.md +170 -0
  126. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-03-success.md +226 -0
  127. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-04-journeys.md +213 -0
  128. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-05-domain.md +207 -0
  129. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-06-innovation.md +226 -0
  130. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-07-project-type.md +237 -0
  131. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-08-scoping.md +228 -0
  132. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-09-functional.md +231 -0
  133. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-10-nonfunctional.md +242 -0
  134. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-11-polish.md +217 -0
  135. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-12-complete.md +124 -0
  136. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-e/step-e-01-discovery.md +247 -0
  137. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-e/step-e-01b-legacy-conversion.md +208 -0
  138. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-e/step-e-02-review.md +249 -0
  139. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-e/step-e-03-edit.md +253 -0
  140. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-e/step-e-04-complete.md +168 -0
  141. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-01-discovery.md +226 -0
  142. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-02-format-detection.md +191 -0
  143. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-02b-parity-check.md +209 -0
  144. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-03-density-validation.md +174 -0
  145. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-04-brief-coverage-validation.md +214 -0
  146. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-05-measurability-validation.md +228 -0
  147. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-06-traceability-validation.md +217 -0
  148. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-07-implementation-leakage-validation.md +205 -0
  149. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-08-domain-compliance-validation.md +243 -0
  150. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-09-project-type-validation.md +263 -0
  151. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-10-smart-validation.md +209 -0
  152. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-11-holistic-quality-validation.md +264 -0
  153. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-12-completeness-validation.md +242 -0
  154. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-13-report-complete.md +231 -0
  155. package/src/bmm/workflows/2-plan-workflows/create-prd/templates/prd-template.md +10 -0
  156. package/src/bmm/workflows/2-plan-workflows/create-prd/workflow-create-prd.md +63 -0
  157. package/src/bmm/workflows/2-plan-workflows/create-prd/workflow-edit-prd.md +65 -0
  158. package/src/bmm/workflows/2-plan-workflows/create-prd/workflow-validate-prd.md +63 -0
  159. package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-01-init.md +135 -0
  160. package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-01b-continue.md +127 -0
  161. package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-02-discovery.md +190 -0
  162. package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-03-core-experience.md +216 -0
  163. package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-04-emotional-response.md +219 -0
  164. package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-05-inspiration.md +234 -0
  165. package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-06-design-system.md +252 -0
  166. package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-07-defining-experience.md +254 -0
  167. package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-08-visual-foundation.md +224 -0
  168. package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-09-design-directions.md +224 -0
  169. package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-10-user-journeys.md +241 -0
  170. package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-11-component-strategy.md +248 -0
  171. package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-12-ux-patterns.md +237 -0
  172. package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-13-responsive-accessibility.md +264 -0
  173. package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-14-complete.md +171 -0
  174. package/src/bmm/workflows/2-plan-workflows/create-ux-design/ux-design-template.md +13 -0
  175. package/src/bmm/workflows/2-plan-workflows/create-ux-design/workflow.md +42 -0
  176. package/src/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-01-document-discovery.md +184 -0
  177. package/src/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-02-prd-analysis.md +172 -0
  178. package/src/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-03-epic-coverage-validation.md +173 -0
  179. package/src/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-04-ux-alignment.md +133 -0
  180. package/src/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-05-epic-quality-review.md +245 -0
  181. package/src/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-06-final-assessment.md +129 -0
  182. package/src/bmm/workflows/3-solutioning/check-implementation-readiness/templates/readiness-report-template.md +4 -0
  183. package/src/bmm/workflows/3-solutioning/check-implementation-readiness/workflow.md +54 -0
  184. package/src/bmm/workflows/3-solutioning/create-architecture/architecture-decision-template.md +12 -0
  185. package/src/bmm/workflows/3-solutioning/create-architecture/data/domain-complexity.csv +13 -0
  186. package/src/bmm/workflows/3-solutioning/create-architecture/data/project-types.csv +7 -0
  187. package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-01-init.md +153 -0
  188. package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-01b-continue.md +164 -0
  189. package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-02-context.md +224 -0
  190. package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-03-starter.md +331 -0
  191. package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-04-decisions.md +318 -0
  192. package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-05-patterns.md +359 -0
  193. package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-06-structure.md +379 -0
  194. package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-07-validation.md +359 -0
  195. package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-08-complete.md +76 -0
  196. package/src/bmm/workflows/3-solutioning/create-architecture/workflow.md +49 -0
  197. package/src/bmm/workflows/3-solutioning/create-epics-and-stories/steps/step-01-validate-prerequisites.md +259 -0
  198. package/src/bmm/workflows/3-solutioning/create-epics-and-stories/steps/step-02-design-epics.md +233 -0
  199. package/src/bmm/workflows/3-solutioning/create-epics-and-stories/steps/step-03-create-stories.md +272 -0
  200. package/src/bmm/workflows/3-solutioning/create-epics-and-stories/steps/step-04-final-validation.md +149 -0
  201. package/src/bmm/workflows/3-solutioning/create-epics-and-stories/templates/epics-template.md +57 -0
  202. package/src/bmm/workflows/3-solutioning/create-epics-and-stories/workflow.md +58 -0
  203. package/src/bmm/workflows/4-implementation/code-review/checklist.md +23 -0
  204. package/src/bmm/workflows/4-implementation/code-review/instructions.xml +227 -0
  205. package/src/bmm/workflows/4-implementation/code-review/workflow.yaml +44 -0
  206. package/src/bmm/workflows/4-implementation/correct-course/checklist.md +288 -0
  207. package/src/bmm/workflows/4-implementation/correct-course/instructions.md +207 -0
  208. package/src/bmm/workflows/4-implementation/correct-course/workflow.yaml +54 -0
  209. package/src/bmm/workflows/4-implementation/create-story/checklist.md +358 -0
  210. package/src/bmm/workflows/4-implementation/create-story/instructions.xml +346 -0
  211. package/src/bmm/workflows/4-implementation/create-story/template.md +49 -0
  212. package/src/bmm/workflows/4-implementation/create-story/workflow.yaml +53 -0
  213. package/src/bmm/workflows/4-implementation/dev-story/checklist.md +80 -0
  214. package/src/bmm/workflows/4-implementation/dev-story/instructions.xml +410 -0
  215. package/src/bmm/workflows/4-implementation/dev-story/workflow.yaml +21 -0
  216. package/src/bmm/workflows/4-implementation/retrospective/instructions.md +1444 -0
  217. package/src/bmm/workflows/4-implementation/retrospective/workflow.yaml +53 -0
  218. package/src/bmm/workflows/4-implementation/sprint-planning/instructions.md +226 -0
  219. package/src/bmm/workflows/4-implementation/sprint-planning/sprint-status-template.yaml +55 -0
  220. package/src/bmm/workflows/4-implementation/sprint-planning/workflow.yaml +47 -0
  221. package/src/bmm/workflows/4-implementation/sprint-status/instructions.md +230 -0
  222. package/src/bmm/workflows/4-implementation/sprint-status/workflow.yaml +25 -0
  223. package/src/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-01-mode-detection.md +174 -0
  224. package/src/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-02-context-gathering.md +118 -0
  225. package/src/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-03-execute.md +111 -0
  226. package/src/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-04-self-check.md +111 -0
  227. package/src/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-05-adversarial-review.md +104 -0
  228. package/src/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-06-resolve-findings.md +146 -0
  229. package/src/bmm/workflows/bmad-quick-flow/quick-dev/workflow.md +50 -0
  230. package/src/bmm/workflows/bmad-quick-flow/quick-spec/steps/step-01-understand.md +191 -0
  231. package/src/bmm/workflows/bmad-quick-flow/quick-spec/steps/step-02-investigate.md +144 -0
  232. package/src/bmm/workflows/bmad-quick-flow/quick-spec/steps/step-03-generate.md +127 -0
  233. package/src/bmm/workflows/bmad-quick-flow/quick-spec/steps/step-04-review.md +200 -0
  234. package/src/bmm/workflows/bmad-quick-flow/quick-spec/tech-spec-template.md +74 -0
  235. package/src/bmm/workflows/bmad-quick-flow/quick-spec/workflow.md +79 -0
  236. package/src/bmm/workflows/document-project/checklist.md +245 -0
  237. package/src/bmm/workflows/document-project/instructions.md +130 -0
  238. package/src/bmm/workflows/document-project/templates/project-scan-report-schema.json +160 -0
  239. package/src/bmm/workflows/document-project/workflow.yaml +22 -0
  240. package/src/bmm/workflows/document-project/workflows/deep-dive-instructions.md +298 -0
  241. package/src/bmm/workflows/document-project/workflows/deep-dive.yaml +31 -0
  242. package/src/bmm/workflows/document-project/workflows/full-scan-instructions.md +1106 -0
  243. package/src/bmm/workflows/document-project/workflows/full-scan.yaml +31 -0
  244. package/src/bmm/workflows/generate-project-context/project-context-template.md +21 -0
  245. package/src/bmm/workflows/generate-project-context/steps/step-01-discover.md +184 -0
  246. package/src/bmm/workflows/generate-project-context/steps/step-02-generate.md +318 -0
  247. package/src/bmm/workflows/generate-project-context/steps/step-03-complete.md +278 -0
  248. package/src/bmm/workflows/generate-project-context/workflow.md +49 -0
  249. package/src/bmm/workflows/qa/automate/checklist.md +33 -0
  250. package/src/bmm/workflows/qa/automate/instructions.md +110 -0
  251. package/src/bmm/workflows/qa/automate/workflow.yaml +44 -0
  252. package/src/core/agents/bmad-master.agent.yaml +12 -21
  253. package/src/core/module-help.csv +9 -0
  254. package/src/core/module.yaml +25 -0
  255. package/src/core/tasks/editorial-review-prose.xml +102 -0
  256. package/src/core/tasks/editorial-review-structure.xml +209 -0
  257. package/src/core/tasks/help.md +85 -0
  258. package/src/core/tasks/index-docs.xml +2 -2
  259. package/src/core/tasks/review-adversarial-general.xml +48 -0
  260. package/src/core/tasks/shard-doc.xml +108 -0
  261. package/src/core/tasks/workflow.xml +39 -74
  262. package/src/core/workflows/advanced-elicitation/methods.csv +51 -0
  263. package/src/core/workflows/advanced-elicitation/workflow.xml +117 -0
  264. package/src/core/workflows/brainstorming/brain-methods.csv +62 -36
  265. package/src/core/workflows/brainstorming/steps/step-01-session-setup.md +197 -0
  266. package/src/core/workflows/brainstorming/steps/step-01b-continue.md +122 -0
  267. package/src/core/workflows/brainstorming/steps/step-02a-user-selected.md +225 -0
  268. package/src/core/workflows/brainstorming/steps/step-02b-ai-recommended.md +237 -0
  269. package/src/core/workflows/brainstorming/steps/step-02c-random-selection.md +209 -0
  270. package/src/core/workflows/brainstorming/steps/step-02d-progressive-flow.md +264 -0
  271. package/src/core/workflows/brainstorming/steps/step-03-technique-execution.md +399 -0
  272. package/src/core/workflows/brainstorming/steps/step-04-idea-organization.md +303 -0
  273. package/src/core/workflows/brainstorming/template.md +13 -104
  274. package/src/core/workflows/brainstorming/workflow.md +58 -0
  275. package/src/core/workflows/party-mode/steps/step-01-agent-loading.md +138 -0
  276. package/src/core/workflows/party-mode/steps/step-02-discussion-orchestration.md +187 -0
  277. package/src/core/workflows/party-mode/steps/step-03-graceful-exit.md +168 -0
  278. package/src/core/workflows/party-mode/workflow.md +194 -0
  279. package/src/utility/agent-components/activation-rules.txt +6 -0
  280. package/src/utility/agent-components/activation-steps.txt +14 -0
  281. package/src/utility/agent-components/agent-command-header.md +1 -0
  282. package/src/utility/agent-components/agent.customize.template.yaml +41 -0
  283. package/src/utility/agent-components/handler-action.txt +4 -0
  284. package/src/utility/agent-components/handler-exec.txt +6 -0
  285. package/src/utility/agent-components/handler-multi.txt +14 -0
  286. package/src/utility/agent-components/handler-tmpl.txt +5 -0
  287. package/src/utility/agent-components/handler-validate-workflow.txt +7 -0
  288. package/src/utility/agent-components/handler-workflow.txt +10 -0
  289. package/src/utility/agent-components/menu-handlers.txt +6 -0
  290. package/test/README.md +4 -4
  291. package/test/adversarial-review-tests/README.md +56 -0
  292. package/test/adversarial-review-tests/sample-content.md +46 -0
  293. package/test/adversarial-review-tests/test-cases.yaml +103 -0
  294. package/test/fixtures/agent-schema/invalid/critical-actions/actions-as-string.agent.yaml +1 -0
  295. package/test/fixtures/agent-schema/invalid/critical-actions/empty-string-in-actions.agent.yaml +1 -0
  296. package/test/fixtures/agent-schema/invalid/menu/empty-menu.agent.yaml +1 -0
  297. package/test/fixtures/agent-schema/invalid/menu/missing-menu.agent.yaml +1 -0
  298. package/test/fixtures/agent-schema/invalid/menu-commands/empty-command-target.agent.yaml +1 -0
  299. package/test/fixtures/agent-schema/invalid/menu-commands/no-command-target.agent.yaml +1 -0
  300. package/test/fixtures/agent-schema/invalid/menu-triggers/camel-case.agent.yaml +1 -0
  301. package/test/fixtures/agent-schema/invalid/menu-triggers/compound-invalid-format.agent.yaml +25 -0
  302. package/test/fixtures/agent-schema/invalid/menu-triggers/compound-mismatched-kebab.agent.yaml +25 -0
  303. package/test/fixtures/agent-schema/invalid/menu-triggers/duplicate-triggers.agent.yaml +1 -0
  304. package/test/fixtures/agent-schema/invalid/menu-triggers/empty-trigger.agent.yaml +1 -0
  305. package/test/fixtures/agent-schema/invalid/menu-triggers/leading-asterisk.agent.yaml +1 -0
  306. package/test/fixtures/agent-schema/invalid/menu-triggers/snake-case.agent.yaml +1 -0
  307. package/test/fixtures/agent-schema/invalid/menu-triggers/trigger-with-spaces.agent.yaml +1 -0
  308. package/test/fixtures/agent-schema/invalid/metadata/empty-module-string.agent.yaml +1 -1
  309. package/test/fixtures/agent-schema/invalid/metadata/extra-metadata-fields.agent.yaml +1 -0
  310. package/test/fixtures/agent-schema/invalid/persona/empty-principles-array.agent.yaml +1 -0
  311. package/test/fixtures/agent-schema/invalid/persona/empty-string-in-principles.agent.yaml +1 -0
  312. package/test/fixtures/agent-schema/invalid/persona/extra-persona-fields.agent.yaml +1 -0
  313. package/test/fixtures/agent-schema/invalid/persona/missing-role.agent.yaml +1 -0
  314. package/test/fixtures/agent-schema/invalid/prompts/empty-content.agent.yaml +1 -0
  315. package/test/fixtures/agent-schema/invalid/prompts/extra-prompt-fields.agent.yaml +1 -0
  316. package/test/fixtures/agent-schema/invalid/prompts/missing-content.agent.yaml +1 -0
  317. package/test/fixtures/agent-schema/invalid/prompts/missing-id.agent.yaml +1 -0
  318. package/test/fixtures/agent-schema/invalid/top-level/extra-top-level-keys.agent.yaml +1 -0
  319. package/test/fixtures/agent-schema/valid/critical-actions/empty-critical-actions.agent.yaml +1 -0
  320. package/test/fixtures/agent-schema/valid/critical-actions/no-critical-actions.agent.yaml +1 -0
  321. package/test/fixtures/agent-schema/valid/critical-actions/valid-critical-actions.agent.yaml +1 -0
  322. package/test/fixtures/agent-schema/valid/menu/multiple-menu-items.agent.yaml +1 -0
  323. package/test/fixtures/agent-schema/valid/menu/single-menu-item.agent.yaml +1 -0
  324. package/test/fixtures/agent-schema/valid/menu-commands/all-command-types.agent.yaml +2 -3
  325. package/test/fixtures/agent-schema/valid/menu-commands/multiple-commands.agent.yaml +1 -0
  326. package/test/fixtures/agent-schema/valid/menu-triggers/compound-triggers.agent.yaml +31 -0
  327. package/test/fixtures/agent-schema/valid/menu-triggers/kebab-case-triggers.agent.yaml +2 -1
  328. package/test/fixtures/agent-schema/valid/metadata/core-agent-with-module.agent.yaml +24 -0
  329. package/test/fixtures/agent-schema/valid/metadata/empty-module-name-in-path.agent.yaml +1 -0
  330. package/test/fixtures/agent-schema/valid/metadata/malformed-path-treated-as-core.agent.yaml +2 -1
  331. package/test/fixtures/agent-schema/valid/metadata/module-agent-correct.agent.yaml +2 -1
  332. package/test/fixtures/agent-schema/valid/metadata/module-agent-missing-module.agent.yaml +23 -0
  333. package/test/fixtures/agent-schema/valid/metadata/wrong-module-value.agent.yaml +24 -0
  334. package/test/fixtures/agent-schema/valid/persona/complete-persona.agent.yaml +1 -0
  335. package/test/fixtures/agent-schema/valid/prompts/empty-prompts.agent.yaml +1 -0
  336. package/test/fixtures/agent-schema/valid/prompts/no-prompts.agent.yaml +1 -0
  337. package/test/fixtures/agent-schema/valid/prompts/valid-prompts-minimal.agent.yaml +1 -0
  338. package/test/fixtures/agent-schema/valid/prompts/valid-prompts-with-description.agent.yaml +1 -0
  339. package/test/fixtures/agent-schema/valid/top-level/minimal-core-agent.agent.yaml +1 -0
  340. package/test/fixtures/file-refs-csv/invalid/all-empty-workflow.csv +3 -0
  341. package/test/fixtures/file-refs-csv/invalid/empty-data.csv +1 -0
  342. package/test/fixtures/file-refs-csv/invalid/no-workflow-column.csv +3 -0
  343. package/test/fixtures/file-refs-csv/invalid/unresolvable-vars.csv +3 -0
  344. package/test/fixtures/file-refs-csv/valid/bmm-style.csv +3 -0
  345. package/test/fixtures/file-refs-csv/valid/core-style.csv +3 -0
  346. package/test/fixtures/file-refs-csv/valid/minimal.csv +2 -0
  347. package/test/test-agent-schema.js +4 -4
  348. package/test/test-file-refs-csv.js +133 -0
  349. package/test/test-installation-components.js +10 -12
  350. package/test/test-rehype-plugins.mjs +1050 -0
  351. package/test/unit-test-schema.js +2 -2
  352. package/tools/build-docs.mjs +463 -0
  353. package/tools/cli/README.md +41 -589
  354. package/tools/cli/bmad-cli.js +67 -1
  355. package/tools/cli/commands/install.js +42 -34
  356. package/tools/cli/commands/status.js +44 -26
  357. package/tools/cli/commands/uninstall.js +146 -23
  358. package/tools/cli/external-official-modules.yaml +53 -0
  359. package/tools/cli/installers/install-messages.yaml +39 -0
  360. package/tools/cli/installers/lib/core/config-collector.js +589 -95
  361. package/tools/cli/installers/lib/core/custom-module-cache.js +260 -0
  362. package/tools/cli/installers/lib/core/dependency-resolver.js +57 -39
  363. package/tools/cli/installers/lib/core/detector.js +19 -125
  364. package/tools/cli/installers/lib/core/ide-config-manager.js +12 -9
  365. package/tools/cli/installers/lib/core/installer.js +2075 -1161
  366. package/tools/cli/installers/lib/core/manifest-generator.js +494 -103
  367. package/tools/cli/installers/lib/core/manifest.js +544 -46
  368. package/tools/cli/installers/lib/custom/handler.js +358 -0
  369. package/tools/cli/installers/lib/ide/_base-ide.js +83 -44
  370. package/tools/cli/installers/lib/ide/_config-driven.js +560 -0
  371. package/tools/cli/installers/lib/ide/codex.js +277 -54
  372. package/tools/cli/installers/lib/ide/github-copilot.js +607 -209
  373. package/tools/cli/installers/lib/ide/kilo.js +169 -75
  374. package/tools/cli/installers/lib/ide/manager.js +179 -48
  375. package/tools/cli/installers/lib/ide/platform-codes.js +100 -0
  376. package/tools/cli/installers/lib/ide/platform-codes.yaml +227 -0
  377. package/tools/cli/installers/lib/ide/shared/agent-command-generator.js +95 -5
  378. package/tools/cli/installers/lib/ide/shared/bmad-artifacts.js +51 -20
  379. package/tools/cli/installers/lib/ide/shared/module-injections.js +6 -3
  380. package/tools/cli/installers/lib/ide/shared/path-utils.js +299 -0
  381. package/tools/cli/installers/lib/ide/shared/task-tool-command-generator.js +262 -14
  382. package/tools/cli/installers/lib/ide/shared/workflow-command-generator.js +108 -27
  383. package/tools/cli/installers/lib/ide/templates/agent-command-template.md +2 -1
  384. package/tools/cli/installers/lib/ide/templates/combined/antigravity.md +8 -0
  385. package/tools/cli/installers/lib/ide/templates/combined/default-agent.md +16 -0
  386. package/tools/cli/installers/lib/ide/templates/combined/default-task.md +10 -0
  387. package/tools/cli/installers/lib/ide/templates/combined/default-tool.md +10 -0
  388. package/tools/cli/installers/lib/ide/templates/combined/default-workflow-yaml.md +15 -0
  389. package/tools/cli/installers/lib/ide/templates/combined/default-workflow.md +7 -0
  390. package/tools/cli/installers/lib/ide/templates/combined/gemini-agent.toml +14 -0
  391. package/tools/cli/installers/lib/ide/templates/combined/gemini-task.toml +11 -0
  392. package/tools/cli/installers/lib/ide/templates/combined/gemini-tool.toml +11 -0
  393. package/tools/cli/installers/lib/ide/templates/combined/gemini-workflow-yaml.toml +16 -0
  394. package/tools/cli/installers/lib/ide/templates/combined/gemini-workflow.toml +14 -0
  395. package/tools/cli/installers/lib/ide/templates/combined/kiro-agent.md +16 -0
  396. package/tools/cli/installers/lib/ide/templates/combined/kiro-task.md +9 -0
  397. package/tools/cli/installers/lib/ide/templates/combined/kiro-tool.md +9 -0
  398. package/tools/cli/installers/lib/ide/templates/combined/kiro-workflow-yaml.md +15 -0
  399. package/tools/cli/installers/lib/ide/templates/combined/kiro-workflow.md +7 -0
  400. package/tools/cli/installers/lib/ide/templates/combined/opencode-agent.md +15 -0
  401. package/tools/cli/installers/lib/ide/templates/combined/opencode-task.md +14 -0
  402. package/tools/cli/installers/lib/ide/templates/combined/opencode-tool.md +14 -0
  403. package/tools/cli/installers/lib/ide/templates/combined/opencode-workflow-yaml.md +17 -0
  404. package/tools/cli/installers/lib/ide/templates/combined/opencode-workflow.md +17 -0
  405. package/tools/cli/installers/lib/ide/templates/combined/rovodev.md +9 -0
  406. package/tools/cli/installers/lib/ide/templates/combined/trae.md +9 -0
  407. package/tools/cli/installers/lib/ide/templates/combined/windsurf-workflow.md +10 -0
  408. package/tools/cli/installers/lib/ide/templates/workflow-command-template.md +2 -1
  409. package/tools/cli/installers/lib/ide/templates/workflow-commander.md +6 -0
  410. package/tools/cli/installers/lib/message-loader.js +83 -0
  411. package/tools/cli/installers/lib/modules/external-manager.js +136 -0
  412. package/tools/cli/installers/lib/modules/manager.js +931 -207
  413. package/tools/cli/lib/activation-builder.js +13 -16
  414. package/tools/cli/lib/agent/compiler.js +525 -0
  415. package/tools/cli/lib/agent/installer.js +680 -0
  416. package/tools/cli/lib/agent/template-engine.js +152 -0
  417. package/tools/cli/lib/agent-analyzer.js +48 -20
  418. package/tools/cli/lib/agent-party-generator.js +5 -17
  419. package/tools/cli/lib/cli-utils.js +76 -104
  420. package/tools/cli/lib/config.js +4 -3
  421. package/tools/cli/lib/platform-codes.js +2 -2
  422. package/tools/cli/lib/project-root.js +6 -0
  423. package/tools/cli/lib/prompts.js +809 -0
  424. package/tools/cli/lib/ui.js +1600 -272
  425. package/tools/cli/lib/xml-handler.js +4 -55
  426. package/tools/cli/lib/yaml-format.js +4 -6
  427. package/tools/cli/lib/yaml-xml-builder.js +183 -61
  428. package/tools/docs/_prompt-external-modules-page.md +59 -0
  429. package/tools/docs/fix-refs.md +91 -0
  430. package/tools/fix-doc-links.js +285 -0
  431. package/tools/lib/xml-utils.js +13 -0
  432. package/tools/maintainer/review-pr-README.md +55 -0
  433. package/tools/maintainer/review-pr.md +242 -0
  434. package/tools/migrate-custom-module-paths.js +124 -0
  435. package/tools/platform-codes.yaml +24 -0
  436. package/tools/schema/agent.js +306 -53
  437. package/tools/validate-agent-schema.js +3 -3
  438. package/tools/validate-doc-links.js +393 -0
  439. package/tools/validate-file-refs.js +554 -0
  440. package/tools/validate-svg-changes.sh +356 -0
  441. package/website/README.md +75 -0
  442. package/website/astro.config.mjs +135 -0
  443. package/website/public/favicon.ico +0 -0
  444. package/website/public/img/bmad-dark.png +0 -0
  445. package/website/public/img/bmad-light.png +0 -0
  446. package/website/public/workflow-map-diagram.html +361 -0
  447. package/website/src/components/Banner.astro +62 -0
  448. package/website/src/components/Header.astro +96 -0
  449. package/website/src/components/MobileMenuFooter.astro +33 -0
  450. package/website/src/content/config.ts +6 -0
  451. package/website/src/lib/site-url.mjs +25 -0
  452. package/website/src/pages/404.astro +11 -0
  453. package/website/src/pages/robots.txt.ts +48 -0
  454. package/website/src/rehype-base-paths.js +112 -0
  455. package/website/src/rehype-markdown-links.js +111 -0
  456. package/website/src/styles/custom.css +509 -0
  457. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -32
  458. package/.github/ISSUE_TEMPLATE/idea_submission.md +0 -109
  459. package/.github/workflows/bundle-latest.yaml +0 -277
  460. package/.github/workflows/manual-release.yaml +0 -212
  461. package/docs/BUNDLE_DISTRIBUTION_SETUP.md +0 -95
  462. package/docs/agent-customization-guide.md +0 -208
  463. package/docs/document-sharding-guide.md +0 -447
  464. package/docs/ide-info/auggie.md +0 -31
  465. package/docs/ide-info/claude-code.md +0 -25
  466. package/docs/ide-info/cline.md +0 -31
  467. package/docs/ide-info/codex.md +0 -21
  468. package/docs/ide-info/crush.md +0 -30
  469. package/docs/ide-info/cursor.md +0 -25
  470. package/docs/ide-info/gemini.md +0 -25
  471. package/docs/ide-info/github-copilot.md +0 -26
  472. package/docs/ide-info/iflow.md +0 -33
  473. package/docs/ide-info/kilo.md +0 -24
  474. package/docs/ide-info/opencode.md +0 -24
  475. package/docs/ide-info/qwen.md +0 -25
  476. package/docs/ide-info/roo.md +0 -27
  477. package/docs/ide-info/trae.md +0 -25
  478. package/docs/ide-info/windsurf.md +0 -22
  479. package/docs/installers-bundlers/ide-injections.md +0 -186
  480. package/docs/installers-bundlers/installers-modules-platforms-reference.md +0 -388
  481. package/docs/installers-bundlers/web-bundler-usage.md +0 -54
  482. package/docs/v4-to-v6-upgrade.md +0 -227
  483. package/docs/web-bundles-gemini-gpt-guide.md +0 -473
  484. package/src/core/_module-installer/install-config.yaml +0 -35
  485. package/src/core/_module-installer/installer.js +0 -60
  486. package/src/core/agents/bmad-web-orchestrator.agent.xml +0 -113
  487. package/src/core/tasks/adv-elicit-methods.csv +0 -39
  488. package/src/core/tasks/advanced-elicitation.xml +0 -106
  489. package/src/core/tasks/validate-workflow.xml +0 -89
  490. package/src/core/tools/shard-doc.xml +0 -109
  491. package/src/core/workflows/brainstorming/README.md +0 -261
  492. package/src/core/workflows/brainstorming/instructions.md +0 -315
  493. package/src/core/workflows/brainstorming/workflow.yaml +0 -43
  494. package/src/core/workflows/party-mode/instructions.md +0 -183
  495. package/src/core/workflows/party-mode/workflow.yaml +0 -27
  496. package/src/modules/bmb/README.md +0 -194
  497. package/src/modules/bmb/_module-installer/install-config.yaml +0 -31
  498. package/src/modules/bmb/agents/bmad-builder.agent.yaml +0 -59
  499. package/src/modules/bmb/workflows/audit-workflow/checklist.md +0 -142
  500. package/src/modules/bmb/workflows/audit-workflow/instructions.md +0 -341
  501. package/src/modules/bmb/workflows/audit-workflow/template.md +0 -118
  502. package/src/modules/bmb/workflows/audit-workflow/workflow.yaml +0 -25
  503. package/src/modules/bmb/workflows/convert-legacy/README.md +0 -262
  504. package/src/modules/bmb/workflows/convert-legacy/checklist.md +0 -205
  505. package/src/modules/bmb/workflows/convert-legacy/instructions.md +0 -377
  506. package/src/modules/bmb/workflows/convert-legacy/workflow.yaml +0 -34
  507. package/src/modules/bmb/workflows/create-agent/README.md +0 -203
  508. package/src/modules/bmb/workflows/create-agent/agent-architecture.md +0 -415
  509. package/src/modules/bmb/workflows/create-agent/agent-command-patterns.md +0 -759
  510. package/src/modules/bmb/workflows/create-agent/agent-types.md +0 -292
  511. package/src/modules/bmb/workflows/create-agent/brainstorm-context.md +0 -174
  512. package/src/modules/bmb/workflows/create-agent/checklist.md +0 -62
  513. package/src/modules/bmb/workflows/create-agent/communication-styles.md +0 -202
  514. package/src/modules/bmb/workflows/create-agent/instructions.md +0 -456
  515. package/src/modules/bmb/workflows/create-agent/workflow.yaml +0 -49
  516. package/src/modules/bmb/workflows/create-module/README.md +0 -229
  517. package/src/modules/bmb/workflows/create-module/brainstorm-context.md +0 -137
  518. package/src/modules/bmb/workflows/create-module/checklist.md +0 -235
  519. package/src/modules/bmb/workflows/create-module/installer-templates/install-config.yaml +0 -92
  520. package/src/modules/bmb/workflows/create-module/installer-templates/installer.js +0 -231
  521. package/src/modules/bmb/workflows/create-module/instructions.md +0 -576
  522. package/src/modules/bmb/workflows/create-module/module-structure.md +0 -400
  523. package/src/modules/bmb/workflows/create-module/workflow.yaml +0 -44
  524. package/src/modules/bmb/workflows/create-workflow/README.md +0 -277
  525. package/src/modules/bmb/workflows/create-workflow/brainstorm-context.md +0 -197
  526. package/src/modules/bmb/workflows/create-workflow/checklist.md +0 -94
  527. package/src/modules/bmb/workflows/create-workflow/instructions.md +0 -724
  528. package/src/modules/bmb/workflows/create-workflow/workflow-creation-guide.md +0 -1306
  529. package/src/modules/bmb/workflows/create-workflow/workflow-template/checklist.md +0 -24
  530. package/src/modules/bmb/workflows/create-workflow/workflow-template/instructions.md +0 -13
  531. package/src/modules/bmb/workflows/create-workflow/workflow-template/template.md +0 -9
  532. package/src/modules/bmb/workflows/create-workflow/workflow-template/workflow.yaml +0 -65
  533. package/src/modules/bmb/workflows/create-workflow/workflow.yaml +0 -42
  534. package/src/modules/bmb/workflows/edit-agent/README.md +0 -112
  535. package/src/modules/bmb/workflows/edit-agent/checklist.md +0 -112
  536. package/src/modules/bmb/workflows/edit-agent/instructions.md +0 -290
  537. package/src/modules/bmb/workflows/edit-agent/workflow.yaml +0 -35
  538. package/src/modules/bmb/workflows/edit-module/README.md +0 -187
  539. package/src/modules/bmb/workflows/edit-module/checklist.md +0 -165
  540. package/src/modules/bmb/workflows/edit-module/instructions.md +0 -339
  541. package/src/modules/bmb/workflows/edit-module/workflow.yaml +0 -36
  542. package/src/modules/bmb/workflows/edit-workflow/README.md +0 -119
  543. package/src/modules/bmb/workflows/edit-workflow/checklist.md +0 -70
  544. package/src/modules/bmb/workflows/edit-workflow/instructions.md +0 -342
  545. package/src/modules/bmb/workflows/edit-workflow/workflow.yaml +0 -29
  546. package/src/modules/bmb/workflows/module-brief/README.md +0 -264
  547. package/src/modules/bmb/workflows/module-brief/checklist.md +0 -116
  548. package/src/modules/bmb/workflows/module-brief/instructions.md +0 -267
  549. package/src/modules/bmb/workflows/module-brief/template.md +0 -275
  550. package/src/modules/bmb/workflows/module-brief/workflow.yaml +0 -31
  551. package/src/modules/bmb/workflows/redoc/README.md +0 -87
  552. package/src/modules/bmb/workflows/redoc/checklist.md +0 -99
  553. package/src/modules/bmb/workflows/redoc/instructions.md +0 -265
  554. package/src/modules/bmb/workflows/redoc/workflow.yaml +0 -34
  555. package/src/modules/bmgd/README.md +0 -208
  556. package/src/modules/bmgd/_module-installer/install-config.yaml +0 -54
  557. package/src/modules/bmgd/agents/game-architect.agent.yaml +0 -33
  558. package/src/modules/bmgd/agents/game-designer.agent.yaml +0 -40
  559. package/src/modules/bmgd/agents/game-dev.agent.yaml +0 -40
  560. package/src/modules/bmgd/agents/game-scrum-master.agent.yaml +0 -75
  561. package/src/modules/bmgd/teams/default-party.csv +0 -10
  562. package/src/modules/bmgd/teams/team-gamedev.yaml +0 -18
  563. package/src/modules/bmgd/workflows/1-preproduction/brainstorm-game/game-brain-methods.csv +0 -26
  564. package/src/modules/bmgd/workflows/1-preproduction/brainstorm-game/game-context.md +0 -115
  565. package/src/modules/bmgd/workflows/1-preproduction/brainstorm-game/instructions.md +0 -128
  566. package/src/modules/bmgd/workflows/1-preproduction/brainstorm-game/workflow.yaml +0 -41
  567. package/src/modules/bmgd/workflows/1-preproduction/game-brief/checklist.md +0 -128
  568. package/src/modules/bmgd/workflows/1-preproduction/game-brief/instructions.md +0 -371
  569. package/src/modules/bmgd/workflows/1-preproduction/game-brief/template.md +0 -205
  570. package/src/modules/bmgd/workflows/1-preproduction/game-brief/workflow.yaml +0 -44
  571. package/src/modules/bmgd/workflows/2-design/gdd/checklist.md +0 -148
  572. package/src/modules/bmgd/workflows/2-design/gdd/game-types/action-platformer.md +0 -45
  573. package/src/modules/bmgd/workflows/2-design/gdd/game-types/adventure.md +0 -84
  574. package/src/modules/bmgd/workflows/2-design/gdd/game-types/card-game.md +0 -76
  575. package/src/modules/bmgd/workflows/2-design/gdd/game-types/fighting.md +0 -89
  576. package/src/modules/bmgd/workflows/2-design/gdd/game-types/horror.md +0 -86
  577. package/src/modules/bmgd/workflows/2-design/gdd/game-types/idle-incremental.md +0 -78
  578. package/src/modules/bmgd/workflows/2-design/gdd/game-types/metroidvania.md +0 -87
  579. package/src/modules/bmgd/workflows/2-design/gdd/game-types/moba.md +0 -74
  580. package/src/modules/bmgd/workflows/2-design/gdd/game-types/party-game.md +0 -79
  581. package/src/modules/bmgd/workflows/2-design/gdd/game-types/puzzle.md +0 -58
  582. package/src/modules/bmgd/workflows/2-design/gdd/game-types/racing.md +0 -88
  583. package/src/modules/bmgd/workflows/2-design/gdd/game-types/rhythm.md +0 -79
  584. package/src/modules/bmgd/workflows/2-design/gdd/game-types/roguelike.md +0 -69
  585. package/src/modules/bmgd/workflows/2-design/gdd/game-types/rpg.md +0 -70
  586. package/src/modules/bmgd/workflows/2-design/gdd/game-types/sandbox.md +0 -79
  587. package/src/modules/bmgd/workflows/2-design/gdd/game-types/shooter.md +0 -62
  588. package/src/modules/bmgd/workflows/2-design/gdd/game-types/simulation.md +0 -73
  589. package/src/modules/bmgd/workflows/2-design/gdd/game-types/sports.md +0 -75
  590. package/src/modules/bmgd/workflows/2-design/gdd/game-types/strategy.md +0 -71
  591. package/src/modules/bmgd/workflows/2-design/gdd/game-types/survival.md +0 -79
  592. package/src/modules/bmgd/workflows/2-design/gdd/game-types/text-based.md +0 -91
  593. package/src/modules/bmgd/workflows/2-design/gdd/game-types/tower-defense.md +0 -79
  594. package/src/modules/bmgd/workflows/2-design/gdd/game-types/turn-based-tactics.md +0 -88
  595. package/src/modules/bmgd/workflows/2-design/gdd/game-types/visual-novel.md +0 -89
  596. package/src/modules/bmgd/workflows/2-design/gdd/game-types.csv +0 -25
  597. package/src/modules/bmgd/workflows/2-design/gdd/gdd-template.md +0 -153
  598. package/src/modules/bmgd/workflows/2-design/gdd/instructions-gdd.md +0 -501
  599. package/src/modules/bmgd/workflows/2-design/gdd/workflow.yaml +0 -81
  600. package/src/modules/bmgd/workflows/2-design/narrative/checklist.md +0 -139
  601. package/src/modules/bmgd/workflows/2-design/narrative/instructions-narrative.md +0 -603
  602. package/src/modules/bmgd/workflows/2-design/narrative/narrative-template.md +0 -195
  603. package/src/modules/bmgd/workflows/2-design/narrative/workflow.yaml +0 -38
  604. package/src/modules/bmgd/workflows/3-technical/game-architecture/architecture-patterns.yaml +0 -321
  605. package/src/modules/bmgd/workflows/3-technical/game-architecture/architecture-template.md +0 -103
  606. package/src/modules/bmgd/workflows/3-technical/game-architecture/checklist.md +0 -240
  607. package/src/modules/bmgd/workflows/3-technical/game-architecture/decision-catalog.yaml +0 -222
  608. package/src/modules/bmgd/workflows/3-technical/game-architecture/instructions.md +0 -699
  609. package/src/modules/bmgd/workflows/3-technical/game-architecture/pattern-categories.csv +0 -13
  610. package/src/modules/bmgd/workflows/3-technical/game-architecture/workflow.yaml +0 -67
  611. package/src/modules/bmgd/workflows/4-production/code-review/backlog_template.md +0 -12
  612. package/src/modules/bmgd/workflows/4-production/code-review/checklist.md +0 -22
  613. package/src/modules/bmgd/workflows/4-production/code-review/instructions.md +0 -398
  614. package/src/modules/bmgd/workflows/4-production/code-review/workflow.yaml +0 -57
  615. package/src/modules/bmgd/workflows/4-production/correct-course/checklist.md +0 -279
  616. package/src/modules/bmgd/workflows/4-production/correct-course/instructions.md +0 -206
  617. package/src/modules/bmgd/workflows/4-production/correct-course/workflow.yaml +0 -52
  618. package/src/modules/bmgd/workflows/4-production/create-story/checklist.md +0 -240
  619. package/src/modules/bmgd/workflows/4-production/create-story/instructions.md +0 -256
  620. package/src/modules/bmgd/workflows/4-production/create-story/template.md +0 -51
  621. package/src/modules/bmgd/workflows/4-production/create-story/workflow.yaml +0 -68
  622. package/src/modules/bmgd/workflows/4-production/dev-story/checklist.md +0 -38
  623. package/src/modules/bmgd/workflows/4-production/dev-story/instructions.md +0 -267
  624. package/src/modules/bmgd/workflows/4-production/dev-story/workflow.yaml +0 -53
  625. package/src/modules/bmgd/workflows/4-production/epic-tech-context/checklist.md +0 -17
  626. package/src/modules/bmgd/workflows/4-production/epic-tech-context/instructions.md +0 -164
  627. package/src/modules/bmgd/workflows/4-production/epic-tech-context/template.md +0 -76
  628. package/src/modules/bmgd/workflows/4-production/epic-tech-context/workflow.yaml +0 -52
  629. package/src/modules/bmgd/workflows/4-production/retrospective/instructions.md +0 -1442
  630. package/src/modules/bmgd/workflows/4-production/retrospective/workflow.yaml +0 -52
  631. package/src/modules/bmgd/workflows/4-production/sprint-planning/instructions.md +0 -234
  632. package/src/modules/bmgd/workflows/4-production/sprint-planning/sprint-status-template.yaml +0 -55
  633. package/src/modules/bmgd/workflows/4-production/sprint-planning/workflow.yaml +0 -50
  634. package/src/modules/bmgd/workflows/4-production/story-context/checklist.md +0 -16
  635. package/src/modules/bmgd/workflows/4-production/story-context/context-template.xml +0 -34
  636. package/src/modules/bmgd/workflows/4-production/story-context/instructions.md +0 -209
  637. package/src/modules/bmgd/workflows/4-production/story-context/workflow.yaml +0 -57
  638. package/src/modules/bmgd/workflows/4-production/story-done/instructions.md +0 -111
  639. package/src/modules/bmgd/workflows/4-production/story-done/workflow.yaml +0 -28
  640. package/src/modules/bmgd/workflows/4-production/story-ready/instructions.md +0 -117
  641. package/src/modules/bmgd/workflows/4-production/story-ready/workflow.yaml +0 -25
  642. package/src/modules/bmm/README.md +0 -128
  643. package/src/modules/bmm/_module-installer/assets/bmm-kb.md +0 -1
  644. package/src/modules/bmm/_module-installer/assets/technical-decisions.md +0 -30
  645. package/src/modules/bmm/_module-installer/install-config.yaml +0 -60
  646. package/src/modules/bmm/_module-installer/installer.js +0 -131
  647. package/src/modules/bmm/_module-installer/platform-specifics/claude-code.js +0 -35
  648. package/src/modules/bmm/_module-installer/platform-specifics/windsurf.js +0 -32
  649. package/src/modules/bmm/agents/analyst.agent.yaml +0 -48
  650. package/src/modules/bmm/agents/architect.agent.yaml +0 -42
  651. package/src/modules/bmm/agents/dev.agent.yaml +0 -40
  652. package/src/modules/bmm/agents/pm.agent.yaml +0 -61
  653. package/src/modules/bmm/agents/sm.agent.yaml +0 -72
  654. package/src/modules/bmm/agents/tea.agent.yaml +0 -65
  655. package/src/modules/bmm/agents/tech-writer.agent.yaml +0 -73
  656. package/src/modules/bmm/agents/ux-designer.agent.yaml +0 -38
  657. package/src/modules/bmm/docs/README.md +0 -235
  658. package/src/modules/bmm/docs/agents-guide.md +0 -1056
  659. package/src/modules/bmm/docs/brownfield-guide.md +0 -754
  660. package/src/modules/bmm/docs/enterprise-agentic-development.md +0 -680
  661. package/src/modules/bmm/docs/faq.md +0 -587
  662. package/src/modules/bmm/docs/glossary.md +0 -320
  663. package/src/modules/bmm/docs/party-mode.md +0 -224
  664. package/src/modules/bmm/docs/quick-spec-flow.md +0 -652
  665. package/src/modules/bmm/docs/quick-start.md +0 -366
  666. package/src/modules/bmm/docs/scale-adaptive-system.md +0 -599
  667. package/src/modules/bmm/docs/test-architecture.md +0 -394
  668. package/src/modules/bmm/docs/workflow-architecture-reference.md +0 -371
  669. package/src/modules/bmm/docs/workflow-document-project-reference.md +0 -487
  670. package/src/modules/bmm/docs/workflows-analysis.md +0 -370
  671. package/src/modules/bmm/docs/workflows-implementation.md +0 -284
  672. package/src/modules/bmm/docs/workflows-planning.md +0 -601
  673. package/src/modules/bmm/docs/workflows-solutioning.md +0 -501
  674. package/src/modules/bmm/teams/default-party.csv +0 -19
  675. package/src/modules/bmm/testarch/knowledge/ci-burn-in.md +0 -675
  676. package/src/modules/bmm/testarch/knowledge/component-tdd.md +0 -486
  677. package/src/modules/bmm/testarch/knowledge/contract-testing.md +0 -957
  678. package/src/modules/bmm/testarch/knowledge/data-factories.md +0 -500
  679. package/src/modules/bmm/testarch/knowledge/email-auth.md +0 -721
  680. package/src/modules/bmm/testarch/knowledge/error-handling.md +0 -725
  681. package/src/modules/bmm/testarch/knowledge/feature-flags.md +0 -750
  682. package/src/modules/bmm/testarch/knowledge/fixture-architecture.md +0 -401
  683. package/src/modules/bmm/testarch/knowledge/network-first.md +0 -486
  684. package/src/modules/bmm/testarch/knowledge/nfr-criteria.md +0 -670
  685. package/src/modules/bmm/testarch/knowledge/playwright-config.md +0 -730
  686. package/src/modules/bmm/testarch/knowledge/probability-impact.md +0 -601
  687. package/src/modules/bmm/testarch/knowledge/risk-governance.md +0 -615
  688. package/src/modules/bmm/testarch/knowledge/selective-testing.md +0 -732
  689. package/src/modules/bmm/testarch/knowledge/selector-resilience.md +0 -527
  690. package/src/modules/bmm/testarch/knowledge/test-healing-patterns.md +0 -644
  691. package/src/modules/bmm/testarch/knowledge/test-levels-framework.md +0 -473
  692. package/src/modules/bmm/testarch/knowledge/test-priorities-matrix.md +0 -373
  693. package/src/modules/bmm/testarch/knowledge/test-quality.md +0 -664
  694. package/src/modules/bmm/testarch/knowledge/timing-debugging.md +0 -372
  695. package/src/modules/bmm/testarch/knowledge/visual-debugging.md +0 -524
  696. package/src/modules/bmm/testarch/tea-index.csv +0 -22
  697. package/src/modules/bmm/workflows/1-analysis/brainstorm-project/instructions.md +0 -110
  698. package/src/modules/bmm/workflows/1-analysis/brainstorm-project/project-context.md +0 -25
  699. package/src/modules/bmm/workflows/1-analysis/brainstorm-project/workflow.yaml +0 -39
  700. package/src/modules/bmm/workflows/1-analysis/domain-research/instructions.md +0 -423
  701. package/src/modules/bmm/workflows/1-analysis/domain-research/template.md +0 -180
  702. package/src/modules/bmm/workflows/1-analysis/domain-research/workflow.yaml +0 -56
  703. package/src/modules/bmm/workflows/1-analysis/product-brief/checklist.md +0 -115
  704. package/src/modules/bmm/workflows/1-analysis/product-brief/instructions.md +0 -522
  705. package/src/modules/bmm/workflows/1-analysis/product-brief/template.md +0 -181
  706. package/src/modules/bmm/workflows/1-analysis/product-brief/workflow.yaml +0 -65
  707. package/src/modules/bmm/workflows/1-analysis/research/checklist-deep-prompt.md +0 -144
  708. package/src/modules/bmm/workflows/1-analysis/research/checklist-technical.md +0 -249
  709. package/src/modules/bmm/workflows/1-analysis/research/checklist.md +0 -299
  710. package/src/modules/bmm/workflows/1-analysis/research/claude-code/injections.yaml +0 -114
  711. package/src/modules/bmm/workflows/1-analysis/research/instructions-deep-prompt.md +0 -437
  712. package/src/modules/bmm/workflows/1-analysis/research/instructions-market.md +0 -674
  713. package/src/modules/bmm/workflows/1-analysis/research/instructions-router.md +0 -133
  714. package/src/modules/bmm/workflows/1-analysis/research/instructions-technical.md +0 -533
  715. package/src/modules/bmm/workflows/1-analysis/research/template-deep-prompt.md +0 -94
  716. package/src/modules/bmm/workflows/1-analysis/research/template-market.md +0 -347
  717. package/src/modules/bmm/workflows/1-analysis/research/template-technical.md +0 -245
  718. package/src/modules/bmm/workflows/1-analysis/research/workflow.yaml +0 -62
  719. package/src/modules/bmm/workflows/2-plan-workflows/create-ux-design/checklist.md +0 -310
  720. package/src/modules/bmm/workflows/2-plan-workflows/create-ux-design/instructions.md +0 -1306
  721. package/src/modules/bmm/workflows/2-plan-workflows/create-ux-design/ux-design-template.md +0 -145
  722. package/src/modules/bmm/workflows/2-plan-workflows/create-ux-design/workflow.yaml +0 -99
  723. package/src/modules/bmm/workflows/2-plan-workflows/prd/checklist.md +0 -346
  724. package/src/modules/bmm/workflows/2-plan-workflows/prd/create-epics-and-stories/epics-template.md +0 -80
  725. package/src/modules/bmm/workflows/2-plan-workflows/prd/create-epics-and-stories/instructions.md +0 -292
  726. package/src/modules/bmm/workflows/2-plan-workflows/prd/create-epics-and-stories/workflow.yaml +0 -53
  727. package/src/modules/bmm/workflows/2-plan-workflows/prd/domain-complexity.csv +0 -13
  728. package/src/modules/bmm/workflows/2-plan-workflows/prd/instructions.md +0 -525
  729. package/src/modules/bmm/workflows/2-plan-workflows/prd/prd-template.md +0 -237
  730. package/src/modules/bmm/workflows/2-plan-workflows/prd/project-types.csv +0 -11
  731. package/src/modules/bmm/workflows/2-plan-workflows/prd/workflow.yaml +0 -76
  732. package/src/modules/bmm/workflows/2-plan-workflows/tech-spec/checklist.md +0 -217
  733. package/src/modules/bmm/workflows/2-plan-workflows/tech-spec/epics-template.md +0 -74
  734. package/src/modules/bmm/workflows/2-plan-workflows/tech-spec/instructions-generate-stories.md +0 -434
  735. package/src/modules/bmm/workflows/2-plan-workflows/tech-spec/instructions.md +0 -978
  736. package/src/modules/bmm/workflows/2-plan-workflows/tech-spec/tech-spec-template.md +0 -181
  737. package/src/modules/bmm/workflows/2-plan-workflows/tech-spec/user-story-template.md +0 -90
  738. package/src/modules/bmm/workflows/2-plan-workflows/tech-spec/workflow.yaml +0 -57
  739. package/src/modules/bmm/workflows/3-solutioning/architecture/architecture-patterns.yaml +0 -321
  740. package/src/modules/bmm/workflows/3-solutioning/architecture/architecture-template.md +0 -103
  741. package/src/modules/bmm/workflows/3-solutioning/architecture/checklist.md +0 -240
  742. package/src/modules/bmm/workflows/3-solutioning/architecture/decision-catalog.yaml +0 -222
  743. package/src/modules/bmm/workflows/3-solutioning/architecture/instructions.md +0 -696
  744. package/src/modules/bmm/workflows/3-solutioning/architecture/pattern-categories.csv +0 -13
  745. package/src/modules/bmm/workflows/3-solutioning/architecture/workflow.yaml +0 -96
  746. package/src/modules/bmm/workflows/3-solutioning/solutioning-gate-check/checklist.md +0 -169
  747. package/src/modules/bmm/workflows/3-solutioning/solutioning-gate-check/instructions.md +0 -302
  748. package/src/modules/bmm/workflows/3-solutioning/solutioning-gate-check/template.md +0 -146
  749. package/src/modules/bmm/workflows/3-solutioning/solutioning-gate-check/workflow.yaml +0 -66
  750. package/src/modules/bmm/workflows/4-implementation/code-review/backlog_template.md +0 -12
  751. package/src/modules/bmm/workflows/4-implementation/code-review/checklist.md +0 -22
  752. package/src/modules/bmm/workflows/4-implementation/code-review/instructions.md +0 -398
  753. package/src/modules/bmm/workflows/4-implementation/code-review/workflow.yaml +0 -57
  754. package/src/modules/bmm/workflows/4-implementation/correct-course/checklist.md +0 -279
  755. package/src/modules/bmm/workflows/4-implementation/correct-course/instructions.md +0 -206
  756. package/src/modules/bmm/workflows/4-implementation/correct-course/workflow.yaml +0 -52
  757. package/src/modules/bmm/workflows/4-implementation/create-story/checklist.md +0 -240
  758. package/src/modules/bmm/workflows/4-implementation/create-story/instructions.md +0 -256
  759. package/src/modules/bmm/workflows/4-implementation/create-story/template.md +0 -51
  760. package/src/modules/bmm/workflows/4-implementation/create-story/workflow.yaml +0 -68
  761. package/src/modules/bmm/workflows/4-implementation/dev-story/checklist.md +0 -38
  762. package/src/modules/bmm/workflows/4-implementation/dev-story/instructions.md +0 -267
  763. package/src/modules/bmm/workflows/4-implementation/dev-story/workflow.yaml +0 -53
  764. package/src/modules/bmm/workflows/4-implementation/epic-tech-context/checklist.md +0 -17
  765. package/src/modules/bmm/workflows/4-implementation/epic-tech-context/instructions.md +0 -164
  766. package/src/modules/bmm/workflows/4-implementation/epic-tech-context/template.md +0 -76
  767. package/src/modules/bmm/workflows/4-implementation/epic-tech-context/workflow.yaml +0 -52
  768. package/src/modules/bmm/workflows/4-implementation/retrospective/instructions.md +0 -1442
  769. package/src/modules/bmm/workflows/4-implementation/retrospective/workflow.yaml +0 -52
  770. package/src/modules/bmm/workflows/4-implementation/sprint-planning/checklist.md +0 -33
  771. package/src/modules/bmm/workflows/4-implementation/sprint-planning/instructions.md +0 -234
  772. package/src/modules/bmm/workflows/4-implementation/sprint-planning/sprint-status-template.yaml +0 -55
  773. package/src/modules/bmm/workflows/4-implementation/sprint-planning/workflow.yaml +0 -50
  774. package/src/modules/bmm/workflows/4-implementation/story-context/checklist.md +0 -16
  775. package/src/modules/bmm/workflows/4-implementation/story-context/context-template.xml +0 -34
  776. package/src/modules/bmm/workflows/4-implementation/story-context/instructions.md +0 -209
  777. package/src/modules/bmm/workflows/4-implementation/story-context/workflow.yaml +0 -57
  778. package/src/modules/bmm/workflows/4-implementation/story-done/instructions.md +0 -111
  779. package/src/modules/bmm/workflows/4-implementation/story-done/workflow.yaml +0 -28
  780. package/src/modules/bmm/workflows/4-implementation/story-ready/instructions.md +0 -117
  781. package/src/modules/bmm/workflows/4-implementation/story-ready/workflow.yaml +0 -25
  782. package/src/modules/bmm/workflows/document-project/checklist.md +0 -245
  783. package/src/modules/bmm/workflows/document-project/instructions.md +0 -222
  784. package/src/modules/bmm/workflows/document-project/templates/project-scan-report-schema.json +0 -160
  785. package/src/modules/bmm/workflows/document-project/workflow.yaml +0 -36
  786. package/src/modules/bmm/workflows/document-project/workflows/deep-dive-instructions.md +0 -298
  787. package/src/modules/bmm/workflows/document-project/workflows/deep-dive.yaml +0 -31
  788. package/src/modules/bmm/workflows/document-project/workflows/full-scan-instructions.md +0 -1106
  789. package/src/modules/bmm/workflows/document-project/workflows/full-scan.yaml +0 -31
  790. package/src/modules/bmm/workflows/techdoc/documentation-standards.md +0 -262
  791. package/src/modules/bmm/workflows/testarch/atdd/atdd-checklist-template.md +0 -363
  792. package/src/modules/bmm/workflows/testarch/atdd/checklist.md +0 -373
  793. package/src/modules/bmm/workflows/testarch/atdd/instructions.md +0 -785
  794. package/src/modules/bmm/workflows/testarch/atdd/workflow.yaml +0 -54
  795. package/src/modules/bmm/workflows/testarch/automate/checklist.md +0 -580
  796. package/src/modules/bmm/workflows/testarch/automate/instructions.md +0 -1303
  797. package/src/modules/bmm/workflows/testarch/automate/workflow.yaml +0 -63
  798. package/src/modules/bmm/workflows/testarch/ci/checklist.md +0 -246
  799. package/src/modules/bmm/workflows/testarch/ci/github-actions-template.yaml +0 -165
  800. package/src/modules/bmm/workflows/testarch/ci/gitlab-ci-template.yaml +0 -128
  801. package/src/modules/bmm/workflows/testarch/ci/instructions.md +0 -517
  802. package/src/modules/bmm/workflows/testarch/ci/workflow.yaml +0 -55
  803. package/src/modules/bmm/workflows/testarch/framework/checklist.md +0 -321
  804. package/src/modules/bmm/workflows/testarch/framework/instructions.md +0 -455
  805. package/src/modules/bmm/workflows/testarch/framework/workflow.yaml +0 -55
  806. package/src/modules/bmm/workflows/testarch/nfr-assess/checklist.md +0 -405
  807. package/src/modules/bmm/workflows/testarch/nfr-assess/instructions.md +0 -722
  808. package/src/modules/bmm/workflows/testarch/nfr-assess/nfr-report-template.md +0 -443
  809. package/src/modules/bmm/workflows/testarch/nfr-assess/workflow.yaml +0 -58
  810. package/src/modules/bmm/workflows/testarch/test-design/checklist.md +0 -234
  811. package/src/modules/bmm/workflows/testarch/test-design/instructions.md +0 -782
  812. package/src/modules/bmm/workflows/testarch/test-design/test-design-template.md +0 -285
  813. package/src/modules/bmm/workflows/testarch/test-design/workflow.yaml +0 -58
  814. package/src/modules/bmm/workflows/testarch/test-review/checklist.md +0 -470
  815. package/src/modules/bmm/workflows/testarch/test-review/instructions.md +0 -608
  816. package/src/modules/bmm/workflows/testarch/test-review/test-review-template.md +0 -388
  817. package/src/modules/bmm/workflows/testarch/test-review/workflow.yaml +0 -55
  818. package/src/modules/bmm/workflows/testarch/trace/checklist.md +0 -654
  819. package/src/modules/bmm/workflows/testarch/trace/instructions.md +0 -1045
  820. package/src/modules/bmm/workflows/testarch/trace/trace-template.md +0 -673
  821. package/src/modules/bmm/workflows/testarch/trace/workflow.yaml +0 -68
  822. package/src/modules/bmm/workflows/workflow-status/init/instructions.md +0 -823
  823. package/src/modules/bmm/workflows/workflow-status/init/workflow.yaml +0 -29
  824. package/src/modules/bmm/workflows/workflow-status/instructions.md +0 -387
  825. package/src/modules/bmm/workflows/workflow-status/paths/enterprise-brownfield.yaml +0 -120
  826. package/src/modules/bmm/workflows/workflow-status/paths/enterprise-greenfield.yaml +0 -108
  827. package/src/modules/bmm/workflows/workflow-status/paths/game-design.yaml +0 -52
  828. package/src/modules/bmm/workflows/workflow-status/paths/method-brownfield.yaml +0 -104
  829. package/src/modules/bmm/workflows/workflow-status/paths/method-greenfield.yaml +0 -95
  830. package/src/modules/bmm/workflows/workflow-status/paths/quick-flow-brownfield.yaml +0 -58
  831. package/src/modules/bmm/workflows/workflow-status/paths/quick-flow-greenfield.yaml +0 -47
  832. package/src/modules/bmm/workflows/workflow-status/project-levels.yaml +0 -59
  833. package/src/modules/bmm/workflows/workflow-status/workflow-status-template.yaml +0 -24
  834. package/src/modules/bmm/workflows/workflow-status/workflow.yaml +0 -30
  835. package/src/modules/cis/README.md +0 -153
  836. package/src/modules/cis/_module-installer/install-config.yaml +0 -16
  837. package/src/modules/cis/_module-installer/installer.js +0 -92
  838. package/src/modules/cis/agents/README.md +0 -104
  839. package/src/modules/cis/agents/brainstorming-coach.agent.yaml +0 -28
  840. package/src/modules/cis/agents/creative-problem-solver.agent.yaml +0 -28
  841. package/src/modules/cis/agents/design-thinking-coach.agent.yaml +0 -28
  842. package/src/modules/cis/agents/innovation-strategist.agent.yaml +0 -28
  843. package/src/modules/cis/agents/storyteller.agent.yaml +0 -28
  844. package/src/modules/cis/teams/creative-squad.yaml +0 -7
  845. package/src/modules/cis/teams/default-party.csv +0 -11
  846. package/src/modules/cis/workflows/README.md +0 -139
  847. package/src/modules/cis/workflows/design-thinking/README.md +0 -56
  848. package/src/modules/cis/workflows/design-thinking/design-methods.csv +0 -31
  849. package/src/modules/cis/workflows/design-thinking/instructions.md +0 -200
  850. package/src/modules/cis/workflows/design-thinking/template.md +0 -111
  851. package/src/modules/cis/workflows/design-thinking/workflow.yaml +0 -43
  852. package/src/modules/cis/workflows/innovation-strategy/README.md +0 -56
  853. package/src/modules/cis/workflows/innovation-strategy/innovation-frameworks.csv +0 -31
  854. package/src/modules/cis/workflows/innovation-strategy/instructions.md +0 -274
  855. package/src/modules/cis/workflows/innovation-strategy/template.md +0 -189
  856. package/src/modules/cis/workflows/innovation-strategy/workflow.yaml +0 -43
  857. package/src/modules/cis/workflows/problem-solving/README.md +0 -56
  858. package/src/modules/cis/workflows/problem-solving/instructions.md +0 -250
  859. package/src/modules/cis/workflows/problem-solving/solving-methods.csv +0 -31
  860. package/src/modules/cis/workflows/problem-solving/template.md +0 -165
  861. package/src/modules/cis/workflows/problem-solving/workflow.yaml +0 -43
  862. package/src/modules/cis/workflows/storytelling/README.md +0 -58
  863. package/src/modules/cis/workflows/storytelling/instructions.md +0 -291
  864. package/src/modules/cis/workflows/storytelling/story-types.csv +0 -26
  865. package/src/modules/cis/workflows/storytelling/template.md +0 -113
  866. package/src/modules/cis/workflows/storytelling/workflow.yaml +0 -43
  867. package/src/utility/models/agent-activation-ide.xml +0 -51
  868. package/src/utility/models/agent-activation-web.xml +0 -60
  869. package/src/utility/models/agent-command-header.md +0 -1
  870. package/src/utility/models/agent-config-template.md +0 -23
  871. package/src/utility/models/agent-in-team-activation.xml +0 -3
  872. package/src/utility/models/fragments/activation-rules.xml +0 -8
  873. package/src/utility/models/fragments/activation-steps.xml +0 -16
  874. package/src/utility/models/fragments/handler-action.xml +0 -4
  875. package/src/utility/models/fragments/handler-exec.xml +0 -5
  876. package/src/utility/models/fragments/handler-tmpl.xml +0 -5
  877. package/src/utility/models/fragments/handler-validate-workflow.xml +0 -7
  878. package/src/utility/models/fragments/handler-workflow.xml +0 -9
  879. package/src/utility/models/fragments/menu-handlers.xml +0 -6
  880. package/src/utility/models/fragments/web-bundle-activation-steps.xml +0 -32
  881. package/src/utility/templates/agent.customize.template.yaml +0 -42
  882. package/test/fixtures/agent-schema/invalid/metadata/core-agent-with-module.agent.yaml +0 -26
  883. package/test/fixtures/agent-schema/invalid/metadata/module-agent-missing-module.agent.yaml +0 -25
  884. package/test/fixtures/agent-schema/invalid/metadata/wrong-module-value.agent.yaml +0 -26
  885. package/tools/cli/bundlers/bundle-web.js +0 -179
  886. package/tools/cli/bundlers/test-analyst.js +0 -28
  887. package/tools/cli/bundlers/test-bundler.js +0 -118
  888. package/tools/cli/bundlers/web-bundler.js +0 -1667
  889. package/tools/cli/commands/build.js +0 -458
  890. package/tools/cli/commands/list.js +0 -28
  891. package/tools/cli/commands/update.js +0 -28
  892. package/tools/cli/installers/lib/ide/auggie.js +0 -179
  893. package/tools/cli/installers/lib/ide/claude-code.js +0 -471
  894. package/tools/cli/installers/lib/ide/cline.js +0 -220
  895. package/tools/cli/installers/lib/ide/crush.js +0 -240
  896. package/tools/cli/installers/lib/ide/cursor.js +0 -352
  897. package/tools/cli/installers/lib/ide/gemini.js +0 -206
  898. package/tools/cli/installers/lib/ide/iflow.js +0 -125
  899. package/tools/cli/installers/lib/ide/opencode.js +0 -212
  900. package/tools/cli/installers/lib/ide/qwen.js +0 -318
  901. package/tools/cli/installers/lib/ide/roo.js +0 -253
  902. package/tools/cli/installers/lib/ide/templates/gemini-agent-command.toml +0 -14
  903. package/tools/cli/installers/lib/ide/templates/gemini-task-command.toml +0 -12
  904. package/tools/cli/installers/lib/ide/trae.js +0 -266
  905. package/tools/cli/installers/lib/ide/windsurf.js +0 -211
  906. package/tools/cli/lib/replace-project-root.js +0 -239
  907. package/tools/cli/regenerate-manifests.js +0 -28
  908. package/tools/cli/test-yaml-builder.js +0 -43
  909. package/tools/flattener/aggregate.js +0 -76
  910. package/tools/flattener/binary.js +0 -80
  911. package/tools/flattener/discovery.js +0 -71
  912. package/tools/flattener/files.js +0 -35
  913. package/tools/flattener/ignoreRules.js +0 -172
  914. package/tools/flattener/main.js +0 -483
  915. package/tools/flattener/projectRoot.js +0 -201
  916. package/tools/flattener/prompts.js +0 -44
  917. package/tools/flattener/stats.helpers.js +0 -368
  918. package/tools/flattener/stats.js +0 -75
  919. package/tools/flattener/test-matrix.js +0 -409
  920. package/tools/flattener/xml.js +0 -88
  921. package/tools/validate-bundles.js +0 -87
  922. package/v6-open-items.md +0 -17
  923. /package/src/{modules/bmm → bmm}/teams/team-fullstack.yaml +0 -0
  924. /package/src/{modules/bmgd/workflows/4-production → bmm/workflows/4-implementation}/sprint-planning/checklist.md +0 -0
  925. /package/src/{modules/bmm → bmm}/workflows/document-project/documentation-requirements.csv +0 -0
  926. /package/src/{modules/bmm → bmm}/workflows/document-project/templates/deep-dive-template.md +0 -0
  927. /package/src/{modules/bmm → bmm}/workflows/document-project/templates/index-template.md +0 -0
  928. /package/src/{modules/bmm → bmm}/workflows/document-project/templates/project-overview-template.md +0 -0
  929. /package/src/{modules/bmm → bmm}/workflows/document-project/templates/source-tree-template.md +0 -0
  930. /package/src/utility/{models/fragments/handler-data.xml → agent-components/handler-data.txt} +0 -0
  931. /package/{src/utility/models/action-command-header.md → tools/cli/installers/lib/ide/templates/split/.gitkeep} +0 -0
@@ -1,7 +1,5 @@
1
1
  const path = require('node:path');
2
2
  const fs = require('fs-extra');
3
- const chalk = require('chalk');
4
- const ora = require('ora');
5
3
  const { Detector } = require('./detector');
6
4
  const { Manifest } = require('./manifest');
7
5
  const { ModuleManager } = require('../modules/manager');
@@ -11,12 +9,13 @@ const { Config } = require('../../../lib/config');
11
9
  const { XmlHandler } = require('../../../lib/xml-handler');
12
10
  const { DependencyResolver } = require('./dependency-resolver');
13
11
  const { ConfigCollector } = require('./config-collector');
14
- // processInstallation no longer needed - LLMs understand {project-root}
15
12
  const { getProjectRoot, getSourcePath, getModulePath } = require('../../../lib/project-root');
16
- const { AgentPartyGenerator } = require('../../../lib/agent-party-generator');
17
13
  const { CLIUtils } = require('../../../lib/cli-utils');
18
14
  const { ManifestGenerator } = require('./manifest-generator');
19
15
  const { IdeConfigManager } = require('./ide-config-manager');
16
+ const { CustomHandler } = require('../custom/handler');
17
+ const prompts = require('../../../lib/prompts');
18
+ const { BMAD_FOLDER_NAME } = require('../ide/shared/path-utils');
20
19
 
21
20
  class Installer {
22
21
  constructor() {
@@ -30,53 +29,58 @@ class Installer {
30
29
  this.dependencyResolver = new DependencyResolver();
31
30
  this.configCollector = new ConfigCollector();
32
31
  this.ideConfigManager = new IdeConfigManager();
33
- this.installedFiles = []; // Track all installed files
32
+ this.installedFiles = new Set(); // Track all installed files
33
+ this.bmadFolderName = BMAD_FOLDER_NAME;
34
34
  }
35
35
 
36
36
  /**
37
37
  * Find the bmad installation directory in a project
38
- * V6+ installations can use ANY folder name but ALWAYS have _cfg/manifest.yaml
38
+ * Always uses the standard _bmad folder name
39
+ * Also checks for legacy _cfg folder for migration
39
40
  * @param {string} projectDir - Project directory
40
- * @returns {Promise<string>} Path to bmad directory
41
+ * @returns {Promise<Object>} { bmadDir: string, hasLegacyCfg: boolean }
41
42
  */
42
43
  async findBmadDir(projectDir) {
44
+ const bmadDir = path.join(projectDir, BMAD_FOLDER_NAME);
45
+
43
46
  // Check if project directory exists
44
47
  if (!(await fs.pathExists(projectDir))) {
45
48
  // Project doesn't exist yet, return default
46
- return path.join(projectDir, 'bmad');
49
+ return { bmadDir, hasLegacyCfg: false };
47
50
  }
48
51
 
49
- // V6+ strategy: Look for ANY directory with _cfg/manifest.yaml
50
- // This is the definitive marker of a V6+ installation
51
- try {
52
- const entries = await fs.readdir(projectDir, { withFileTypes: true });
53
- for (const entry of entries) {
54
- if (entry.isDirectory()) {
55
- const manifestPath = path.join(projectDir, entry.name, '_cfg', 'manifest.yaml');
56
- if (await fs.pathExists(manifestPath)) {
57
- // Found a V6+ installation
58
- return path.join(projectDir, entry.name);
59
- }
60
- }
52
+ // Check for legacy _cfg folder if bmad directory exists
53
+ let hasLegacyCfg = false;
54
+ if (await fs.pathExists(bmadDir)) {
55
+ const legacyCfgPath = path.join(bmadDir, '_cfg');
56
+ if (await fs.pathExists(legacyCfgPath)) {
57
+ hasLegacyCfg = true;
61
58
  }
62
- } catch {
63
- // Ignore errors, fall through to default
64
59
  }
65
60
 
66
- // No V6+ installation found, return default
67
- // This will be used for new installations
68
- return path.join(projectDir, 'bmad');
61
+ return { bmadDir, hasLegacyCfg };
69
62
  }
70
63
 
71
64
  /**
72
- * Copy a file and replace {bmad_folder} placeholder with actual folder name
73
- * @param {string} sourcePath - Source file path
74
- * @param {string} targetPath - Target file path
75
- * @param {string} bmadFolderName - The bmad folder name to use for replacement
65
+ * @function copyFileWithPlaceholderReplacement
66
+ * @intent Copy files from BMAD source to installation directory with dynamic content transformation
67
+ * @why Enables installation-time customization: _bmad replacement
68
+ * @param {string} sourcePath - Absolute path to source file in BMAD repository
69
+ * @param {string} targetPath - Absolute path to destination file in user's project
70
+ * @param {string} bmadFolderName - User's chosen bmad folder name (default: 'bmad')
71
+ * @returns {Promise<void>} Resolves when file copy and transformation complete
72
+ * @sideeffects Writes transformed file to targetPath, creates parent directories if needed
73
+ * @edgecases Binary files bypass transformation, falls back to raw copy if UTF-8 read fails
74
+ * @calledby installCore(), installModule(), IDE installers during file vendoring
75
+ * @calls fs.readFile(), fs.writeFile(), fs.copy()
76
+ *
77
+
78
+ *
79
+ * 3. Document marker in instructions.md (if applicable)
76
80
  */
77
- async copyFileWithPlaceholderReplacement(sourcePath, targetPath, bmadFolderName) {
81
+ async copyFileWithPlaceholderReplacement(sourcePath, targetPath) {
78
82
  // List of text file extensions that should have placeholder replacement
79
- const textExtensions = ['.md', '.yaml', '.yml', '.txt', '.json', '.js', '.ts', '.html', '.css', '.sh', '.bat', '.csv'];
83
+ const textExtensions = ['.md', '.yaml', '.yml', '.txt', '.json', '.js', '.ts', '.html', '.css', '.sh', '.bat', '.csv', '.xml'];
80
84
  const ext = path.extname(sourcePath).toLowerCase();
81
85
 
82
86
  // Check if this is a text file that might contain placeholders
@@ -85,11 +89,6 @@ class Installer {
85
89
  // Read the file content
86
90
  let content = await fs.readFile(sourcePath, 'utf8');
87
91
 
88
- // Replace {bmad_folder} placeholder with actual folder name
89
- if (content.includes('{bmad_folder}')) {
90
- content = content.replaceAll('{bmad_folder}', bmadFolderName);
91
- }
92
-
93
92
  // Write to target with replaced content
94
93
  await fs.ensureDir(path.dirname(targetPath));
95
94
  await fs.writeFile(targetPath, content, 'utf8');
@@ -110,16 +109,24 @@ class Installer {
110
109
  * @param {boolean} isFullReinstall - Whether this is a full reinstall
111
110
  * @param {Array} previousIdes - Previously configured IDEs (for reinstalls)
112
111
  * @param {Array} preSelectedIdes - Pre-selected IDEs from early prompt (optional)
112
+ * @param {boolean} skipPrompts - Skip prompts and use defaults (for --yes flag)
113
113
  * @returns {Object} Tool/IDE selection and configurations
114
114
  */
115
- async collectToolConfigurations(projectDir, selectedModules, isFullReinstall = false, previousIdes = [], preSelectedIdes = null) {
115
+ async collectToolConfigurations(
116
+ projectDir,
117
+ selectedModules,
118
+ isFullReinstall = false,
119
+ previousIdes = [],
120
+ preSelectedIdes = null,
121
+ skipPrompts = false,
122
+ ) {
116
123
  // Use pre-selected IDEs if provided, otherwise prompt
117
124
  let toolConfig;
118
125
  if (preSelectedIdes === null) {
119
126
  // Fallback: prompt for tool selection (backwards compatibility)
120
127
  const { UI } = require('../../../lib/ui');
121
128
  const ui = new UI();
122
- toolConfig = await ui.promptToolSelection(projectDir, selectedModules);
129
+ toolConfig = await ui.promptToolSelection(projectDir);
123
130
  } else {
124
131
  // IDEs were already selected during initial prompts
125
132
  toolConfig = {
@@ -131,7 +138,7 @@ class Installer {
131
138
  // Check for already configured IDEs
132
139
  const { Detector } = require('./detector');
133
140
  const detector = new Detector();
134
- const bmadDir = path.join(projectDir, this.bmadFolderName || 'bmad');
141
+ const bmadDir = path.join(projectDir, BMAD_FOLDER_NAME);
135
142
 
136
143
  // During full reinstall, use the saved previous IDEs since bmad dir was deleted
137
144
  // Otherwise detect from existing installation
@@ -158,54 +165,40 @@ class Installer {
158
165
  }
159
166
 
160
167
  if (!toolConfig.skipIde && toolConfig.ides && toolConfig.ides.length > 0) {
168
+ // Ensure IDE manager is initialized
169
+ await this.ideManager.ensureInitialized();
170
+
161
171
  // Determine which IDEs are newly selected (not previously configured)
162
172
  const newlySelectedIdes = toolConfig.ides.filter((ide) => !previouslyConfiguredIdes.includes(ide));
163
173
 
164
174
  if (newlySelectedIdes.length > 0) {
165
- console.log('\n'); // Add spacing before IDE questions
166
-
175
+ // Collect configuration for IDEs that support it
167
176
  for (const ide of newlySelectedIdes) {
168
- // List of IDEs that have interactive prompts
169
- const needsPrompts = ['claude-code', 'github-copilot', 'roo', 'cline', 'auggie', 'codex', 'qwen', 'gemini'].includes(ide);
170
-
171
- if (needsPrompts) {
172
- // Get IDE handler and collect configuration
173
- try {
174
- // Dynamically load the IDE setup module
175
- const ideModule = require(`../ide/${ide}`);
176
-
177
- // Get the setup class (handle different export formats)
178
- let SetupClass;
179
- const className =
180
- ide
181
- .split('-')
182
- .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
183
- .join('') + 'Setup';
184
-
185
- if (ideModule[className]) {
186
- SetupClass = ideModule[className];
187
- } else if (ideModule.default) {
188
- SetupClass = ideModule.default;
189
- } else {
190
- // Skip if no setup class found
191
- continue;
192
- }
177
+ try {
178
+ const handler = this.ideManager.handlers.get(ide);
193
179
 
194
- const ideSetup = new SetupClass();
180
+ if (!handler) {
181
+ await prompts.log.warn(`Warning: IDE '${ide}' handler not found`);
182
+ continue;
183
+ }
195
184
 
196
- // Check if this IDE has a collectConfiguration method
197
- if (typeof ideSetup.collectConfiguration === 'function') {
198
- console.log(chalk.cyan(`\nConfiguring ${ide}...`));
199
- ideConfigurations[ide] = await ideSetup.collectConfiguration({
200
- selectedModules: selectedModules || [],
201
- projectDir,
202
- bmadDir,
203
- });
204
- }
205
- } catch {
206
- // IDE doesn't have a setup file or collectConfiguration method
207
- console.warn(chalk.yellow(`Warning: Could not load configuration for ${ide}`));
185
+ // Check if this IDE handler has a collectConfiguration method
186
+ // (custom installers like Codex, Kilo may have this)
187
+ if (typeof handler.collectConfiguration === 'function') {
188
+ await prompts.log.info(`Configuring ${ide}...`);
189
+ ideConfigurations[ide] = await handler.collectConfiguration({
190
+ selectedModules: selectedModules || [],
191
+ projectDir,
192
+ bmadDir,
193
+ skipPrompts,
194
+ });
195
+ } else {
196
+ // Config-driven IDEs don't need configuration - mark as ready
197
+ ideConfigurations[ide] = { _noConfigNeeded: true };
208
198
  }
199
+ } catch (error) {
200
+ // IDE doesn't support configuration or has an error
201
+ await prompts.log.warn(`Warning: Could not load configuration for ${ide}: ${error.message}`);
209
202
  }
210
203
  }
211
204
  }
@@ -213,7 +206,7 @@ class Installer {
213
206
  // Log which IDEs are already configured and being kept
214
207
  const keptIdes = toolConfig.ides.filter((ide) => previouslyConfiguredIdes.includes(ide));
215
208
  if (keptIdes.length > 0) {
216
- console.log(chalk.dim(`\nKeeping existing configuration for: ${keptIdes.join(', ')}`));
209
+ await prompts.log.message(`Keeping existing configuration for: ${keptIdes.join(', ')}`);
217
210
  }
218
211
  }
219
212
 
@@ -233,20 +226,30 @@ class Installer {
233
226
  * @param {string[]} config.ides - IDEs to configure
234
227
  * @param {boolean} config.skipIde - Skip IDE configuration
235
228
  */
236
- async install(config) {
237
- // Display BMAD logo
238
- CLIUtils.displayLogo();
229
+ async install(originalConfig) {
230
+ // Clone config to avoid mutating the caller's object
231
+ const config = { ...originalConfig };
232
+
233
+ // Check if core config was already collected in UI
234
+ const hasCoreConfig = config.coreConfig && Object.keys(config.coreConfig).length > 0;
239
235
 
240
- // Display welcome message
241
- CLIUtils.displaySection('BMAD™ Installation', 'Version ' + require(path.join(getProjectRoot(), 'package.json')).version);
236
+ // Only display logo if core config wasn't already collected (meaning we're not continuing from UI)
237
+ if (!hasCoreConfig) {
238
+ // Display BMAD logo
239
+ await CLIUtils.displayLogo();
240
+
241
+ // Display welcome message
242
+ await CLIUtils.displaySection('BMad™ Installation', 'Version ' + require(path.join(getProjectRoot(), 'package.json')).version);
243
+ }
242
244
 
243
245
  // Note: Legacy V4 detection now happens earlier in UI.promptInstall()
244
246
  // before any config collection, so we don't need to check again here
245
247
 
246
248
  const projectDir = path.resolve(config.directory);
249
+ const bmadDir = path.join(projectDir, BMAD_FOLDER_NAME);
247
250
 
248
251
  // If core config was pre-collected (from interactive mode), use it
249
- if (config.coreConfig) {
252
+ if (config.coreConfig && Object.keys(config.coreConfig).length > 0) {
250
253
  this.configCollector.collectedConfig.core = config.coreConfig;
251
254
  // Also store in allAnswers for cross-referencing
252
255
  this.configCollector.allAnswers = {};
@@ -257,190 +260,307 @@ class Installer {
257
260
 
258
261
  // Collect configurations for modules (skip if quick update already collected them)
259
262
  let moduleConfigs;
263
+ let customModulePaths = new Map();
264
+
260
265
  if (config._quickUpdate) {
261
266
  // Quick update already collected all configs, use them directly
262
267
  moduleConfigs = this.configCollector.collectedConfig;
263
- } else {
264
- // Regular install - collect configurations (core was already collected in UI.promptInstall if interactive)
265
- moduleConfigs = await this.configCollector.collectAllConfigurations(config.modules || [], path.resolve(config.directory));
266
- }
267
-
268
- // Get bmad_folder from config (default to 'bmad' for backwards compatibility)
269
- const bmadFolderName = moduleConfigs.core && moduleConfigs.core.bmad_folder ? moduleConfigs.core.bmad_folder : 'bmad';
270
- this.bmadFolderName = bmadFolderName; // Store for use in other methods
271
-
272
- // Set bmad folder name on module manager and IDE manager for placeholder replacement
273
- this.moduleManager.setBmadFolderName(bmadFolderName);
274
- this.ideManager.setBmadFolderName(bmadFolderName);
275
268
 
276
- // Tool selection will be collected after we determine if it's a reinstall/update/new install
269
+ // For quick update, populate customModulePaths from _customModuleSources
270
+ if (config._customModuleSources) {
271
+ for (const [moduleId, customInfo] of config._customModuleSources) {
272
+ customModulePaths.set(moduleId, customInfo.sourcePath);
273
+ }
274
+ }
275
+ } else {
276
+ // For regular updates (modify flow), check manifest for custom module sources
277
+ if (config._isUpdate && config._existingInstall && config._existingInstall.customModules) {
278
+ for (const customModule of config._existingInstall.customModules) {
279
+ // Ensure we have an absolute sourcePath
280
+ let absoluteSourcePath = customModule.sourcePath;
281
+
282
+ // Check if sourcePath is a cache-relative path (starts with _config)
283
+ if (absoluteSourcePath && absoluteSourcePath.startsWith('_config')) {
284
+ // Convert cache-relative path to absolute path
285
+ absoluteSourcePath = path.join(bmadDir, absoluteSourcePath);
286
+ }
287
+ // If no sourcePath but we have relativePath, convert it
288
+ else if (!absoluteSourcePath && customModule.relativePath) {
289
+ // relativePath is relative to the project root (parent of bmad dir)
290
+ absoluteSourcePath = path.resolve(projectDir, customModule.relativePath);
291
+ }
292
+ // Ensure sourcePath is absolute for anything else
293
+ else if (absoluteSourcePath && !path.isAbsolute(absoluteSourcePath)) {
294
+ absoluteSourcePath = path.resolve(absoluteSourcePath);
295
+ }
277
296
 
278
- const spinner = ora('Preparing installation...').start();
297
+ if (absoluteSourcePath) {
298
+ customModulePaths.set(customModule.id, absoluteSourcePath);
299
+ }
300
+ }
301
+ }
279
302
 
280
- try {
281
- // Resolve target directory (path.resolve handles platform differences)
282
- const projectDir = path.resolve(config.directory);
303
+ // Build custom module paths map from customContent
283
304
 
284
- // Check if bmad_folder has changed from existing installation (only if project dir exists)
285
- let existingBmadDir = null;
286
- let existingBmadFolderName = null;
305
+ // Handle selectedFiles (from existing install path or manual directory input)
306
+ if (config.customContent && config.customContent.selected && config.customContent.selectedFiles) {
307
+ const customHandler = new CustomHandler();
308
+ for (const customFile of config.customContent.selectedFiles) {
309
+ const customInfo = await customHandler.getCustomInfo(customFile, path.resolve(config.directory));
310
+ if (customInfo && customInfo.id) {
311
+ customModulePaths.set(customInfo.id, customInfo.path);
312
+ }
313
+ }
314
+ }
287
315
 
288
- if (await fs.pathExists(projectDir)) {
289
- existingBmadDir = await this.findBmadDir(projectDir);
290
- existingBmadFolderName = path.basename(existingBmadDir);
316
+ // Handle new custom content sources from UI
317
+ if (config.customContent && config.customContent.sources) {
318
+ for (const source of config.customContent.sources) {
319
+ customModulePaths.set(source.id, source.path);
320
+ }
291
321
  }
292
322
 
293
- const targetBmadDir = path.join(projectDir, bmadFolderName);
323
+ // Handle cachedModules (from new install path where modules are cached)
324
+ // Only include modules that were actually selected for installation
325
+ if (config.customContent && config.customContent.cachedModules) {
326
+ // Get selected cached module IDs (if available)
327
+ const selectedCachedIds = config.customContent.selectedCachedModules || [];
328
+ // If no selection info, include all cached modules (for backward compatibility)
329
+ const shouldIncludeAll = selectedCachedIds.length === 0 && config.customContent.selected;
330
+
331
+ for (const cachedModule of config.customContent.cachedModules) {
332
+ // For cached modules, the path is the cachePath which contains the module.yaml
333
+ if (
334
+ cachedModule.id &&
335
+ cachedModule.cachePath && // Include if selected or if we should include all
336
+ (shouldIncludeAll || selectedCachedIds.includes(cachedModule.id))
337
+ ) {
338
+ customModulePaths.set(cachedModule.id, cachedModule.cachePath);
339
+ }
340
+ }
341
+ }
294
342
 
295
- // If bmad_folder changed during update/upgrade, back up old folder and do fresh install
296
- if (existingBmadDir && (await fs.pathExists(existingBmadDir)) && existingBmadFolderName !== bmadFolderName) {
297
- spinner.stop();
298
- console.log(chalk.yellow(`\n⚠️ bmad_folder has changed: ${existingBmadFolderName} → ${bmadFolderName}`));
299
- console.log(chalk.yellow('This will result in a fresh installation to the new folder.'));
343
+ // Get list of all modules including custom modules
344
+ // Order: core first, then official modules, then custom modules
345
+ const allModulesForConfig = ['core'];
300
346
 
301
- const inquirer = require('inquirer');
302
- const { confirmFreshInstall } = await inquirer.prompt([
303
- {
304
- type: 'confirm',
305
- name: 'confirmFreshInstall',
306
- message: chalk.cyan('Proceed with fresh install? (Your old folder will be backed up)'),
307
- default: true,
308
- },
309
- ]);
347
+ // Add official modules (excluding core and any custom modules)
348
+ const officialModules = (config.modules || []).filter((m) => m !== 'core' && !customModulePaths.has(m));
349
+ allModulesForConfig.push(...officialModules);
310
350
 
311
- if (!confirmFreshInstall) {
312
- console.log(chalk.yellow('Installation cancelled.'));
313
- return { success: false, cancelled: true };
351
+ // Add custom modules at the end
352
+ for (const [moduleId] of customModulePaths) {
353
+ if (!allModulesForConfig.includes(moduleId)) {
354
+ allModulesForConfig.push(moduleId);
314
355
  }
356
+ }
315
357
 
316
- spinner.start('Backing up existing installation...');
317
-
318
- // Find a unique backup name
319
- let backupDir = `${existingBmadDir}-bak`;
320
- let counter = 1;
321
- while (await fs.pathExists(backupDir)) {
322
- backupDir = `${existingBmadDir}-bak-${counter}`;
323
- counter++;
324
- }
358
+ // Check if core was already collected in UI
359
+ if (config.coreConfig && Object.keys(config.coreConfig).length > 0) {
360
+ // Core already collected, skip it in config collection
361
+ const modulesWithoutCore = allModulesForConfig.filter((m) => m !== 'core');
362
+ moduleConfigs = await this.configCollector.collectAllConfigurations(modulesWithoutCore, path.resolve(config.directory), {
363
+ customModulePaths,
364
+ skipPrompts: config.skipPrompts,
365
+ });
366
+ } else {
367
+ // Core not collected yet, include it
368
+ moduleConfigs = await this.configCollector.collectAllConfigurations(allModulesForConfig, path.resolve(config.directory), {
369
+ customModulePaths,
370
+ skipPrompts: config.skipPrompts,
371
+ });
372
+ }
373
+ }
325
374
 
326
- // Rename the old folder to backup
327
- await fs.move(existingBmadDir, backupDir);
375
+ // Set bmad folder name on module manager and IDE manager for placeholder replacement
376
+ this.moduleManager.setBmadFolderName(BMAD_FOLDER_NAME);
377
+ this.moduleManager.setCoreConfig(moduleConfigs.core || {});
378
+ this.moduleManager.setCustomModulePaths(customModulePaths);
379
+ this.ideManager.setBmadFolderName(BMAD_FOLDER_NAME);
328
380
 
329
- spinner.succeed(`Backed up ${existingBmadFolderName} ${path.basename(backupDir)}`);
330
- console.log(chalk.cyan('\n📋 Important:'));
331
- console.log(chalk.dim(` - Your old installation has been backed up to: ${path.basename(backupDir)}`));
332
- console.log(chalk.dim(` - If you had custom agents or configurations, copy them from:`));
333
- console.log(chalk.dim(` ${path.basename(backupDir)}/_cfg/`));
334
- console.log(chalk.dim(` - To the new location:`));
335
- console.log(chalk.dim(` ${bmadFolderName}/_cfg/`));
336
- console.log('');
381
+ // Tool selection will be collected after we determine if it's a reinstall/update/new install
337
382
 
338
- spinner.start('Starting fresh installation...');
339
- }
383
+ const spinner = await prompts.spinner();
384
+ spinner.start('Preparing installation...');
340
385
 
386
+ try {
341
387
  // Create a project directory if it doesn't exist (user already confirmed)
342
388
  if (!(await fs.pathExists(projectDir))) {
343
- spinner.text = 'Creating installation directory...';
389
+ spinner.message('Creating installation directory...');
344
390
  try {
345
391
  // fs.ensureDir handles platform-specific directory creation
346
392
  // It will recursively create all necessary parent directories
347
393
  await fs.ensureDir(projectDir);
348
394
  } catch (error) {
349
- spinner.fail('Failed to create installation directory');
350
- console.error(chalk.red(`Error: ${error.message}`));
395
+ spinner.error('Failed to create installation directory');
396
+ await prompts.log.error(`Error: ${error.message}`);
351
397
  // More detailed error for common issues
352
398
  if (error.code === 'EACCES') {
353
- console.error(chalk.red('Permission denied. Check parent directory permissions.'));
399
+ await prompts.log.error('Permission denied. Check parent directory permissions.');
354
400
  } else if (error.code === 'ENOSPC') {
355
- console.error(chalk.red('No space left on device.'));
401
+ await prompts.log.error('No space left on device.');
356
402
  }
357
403
  throw new Error(`Cannot create directory: ${projectDir}`);
358
404
  }
359
405
  }
360
406
 
361
- const bmadDir = path.join(projectDir, bmadFolderName);
362
-
363
407
  // Check existing installation
364
- spinner.text = 'Checking for existing installation...';
408
+ spinner.message('Checking for existing installation...');
365
409
  const existingInstall = await this.detector.detect(bmadDir);
366
410
 
367
411
  if (existingInstall.installed && !config.force && !config._quickUpdate) {
368
- spinner.stop();
412
+ spinner.stop('Existing installation detected');
369
413
 
370
414
  // Check if user already decided what to do (from early menu in ui.js)
371
415
  let action = null;
372
- if (config._requestedReinstall) {
373
- action = 'reinstall';
374
- } else if (config.actionType === 'update') {
416
+ if (config.actionType === 'update') {
417
+ action = 'update';
418
+ } else if (config.skipPrompts) {
419
+ // Non-interactive mode: default to update
375
420
  action = 'update';
376
421
  } else {
377
422
  // Fallback: Ask the user (backwards compatibility for other code paths)
378
- console.log(chalk.yellow('\n⚠️ Existing BMAD installation detected'));
379
- console.log(chalk.dim(` Location: ${bmadDir}`));
380
- console.log(chalk.dim(` Version: ${existingInstall.version}`));
423
+ await prompts.log.warn('Existing BMAD installation detected');
424
+ await prompts.log.message(` Location: ${bmadDir}`);
425
+ await prompts.log.message(` Version: ${existingInstall.version}`);
381
426
 
382
427
  const promptResult = await this.promptUpdateAction();
383
428
  action = promptResult.action;
384
429
  }
385
430
 
386
- if (action === 'cancel') {
387
- console.log('Installation cancelled.');
388
- return { success: false, cancelled: true };
389
- }
431
+ if (action === 'update') {
432
+ // Store that we're updating for later processing
433
+ config._isUpdate = true;
434
+ config._existingInstall = existingInstall;
390
435
 
391
- if (action === 'reinstall') {
392
- // Warn about destructive operation
393
- console.log(chalk.red.bold('\n⚠️ WARNING: This is a destructive operation!'));
394
- console.log(chalk.red('All custom files and modifications in the bmad directory will be lost.'));
395
-
396
- const inquirer = require('inquirer');
397
- const { confirmReinstall } = await inquirer.prompt([
398
- {
399
- type: 'confirm',
400
- name: 'confirmReinstall',
401
- message: chalk.yellow('Are you sure you want to delete and reinstall?'),
402
- default: false,
403
- },
404
- ]);
436
+ // Detect modules that were previously installed but are NOT in the new selection (to be removed)
437
+ const previouslyInstalledModules = new Set(existingInstall.modules.map((m) => m.id));
438
+ const newlySelectedModules = new Set(config.modules || []);
439
+
440
+ // Find modules to remove (installed but not in new selection)
441
+ // Exclude 'core' from being removable
442
+ const modulesToRemove = [...previouslyInstalledModules].filter((m) => !newlySelectedModules.has(m) && m !== 'core');
443
+
444
+ // If there are modules to remove, ask for confirmation
445
+ if (modulesToRemove.length > 0) {
446
+ if (config.skipPrompts) {
447
+ // Non-interactive mode: preserve modules (matches prompt default: false)
448
+ for (const moduleId of modulesToRemove) {
449
+ if (!config.modules) config.modules = [];
450
+ config.modules.push(moduleId);
451
+ }
452
+ spinner.start('Preparing update...');
453
+ } else {
454
+ if (spinner.isSpinning) {
455
+ spinner.stop('Module changes reviewed');
456
+ }
405
457
 
406
- if (!confirmReinstall) {
407
- console.log('Installation cancelled.');
408
- return { success: false, cancelled: true };
409
- }
458
+ await prompts.log.warn('Modules to be removed:');
459
+ for (const moduleId of modulesToRemove) {
460
+ const moduleInfo = existingInstall.modules.find((m) => m.id === moduleId);
461
+ const displayName = moduleInfo?.name || moduleId;
462
+ const modulePath = path.join(bmadDir, moduleId);
463
+ await prompts.log.error(` - ${displayName} (${modulePath})`);
464
+ }
410
465
 
411
- // Remember previously configured IDEs before deleting
412
- config._previouslyConfiguredIdes = existingInstall.ides || [];
466
+ const confirmRemoval = await prompts.confirm({
467
+ message: `Remove ${modulesToRemove.length} module(s) from BMAD installation?`,
468
+ default: false,
469
+ });
413
470
 
414
- // Remove existing installation
415
- await fs.remove(bmadDir);
416
- console.log(chalk.green('✓ Removed existing installation\n'));
471
+ if (confirmRemoval) {
472
+ // Remove module folders
473
+ for (const moduleId of modulesToRemove) {
474
+ const modulePath = path.join(bmadDir, moduleId);
475
+ try {
476
+ if (await fs.pathExists(modulePath)) {
477
+ await fs.remove(modulePath);
478
+ await prompts.log.message(` Removed: ${moduleId}`);
479
+ }
480
+ } catch (error) {
481
+ await prompts.log.warn(` Warning: Failed to remove ${moduleId}: ${error.message}`);
482
+ }
483
+ }
484
+ await prompts.log.success(` Removed ${modulesToRemove.length} module(s)`);
485
+ } else {
486
+ await prompts.log.message(' Module removal cancelled');
487
+ // Add the modules back to the selection since user cancelled removal
488
+ for (const moduleId of modulesToRemove) {
489
+ if (!config.modules) config.modules = [];
490
+ config.modules.push(moduleId);
491
+ }
492
+ }
417
493
 
418
- // Mark this as a full reinstall so we re-collect IDE configurations
419
- config._isFullReinstall = true;
420
- } else if (action === 'update') {
421
- // Store that we're updating for later processing
422
- config._isUpdate = true;
423
- config._existingInstall = existingInstall;
494
+ spinner.start('Preparing update...');
495
+ }
496
+ }
424
497
 
425
498
  // Detect custom and modified files BEFORE updating (compare current files vs files-manifest.csv)
426
499
  const existingFilesManifest = await this.readFilesManifest(bmadDir);
427
- console.log(chalk.dim(`DEBUG: Read ${existingFilesManifest.length} files from manifest`));
428
- console.log(chalk.dim(`DEBUG: Manifest has hashes: ${existingFilesManifest.some((f) => f.hash)}`));
429
-
430
500
  const { customFiles, modifiedFiles } = await this.detectCustomFiles(bmadDir, existingFilesManifest);
431
501
 
432
- console.log(chalk.dim(`DEBUG: Found ${customFiles.length} custom files, ${modifiedFiles.length} modified files`));
433
- if (modifiedFiles.length > 0) {
434
- console.log(chalk.yellow('DEBUG: Modified files:'));
435
- for (const f of modifiedFiles) console.log(chalk.dim(` - ${f.path}`));
436
- }
437
-
438
502
  config._customFiles = customFiles;
439
503
  config._modifiedFiles = modifiedFiles;
440
504
 
505
+ // Preserve existing core configuration during updates
506
+ // Read the current core config.yaml to maintain user's settings
507
+ const coreConfigPath = path.join(bmadDir, 'core', 'config.yaml');
508
+ if ((await fs.pathExists(coreConfigPath)) && (!config.coreConfig || Object.keys(config.coreConfig).length === 0)) {
509
+ try {
510
+ const yaml = require('yaml');
511
+ const coreConfigContent = await fs.readFile(coreConfigPath, 'utf8');
512
+ const existingCoreConfig = yaml.parse(coreConfigContent);
513
+
514
+ // Store in config.coreConfig so it's preserved through the installation
515
+ config.coreConfig = existingCoreConfig;
516
+
517
+ // Also store in configCollector for use during config collection
518
+ this.configCollector.collectedConfig.core = existingCoreConfig;
519
+ } catch (error) {
520
+ await prompts.log.warn(`Warning: Could not read existing core config: ${error.message}`);
521
+ }
522
+ }
523
+
524
+ // Also check cache directory for custom modules (like quick update does)
525
+ const cacheDir = path.join(bmadDir, '_config', 'custom');
526
+ if (await fs.pathExists(cacheDir)) {
527
+ const cachedModules = await fs.readdir(cacheDir, { withFileTypes: true });
528
+
529
+ for (const cachedModule of cachedModules) {
530
+ const moduleId = cachedModule.name;
531
+ const cachedPath = path.join(cacheDir, moduleId);
532
+
533
+ // Skip if path doesn't exist (broken symlink, deleted dir) - avoids lstat ENOENT
534
+ if (!(await fs.pathExists(cachedPath)) || !cachedModule.isDirectory()) {
535
+ continue;
536
+ }
537
+
538
+ // Skip if we already have this module from manifest
539
+ if (customModulePaths.has(moduleId)) {
540
+ continue;
541
+ }
542
+
543
+ // Check if this is an external official module - skip cache for those
544
+ const isExternal = await this.moduleManager.isExternalModule(moduleId);
545
+ if (isExternal) {
546
+ // External modules are handled via cloneExternalModule, not from cache
547
+ continue;
548
+ }
549
+
550
+ // Check if this is actually a custom module (has module.yaml)
551
+ const moduleYamlPath = path.join(cachedPath, 'module.yaml');
552
+ if (await fs.pathExists(moduleYamlPath)) {
553
+ customModulePaths.set(moduleId, cachedPath);
554
+ }
555
+ }
556
+
557
+ // Update module manager with the new custom module paths from cache
558
+ this.moduleManager.setCustomModulePaths(customModulePaths);
559
+ }
560
+
441
561
  // If there are custom files, back them up temporarily
442
562
  if (customFiles.length > 0) {
443
- const tempBackupDir = path.join(projectDir, '.bmad-custom-backup-temp');
563
+ const tempBackupDir = path.join(projectDir, '_bmad-custom-backup-temp');
444
564
  await fs.ensureDir(tempBackupDir);
445
565
 
446
566
  spinner.start(`Backing up ${customFiles.length} custom files...`);
@@ -450,35 +570,31 @@ class Installer {
450
570
  await fs.ensureDir(path.dirname(backupPath));
451
571
  await fs.copy(customFile, backupPath);
452
572
  }
453
- spinner.succeed(`Backed up ${customFiles.length} custom files`);
573
+ spinner.stop(`Backed up ${customFiles.length} custom files`);
454
574
 
455
575
  config._tempBackupDir = tempBackupDir;
456
576
  }
457
577
 
458
578
  // For modified files, back them up to temp directory (will be restored as .bak files after install)
459
579
  if (modifiedFiles.length > 0) {
460
- const tempModifiedBackupDir = path.join(projectDir, '.bmad-modified-backup-temp');
580
+ const tempModifiedBackupDir = path.join(projectDir, '_bmad-modified-backup-temp');
461
581
  await fs.ensureDir(tempModifiedBackupDir);
462
582
 
463
- console.log(chalk.yellow(`\nDEBUG: Backing up ${modifiedFiles.length} modified files to temp location`));
464
583
  spinner.start(`Backing up ${modifiedFiles.length} modified files...`);
465
584
  for (const modifiedFile of modifiedFiles) {
466
585
  const relativePath = path.relative(bmadDir, modifiedFile.path);
467
586
  const tempBackupPath = path.join(tempModifiedBackupDir, relativePath);
468
- console.log(chalk.dim(`DEBUG: Backing up ${relativePath} to temp`));
469
587
  await fs.ensureDir(path.dirname(tempBackupPath));
470
588
  await fs.copy(modifiedFile.path, tempBackupPath, { overwrite: true });
471
589
  }
472
- spinner.succeed(`Backed up ${modifiedFiles.length} modified files`);
590
+ spinner.stop(`Backed up ${modifiedFiles.length} modified files`);
473
591
 
474
592
  config._tempModifiedBackupDir = tempModifiedBackupDir;
475
- } else {
476
- console.log(chalk.dim('DEBUG: No modified files detected'));
477
593
  }
478
594
  }
479
595
  } else if (existingInstall.installed && config._quickUpdate) {
480
596
  // Quick update mode - automatically treat as update without prompting
481
- spinner.text = 'Preparing quick update...';
597
+ spinner.message('Preparing quick update...');
482
598
  config._isUpdate = true;
483
599
  config._existingInstall = existingInstall;
484
600
 
@@ -489,9 +605,46 @@ class Installer {
489
605
  config._customFiles = customFiles;
490
606
  config._modifiedFiles = modifiedFiles;
491
607
 
608
+ // Also check cache directory for custom modules (like quick update does)
609
+ const cacheDir = path.join(bmadDir, '_config', 'custom');
610
+ if (await fs.pathExists(cacheDir)) {
611
+ const cachedModules = await fs.readdir(cacheDir, { withFileTypes: true });
612
+
613
+ for (const cachedModule of cachedModules) {
614
+ const moduleId = cachedModule.name;
615
+ const cachedPath = path.join(cacheDir, moduleId);
616
+
617
+ // Skip if path doesn't exist (broken symlink, deleted dir) - avoids lstat ENOENT
618
+ if (!(await fs.pathExists(cachedPath)) || !cachedModule.isDirectory()) {
619
+ continue;
620
+ }
621
+
622
+ // Skip if we already have this module from manifest
623
+ if (customModulePaths.has(moduleId)) {
624
+ continue;
625
+ }
626
+
627
+ // Check if this is an external official module - skip cache for those
628
+ const isExternal = await this.moduleManager.isExternalModule(moduleId);
629
+ if (isExternal) {
630
+ // External modules are handled via cloneExternalModule, not from cache
631
+ continue;
632
+ }
633
+
634
+ // Check if this is actually a custom module (has module.yaml)
635
+ const moduleYamlPath = path.join(cachedPath, 'module.yaml');
636
+ if (await fs.pathExists(moduleYamlPath)) {
637
+ customModulePaths.set(moduleId, cachedPath);
638
+ }
639
+ }
640
+
641
+ // Update module manager with the new custom module paths from cache
642
+ this.moduleManager.setCustomModulePaths(customModulePaths);
643
+ }
644
+
492
645
  // Back up custom files
493
646
  if (customFiles.length > 0) {
494
- const tempBackupDir = path.join(projectDir, '.bmad-custom-backup-temp');
647
+ const tempBackupDir = path.join(projectDir, '_bmad-custom-backup-temp');
495
648
  await fs.ensureDir(tempBackupDir);
496
649
 
497
650
  spinner.start(`Backing up ${customFiles.length} custom files...`);
@@ -501,13 +654,13 @@ class Installer {
501
654
  await fs.ensureDir(path.dirname(backupPath));
502
655
  await fs.copy(customFile, backupPath);
503
656
  }
504
- spinner.succeed(`Backed up ${customFiles.length} custom files`);
657
+ spinner.stop(`Backed up ${customFiles.length} custom files`);
505
658
  config._tempBackupDir = tempBackupDir;
506
659
  }
507
660
 
508
661
  // Back up modified files
509
662
  if (modifiedFiles.length > 0) {
510
- const tempModifiedBackupDir = path.join(projectDir, '.bmad-modified-backup-temp');
663
+ const tempModifiedBackupDir = path.join(projectDir, '_bmad-modified-backup-temp');
511
664
  await fs.ensureDir(tempModifiedBackupDir);
512
665
 
513
666
  spinner.start(`Backing up ${modifiedFiles.length} modified files...`);
@@ -517,14 +670,14 @@ class Installer {
517
670
  await fs.ensureDir(path.dirname(tempBackupPath));
518
671
  await fs.copy(modifiedFile.path, tempBackupPath, { overwrite: true });
519
672
  }
520
- spinner.succeed(`Backed up ${modifiedFiles.length} modified files`);
673
+ spinner.stop(`Backed up ${modifiedFiles.length} modified files`);
521
674
  config._tempModifiedBackupDir = tempModifiedBackupDir;
522
675
  }
523
676
  }
524
677
 
525
678
  // Now collect tool configurations after we know if it's a reinstall
526
679
  // Skip for quick update since we already have the IDE list
527
- spinner.stop();
680
+ spinner.stop('Pre-checks complete');
528
681
  let toolSelection;
529
682
  if (config._quickUpdate) {
530
683
  // Quick update already has IDEs configured, use saved configurations
@@ -547,13 +700,15 @@ class Installer {
547
700
  } else {
548
701
  // Pass pre-selected IDEs from early prompt (if available)
549
702
  // This allows IDE selection to happen before file copying, improving UX
550
- const preSelectedIdes = config.ides && config.ides.length > 0 ? config.ides : null;
703
+ // Use config.ides if it's an array (even if empty), null means prompt
704
+ const preSelectedIdes = Array.isArray(config.ides) ? config.ides : null;
551
705
  toolSelection = await this.collectToolConfigurations(
552
706
  path.resolve(config.directory),
553
707
  config.modules,
554
708
  config._isFullReinstall || false,
555
709
  config._previouslyConfiguredIdes || [],
556
710
  preSelectedIdes,
711
+ config.skipPrompts || false,
557
712
  );
558
713
  }
559
714
 
@@ -562,356 +717,802 @@ class Installer {
562
717
  config.skipIde = toolSelection.skipIde;
563
718
  const ideConfigurations = toolSelection.configurations;
564
719
 
565
- spinner.start('Continuing installation...');
566
-
567
- // Create bmad directory structure
568
- spinner.text = 'Creating directory structure...';
569
- await this.createDirectoryStructure(bmadDir);
570
-
571
- // Resolve dependencies for selected modules
572
- spinner.text = 'Resolving dependencies...';
573
- const projectRoot = getProjectRoot();
574
- const modulesToInstall = config.installCore ? ['core', ...config.modules] : config.modules;
575
-
576
- // For dependency resolution, we need to pass the project root
577
- const resolution = await this.dependencyResolver.resolve(projectRoot, config.modules || [], { verbose: config.verbose });
578
-
579
- if (config.verbose) {
580
- spinner.succeed('Dependencies resolved');
581
- } else {
582
- spinner.succeed('Dependencies resolved');
583
- }
720
+ // Detect IDEs that were previously installed but are NOT in the new selection (to be removed)
721
+ if (config._isUpdate && config._existingInstall) {
722
+ const previouslyInstalledIdes = new Set(config._existingInstall.ides || []);
723
+ const newlySelectedIdes = new Set(config.ides || []);
724
+
725
+ const idesToRemove = [...previouslyInstalledIdes].filter((ide) => !newlySelectedIdes.has(ide));
726
+
727
+ if (idesToRemove.length > 0) {
728
+ if (config.skipPrompts) {
729
+ // Non-interactive mode: silently preserve existing IDE configs
730
+ if (!config.ides) config.ides = [];
731
+ const savedIdeConfigs = await this.ideConfigManager.loadAllIdeConfigs(bmadDir);
732
+ for (const ide of idesToRemove) {
733
+ config.ides.push(ide);
734
+ if (savedIdeConfigs[ide] && !ideConfigurations[ide]) {
735
+ ideConfigurations[ide] = savedIdeConfigs[ide];
736
+ }
737
+ }
738
+ } else {
739
+ if (spinner.isSpinning) {
740
+ spinner.stop('IDE changes reviewed');
741
+ }
584
742
 
585
- // Install core if requested or if dependencies require it
586
- if (config.installCore || resolution.byModule.core) {
587
- spinner.start('Installing BMAD core...');
588
- await this.installCoreWithDependencies(bmadDir, resolution.byModule.core);
589
- spinner.succeed('Core installed');
590
- }
743
+ await prompts.log.warn('IDEs to be removed:');
744
+ for (const ide of idesToRemove) {
745
+ await prompts.log.error(` - ${ide}`);
746
+ }
591
747
 
592
- // Install modules with their dependencies
593
- if (config.modules && config.modules.length > 0) {
594
- for (const moduleName of config.modules) {
595
- spinner.start(`Installing module: ${moduleName}...`);
596
- await this.installModuleWithDependencies(moduleName, bmadDir, resolution.byModule[moduleName]);
597
- spinner.succeed(`Module installed: ${moduleName}`);
598
- }
748
+ const confirmRemoval = await prompts.confirm({
749
+ message: `Remove BMAD configuration for ${idesToRemove.length} IDE(s)?`,
750
+ default: false,
751
+ });
599
752
 
600
- // Install partial modules (only dependencies)
601
- for (const [module, files] of Object.entries(resolution.byModule)) {
602
- if (!config.modules.includes(module) && module !== 'core') {
603
- const totalFiles =
604
- files.agents.length +
605
- files.tasks.length +
606
- files.tools.length +
607
- files.templates.length +
608
- files.data.length +
609
- files.other.length;
610
- if (totalFiles > 0) {
611
- spinner.start(`Installing ${module} dependencies...`);
612
- await this.installPartialModule(module, bmadDir, files);
613
- spinner.succeed(`${module} dependencies installed`);
753
+ if (confirmRemoval) {
754
+ await this.ideManager.ensureInitialized();
755
+ for (const ide of idesToRemove) {
756
+ try {
757
+ const handler = this.ideManager.handlers.get(ide);
758
+ if (handler) {
759
+ await handler.cleanup(projectDir);
760
+ }
761
+ await this.ideConfigManager.deleteIdeConfig(bmadDir, ide);
762
+ await prompts.log.message(` Removed: ${ide}`);
763
+ } catch (error) {
764
+ await prompts.log.warn(` Warning: Failed to remove ${ide}: ${error.message}`);
765
+ }
766
+ }
767
+ await prompts.log.success(` Removed ${idesToRemove.length} IDE(s)`);
768
+ } else {
769
+ await prompts.log.message(' IDE removal cancelled');
770
+ // Add IDEs back to selection and restore their saved configurations
771
+ if (!config.ides) config.ides = [];
772
+ const savedIdeConfigs = await this.ideConfigManager.loadAllIdeConfigs(bmadDir);
773
+ for (const ide of idesToRemove) {
774
+ config.ides.push(ide);
775
+ if (savedIdeConfigs[ide] && !ideConfigurations[ide]) {
776
+ ideConfigurations[ide] = savedIdeConfigs[ide];
777
+ }
778
+ }
614
779
  }
780
+
781
+ spinner.start('Preparing installation...');
615
782
  }
616
783
  }
617
784
  }
618
785
 
619
- // Generate clean config.yaml files for each installed module
620
- spinner.start('Generating module configurations...');
621
- await this.generateModuleConfigs(bmadDir, moduleConfigs);
622
- spinner.succeed('Module configurations generated');
786
+ // Results collector for consolidated summary
787
+ const results = [];
788
+ const addResult = (step, status, detail = '') => results.push({ step, status, detail });
623
789
 
624
- // Create agent configuration files
625
- // Note: Legacy createAgentConfigs removed - using YAML customize system instead
626
- // Customize templates are now created in processAgentFiles when building YAML agents
790
+ if (spinner.isSpinning) {
791
+ spinner.message('Preparing installation...');
792
+ } else {
793
+ spinner.start('Preparing installation...');
794
+ }
627
795
 
628
- // Pre-register manifest files that will be created (except files-manifest.csv to avoid recursion)
629
- const cfgDir = path.join(bmadDir, '_cfg');
630
- this.installedFiles.push(
631
- path.join(cfgDir, 'manifest.yaml'),
632
- path.join(cfgDir, 'workflow-manifest.csv'),
633
- path.join(cfgDir, 'agent-manifest.csv'),
634
- path.join(cfgDir, 'task-manifest.csv'),
635
- );
796
+ // Create bmad directory structure
797
+ spinner.message('Creating directory structure...');
798
+ await this.createDirectoryStructure(bmadDir);
636
799
 
637
- // Generate CSV manifests for workflows, agents, tasks AND ALL FILES with hashes BEFORE IDE setup
638
- spinner.start('Generating workflow and agent manifests...');
639
- const manifestGen = new ManifestGenerator();
800
+ // Cache custom modules if any
801
+ if (customModulePaths && customModulePaths.size > 0) {
802
+ spinner.message('Caching custom modules...');
803
+ const { CustomModuleCache } = require('./custom-module-cache');
804
+ const customCache = new CustomModuleCache(bmadDir);
640
805
 
641
- // Include preserved modules (from quick update) in the manifest
642
- const allModulesToList = config._preserveModules ? [...(config.modules || []), ...config._preserveModules] : config.modules || [];
806
+ for (const [moduleId, sourcePath] of customModulePaths) {
807
+ const cachedInfo = await customCache.cacheModule(moduleId, sourcePath, {
808
+ sourcePath: sourcePath, // Store original path for updates
809
+ });
643
810
 
644
- const manifestStats = await manifestGen.generateManifests(bmadDir, config.modules || [], this.installedFiles, {
645
- ides: config.ides || [],
646
- preservedModules: config._preserveModules || [], // Scan these from installed bmad/ dir
647
- });
811
+ // Update the customModulePaths to use the cached location
812
+ customModulePaths.set(moduleId, cachedInfo.cachePath);
813
+ }
648
814
 
649
- spinner.succeed(
650
- `Manifests generated: ${manifestStats.workflows} workflows, ${manifestStats.agents} agents, ${manifestStats.tasks} tasks, ${manifestStats.tools} tools, ${manifestStats.files} files`,
651
- );
815
+ // Update module manager with the cached paths
816
+ this.moduleManager.setCustomModulePaths(customModulePaths);
817
+ addResult('Custom modules cached', 'ok');
818
+ }
652
819
 
653
- // Configure IDEs and copy documentation
654
- if (!config.skipIde && config.ides && config.ides.length > 0) {
655
- // Filter out any undefined/null values from the IDE list
656
- const validIdes = config.ides.filter((ide) => ide && typeof ide === 'string');
820
+ const projectRoot = getProjectRoot();
657
821
 
658
- if (validIdes.length === 0) {
659
- console.log(chalk.yellow('⚠️ No valid IDEs selected. Skipping IDE configuration.'));
660
- } else {
661
- // Check if any IDE might need prompting (no pre-collected config)
662
- const needsPrompting = validIdes.some((ide) => !ideConfigurations[ide]);
822
+ // Custom content is already handled in UI before module selection
823
+ const finalCustomContent = config.customContent;
663
824
 
664
- if (!needsPrompting) {
665
- spinner.start('Configuring IDEs...');
666
- }
825
+ // Prepare modules list including cached custom modules
826
+ let allModules = [...(config.modules || [])];
667
827
 
668
- // Temporarily suppress console output if not verbose
669
- const originalLog = console.log;
670
- if (!config.verbose) {
671
- console.log = () => {};
828
+ // During quick update, we might have custom module sources from the manifest
829
+ if (config._customModuleSources) {
830
+ // Add custom modules from stored sources
831
+ for (const [moduleId, customInfo] of config._customModuleSources) {
832
+ if (!allModules.includes(moduleId) && (await fs.pathExists(customInfo.sourcePath))) {
833
+ allModules.push(moduleId);
672
834
  }
835
+ }
836
+ }
673
837
 
674
- for (const ide of validIdes) {
675
- // Only show spinner if we have pre-collected config (no prompts expected)
676
- if (ideConfigurations[ide] && !needsPrompting) {
677
- spinner.text = `Configuring ${ide}...`;
678
- } else if (!ideConfigurations[ide]) {
679
- // Stop spinner before prompting
680
- if (spinner.isSpinning) {
681
- spinner.stop();
682
- }
683
- console.log(chalk.cyan(`\nConfiguring ${ide}...`));
684
- }
685
-
686
- // Pass pre-collected configuration to avoid re-prompting
687
- await this.ideManager.setup(ide, projectDir, bmadDir, {
688
- selectedModules: config.modules || [],
689
- preCollectedConfig: ideConfigurations[ide] || null,
690
- verbose: config.verbose,
691
- });
692
-
693
- // Save IDE configuration for future updates
694
- if (ideConfigurations[ide] && !ideConfigurations[ide]._alreadyConfigured) {
695
- await this.ideConfigManager.saveIdeConfig(bmadDir, ide, ideConfigurations[ide]);
696
- }
697
-
698
- // Restart spinner if we stopped it
699
- if (!ideConfigurations[ide] && !spinner.isSpinning) {
700
- spinner.start('Configuring IDEs...');
701
- }
838
+ // Add cached custom modules
839
+ if (finalCustomContent && finalCustomContent.cachedModules) {
840
+ for (const cachedModule of finalCustomContent.cachedModules) {
841
+ if (!allModules.includes(cachedModule.id)) {
842
+ allModules.push(cachedModule.id);
702
843
  }
844
+ }
845
+ }
703
846
 
704
- // Restore console.log
705
- console.log = originalLog;
706
-
707
- if (spinner.isSpinning) {
708
- spinner.succeed(`Configured ${validIdes.length} IDE${validIdes.length > 1 ? 's' : ''}`);
709
- } else {
710
- console.log(chalk.green(`✓ Configured ${validIdes.length} IDE${validIdes.length > 1 ? 's' : ''}`));
847
+ // Regular custom content from user input (non-cached)
848
+ if (finalCustomContent && finalCustomContent.selected && finalCustomContent.selectedFiles) {
849
+ // Add custom modules to the installation list
850
+ const customHandler = new CustomHandler();
851
+ for (const customFile of finalCustomContent.selectedFiles) {
852
+ const customInfo = await customHandler.getCustomInfo(customFile, projectDir);
853
+ if (customInfo && customInfo.id) {
854
+ allModules.push(customInfo.id);
711
855
  }
712
856
  }
857
+ }
713
858
 
714
- // Copy IDE-specific documentation (only for valid IDEs)
715
- const validIdesForDocs = (config.ides || []).filter((ide) => ide && typeof ide === 'string');
716
- if (validIdesForDocs.length > 0) {
717
- spinner.start('Copying IDE documentation...');
718
- await this.copyIdeDocumentation(validIdesForDocs, bmadDir);
719
- spinner.succeed('IDE documentation copied');
720
- }
859
+ // Don't include core again if already installed
860
+ if (config.installCore) {
861
+ allModules = allModules.filter((m) => m !== 'core');
721
862
  }
722
863
 
723
- // Run module-specific installers after IDE setup
724
- spinner.start('Running module-specific installers...');
864
+ // For dependency resolution, we only need regular modules (not custom modules)
865
+ // Custom modules are already installed in _bmad and don't need dependency resolution from source
866
+ const regularModulesForResolution = allModules.filter((module) => {
867
+ // Check if this is a custom module
868
+ const isCustom =
869
+ customModulePaths.has(module) ||
870
+ (finalCustomContent && finalCustomContent.cachedModules && finalCustomContent.cachedModules.some((cm) => cm.id === module)) ||
871
+ (finalCustomContent &&
872
+ finalCustomContent.selected &&
873
+ finalCustomContent.selectedFiles &&
874
+ finalCustomContent.selectedFiles.some((f) => f.includes(module)));
875
+ return !isCustom;
876
+ });
877
+
878
+ // Stop spinner before tasks() takes over progress display
879
+ spinner.stop('Preparation complete');
880
+
881
+ // ─────────────────────────────────────────────────────────────────────────
882
+ // FIRST TASKS BLOCK: Core installation through manifests (non-interactive)
883
+ // ─────────────────────────────────────────────────────────────────────────
884
+ const isQuickUpdate = config._quickUpdate || false;
725
885
 
726
- // Run core module installer if core was installed
727
- if (config.installCore || resolution.byModule.core) {
728
- spinner.text = 'Running core module installer...';
886
+ // Shared resolution result across task callbacks (closure-scoped, not on `this`)
887
+ let taskResolution;
729
888
 
730
- await this.moduleManager.runModuleInstaller('core', bmadDir, {
731
- installedIDEs: config.ides || [],
732
- moduleConfig: moduleConfigs.core || {},
733
- logger: {
734
- log: (msg) => console.log(msg),
735
- error: (msg) => console.error(msg),
736
- warn: (msg) => console.warn(msg),
889
+ // Collect directory creation results for output after tasks() completes
890
+ const dirResults = { createdDirs: [], movedDirs: [], createdWdsFolders: [] };
891
+
892
+ // Build task list conditionally
893
+ const installTasks = [];
894
+
895
+ // Core installation task
896
+ if (config.installCore) {
897
+ installTasks.push({
898
+ title: isQuickUpdate ? 'Updating BMAD core' : 'Installing BMAD core',
899
+ task: async (message) => {
900
+ await this.installCoreWithDependencies(bmadDir, { core: {} });
901
+ addResult('Core', 'ok', isQuickUpdate ? 'updated' : 'installed');
902
+ await this.generateModuleConfigs(bmadDir, { core: config.coreConfig || {} });
903
+ return isQuickUpdate ? 'Core updated' : 'Core installed';
737
904
  },
738
905
  });
739
906
  }
740
907
 
741
- // Run installers for user-selected modules
742
- if (config.modules && config.modules.length > 0) {
743
- for (const moduleName of config.modules) {
744
- spinner.text = `Running ${moduleName} module installer...`;
908
+ // Dependency resolution task
909
+ installTasks.push({
910
+ title: 'Resolving dependencies',
911
+ task: async (message) => {
912
+ // Create a temporary module manager that knows about custom content locations
913
+ const tempModuleManager = new ModuleManager({
914
+ bmadDir: bmadDir,
915
+ });
745
916
 
746
- // Pass installed IDEs and module config to module installer
747
- await this.moduleManager.runModuleInstaller(moduleName, bmadDir, {
748
- installedIDEs: config.ides || [],
749
- moduleConfig: moduleConfigs[moduleName] || {},
750
- logger: {
751
- log: (msg) => console.log(msg),
752
- error: (msg) => console.error(msg),
753
- warn: (msg) => console.warn(msg),
754
- },
917
+ taskResolution = await this.dependencyResolver.resolve(projectRoot, regularModulesForResolution, {
918
+ verbose: config.verbose,
919
+ moduleManager: tempModuleManager,
755
920
  });
756
- }
757
- }
921
+ return 'Dependencies resolved';
922
+ },
923
+ });
924
+
925
+ // Module installation task
926
+ if (allModules && allModules.length > 0) {
927
+ installTasks.push({
928
+ title: isQuickUpdate ? `Updating ${allModules.length} module(s)` : `Installing ${allModules.length} module(s)`,
929
+ task: async (message) => {
930
+ const resolution = taskResolution;
931
+ const installedModuleNames = new Set();
932
+
933
+ for (const moduleName of allModules) {
934
+ if (installedModuleNames.has(moduleName)) continue;
935
+ installedModuleNames.add(moduleName);
936
+
937
+ message(`${isQuickUpdate ? 'Updating' : 'Installing'} ${moduleName}...`);
938
+
939
+ // Check if this is a custom module
940
+ let isCustomModule = false;
941
+ let customInfo = null;
942
+
943
+ // First check if we have a cached version
944
+ if (finalCustomContent && finalCustomContent.cachedModules) {
945
+ const cachedModule = finalCustomContent.cachedModules.find((m) => m.id === moduleName);
946
+ if (cachedModule) {
947
+ isCustomModule = true;
948
+ customInfo = { id: moduleName, path: cachedModule.cachePath, config: {} };
949
+ }
950
+ }
758
951
 
759
- spinner.succeed('Module-specific installers completed');
952
+ // Then check custom module sources from manifest (for quick update)
953
+ if (!isCustomModule && config._customModuleSources && config._customModuleSources.has(moduleName)) {
954
+ customInfo = config._customModuleSources.get(moduleName);
955
+ isCustomModule = true;
956
+ if (customInfo.sourcePath && !customInfo.path) {
957
+ customInfo.path = path.isAbsolute(customInfo.sourcePath)
958
+ ? customInfo.sourcePath
959
+ : path.join(bmadDir, customInfo.sourcePath);
960
+ }
961
+ }
760
962
 
761
- // Note: Manifest files are already created by ManifestGenerator above
762
- // No need to create legacy manifest.csv anymore
963
+ // Finally check regular custom content
964
+ if (!isCustomModule && finalCustomContent && finalCustomContent.selected && finalCustomContent.selectedFiles) {
965
+ const customHandler = new CustomHandler();
966
+ for (const customFile of finalCustomContent.selectedFiles) {
967
+ const info = await customHandler.getCustomInfo(customFile, projectDir);
968
+ if (info && info.id === moduleName) {
969
+ isCustomModule = true;
970
+ customInfo = info;
971
+ break;
972
+ }
973
+ }
974
+ }
763
975
 
764
- // If this was an update, restore custom files
765
- let customFiles = [];
766
- let modifiedFiles = [];
767
- if (config._isUpdate) {
768
- if (config._customFiles && config._customFiles.length > 0) {
769
- spinner.start(`Restoring ${config._customFiles.length} custom files...`);
976
+ if (isCustomModule && customInfo) {
977
+ if (!customModulePaths.has(moduleName) && customInfo.path) {
978
+ customModulePaths.set(moduleName, customInfo.path);
979
+ this.moduleManager.setCustomModulePaths(customModulePaths);
980
+ }
770
981
 
771
- for (const originalPath of config._customFiles) {
772
- const relativePath = path.relative(bmadDir, originalPath);
773
- const backupPath = path.join(config._tempBackupDir, relativePath);
982
+ const collectedModuleConfig = moduleConfigs[moduleName] || {};
983
+ await this.moduleManager.install(
984
+ moduleName,
985
+ bmadDir,
986
+ (filePath) => {
987
+ this.installedFiles.add(filePath);
988
+ },
989
+ {
990
+ isCustom: true,
991
+ moduleConfig: collectedModuleConfig,
992
+ isQuickUpdate: isQuickUpdate,
993
+ installer: this,
994
+ silent: true,
995
+ },
996
+ );
997
+ await this.generateModuleConfigs(bmadDir, {
998
+ [moduleName]: { ...config.coreConfig, ...customInfo.config, ...collectedModuleConfig },
999
+ });
1000
+ } else {
1001
+ if (!resolution || !resolution.byModule) {
1002
+ addResult(`Module: ${moduleName}`, 'warn', 'skipped (no resolution data)');
1003
+ continue;
1004
+ }
1005
+ if (moduleName === 'core') {
1006
+ await this.installCoreWithDependencies(bmadDir, resolution.byModule[moduleName]);
1007
+ } else {
1008
+ await this.installModuleWithDependencies(moduleName, bmadDir, resolution.byModule[moduleName]);
1009
+ }
1010
+ }
774
1011
 
775
- if (await fs.pathExists(backupPath)) {
776
- await fs.ensureDir(path.dirname(originalPath));
777
- await fs.copy(backupPath, originalPath, { overwrite: true });
1012
+ addResult(`Module: ${moduleName}`, 'ok', isQuickUpdate ? 'updated' : 'installed');
778
1013
  }
779
- }
780
1014
 
781
- // Clean up temp backup
782
- if (config._tempBackupDir && (await fs.pathExists(config._tempBackupDir))) {
783
- await fs.remove(config._tempBackupDir);
784
- }
785
-
786
- spinner.succeed(`Restored ${config._customFiles.length} custom files`);
787
- customFiles = config._customFiles;
788
- }
1015
+ // Install partial modules (only dependencies)
1016
+ if (!resolution || !resolution.byModule) {
1017
+ return `${allModules.length} module(s) ${isQuickUpdate ? 'updated' : 'installed'}`;
1018
+ }
1019
+ for (const [module, files] of Object.entries(resolution.byModule)) {
1020
+ if (!allModules.includes(module) && module !== 'core') {
1021
+ const totalFiles =
1022
+ files.agents.length +
1023
+ files.tasks.length +
1024
+ files.tools.length +
1025
+ files.templates.length +
1026
+ files.data.length +
1027
+ files.other.length;
1028
+ if (totalFiles > 0) {
1029
+ message(`Installing ${module} dependencies...`);
1030
+ await this.installPartialModule(module, bmadDir, files);
1031
+ }
1032
+ }
1033
+ }
789
1034
 
790
- if (config._modifiedFiles && config._modifiedFiles.length > 0) {
791
- modifiedFiles = config._modifiedFiles;
1035
+ return `${allModules.length} module(s) ${isQuickUpdate ? 'updated' : 'installed'}`;
1036
+ },
1037
+ });
1038
+ }
792
1039
 
793
- // Restore modified files as .bak files
794
- if (config._tempModifiedBackupDir && (await fs.pathExists(config._tempModifiedBackupDir))) {
795
- spinner.start(`Restoring ${modifiedFiles.length} modified files as .bak...`);
1040
+ // Module directory creation task
1041
+ installTasks.push({
1042
+ title: 'Creating module directories',
1043
+ task: async (message) => {
1044
+ const resolution = taskResolution;
1045
+ if (!resolution || !resolution.byModule) {
1046
+ addResult('Module directories', 'warn', 'no resolution data');
1047
+ return 'Module directories skipped (no resolution data)';
1048
+ }
1049
+ const verboseMode = process.env.BMAD_VERBOSE_INSTALL === 'true' || config.verbose;
1050
+ const moduleLogger = {
1051
+ log: async (msg) => (verboseMode ? await prompts.log.message(msg) : undefined),
1052
+ error: async (msg) => await prompts.log.error(msg),
1053
+ warn: async (msg) => await prompts.log.warn(msg),
1054
+ };
796
1055
 
797
- for (const modifiedFile of modifiedFiles) {
798
- const relativePath = path.relative(bmadDir, modifiedFile.path);
799
- const tempBackupPath = path.join(config._tempModifiedBackupDir, relativePath);
800
- const bakPath = modifiedFile.path + '.bak';
1056
+ // Core module directories
1057
+ if (config.installCore || resolution.byModule.core) {
1058
+ const result = await this.moduleManager.createModuleDirectories('core', bmadDir, {
1059
+ installedIDEs: config.ides || [],
1060
+ moduleConfig: moduleConfigs.core || {},
1061
+ existingModuleConfig: this.configCollector.existingConfig?.core || {},
1062
+ coreConfig: moduleConfigs.core || {},
1063
+ logger: moduleLogger,
1064
+ silent: true,
1065
+ });
1066
+ if (result) {
1067
+ dirResults.createdDirs.push(...result.createdDirs);
1068
+ dirResults.movedDirs.push(...(result.movedDirs || []));
1069
+ dirResults.createdWdsFolders.push(...result.createdWdsFolders);
1070
+ }
1071
+ }
801
1072
 
802
- if (await fs.pathExists(tempBackupPath)) {
803
- await fs.ensureDir(path.dirname(bakPath));
804
- await fs.copy(tempBackupPath, bakPath, { overwrite: true });
1073
+ // User-selected module directories
1074
+ if (config.modules && config.modules.length > 0) {
1075
+ for (const moduleName of config.modules) {
1076
+ message(`Setting up ${moduleName}...`);
1077
+ const result = await this.moduleManager.createModuleDirectories(moduleName, bmadDir, {
1078
+ installedIDEs: config.ides || [],
1079
+ moduleConfig: moduleConfigs[moduleName] || {},
1080
+ existingModuleConfig: this.configCollector.existingConfig?.[moduleName] || {},
1081
+ coreConfig: moduleConfigs.core || {},
1082
+ logger: moduleLogger,
1083
+ silent: true,
1084
+ });
1085
+ if (result) {
1086
+ dirResults.createdDirs.push(...result.createdDirs);
1087
+ dirResults.movedDirs.push(...(result.movedDirs || []));
1088
+ dirResults.createdWdsFolders.push(...result.createdWdsFolders);
805
1089
  }
806
1090
  }
1091
+ }
807
1092
 
808
- // Clean up temp backup
809
- await fs.remove(config._tempModifiedBackupDir);
1093
+ addResult('Module directories', 'ok');
1094
+ return 'Module directories created';
1095
+ },
1096
+ });
810
1097
 
811
- spinner.succeed(`Restored ${modifiedFiles.length} modified files as .bak`);
1098
+ // Configuration generation task (stored as named reference for deferred execution)
1099
+ const configTask = {
1100
+ title: 'Generating configurations',
1101
+ task: async (message) => {
1102
+ // Generate clean config.yaml files for each installed module
1103
+ await this.generateModuleConfigs(bmadDir, moduleConfigs);
1104
+ addResult('Configurations', 'ok', 'generated');
1105
+
1106
+ // Pre-register manifest files
1107
+ const cfgDir = path.join(bmadDir, '_config');
1108
+ this.installedFiles.add(path.join(cfgDir, 'manifest.yaml'));
1109
+ this.installedFiles.add(path.join(cfgDir, 'workflow-manifest.csv'));
1110
+ this.installedFiles.add(path.join(cfgDir, 'agent-manifest.csv'));
1111
+ this.installedFiles.add(path.join(cfgDir, 'task-manifest.csv'));
1112
+
1113
+ // Generate CSV manifests for workflows, agents, tasks AND ALL FILES with hashes
1114
+ // This must happen BEFORE mergeModuleHelpCatalogs because it depends on agent-manifest.csv
1115
+ message('Generating manifests...');
1116
+ const manifestGen = new ManifestGenerator();
1117
+
1118
+ const allModulesForManifest = config._quickUpdate
1119
+ ? config._existingModules || allModules || []
1120
+ : config._preserveModules
1121
+ ? [...allModules, ...config._preserveModules]
1122
+ : allModules || [];
1123
+
1124
+ let modulesForCsvPreserve;
1125
+ if (config._quickUpdate) {
1126
+ modulesForCsvPreserve = config._existingModules || allModules || [];
1127
+ } else {
1128
+ modulesForCsvPreserve = config._preserveModules ? [...allModules, ...config._preserveModules] : allModules;
812
1129
  }
813
- }
814
- }
815
1130
 
816
- spinner.stop();
1131
+ const manifestStats = await manifestGen.generateManifests(bmadDir, allModulesForManifest, [...this.installedFiles], {
1132
+ ides: config.ides || [],
1133
+ preservedModules: modulesForCsvPreserve,
1134
+ });
817
1135
 
818
- // Report custom and modified files if any were found
819
- if (customFiles.length > 0) {
820
- console.log(chalk.cyan(`\n📁 Custom files preserved: ${customFiles.length}`));
821
- console.log(chalk.dim('The following custom files were found and restored:\n'));
822
- for (const file of customFiles) {
823
- console.log(chalk.dim(` - ${path.relative(bmadDir, file)}`));
824
- }
825
- console.log('');
826
- }
1136
+ addResult(
1137
+ 'Manifests',
1138
+ 'ok',
1139
+ `${manifestStats.workflows} workflows, ${manifestStats.agents} agents, ${manifestStats.tasks} tasks, ${manifestStats.tools} tools`,
1140
+ );
827
1141
 
828
- if (modifiedFiles.length > 0) {
829
- console.log(chalk.yellow(`\n⚠️ Modified files detected: ${modifiedFiles.length}`));
830
- console.log(chalk.dim('The following files were modified and backed up with .bak extension:\n'));
831
- for (const file of modifiedFiles) {
832
- console.log(chalk.dim(` - ${file.relativePath} → ${file.relativePath}.bak`));
833
- }
834
- console.log(chalk.dim('\nThese files have been updated with the new version.'));
835
- console.log(chalk.dim('Review the .bak files to see your changes and merge if needed.\n'));
836
- }
1142
+ // Merge help catalogs
1143
+ message('Generating help catalog...');
1144
+ await this.mergeModuleHelpCatalogs(bmadDir);
1145
+ addResult('Help catalog', 'ok');
837
1146
 
838
- // Display completion message
839
- const { UI } = require('../../../lib/ui');
840
- const ui = new UI();
841
- ui.showInstallSummary({
842
- path: bmadDir,
843
- modules: config.modules,
844
- ides: config.ides,
845
- customFiles: customFiles.length > 0 ? customFiles : undefined,
846
- });
1147
+ return 'Configurations generated';
1148
+ },
1149
+ };
1150
+ installTasks.push(configTask);
847
1151
 
848
- return { success: true, path: bmadDir, modules: config.modules, ides: config.ides };
849
- } catch (error) {
850
- spinner.fail('Installation failed');
851
- throw error;
1152
+ // Run all tasks except config (which runs after directory output)
1153
+ const mainTasks = installTasks.filter((t) => t !== configTask);
1154
+ await prompts.tasks(mainTasks);
1155
+
1156
+ // Render directory creation output right after directory task
1157
+ const color = await prompts.getColor();
1158
+ if (dirResults.movedDirs.length > 0) {
1159
+ const lines = dirResults.movedDirs.map((d) => ` ${d}`).join('\n');
1160
+ await prompts.log.message(color.cyan(`Moved directories:\n${lines}`));
1161
+ }
1162
+ if (dirResults.createdDirs.length > 0) {
1163
+ const lines = dirResults.createdDirs.map((d) => ` ${d}`).join('\n');
1164
+ await prompts.log.message(color.yellow(`Created directories:\n${lines}`));
1165
+ }
1166
+ if (dirResults.createdWdsFolders.length > 0) {
1167
+ const lines = dirResults.createdWdsFolders.map((f) => color.dim(` \u2713 ${f}/`)).join('\n');
1168
+ await prompts.log.message(color.cyan(`Created WDS folder structure:\n${lines}`));
1169
+ }
1170
+
1171
+ // Now run configuration generation
1172
+ await prompts.tasks([configTask]);
1173
+
1174
+ // Resolution is now available via closure-scoped taskResolution
1175
+ const resolution = taskResolution;
1176
+
1177
+ // ─────────────────────────────────────────────────────────────────────────
1178
+ // IDE SETUP: Keep as spinner since it may prompt for user input
1179
+ // ─────────────────────────────────────────────────────────────────────────
1180
+ if (!config.skipIde && config.ides && config.ides.length > 0) {
1181
+ await this.ideManager.ensureInitialized();
1182
+ const validIdes = config.ides.filter((ide) => ide && typeof ide === 'string');
1183
+
1184
+ if (validIdes.length === 0) {
1185
+ addResult('IDE configuration', 'warn', 'no valid IDEs selected');
1186
+ } else {
1187
+ const needsPrompting = validIdes.some((ide) => !ideConfigurations[ide]);
1188
+ const ideSpinner = await prompts.spinner();
1189
+ ideSpinner.start('Configuring tools...');
1190
+
1191
+ try {
1192
+ for (const ide of validIdes) {
1193
+ if (!needsPrompting || ideConfigurations[ide]) {
1194
+ ideSpinner.message(`Configuring ${ide}...`);
1195
+ } else {
1196
+ if (ideSpinner.isSpinning) {
1197
+ ideSpinner.stop('Ready for IDE configuration');
1198
+ }
1199
+ }
1200
+
1201
+ // Suppress stray console output for pre-configured IDEs (no user interaction)
1202
+ const ideHasConfig = Boolean(ideConfigurations[ide]);
1203
+ const originalLog = console.log;
1204
+ if (!config.verbose && ideHasConfig) {
1205
+ console.log = () => {};
1206
+ }
1207
+ try {
1208
+ const setupResult = await this.ideManager.setup(ide, projectDir, bmadDir, {
1209
+ selectedModules: allModules || [],
1210
+ preCollectedConfig: ideConfigurations[ide] || null,
1211
+ verbose: config.verbose,
1212
+ silent: ideHasConfig,
1213
+ });
1214
+
1215
+ if (ideConfigurations[ide] && !ideConfigurations[ide]._alreadyConfigured) {
1216
+ await this.ideConfigManager.saveIdeConfig(bmadDir, ide, ideConfigurations[ide]);
1217
+ }
1218
+
1219
+ if (setupResult.success) {
1220
+ addResult(ide, 'ok', setupResult.detail || '');
1221
+ } else {
1222
+ addResult(ide, 'error', setupResult.error || 'failed');
1223
+ }
1224
+ } finally {
1225
+ console.log = originalLog;
1226
+ }
1227
+
1228
+ if (needsPrompting && !ideSpinner.isSpinning) {
1229
+ ideSpinner.start('Configuring tools...');
1230
+ }
1231
+ }
1232
+ } finally {
1233
+ if (ideSpinner.isSpinning) {
1234
+ ideSpinner.stop('Tool configuration complete');
1235
+ }
1236
+ }
1237
+ }
1238
+ }
1239
+
1240
+ // ─────────────────────────────────────────────────────────────────────────
1241
+ // SECOND TASKS BLOCK: Post-IDE operations (non-interactive)
1242
+ // ─────────────────────────────────────────────────────────────────────────
1243
+ const postIdeTasks = [];
1244
+
1245
+ // File restoration task (only for updates)
1246
+ if (
1247
+ config._isUpdate &&
1248
+ ((config._customFiles && config._customFiles.length > 0) || (config._modifiedFiles && config._modifiedFiles.length > 0))
1249
+ ) {
1250
+ postIdeTasks.push({
1251
+ title: 'Finalizing installation',
1252
+ task: async (message) => {
1253
+ let customFiles = [];
1254
+ let modifiedFiles = [];
1255
+
1256
+ if (config._customFiles && config._customFiles.length > 0) {
1257
+ message(`Restoring ${config._customFiles.length} custom files...`);
1258
+
1259
+ for (const originalPath of config._customFiles) {
1260
+ const relativePath = path.relative(bmadDir, originalPath);
1261
+ const backupPath = path.join(config._tempBackupDir, relativePath);
1262
+
1263
+ if (await fs.pathExists(backupPath)) {
1264
+ await fs.ensureDir(path.dirname(originalPath));
1265
+ await fs.copy(backupPath, originalPath, { overwrite: true });
1266
+ }
1267
+ }
1268
+
1269
+ if (config._tempBackupDir && (await fs.pathExists(config._tempBackupDir))) {
1270
+ await fs.remove(config._tempBackupDir);
1271
+ }
1272
+
1273
+ customFiles = config._customFiles;
1274
+ }
1275
+
1276
+ if (config._modifiedFiles && config._modifiedFiles.length > 0) {
1277
+ modifiedFiles = config._modifiedFiles;
1278
+
1279
+ if (config._tempModifiedBackupDir && (await fs.pathExists(config._tempModifiedBackupDir))) {
1280
+ message(`Restoring ${modifiedFiles.length} modified files as .bak...`);
1281
+
1282
+ for (const modifiedFile of modifiedFiles) {
1283
+ const relativePath = path.relative(bmadDir, modifiedFile.path);
1284
+ const tempBackupPath = path.join(config._tempModifiedBackupDir, relativePath);
1285
+ const bakPath = modifiedFile.path + '.bak';
1286
+
1287
+ if (await fs.pathExists(tempBackupPath)) {
1288
+ await fs.ensureDir(path.dirname(bakPath));
1289
+ await fs.copy(tempBackupPath, bakPath, { overwrite: true });
1290
+ }
1291
+ }
1292
+
1293
+ await fs.remove(config._tempModifiedBackupDir);
1294
+ }
1295
+ }
1296
+
1297
+ // Store for summary access
1298
+ config._restoredCustomFiles = customFiles;
1299
+ config._restoredModifiedFiles = modifiedFiles;
1300
+
1301
+ return 'Installation finalized';
1302
+ },
1303
+ });
1304
+ }
1305
+
1306
+ await prompts.tasks(postIdeTasks);
1307
+
1308
+ // Retrieve restored file info for summary
1309
+ const customFiles = config._restoredCustomFiles || [];
1310
+ const modifiedFiles = config._restoredModifiedFiles || [];
1311
+
1312
+ // Render consolidated summary
1313
+ await this.renderInstallSummary(results, {
1314
+ bmadDir,
1315
+ modules: config.modules,
1316
+ ides: config.ides,
1317
+ customFiles: customFiles.length > 0 ? customFiles : undefined,
1318
+ modifiedFiles: modifiedFiles.length > 0 ? modifiedFiles : undefined,
1319
+ });
1320
+
1321
+ return {
1322
+ success: true,
1323
+ path: bmadDir,
1324
+ modules: config.modules,
1325
+ ides: config.ides,
1326
+ projectDir: projectDir,
1327
+ };
1328
+ } catch (error) {
1329
+ try {
1330
+ if (spinner.isSpinning) {
1331
+ spinner.error('Installation failed');
1332
+ } else {
1333
+ await prompts.log.error('Installation failed');
1334
+ }
1335
+ } catch {
1336
+ // Ensure the original error is never swallowed by a logging failure
1337
+ }
1338
+ throw error;
1339
+ }
1340
+ }
1341
+
1342
+ /**
1343
+ * Render a consolidated install summary using prompts.note()
1344
+ * @param {Array} results - Array of {step, status: 'ok'|'error'|'warn', detail}
1345
+ * @param {Object} context - {bmadDir, modules, ides, customFiles, modifiedFiles}
1346
+ */
1347
+ async renderInstallSummary(results, context = {}) {
1348
+ const color = await prompts.getColor();
1349
+
1350
+ // Build step lines with status indicators
1351
+ const lines = [];
1352
+ for (const r of results) {
1353
+ let icon;
1354
+ if (r.status === 'ok') {
1355
+ icon = color.green('\u2713');
1356
+ } else if (r.status === 'warn') {
1357
+ icon = color.yellow('!');
1358
+ } else {
1359
+ icon = color.red('\u2717');
1360
+ }
1361
+ const detail = r.detail ? color.dim(` (${r.detail})`) : '';
1362
+ lines.push(` ${icon} ${r.step}${detail}`);
852
1363
  }
1364
+
1365
+ // Context and warnings
1366
+ lines.push('');
1367
+ if (context.bmadDir) {
1368
+ lines.push(` Installed to: ${color.dim(context.bmadDir)}`);
1369
+ }
1370
+ if (context.customFiles && context.customFiles.length > 0) {
1371
+ lines.push(` ${color.cyan(`Custom files preserved: ${context.customFiles.length}`)}`);
1372
+ }
1373
+ if (context.modifiedFiles && context.modifiedFiles.length > 0) {
1374
+ lines.push(` ${color.yellow(`Modified files backed up (.bak): ${context.modifiedFiles.length}`)}`);
1375
+ }
1376
+
1377
+ // Next steps
1378
+ lines.push(
1379
+ '',
1380
+ ' Next steps:',
1381
+ ` Read our new Docs Site: ${color.dim('https://docs.bmad-method.org/')}`,
1382
+ ` Join our Discord: ${color.dim('https://discord.gg/gk8jAdXWmj')}`,
1383
+ ` Star us on GitHub: ${color.dim('https://github.com/bmad-code-org/BMAD-METHOD/')}`,
1384
+ ` Subscribe on YouTube: ${color.dim('https://www.youtube.com/@BMadCode')}`,
1385
+ ` Run ${color.cyan('/bmad-help')} with your IDE Agent and ask it how to get started`,
1386
+ );
1387
+
1388
+ await prompts.note(lines.join('\n'), 'BMAD is ready to use!');
853
1389
  }
854
1390
 
855
1391
  /**
856
1392
  * Update existing installation
857
1393
  */
858
1394
  async update(config) {
859
- const spinner = ora('Checking installation...').start();
1395
+ const spinner = await prompts.spinner();
1396
+ spinner.start('Checking installation...');
860
1397
 
861
1398
  try {
862
1399
  const projectDir = path.resolve(config.directory);
863
- const bmadDir = await this.findBmadDir(projectDir);
1400
+ const { bmadDir } = await this.findBmadDir(projectDir);
864
1401
  const existingInstall = await this.detector.detect(bmadDir);
865
1402
 
866
1403
  if (!existingInstall.installed) {
867
- spinner.fail('No BMAD installation found');
1404
+ spinner.stop('No BMAD installation found');
868
1405
  throw new Error(`No BMAD installation found at ${bmadDir}`);
869
1406
  }
870
1407
 
871
- spinner.text = 'Analyzing update requirements...';
1408
+ spinner.message('Analyzing update requirements...');
872
1409
 
873
1410
  // Compare versions and determine what needs updating
874
1411
  const currentVersion = existingInstall.version;
875
1412
  const newVersion = require(path.join(getProjectRoot(), 'package.json')).version;
876
1413
 
1414
+ // Check for custom modules with missing sources before update
1415
+ const customModuleSources = new Map();
1416
+
1417
+ // Check manifest for backward compatibility
1418
+ if (existingInstall.customModules) {
1419
+ for (const customModule of existingInstall.customModules) {
1420
+ customModuleSources.set(customModule.id, customModule);
1421
+ }
1422
+ }
1423
+
1424
+ // Also check cache directory
1425
+ const cacheDir = path.join(bmadDir, '_config', 'custom');
1426
+ if (await fs.pathExists(cacheDir)) {
1427
+ const cachedModules = await fs.readdir(cacheDir, { withFileTypes: true });
1428
+
1429
+ for (const cachedModule of cachedModules) {
1430
+ if (cachedModule.isDirectory()) {
1431
+ const moduleId = cachedModule.name;
1432
+
1433
+ // Skip if we already have this module
1434
+ if (customModuleSources.has(moduleId)) {
1435
+ continue;
1436
+ }
1437
+
1438
+ // Check if this is an external official module - skip cache for those
1439
+ const isExternal = await this.moduleManager.isExternalModule(moduleId);
1440
+ if (isExternal) {
1441
+ // External modules are handled via cloneExternalModule, not from cache
1442
+ continue;
1443
+ }
1444
+
1445
+ const cachedPath = path.join(cacheDir, moduleId);
1446
+
1447
+ // Check if this is actually a custom module (has module.yaml)
1448
+ const moduleYamlPath = path.join(cachedPath, 'module.yaml');
1449
+ if (await fs.pathExists(moduleYamlPath)) {
1450
+ customModuleSources.set(moduleId, {
1451
+ id: moduleId,
1452
+ name: moduleId,
1453
+ sourcePath: path.join('_config', 'custom', moduleId), // Relative path
1454
+ cached: true,
1455
+ });
1456
+ }
1457
+ }
1458
+ }
1459
+ }
1460
+
1461
+ if (customModuleSources.size > 0) {
1462
+ spinner.stop('Update analysis complete');
1463
+ await prompts.log.warn('Checking custom module sources before update...');
1464
+
1465
+ const projectRoot = getProjectRoot();
1466
+ await this.handleMissingCustomSources(
1467
+ customModuleSources,
1468
+ bmadDir,
1469
+ projectRoot,
1470
+ 'update',
1471
+ existingInstall.modules.map((m) => m.id),
1472
+ config.skipPrompts || false,
1473
+ );
1474
+
1475
+ spinner.start('Preparing update...');
1476
+ }
1477
+
877
1478
  if (config.dryRun) {
878
- spinner.stop();
879
- console.log(chalk.cyan('\n🔍 Update Preview (Dry Run)\n'));
880
- console.log(chalk.bold('Current version:'), currentVersion);
881
- console.log(chalk.bold('New version:'), newVersion);
882
- console.log(chalk.bold('Core:'), existingInstall.hasCore ? 'Will be updated' : 'Not installed');
1479
+ spinner.stop('Dry run analysis complete');
1480
+ let dryRunContent = `Current version: ${currentVersion}\n`;
1481
+ dryRunContent += `New version: ${newVersion}\n`;
1482
+ dryRunContent += `Core: ${existingInstall.hasCore ? 'Will be updated' : 'Not installed'}`;
883
1483
 
884
1484
  if (existingInstall.modules.length > 0) {
885
- console.log(chalk.bold('\nModules to update:'));
1485
+ dryRunContent += '\n\nModules to update:';
886
1486
  for (const mod of existingInstall.modules) {
887
- console.log(` - ${mod.id}`);
1487
+ dryRunContent += `\n - ${mod.id}`;
888
1488
  }
889
1489
  }
1490
+ await prompts.note(dryRunContent, 'Update Preview (Dry Run)');
890
1491
  return;
891
1492
  }
892
1493
 
893
1494
  // Perform actual update
894
1495
  if (existingInstall.hasCore) {
895
- spinner.text = 'Updating core...';
1496
+ spinner.message('Updating core...');
896
1497
  await this.updateCore(bmadDir, config.force);
897
1498
  }
898
1499
 
899
1500
  for (const module of existingInstall.modules) {
900
- spinner.text = `Updating module: ${module.id}...`;
901
- await this.moduleManager.update(module.id, bmadDir, config.force);
1501
+ spinner.message(`Updating module: ${module.id}...`);
1502
+ await this.moduleManager.update(module.id, bmadDir, config.force, { installer: this });
902
1503
  }
903
1504
 
904
1505
  // Update manifest
905
- spinner.text = 'Updating manifest...';
1506
+ spinner.message('Updating manifest...');
906
1507
  await this.manifest.update(bmadDir, {
907
1508
  version: newVersion,
908
1509
  updateDate: new Date().toISOString(),
909
1510
  });
910
1511
 
911
- spinner.succeed('Update complete');
1512
+ spinner.stop('Update complete');
912
1513
  return { success: true };
913
1514
  } catch (error) {
914
- spinner.fail('Update failed');
1515
+ spinner.error('Update failed');
915
1516
  throw error;
916
1517
  }
917
1518
  }
@@ -921,7 +1522,7 @@ class Installer {
921
1522
  */
922
1523
  async getStatus(directory) {
923
1524
  const projectDir = path.resolve(directory);
924
- const bmadDir = await this.findBmadDir(projectDir);
1525
+ const { bmadDir } = await this.findBmadDir(projectDir);
925
1526
  return await this.detector.detect(bmadDir);
926
1527
  }
927
1528
 
@@ -933,29 +1534,401 @@ class Installer {
933
1534
  }
934
1535
 
935
1536
  /**
936
- * Uninstall BMAD
1537
+ * Uninstall BMAD with selective removal options
1538
+ * @param {string} directory - Project directory
1539
+ * @param {Object} options - Uninstall options
1540
+ * @param {boolean} [options.removeModules=true] - Remove _bmad/ directory
1541
+ * @param {boolean} [options.removeIdeConfigs=true] - Remove IDE configurations
1542
+ * @param {boolean} [options.removeOutputFolder=false] - Remove user artifacts output folder
1543
+ * @returns {Object} Result with success status and removed components
937
1544
  */
938
- async uninstall(directory) {
1545
+ async uninstall(directory, options = {}) {
939
1546
  const projectDir = path.resolve(directory);
940
- const bmadDir = await this.findBmadDir(projectDir);
1547
+ const { bmadDir } = await this.findBmadDir(projectDir);
1548
+
1549
+ if (!(await fs.pathExists(bmadDir))) {
1550
+ return { success: false, reason: 'not-installed' };
1551
+ }
1552
+
1553
+ // 1. DETECT: Read state BEFORE deleting anything
1554
+ const existingInstall = await this.detector.detect(bmadDir);
1555
+ const outputFolder = await this._readOutputFolder(bmadDir);
1556
+
1557
+ const removed = { modules: false, ideConfigs: false, outputFolder: false };
1558
+
1559
+ // 2. IDE CLEANUP (before _bmad/ deletion so configs are accessible)
1560
+ if (options.removeIdeConfigs !== false) {
1561
+ await this.uninstallIdeConfigs(projectDir, existingInstall, { silent: options.silent });
1562
+ removed.ideConfigs = true;
1563
+ }
1564
+
1565
+ // 3. OUTPUT FOLDER (only if explicitly requested)
1566
+ if (options.removeOutputFolder === true && outputFolder) {
1567
+ removed.outputFolder = await this.uninstallOutputFolder(projectDir, outputFolder);
1568
+ }
1569
+
1570
+ // 4. BMAD DIRECTORY (last, after everything that needs it)
1571
+ if (options.removeModules !== false) {
1572
+ removed.modules = await this.uninstallModules(projectDir);
1573
+ }
1574
+
1575
+ return { success: true, removed, version: existingInstall.version };
1576
+ }
1577
+
1578
+ /**
1579
+ * Uninstall IDE configurations only
1580
+ * @param {string} projectDir - Project directory
1581
+ * @param {Object} existingInstall - Detection result from detector.detect()
1582
+ * @param {Object} [options] - Options (e.g. { silent: true })
1583
+ * @returns {Promise<Object>} Results from IDE cleanup
1584
+ */
1585
+ async uninstallIdeConfigs(projectDir, existingInstall, options = {}) {
1586
+ await this.ideManager.ensureInitialized();
1587
+ const cleanupOptions = { isUninstall: true, silent: options.silent };
1588
+ const ideList = existingInstall.ides || [];
1589
+ if (ideList.length > 0) {
1590
+ return this.ideManager.cleanupByList(projectDir, ideList, cleanupOptions);
1591
+ }
1592
+ return this.ideManager.cleanup(projectDir, cleanupOptions);
1593
+ }
941
1594
 
1595
+ /**
1596
+ * Remove user artifacts output folder
1597
+ * @param {string} projectDir - Project directory
1598
+ * @param {string} outputFolder - Output folder name (relative)
1599
+ * @returns {Promise<boolean>} Whether the folder was removed
1600
+ */
1601
+ async uninstallOutputFolder(projectDir, outputFolder) {
1602
+ if (!outputFolder) return false;
1603
+ const resolvedProject = path.resolve(projectDir);
1604
+ const outputPath = path.resolve(resolvedProject, outputFolder);
1605
+ if (!outputPath.startsWith(resolvedProject + path.sep)) {
1606
+ return false;
1607
+ }
1608
+ if (await fs.pathExists(outputPath)) {
1609
+ await fs.remove(outputPath);
1610
+ return true;
1611
+ }
1612
+ return false;
1613
+ }
1614
+
1615
+ /**
1616
+ * Remove the _bmad/ directory
1617
+ * @param {string} projectDir - Project directory
1618
+ * @returns {Promise<boolean>} Whether the directory was removed
1619
+ */
1620
+ async uninstallModules(projectDir) {
1621
+ const { bmadDir } = await this.findBmadDir(projectDir);
942
1622
  if (await fs.pathExists(bmadDir)) {
943
1623
  await fs.remove(bmadDir);
1624
+ return true;
1625
+ }
1626
+ return false;
1627
+ }
1628
+
1629
+ /**
1630
+ * Get the configured output folder name for a project
1631
+ * Resolves bmadDir internally from projectDir
1632
+ * @param {string} projectDir - Project directory
1633
+ * @returns {string} Output folder name (relative, default: '_bmad-output')
1634
+ */
1635
+ async getOutputFolder(projectDir) {
1636
+ const { bmadDir } = await this.findBmadDir(projectDir);
1637
+ return this._readOutputFolder(bmadDir);
1638
+ }
1639
+
1640
+ /**
1641
+ * Read the output_folder setting from module config files
1642
+ * Checks bmm/config.yaml first, then other module configs
1643
+ * @param {string} bmadDir - BMAD installation directory
1644
+ * @returns {string} Output folder path or default
1645
+ */
1646
+ async _readOutputFolder(bmadDir) {
1647
+ const yaml = require('yaml');
1648
+
1649
+ // Check bmm/config.yaml first (most common)
1650
+ const bmmConfigPath = path.join(bmadDir, 'bmm', 'config.yaml');
1651
+ if (await fs.pathExists(bmmConfigPath)) {
1652
+ try {
1653
+ const content = await fs.readFile(bmmConfigPath, 'utf8');
1654
+ const config = yaml.parse(content);
1655
+ if (config && config.output_folder) {
1656
+ // Strip {project-root}/ prefix if present
1657
+ return config.output_folder.replace(/^\{project-root\}[/\\]/, '');
1658
+ }
1659
+ } catch {
1660
+ // Fall through to other modules
1661
+ }
944
1662
  }
945
1663
 
946
- // Clean up IDE configurations
947
- await this.ideManager.cleanup(projectDir);
1664
+ // Scan other module config.yaml files
1665
+ try {
1666
+ const entries = await fs.readdir(bmadDir, { withFileTypes: true });
1667
+ for (const entry of entries) {
1668
+ if (!entry.isDirectory() || entry.name === 'bmm' || entry.name.startsWith('_')) continue;
1669
+ const configPath = path.join(bmadDir, entry.name, 'config.yaml');
1670
+ if (await fs.pathExists(configPath)) {
1671
+ try {
1672
+ const content = await fs.readFile(configPath, 'utf8');
1673
+ const config = yaml.parse(content);
1674
+ if (config && config.output_folder) {
1675
+ return config.output_folder.replace(/^\{project-root\}[/\\]/, '');
1676
+ }
1677
+ } catch {
1678
+ // Continue scanning
1679
+ }
1680
+ }
1681
+ }
1682
+ } catch {
1683
+ // Directory scan failed
1684
+ }
948
1685
 
949
- return { success: true };
1686
+ // Default fallback
1687
+ return '_bmad-output';
950
1688
  }
951
1689
 
952
1690
  /**
953
1691
  * Private: Create directory structure
954
1692
  */
1693
+ /**
1694
+ * Merge all module-help.csv files into a single bmad-help.csv
1695
+ * Scans all installed modules for module-help.csv and merges them
1696
+ * Enriches agent info from agent-manifest.csv
1697
+ * Output is written to _bmad/_config/bmad-help.csv
1698
+ * @param {string} bmadDir - BMAD installation directory
1699
+ */
1700
+ async mergeModuleHelpCatalogs(bmadDir) {
1701
+ const allRows = [];
1702
+ const headerRow =
1703
+ 'module,phase,name,code,sequence,workflow-file,command,required,agent-name,agent-command,agent-display-name,agent-title,options,description,output-location,outputs';
1704
+
1705
+ // Load agent manifest for agent info lookup
1706
+ const agentManifestPath = path.join(bmadDir, '_config', 'agent-manifest.csv');
1707
+ const agentInfo = new Map(); // agent-name -> {command, displayName, title+icon}
1708
+
1709
+ if (await fs.pathExists(agentManifestPath)) {
1710
+ const manifestContent = await fs.readFile(agentManifestPath, 'utf8');
1711
+ const lines = manifestContent.split('\n').filter((line) => line.trim());
1712
+
1713
+ for (const line of lines) {
1714
+ if (line.startsWith('name,')) continue; // Skip header
1715
+
1716
+ const cols = line.split(',');
1717
+ if (cols.length >= 4) {
1718
+ const agentName = cols[0].replaceAll('"', '').trim();
1719
+ const displayName = cols[1].replaceAll('"', '').trim();
1720
+ const title = cols[2].replaceAll('"', '').trim();
1721
+ const icon = cols[3].replaceAll('"', '').trim();
1722
+ const module = cols[10] ? cols[10].replaceAll('"', '').trim() : '';
1723
+
1724
+ // Build agent command: bmad:module:agent:name
1725
+ const agentCommand = module ? `bmad:${module}:agent:${agentName}` : `bmad:agent:${agentName}`;
1726
+
1727
+ agentInfo.set(agentName, {
1728
+ command: agentCommand,
1729
+ displayName: displayName || agentName,
1730
+ title: icon && title ? `${icon} ${title}` : title || agentName,
1731
+ });
1732
+ }
1733
+ }
1734
+ }
1735
+
1736
+ // Get all installed module directories
1737
+ const entries = await fs.readdir(bmadDir, { withFileTypes: true });
1738
+ const installedModules = entries
1739
+ .filter((entry) => entry.isDirectory() && entry.name !== '_config' && entry.name !== 'docs' && entry.name !== '_memory')
1740
+ .map((entry) => entry.name);
1741
+
1742
+ // Add core module to scan (it's installed at root level as _config, but we check src/core)
1743
+ const coreModulePath = getSourcePath('core');
1744
+ const modulePaths = new Map();
1745
+
1746
+ // Map all module source paths
1747
+ if (await fs.pathExists(coreModulePath)) {
1748
+ modulePaths.set('core', coreModulePath);
1749
+ }
1750
+
1751
+ // Map installed module paths
1752
+ for (const moduleName of installedModules) {
1753
+ const modulePath = path.join(bmadDir, moduleName);
1754
+ modulePaths.set(moduleName, modulePath);
1755
+ }
1756
+
1757
+ // Scan each module for module-help.csv
1758
+ for (const [moduleName, modulePath] of modulePaths) {
1759
+ const helpFilePath = path.join(modulePath, 'module-help.csv');
1760
+
1761
+ if (await fs.pathExists(helpFilePath)) {
1762
+ try {
1763
+ const content = await fs.readFile(helpFilePath, 'utf8');
1764
+ const lines = content.split('\n').filter((line) => line.trim() && !line.startsWith('#'));
1765
+
1766
+ for (const line of lines) {
1767
+ // Skip header row
1768
+ if (line.startsWith('module,')) {
1769
+ continue;
1770
+ }
1771
+
1772
+ // Parse the line - handle quoted fields with commas
1773
+ const columns = this.parseCSVLine(line);
1774
+ if (columns.length >= 12) {
1775
+ // Map old schema to new schema
1776
+ // Old: module,phase,name,code,sequence,workflow-file,command,required,agent,options,description,output-location,outputs
1777
+ // New: module,phase,name,code,sequence,workflow-file,command,required,agent-name,agent-command,agent-display-name,agent-title,options,description,output-location,outputs
1778
+
1779
+ const [
1780
+ module,
1781
+ phase,
1782
+ name,
1783
+ code,
1784
+ sequence,
1785
+ workflowFile,
1786
+ command,
1787
+ required,
1788
+ agentName,
1789
+ options,
1790
+ description,
1791
+ outputLocation,
1792
+ outputs,
1793
+ ] = columns;
1794
+
1795
+ // If module column is empty, set it to this module's name (except for core which stays empty for universal tools)
1796
+ const finalModule = (!module || module.trim() === '') && moduleName !== 'core' ? moduleName : module || '';
1797
+
1798
+ // Lookup agent info
1799
+ const cleanAgentName = agentName ? agentName.trim() : '';
1800
+ const agentData = agentInfo.get(cleanAgentName) || { command: '', displayName: '', title: '' };
1801
+
1802
+ // Build new row with agent info
1803
+ const newRow = [
1804
+ finalModule,
1805
+ phase || '',
1806
+ name || '',
1807
+ code || '',
1808
+ sequence || '',
1809
+ workflowFile || '',
1810
+ command || '',
1811
+ required || 'false',
1812
+ cleanAgentName,
1813
+ agentData.command,
1814
+ agentData.displayName,
1815
+ agentData.title,
1816
+ options || '',
1817
+ description || '',
1818
+ outputLocation || '',
1819
+ outputs || '',
1820
+ ];
1821
+
1822
+ allRows.push(newRow.map((c) => this.escapeCSVField(c)).join(','));
1823
+ }
1824
+ }
1825
+
1826
+ if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
1827
+ await prompts.log.message(` Merged module-help from: ${moduleName}`);
1828
+ }
1829
+ } catch (error) {
1830
+ await prompts.log.warn(` Warning: Failed to read module-help.csv from ${moduleName}: ${error.message}`);
1831
+ }
1832
+ }
1833
+ }
1834
+
1835
+ // Sort by module, then phase, then sequence
1836
+ allRows.sort((a, b) => {
1837
+ const colsA = this.parseCSVLine(a);
1838
+ const colsB = this.parseCSVLine(b);
1839
+
1840
+ // Module comparison (empty module/universal tools come first)
1841
+ const moduleA = (colsA[0] || '').toLowerCase();
1842
+ const moduleB = (colsB[0] || '').toLowerCase();
1843
+ if (moduleA !== moduleB) {
1844
+ return moduleA.localeCompare(moduleB);
1845
+ }
1846
+
1847
+ // Phase comparison
1848
+ const phaseA = colsA[1] || '';
1849
+ const phaseB = colsB[1] || '';
1850
+ if (phaseA !== phaseB) {
1851
+ return phaseA.localeCompare(phaseB);
1852
+ }
1853
+
1854
+ // Sequence comparison
1855
+ const seqA = parseInt(colsA[4] || '0', 10);
1856
+ const seqB = parseInt(colsB[4] || '0', 10);
1857
+ return seqA - seqB;
1858
+ });
1859
+
1860
+ // Write merged catalog
1861
+ const outputDir = path.join(bmadDir, '_config');
1862
+ await fs.ensureDir(outputDir);
1863
+ const outputPath = path.join(outputDir, 'bmad-help.csv');
1864
+
1865
+ const mergedContent = [headerRow, ...allRows].join('\n');
1866
+ await fs.writeFile(outputPath, mergedContent, 'utf8');
1867
+
1868
+ // Track the installed file
1869
+ this.installedFiles.add(outputPath);
1870
+
1871
+ if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
1872
+ await prompts.log.message(` Generated bmad-help.csv: ${allRows.length} workflows`);
1873
+ }
1874
+ }
1875
+
1876
+ /**
1877
+ * Parse a CSV line, handling quoted fields
1878
+ * @param {string} line - CSV line to parse
1879
+ * @returns {Array} Array of field values
1880
+ */
1881
+ parseCSVLine(line) {
1882
+ const result = [];
1883
+ let current = '';
1884
+ let inQuotes = false;
1885
+
1886
+ for (let i = 0; i < line.length; i++) {
1887
+ const char = line[i];
1888
+ const nextChar = line[i + 1];
1889
+
1890
+ if (char === '"') {
1891
+ if (inQuotes && nextChar === '"') {
1892
+ // Escaped quote
1893
+ current += '"';
1894
+ i++; // Skip next quote
1895
+ } else {
1896
+ // Toggle quote mode
1897
+ inQuotes = !inQuotes;
1898
+ }
1899
+ } else if (char === ',' && !inQuotes) {
1900
+ result.push(current);
1901
+ current = '';
1902
+ } else {
1903
+ current += char;
1904
+ }
1905
+ }
1906
+ result.push(current);
1907
+ return result;
1908
+ }
1909
+
1910
+ /**
1911
+ * Escape a CSV field if it contains special characters
1912
+ * @param {string} field - Field value to escape
1913
+ * @returns {string} Escaped field
1914
+ */
1915
+ escapeCSVField(field) {
1916
+ if (field === null || field === undefined) {
1917
+ return '';
1918
+ }
1919
+ const str = String(field);
1920
+ // If field contains comma, quote, or newline, wrap in quotes and escape inner quotes
1921
+ if (str.includes(',') || str.includes('"') || str.includes('\n')) {
1922
+ return `"${str.replaceAll('"', '""')}"`;
1923
+ }
1924
+ return str;
1925
+ }
1926
+
955
1927
  async createDirectoryStructure(bmadDir) {
956
1928
  await fs.ensureDir(bmadDir);
957
- await fs.ensureDir(path.join(bmadDir, '_cfg'));
958
- await fs.ensureDir(path.join(bmadDir, '_cfg', 'agents'));
1929
+ await fs.ensureDir(path.join(bmadDir, '_config'));
1930
+ await fs.ensureDir(path.join(bmadDir, '_config', 'agents'));
1931
+ await fs.ensureDir(path.join(bmadDir, '_config', 'custom'));
959
1932
  }
960
1933
 
961
1934
  /**
@@ -964,7 +1937,7 @@ class Installer {
964
1937
  * @param {Object} moduleConfigs - Collected configuration values
965
1938
  */
966
1939
  async generateModuleConfigs(bmadDir, moduleConfigs) {
967
- const yaml = require('js-yaml');
1940
+ const yaml = require('yaml');
968
1941
 
969
1942
  // Extract core config values to share with other modules
970
1943
  const coreConfig = moduleConfigs.core || {};
@@ -972,7 +1945,7 @@ class Installer {
972
1945
  // Get all installed module directories
973
1946
  const entries = await fs.readdir(bmadDir, { withFileTypes: true });
974
1947
  const installedModules = entries
975
- .filter((entry) => entry.isDirectory() && entry.name !== '_cfg' && entry.name !== 'docs')
1948
+ .filter((entry) => entry.isDirectory() && entry.name !== '_config' && entry.name !== 'docs')
976
1949
  .map((entry) => entry.name);
977
1950
 
978
1951
  // Generate config.yaml for each installed module
@@ -1010,12 +1983,14 @@ class Installer {
1010
1983
  coreSection = '\n# Core Configuration Values\n';
1011
1984
  }
1012
1985
 
1986
+ // Clean the config to remove any non-serializable values (like functions)
1987
+ const cleanConfig = structuredClone(finalConfig);
1988
+
1013
1989
  // Convert config to YAML
1014
- let yamlContent = yaml.dump(finalConfig, {
1990
+ let yamlContent = yaml.stringify(cleanConfig, {
1015
1991
  indent: 2,
1016
- lineWidth: -1,
1017
- noRefs: true,
1018
- sortKeys: false,
1992
+ lineWidth: 0,
1993
+ minContentWidth: 0,
1019
1994
  });
1020
1995
 
1021
1996
  // If we have core values, reorganize the YAML to group them with their comment
@@ -1047,7 +2022,7 @@ class Installer {
1047
2022
  await fs.writeFile(configPath, content.endsWith('\n') ? content : content + '\n', 'utf8');
1048
2023
 
1049
2024
  // Track the config file in installedFiles
1050
- this.installedFiles.push(configPath);
2025
+ this.installedFiles.add(configPath);
1051
2026
  }
1052
2027
  }
1053
2028
  }
@@ -1060,14 +2035,7 @@ class Installer {
1060
2035
  async installCoreWithDependencies(bmadDir, coreFiles) {
1061
2036
  const sourcePath = getModulePath('core');
1062
2037
  const targetPath = path.join(bmadDir, 'core');
1063
-
1064
- // Install full core
1065
2038
  await this.installCore(bmadDir);
1066
-
1067
- // If there are specific dependency files, ensure they're included
1068
- if (coreFiles) {
1069
- // Already handled by installCore for core module
1070
- }
1071
2039
  }
1072
2040
 
1073
2041
  /**
@@ -1086,11 +2054,13 @@ class Installer {
1086
2054
  moduleName,
1087
2055
  bmadDir,
1088
2056
  (filePath) => {
1089
- this.installedFiles.push(filePath);
2057
+ this.installedFiles.add(filePath);
1090
2058
  },
1091
2059
  {
1092
2060
  skipModuleInstaller: true, // We'll run it later after IDE setup
1093
2061
  moduleConfig: moduleConfig, // Pass module config for conditional filtering
2062
+ installer: this,
2063
+ silent: true,
1094
2064
  },
1095
2065
  );
1096
2066
 
@@ -1122,8 +2092,8 @@ class Installer {
1122
2092
  const targetPath = path.join(agentsDir, fileName);
1123
2093
 
1124
2094
  if (await fs.pathExists(sourcePath)) {
1125
- await this.copyFileWithPlaceholderReplacement(sourcePath, targetPath, this.bmadFolderName || 'bmad');
1126
- this.installedFiles.push(targetPath);
2095
+ await this.copyFileWithPlaceholderReplacement(sourcePath, targetPath);
2096
+ this.installedFiles.add(targetPath);
1127
2097
  }
1128
2098
  }
1129
2099
  }
@@ -1138,8 +2108,8 @@ class Installer {
1138
2108
  const targetPath = path.join(tasksDir, fileName);
1139
2109
 
1140
2110
  if (await fs.pathExists(sourcePath)) {
1141
- await this.copyFileWithPlaceholderReplacement(sourcePath, targetPath, this.bmadFolderName || 'bmad');
1142
- this.installedFiles.push(targetPath);
2111
+ await this.copyFileWithPlaceholderReplacement(sourcePath, targetPath);
2112
+ this.installedFiles.add(targetPath);
1143
2113
  }
1144
2114
  }
1145
2115
  }
@@ -1154,8 +2124,8 @@ class Installer {
1154
2124
  const targetPath = path.join(toolsDir, fileName);
1155
2125
 
1156
2126
  if (await fs.pathExists(sourcePath)) {
1157
- await this.copyFileWithPlaceholderReplacement(sourcePath, targetPath, this.bmadFolderName || 'bmad');
1158
- this.installedFiles.push(targetPath);
2127
+ await this.copyFileWithPlaceholderReplacement(sourcePath, targetPath);
2128
+ this.installedFiles.add(targetPath);
1159
2129
  }
1160
2130
  }
1161
2131
  }
@@ -1170,8 +2140,8 @@ class Installer {
1170
2140
  const targetPath = path.join(templatesDir, fileName);
1171
2141
 
1172
2142
  if (await fs.pathExists(sourcePath)) {
1173
- await this.copyFileWithPlaceholderReplacement(sourcePath, targetPath, this.bmadFolderName || 'bmad');
1174
- this.installedFiles.push(targetPath);
2143
+ await this.copyFileWithPlaceholderReplacement(sourcePath, targetPath);
2144
+ this.installedFiles.add(targetPath);
1175
2145
  }
1176
2146
  }
1177
2147
  }
@@ -1185,8 +2155,8 @@ class Installer {
1185
2155
  await fs.ensureDir(path.dirname(targetPath));
1186
2156
 
1187
2157
  if (await fs.pathExists(dataPath)) {
1188
- await this.copyFileWithPlaceholderReplacement(dataPath, targetPath, this.bmadFolderName || 'bmad');
1189
- this.installedFiles.push(targetPath);
2158
+ await this.copyFileWithPlaceholderReplacement(dataPath, targetPath);
2159
+ this.installedFiles.add(targetPath);
1190
2160
  }
1191
2161
  }
1192
2162
  }
@@ -1207,25 +2177,55 @@ class Installer {
1207
2177
  const sourcePath = getModulePath('core');
1208
2178
  const targetPath = path.join(bmadDir, 'core');
1209
2179
 
1210
- // Copy core files with filtering for localskip agents
1211
- await this.copyDirectoryWithFiltering(sourcePath, targetPath);
2180
+ // Copy core files (skip .agent.yaml files like modules do)
2181
+ await this.copyCoreFiles(sourcePath, targetPath);
2182
+
2183
+ // Compile agents using the same compiler as modules
2184
+ const { ModuleManager } = require('../modules/manager');
2185
+ const moduleManager = new ModuleManager();
2186
+ await moduleManager.compileModuleAgents(sourcePath, targetPath, 'core', bmadDir, this);
1212
2187
 
1213
2188
  // Process agent files to inject activation block
1214
2189
  await this.processAgentFiles(targetPath, 'core');
1215
2190
  }
1216
2191
 
1217
2192
  /**
1218
- * Copy directory with filtering for localskip agents
1219
- * @param {string} sourcePath - Source directory path
1220
- * @param {string} targetPath - Target directory path
2193
+ * Copy core files (similar to copyModuleWithFiltering but for core)
2194
+ * @param {string} sourcePath - Source path
2195
+ * @param {string} targetPath - Target path
1221
2196
  */
1222
- async copyDirectoryWithFiltering(sourcePath, targetPath) {
1223
- // Get all files in source directory
2197
+ async copyCoreFiles(sourcePath, targetPath) {
2198
+ // Get all files in source
1224
2199
  const files = await this.getFileList(sourcePath);
1225
2200
 
1226
2201
  for (const file of files) {
2202
+ // Skip sub-modules directory - these are IDE-specific and handled separately
2203
+ if (file.startsWith('sub-modules/')) {
2204
+ continue;
2205
+ }
2206
+
2207
+ // Skip sidecar directories - they are handled separately during agent compilation
2208
+ if (
2209
+ path
2210
+ .dirname(file)
2211
+ .split('/')
2212
+ .some((dir) => dir.toLowerCase().includes('sidecar'))
2213
+ ) {
2214
+ continue;
2215
+ }
2216
+
2217
+ // Skip module.yaml at root - it's only needed at install time
2218
+ if (file === 'module.yaml') {
2219
+ continue;
2220
+ }
2221
+
1227
2222
  // Skip config.yaml templates - we'll generate clean ones with actual values
1228
- if (file === 'config.yaml' || file.endsWith('/config.yaml')) {
2223
+ if (file === 'config.yaml' || file.endsWith('/config.yaml') || file === 'custom.yaml' || file.endsWith('/custom.yaml')) {
2224
+ continue;
2225
+ }
2226
+
2227
+ // Skip .agent.yaml files - they will be compiled separately
2228
+ if (file.endsWith('.agent.yaml')) {
1229
2229
  continue;
1230
2230
  }
1231
2231
 
@@ -1233,23 +2233,24 @@ class Installer {
1233
2233
  const targetFile = path.join(targetPath, file);
1234
2234
 
1235
2235
  // Check if this is an agent file
1236
- if (file.includes('agents/') && file.endsWith('.md')) {
2236
+ if (file.startsWith('agents/') && file.endsWith('.md')) {
1237
2237
  // Read the file to check for localskip
1238
2238
  const content = await fs.readFile(sourceFile, 'utf8');
1239
2239
 
1240
2240
  // Check for localskip="true" in the agent tag
1241
2241
  const agentMatch = content.match(/<agent[^>]*\slocalskip="true"[^>]*>/);
1242
2242
  if (agentMatch) {
1243
- console.log(chalk.dim(` Skipping web-only agent: ${path.basename(file)}`));
2243
+ await prompts.log.message(` Skipping web-only agent: ${path.basename(file)}`);
1244
2244
  continue; // Skip this agent
1245
2245
  }
1246
2246
  }
1247
2247
 
1248
2248
  // Copy the file with placeholder replacement
1249
- await this.copyFileWithPlaceholderReplacement(sourceFile, targetFile, this.bmadFolderName || 'bmad');
2249
+ await fs.ensureDir(path.dirname(targetFile));
2250
+ await this.copyFileWithPlaceholderReplacement(sourceFile, targetFile);
1250
2251
 
1251
2252
  // Track the installed file
1252
- this.installedFiles.push(targetFile);
2253
+ this.installedFiles.add(targetFile);
1253
2254
  }
1254
2255
  }
1255
2256
 
@@ -1267,10 +2268,6 @@ class Installer {
1267
2268
  const fullPath = path.join(dir, entry.name);
1268
2269
 
1269
2270
  if (entry.isDirectory()) {
1270
- // Skip _module-installer directories
1271
- if (entry.name === '_module-installer') {
1272
- continue;
1273
- }
1274
2271
  const subFiles = await this.getFileList(fullPath, baseDir);
1275
2272
  files.push(...subFiles);
1276
2273
  } else {
@@ -1296,372 +2293,42 @@ class Installer {
1296
2293
 
1297
2294
  // Determine project directory (parent of bmad/ directory)
1298
2295
  const bmadDir = path.dirname(modulePath);
1299
- const projectDir = path.dirname(bmadDir);
1300
- const cfgAgentsDir = path.join(bmadDir, '_cfg', 'agents');
2296
+ const cfgAgentsDir = path.join(bmadDir, '_config', 'agents');
1301
2297
 
1302
- // Ensure _cfg/agents directory exists
2298
+ // Ensure _config/agents directory exists
1303
2299
  await fs.ensureDir(cfgAgentsDir);
1304
2300
 
1305
2301
  // Get all agent files
1306
2302
  const agentFiles = await fs.readdir(agentsPath);
1307
2303
 
1308
2304
  for (const agentFile of agentFiles) {
1309
- // Handle YAML agents - build them to .md
2305
+ // Skip .agent.yaml files - they should already be compiled by compileModuleAgents
1310
2306
  if (agentFile.endsWith('.agent.yaml')) {
1311
- const agentName = agentFile.replace('.agent.yaml', '');
1312
- const yamlPath = path.join(agentsPath, agentFile);
1313
- const mdPath = path.join(agentsPath, `${agentName}.md`);
1314
- const customizePath = path.join(cfgAgentsDir, `${moduleName}-${agentName}.customize.yaml`);
1315
-
1316
- // Create customize template if it doesn't exist
1317
- if (!(await fs.pathExists(customizePath))) {
1318
- const genericTemplatePath = getSourcePath('utility', 'templates', 'agent.customize.template.yaml');
1319
- if (await fs.pathExists(genericTemplatePath)) {
1320
- await this.copyFileWithPlaceholderReplacement(genericTemplatePath, customizePath, this.bmadFolderName || 'bmad');
1321
- console.log(chalk.dim(` Created customize: ${moduleName}-${agentName}.customize.yaml`));
2307
+ continue;
2308
+ }
2309
+
2310
+ // Only process .md files (already compiled from YAML)
2311
+ if (!agentFile.endsWith('.md')) {
2312
+ continue;
2313
+ }
2314
+
2315
+ const agentName = agentFile.replace('.md', '');
2316
+ const mdPath = path.join(agentsPath, agentFile);
2317
+ const customizePath = path.join(cfgAgentsDir, `${moduleName}-${agentName}.customize.yaml`);
2318
+
2319
+ // For .md files that are already compiled, we don't need to do much
2320
+ // Just ensure the customize template exists
2321
+ if (!(await fs.pathExists(customizePath))) {
2322
+ const genericTemplatePath = getSourcePath('utility', 'agent-components', 'agent.customize.template.yaml');
2323
+ if (await fs.pathExists(genericTemplatePath)) {
2324
+ await this.copyFileWithPlaceholderReplacement(genericTemplatePath, customizePath);
2325
+ if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
2326
+ await prompts.log.message(` Created customize: ${moduleName}-${agentName}.customize.yaml`);
1322
2327
  }
1323
2328
  }
1324
-
1325
- // Build YAML + customize to .md
1326
- const customizeExists = await fs.pathExists(customizePath);
1327
- const xmlContent = await this.xmlHandler.buildFromYaml(yamlPath, customizeExists ? customizePath : null, {
1328
- includeMetadata: true,
1329
- });
1330
-
1331
- // DO NOT replace {project-root} - LLMs understand this placeholder at runtime
1332
- // const processedContent = xmlContent.replaceAll('{project-root}', projectDir);
1333
-
1334
- // Write the built .md file to bmad/{module}/agents/ with POSIX-compliant final newline
1335
- const content = xmlContent.endsWith('\n') ? xmlContent : xmlContent + '\n';
1336
- await fs.writeFile(mdPath, content, 'utf8');
1337
- this.installedFiles.push(mdPath);
1338
-
1339
- // Remove the source YAML file - we can regenerate from installer source if needed
1340
- await fs.remove(yamlPath);
1341
-
1342
- console.log(chalk.dim(` Built agent: ${agentName}.md`));
1343
- }
1344
- // Handle legacy .md agents - inject activation if needed
1345
- else if (agentFile.endsWith('.md')) {
1346
- const agentPath = path.join(agentsPath, agentFile);
1347
- let content = await fs.readFile(agentPath, 'utf8');
1348
-
1349
- // Check if content has agent XML and no activation block
1350
- if (content.includes('<agent') && !content.includes('<activation')) {
1351
- // Inject the activation block using XML handler
1352
- content = this.xmlHandler.injectActivationSimple(content);
1353
- // Ensure POSIX-compliant final newline
1354
- const finalContent = content.endsWith('\n') ? content : content + '\n';
1355
- await fs.writeFile(agentPath, finalContent, 'utf8');
1356
- }
1357
- }
1358
- }
1359
- }
1360
-
1361
- /**
1362
- * Build standalone agents in bmad/agents/ directory
1363
- * @param {string} bmadDir - Path to bmad directory
1364
- * @param {string} projectDir - Path to project directory
1365
- */
1366
- async buildStandaloneAgents(bmadDir, projectDir) {
1367
- const standaloneAgentsPath = path.join(bmadDir, 'agents');
1368
- const cfgAgentsDir = path.join(bmadDir, '_cfg', 'agents');
1369
-
1370
- // Check if standalone agents directory exists
1371
- if (!(await fs.pathExists(standaloneAgentsPath))) {
1372
- return;
1373
- }
1374
-
1375
- // Get all subdirectories in agents/
1376
- const agentDirs = await fs.readdir(standaloneAgentsPath, { withFileTypes: true });
1377
-
1378
- for (const agentDir of agentDirs) {
1379
- if (!agentDir.isDirectory()) continue;
1380
-
1381
- const agentDirPath = path.join(standaloneAgentsPath, agentDir.name);
1382
-
1383
- // Find any .agent.yaml file in the directory
1384
- const files = await fs.readdir(agentDirPath);
1385
- const yamlFile = files.find((f) => f.endsWith('.agent.yaml'));
1386
-
1387
- if (!yamlFile) continue;
1388
-
1389
- const agentName = path.basename(yamlFile, '.agent.yaml');
1390
- const sourceYamlPath = path.join(agentDirPath, yamlFile);
1391
- const targetMdPath = path.join(agentDirPath, `${agentName}.md`);
1392
- const customizePath = path.join(cfgAgentsDir, `${agentName}.customize.yaml`);
1393
-
1394
- // Check for customizations
1395
- const customizeExists = await fs.pathExists(customizePath);
1396
- let customizedFields = [];
1397
-
1398
- if (customizeExists) {
1399
- const customizeContent = await fs.readFile(customizePath, 'utf8');
1400
- const yaml = require('js-yaml');
1401
- const customizeYaml = yaml.load(customizeContent);
1402
-
1403
- // Detect what fields are customized (similar to rebuildAgentFiles)
1404
- if (customizeYaml) {
1405
- if (customizeYaml.persona) {
1406
- for (const [key, value] of Object.entries(customizeYaml.persona)) {
1407
- if (value !== '' && value !== null && !(Array.isArray(value) && value.length === 0)) {
1408
- customizedFields.push(`persona.${key}`);
1409
- }
1410
- }
1411
- }
1412
- if (customizeYaml.agent?.metadata) {
1413
- for (const [key, value] of Object.entries(customizeYaml.agent.metadata)) {
1414
- if (value !== '' && value !== null) {
1415
- customizedFields.push(`metadata.${key}`);
1416
- }
1417
- }
1418
- }
1419
- if (customizeYaml.critical_actions && customizeYaml.critical_actions.length > 0) {
1420
- customizedFields.push('critical_actions');
1421
- }
1422
- if (customizeYaml.menu && customizeYaml.menu.length > 0) {
1423
- customizedFields.push('menu');
1424
- }
1425
- }
1426
- }
1427
-
1428
- // Build YAML to XML .md
1429
- const xmlContent = await this.xmlHandler.buildFromYaml(sourceYamlPath, customizeExists ? customizePath : null, {
1430
- includeMetadata: true,
1431
- });
1432
-
1433
- // DO NOT replace {project-root} - LLMs understand this placeholder at runtime
1434
- // const processedContent = xmlContent.replaceAll('{project-root}', projectDir);
1435
-
1436
- // Write the built .md file with POSIX-compliant final newline
1437
- const content = xmlContent.endsWith('\n') ? xmlContent : xmlContent + '\n';
1438
- await fs.writeFile(targetMdPath, content, 'utf8');
1439
-
1440
- // Display result
1441
- if (customizedFields.length > 0) {
1442
- console.log(chalk.dim(` Built standalone agent: ${agentName}.md `) + chalk.yellow(`(customized: ${customizedFields.join(', ')})`));
1443
- } else {
1444
- console.log(chalk.dim(` Built standalone agent: ${agentName}.md`));
1445
- }
1446
- }
1447
- }
1448
-
1449
- /**
1450
- * Rebuild agent files from installer source (for compile command)
1451
- * @param {string} modulePath - Path to module in bmad/ installation
1452
- * @param {string} moduleName - Module name
1453
- */
1454
- async rebuildAgentFiles(modulePath, moduleName) {
1455
- // Get source agents directory from installer
1456
- const sourceAgentsPath =
1457
- moduleName === 'core' ? path.join(getModulePath('core'), 'agents') : path.join(getSourcePath(`modules/${moduleName}`), 'agents');
1458
-
1459
- if (!(await fs.pathExists(sourceAgentsPath))) {
1460
- return; // No source agents to rebuild
1461
- }
1462
-
1463
- // Determine project directory (parent of bmad/ directory)
1464
- const bmadDir = path.dirname(modulePath);
1465
- const projectDir = path.dirname(bmadDir);
1466
- const cfgAgentsDir = path.join(bmadDir, '_cfg', 'agents');
1467
- const targetAgentsPath = path.join(modulePath, 'agents');
1468
-
1469
- // Ensure target directory exists
1470
- await fs.ensureDir(targetAgentsPath);
1471
-
1472
- // Get all YAML agent files from source
1473
- const sourceFiles = await fs.readdir(sourceAgentsPath);
1474
-
1475
- for (const file of sourceFiles) {
1476
- if (file.endsWith('.agent.yaml')) {
1477
- const agentName = file.replace('.agent.yaml', '');
1478
- const sourceYamlPath = path.join(sourceAgentsPath, file);
1479
- const targetMdPath = path.join(targetAgentsPath, `${agentName}.md`);
1480
- const customizePath = path.join(cfgAgentsDir, `${moduleName}-${agentName}.customize.yaml`);
1481
-
1482
- // Check for customizations
1483
- const customizeExists = await fs.pathExists(customizePath);
1484
- let customizedFields = [];
1485
-
1486
- if (customizeExists) {
1487
- const customizeContent = await fs.readFile(customizePath, 'utf8');
1488
- const yaml = require('js-yaml');
1489
- const customizeYaml = yaml.load(customizeContent);
1490
-
1491
- // Detect what fields are customized
1492
- if (customizeYaml) {
1493
- if (customizeYaml.persona) {
1494
- for (const [key, value] of Object.entries(customizeYaml.persona)) {
1495
- if (value !== '' && value !== null && !(Array.isArray(value) && value.length === 0)) {
1496
- customizedFields.push(`persona.${key}`);
1497
- }
1498
- }
1499
- }
1500
- if (customizeYaml.agent?.metadata) {
1501
- for (const [key, value] of Object.entries(customizeYaml.agent.metadata)) {
1502
- if (value !== '' && value !== null) {
1503
- customizedFields.push(`metadata.${key}`);
1504
- }
1505
- }
1506
- }
1507
- if (customizeYaml.critical_actions && customizeYaml.critical_actions.length > 0) {
1508
- customizedFields.push('critical_actions');
1509
- }
1510
- if (customizeYaml.memories && customizeYaml.memories.length > 0) {
1511
- customizedFields.push('memories');
1512
- }
1513
- if (customizeYaml.menu && customizeYaml.menu.length > 0) {
1514
- customizedFields.push('menu');
1515
- }
1516
- if (customizeYaml.prompts && customizeYaml.prompts.length > 0) {
1517
- customizedFields.push('prompts');
1518
- }
1519
- }
1520
- }
1521
-
1522
- // Build YAML + customize to .md
1523
- const xmlContent = await this.xmlHandler.buildFromYaml(sourceYamlPath, customizeExists ? customizePath : null, {
1524
- includeMetadata: true,
1525
- });
1526
-
1527
- // DO NOT replace {project-root} - LLMs understand this placeholder at runtime
1528
- // const processedContent = xmlContent.replaceAll('{project-root}', projectDir);
1529
-
1530
- // Write the rebuilt .md file with POSIX-compliant final newline
1531
- const content = xmlContent.endsWith('\n') ? xmlContent : xmlContent + '\n';
1532
- await fs.writeFile(targetMdPath, content, 'utf8');
1533
-
1534
- // Display result with customizations if any
1535
- if (customizedFields.length > 0) {
1536
- console.log(chalk.dim(` Rebuilt agent: ${agentName}.md `) + chalk.yellow(`(customized: ${customizedFields.join(', ')})`));
1537
- } else {
1538
- console.log(chalk.dim(` Rebuilt agent: ${agentName}.md`));
1539
- }
1540
- }
1541
- }
1542
- }
1543
-
1544
- /**
1545
- * Compile/rebuild all agents and tasks for quick updates
1546
- * @param {Object} config - Compilation configuration
1547
- * @returns {Object} Compilation results
1548
- */
1549
- async compileAgents(config) {
1550
- const ora = require('ora');
1551
- const spinner = ora('Starting agent compilation...').start();
1552
-
1553
- try {
1554
- const projectDir = path.resolve(config.directory);
1555
- const bmadDir = await this.findBmadDir(projectDir);
1556
-
1557
- // Check if bmad directory exists
1558
- if (!(await fs.pathExists(bmadDir))) {
1559
- spinner.fail('No BMAD installation found');
1560
- throw new Error(`BMAD not installed at ${bmadDir}`);
1561
- }
1562
-
1563
- let agentCount = 0;
1564
- let taskCount = 0;
1565
-
1566
- // Process all modules in bmad directory
1567
- spinner.text = 'Rebuilding agent files...';
1568
- const entries = await fs.readdir(bmadDir, { withFileTypes: true });
1569
-
1570
- for (const entry of entries) {
1571
- if (entry.isDirectory() && entry.name !== '_cfg' && entry.name !== 'docs') {
1572
- const modulePath = path.join(bmadDir, entry.name);
1573
-
1574
- // Special handling for standalone agents in bmad/agents/ directory
1575
- if (entry.name === 'agents') {
1576
- spinner.text = 'Building standalone agents...';
1577
- await this.buildStandaloneAgents(bmadDir, projectDir);
1578
-
1579
- // Count standalone agents
1580
- const standaloneAgentsPath = path.join(bmadDir, 'agents');
1581
- const standaloneAgentDirs = await fs.readdir(standaloneAgentsPath, { withFileTypes: true });
1582
- for (const agentDir of standaloneAgentDirs) {
1583
- if (agentDir.isDirectory()) {
1584
- const agentDirPath = path.join(standaloneAgentsPath, agentDir.name);
1585
- const agentFiles = await fs.readdir(agentDirPath);
1586
- agentCount += agentFiles.filter((f) => f.endsWith('.md') && !f.endsWith('.agent.yaml')).length;
1587
- }
1588
- }
1589
- } else {
1590
- // Rebuild module agents from installer source
1591
- const agentsPath = path.join(modulePath, 'agents');
1592
- if (await fs.pathExists(agentsPath)) {
1593
- await this.rebuildAgentFiles(modulePath, entry.name);
1594
- const agentFiles = await fs.readdir(agentsPath);
1595
- agentCount += agentFiles.filter((f) => f.endsWith('.md')).length;
1596
- }
1597
-
1598
- // Count tasks (already built)
1599
- const tasksPath = path.join(modulePath, 'tasks');
1600
- if (await fs.pathExists(tasksPath)) {
1601
- const taskFiles = await fs.readdir(tasksPath);
1602
- taskCount += taskFiles.filter((f) => f.endsWith('.md')).length;
1603
- }
1604
- }
1605
- }
1606
- }
1607
-
1608
- // Regenerate manifests after compilation
1609
- spinner.start('Regenerating manifests...');
1610
- const installedModules = entries
1611
- .filter((e) => e.isDirectory() && e.name !== '_cfg' && e.name !== 'docs' && e.name !== 'agents' && e.name !== 'core')
1612
- .map((e) => e.name);
1613
- const manifestGen = new ManifestGenerator();
1614
-
1615
- // Get existing IDE list from manifest
1616
- const existingManifestPath = path.join(bmadDir, '_cfg', 'manifest.yaml');
1617
- let existingIdes = [];
1618
- if (await fs.pathExists(existingManifestPath)) {
1619
- const manifestContent = await fs.readFile(existingManifestPath, 'utf8');
1620
- const yaml = require('js-yaml');
1621
- const manifest = yaml.load(manifestContent);
1622
- existingIdes = manifest.ides || [];
1623
- }
1624
-
1625
- await manifestGen.generateManifests(bmadDir, installedModules, [], {
1626
- ides: existingIdes,
1627
- });
1628
- spinner.succeed('Manifests regenerated');
1629
-
1630
- // Update IDE configurations using the existing IDE list from manifest
1631
- if (existingIdes && existingIdes.length > 0) {
1632
- spinner.start('Updating IDE configurations...');
1633
-
1634
- for (const ide of existingIdes) {
1635
- spinner.text = `Updating ${ide}...`;
1636
-
1637
- // Stop spinner before IDE setup to prevent blocking any potential prompts
1638
- // However, we pass _alreadyConfigured to skip all prompts during compile
1639
- spinner.stop();
1640
-
1641
- await this.ideManager.setup(ide, projectDir, bmadDir, {
1642
- selectedModules: installedModules,
1643
- skipModuleInstall: true, // Skip module installation, just update IDE files
1644
- verbose: config.verbose,
1645
- preCollectedConfig: { _alreadyConfigured: true }, // Skip all interactive prompts during compile
1646
- });
1647
-
1648
- // Restart spinner for next IDE
1649
- if (existingIdes.indexOf(ide) < existingIdes.length - 1) {
1650
- spinner.start('Updating IDE configurations...');
1651
- }
1652
- }
1653
-
1654
- console.log(chalk.green('✓ IDE configurations updated'));
1655
- } else {
1656
- console.log(chalk.yellow('⚠️ No IDEs configured. Skipping IDE update.'));
1657
- }
1658
-
1659
- return { agentCount, taskCount };
1660
- } catch (error) {
1661
- spinner.fail('Compilation failed');
1662
- throw error;
1663
- }
1664
- }
2329
+ }
2330
+ }
2331
+ }
1665
2332
 
1666
2333
  /**
1667
2334
  * Private: Update core
@@ -1676,6 +2343,12 @@ class Installer {
1676
2343
  } else {
1677
2344
  // Selective update - preserve user modifications
1678
2345
  await this.fileOps.syncDirectory(sourcePath, targetPath);
2346
+
2347
+ // Recompile agents (#1133)
2348
+ const { ModuleManager } = require('../modules/manager');
2349
+ const moduleManager = new ModuleManager();
2350
+ await moduleManager.compileModuleAgents(sourcePath, targetPath, 'core', bmadDir, this);
2351
+ await this.processAgentFiles(targetPath, 'core');
1679
2352
  }
1680
2353
  }
1681
2354
 
@@ -1685,45 +2358,172 @@ class Installer {
1685
2358
  * @returns {Object} Update result
1686
2359
  */
1687
2360
  async quickUpdate(config) {
1688
- const ora = require('ora');
1689
- const spinner = ora('Starting quick update...').start();
2361
+ const spinner = await prompts.spinner();
2362
+ spinner.start('Starting quick update...');
1690
2363
 
1691
2364
  try {
1692
2365
  const projectDir = path.resolve(config.directory);
1693
- const bmadDir = await this.findBmadDir(projectDir);
2366
+ const { bmadDir } = await this.findBmadDir(projectDir);
1694
2367
 
1695
2368
  // Check if bmad directory exists
1696
2369
  if (!(await fs.pathExists(bmadDir))) {
1697
- spinner.fail('No BMAD installation found');
2370
+ spinner.stop('No BMAD installation found');
1698
2371
  throw new Error(`BMAD not installed at ${bmadDir}. Use regular install for first-time setup.`);
1699
2372
  }
1700
2373
 
1701
- spinner.text = 'Detecting installed modules and configuration...';
2374
+ spinner.message('Detecting installed modules and configuration...');
1702
2375
 
1703
2376
  // Detect existing installation
1704
2377
  const existingInstall = await this.detector.detect(bmadDir);
1705
2378
  const installedModules = existingInstall.modules.map((m) => m.id);
1706
2379
  const configuredIdes = existingInstall.ides || [];
2380
+ const projectRoot = path.dirname(bmadDir);
2381
+
2382
+ // Get custom module sources: first from --custom-content (re-cache from source), then from cache
2383
+ const customModuleSources = new Map();
2384
+ if (config.customContent?.sources?.length > 0) {
2385
+ for (const source of config.customContent.sources) {
2386
+ if (source.id && source.path && (await fs.pathExists(source.path))) {
2387
+ customModuleSources.set(source.id, {
2388
+ id: source.id,
2389
+ name: source.name || source.id,
2390
+ sourcePath: source.path,
2391
+ cached: false, // From CLI, will be re-cached
2392
+ });
2393
+ }
2394
+ }
2395
+ }
2396
+ const cacheDir = path.join(bmadDir, '_config', 'custom');
2397
+ if (await fs.pathExists(cacheDir)) {
2398
+ const cachedModules = await fs.readdir(cacheDir, { withFileTypes: true });
2399
+
2400
+ for (const cachedModule of cachedModules) {
2401
+ const moduleId = cachedModule.name;
2402
+ const cachedPath = path.join(cacheDir, moduleId);
2403
+
2404
+ // Skip if path doesn't exist (broken symlink, deleted dir) - avoids lstat ENOENT
2405
+ if (!(await fs.pathExists(cachedPath))) {
2406
+ continue;
2407
+ }
2408
+ if (!cachedModule.isDirectory()) {
2409
+ continue;
2410
+ }
2411
+
2412
+ // Skip if we already have this module from manifest
2413
+ if (customModuleSources.has(moduleId)) {
2414
+ continue;
2415
+ }
2416
+
2417
+ // Check if this is an external official module - skip cache for those
2418
+ const isExternal = await this.moduleManager.isExternalModule(moduleId);
2419
+ if (isExternal) {
2420
+ // External modules are handled via cloneExternalModule, not from cache
2421
+ continue;
2422
+ }
2423
+
2424
+ // Check if this is actually a custom module (has module.yaml)
2425
+ const moduleYamlPath = path.join(cachedPath, 'module.yaml');
2426
+ if (await fs.pathExists(moduleYamlPath)) {
2427
+ // For quick update, we always rebuild from cache
2428
+ customModuleSources.set(moduleId, {
2429
+ id: moduleId,
2430
+ name: moduleId, // We'll read the actual name if needed
2431
+ sourcePath: cachedPath,
2432
+ cached: true, // Flag to indicate this is from cache
2433
+ });
2434
+ }
2435
+ }
2436
+ }
1707
2437
 
1708
2438
  // Load saved IDE configurations
1709
2439
  const savedIdeConfigs = await this.ideConfigManager.loadAllIdeConfigs(bmadDir);
1710
2440
 
1711
2441
  // Get available modules (what we have source for)
1712
- const availableModules = await this.moduleManager.listAvailable();
1713
- const availableModuleIds = new Set(availableModules.map((m) => m.id));
2442
+ const availableModulesData = await this.moduleManager.listAvailable();
2443
+ const availableModules = [...availableModulesData.modules, ...availableModulesData.customModules];
2444
+
2445
+ // Add external official modules to available modules
2446
+ // These can always be obtained by cloning from their remote URLs
2447
+ const { ExternalModuleManager } = require('../modules/external-manager');
2448
+ const externalManager = new ExternalModuleManager();
2449
+ const externalModules = await externalManager.listAvailable();
2450
+ for (const externalModule of externalModules) {
2451
+ // Only add if not already in the list and is installed
2452
+ if (installedModules.includes(externalModule.code) && !availableModules.some((m) => m.id === externalModule.code)) {
2453
+ availableModules.push({
2454
+ id: externalModule.code,
2455
+ name: externalModule.name,
2456
+ isExternal: true,
2457
+ fromExternal: true,
2458
+ });
2459
+ }
2460
+ }
2461
+
2462
+ // Add custom modules from manifest if their sources exist
2463
+ for (const [moduleId, customModule] of customModuleSources) {
2464
+ // Use the absolute sourcePath
2465
+ const sourcePath = customModule.sourcePath;
2466
+
2467
+ // Check if source exists at the recorded path
2468
+ if (
2469
+ sourcePath &&
2470
+ (await fs.pathExists(sourcePath)) && // Add to available modules if not already there
2471
+ !availableModules.some((m) => m.id === moduleId)
2472
+ ) {
2473
+ availableModules.push({
2474
+ id: moduleId,
2475
+ name: customModule.name || moduleId,
2476
+ path: sourcePath,
2477
+ isCustom: true,
2478
+ fromManifest: true,
2479
+ });
2480
+ }
2481
+ }
2482
+
2483
+ // Handle missing custom module sources using shared method
2484
+ const customModuleResult = await this.handleMissingCustomSources(
2485
+ customModuleSources,
2486
+ bmadDir,
2487
+ projectRoot,
2488
+ 'update',
2489
+ installedModules,
2490
+ config.skipPrompts || false,
2491
+ );
2492
+
2493
+ const { validCustomModules, keptModulesWithoutSources } = customModuleResult;
2494
+
2495
+ const customModulesFromManifest = validCustomModules.map((m) => ({
2496
+ ...m,
2497
+ isCustom: true,
2498
+ hasUpdate: true,
2499
+ }));
2500
+
2501
+ const allAvailableModules = [...availableModules, ...customModulesFromManifest];
2502
+ const availableModuleIds = new Set(allAvailableModules.map((m) => m.id));
2503
+
2504
+ // Core module is special - never include it in update flow
2505
+ const nonCoreInstalledModules = installedModules.filter((id) => id !== 'core');
1714
2506
 
1715
2507
  // Only update modules that are BOTH installed AND available (we have source for)
1716
- const modulesToUpdate = installedModules.filter((id) => availableModuleIds.has(id));
1717
- const skippedModules = installedModules.filter((id) => !availableModuleIds.has(id));
2508
+ const modulesToUpdate = nonCoreInstalledModules.filter((id) => availableModuleIds.has(id));
2509
+ const skippedModules = nonCoreInstalledModules.filter((id) => !availableModuleIds.has(id));
2510
+
2511
+ // Add custom modules that were kept without sources to the skipped modules
2512
+ // This ensures their agents are preserved in the manifest
2513
+ for (const keptModule of keptModulesWithoutSources) {
2514
+ if (!skippedModules.includes(keptModule)) {
2515
+ skippedModules.push(keptModule);
2516
+ }
2517
+ }
1718
2518
 
1719
- spinner.succeed(`Found ${modulesToUpdate.length} module(s) to update and ${configuredIdes.length} configured tool(s)`);
2519
+ spinner.stop(`Found ${modulesToUpdate.length} module(s) to update and ${configuredIdes.length} configured tool(s)`);
1720
2520
 
1721
2521
  if (skippedModules.length > 0) {
1722
- console.log(chalk.yellow(`⚠️ Skipping ${skippedModules.length} module(s) - no source available: ${skippedModules.join(', ')}`));
2522
+ await prompts.log.warn(`Skipping ${skippedModules.length} module(s) - no source available: ${skippedModules.join(', ')}`);
1723
2523
  }
1724
2524
 
1725
2525
  // Load existing configs and collect new fields (if any)
1726
- console.log(chalk.cyan('\n📋 Checking for new configuration options...'));
2526
+ await prompts.log.info('Checking for new configuration options...');
1727
2527
  await this.configCollector.loadExistingConfig(projectDir);
1728
2528
 
1729
2529
  let promptedForNewFields = false;
@@ -1743,7 +2543,7 @@ class Installer {
1743
2543
  }
1744
2544
 
1745
2545
  if (!promptedForNewFields) {
1746
- console.log(chalk.green('All configuration is up to date, no new options to configure'));
2546
+ await prompts.log.success('All configuration is up to date, no new options to configure');
1747
2547
  }
1748
2548
 
1749
2549
  // Add metadata
@@ -1753,9 +2553,6 @@ class Installer {
1753
2553
  lastModified: new Date().toISOString(),
1754
2554
  };
1755
2555
 
1756
- // Now run the full installation with the collected configs
1757
- spinner.start('Updating BMAD installation...');
1758
-
1759
2556
  // Build the config object for the installer
1760
2557
  const installConfig = {
1761
2558
  directory: projectDir,
@@ -1768,12 +2565,19 @@ class Installer {
1768
2565
  _quickUpdate: true, // Flag to skip certain prompts
1769
2566
  _preserveModules: skippedModules, // Preserve these in manifest even though we didn't update them
1770
2567
  _savedIdeConfigs: savedIdeConfigs, // Pass saved IDE configs to installer
2568
+ _customModuleSources: customModuleSources, // Pass custom module sources for updates
2569
+ _existingModules: installedModules, // Pass all installed modules for manifest generation
2570
+ customContent: config.customContent, // Pass through for re-caching from source
1771
2571
  };
1772
2572
 
1773
2573
  // Call the standard install method
1774
2574
  const result = await this.install(installConfig);
1775
2575
 
1776
- spinner.succeed('Quick update complete!');
2576
+ // Only succeed the spinner if it's still spinning
2577
+ // (install method might have stopped it if folder name changed)
2578
+ if (spinner.isSpinning) {
2579
+ spinner.stop('Quick update complete!');
2580
+ }
1777
2581
 
1778
2582
  return {
1779
2583
  success: true,
@@ -1784,115 +2588,170 @@ class Installer {
1784
2588
  ides: configuredIdes,
1785
2589
  };
1786
2590
  } catch (error) {
1787
- spinner.fail('Quick update failed');
2591
+ spinner.error('Quick update failed');
1788
2592
  throw error;
1789
2593
  }
1790
2594
  }
1791
2595
 
1792
2596
  /**
1793
- * Private: Prompt for update action
1794
- */
1795
- async promptUpdateAction() {
1796
- const inquirer = require('inquirer');
1797
- return await inquirer.prompt([
1798
- {
1799
- type: 'list',
1800
- name: 'action',
1801
- message: 'What would you like to do?',
1802
- choices: [
1803
- { name: 'Update existing installation', value: 'update' },
1804
- { name: 'Remove and reinstall', value: 'reinstall' },
1805
- { name: 'Cancel', value: 'cancel' },
1806
- ],
1807
- },
1808
- ]);
1809
- }
1810
-
1811
- /**
1812
- * Handle legacy BMAD v4 migration with automatic backup
1813
- * @param {string} projectDir - Project directory
1814
- * @param {Object} legacyV4 - Legacy V4 detection result with offenders array
2597
+ * Compile agents with customizations only
2598
+ * @param {Object} config - Configuration with directory
2599
+ * @returns {Object} Compilation result
1815
2600
  */
1816
- async handleLegacyV4Migration(projectDir, legacyV4) {
1817
- console.log(chalk.yellow.bold('\n⚠️ Legacy BMAD v4 detected'));
1818
- console.log(chalk.dim('The installer found legacy artefacts in your project.\n'));
1819
-
1820
- // Separate .bmad* folders (auto-backup) from other offending paths (manual cleanup)
1821
- const bmadFolders = legacyV4.offenders.filter((p) => {
1822
- const name = path.basename(p);
1823
- return name.startsWith('.bmad'); // Only dot-prefixed folders get auto-backed up
1824
- });
1825
- const otherOffenders = legacyV4.offenders.filter((p) => {
1826
- const name = path.basename(p);
1827
- return !name.startsWith('.bmad'); // Everything else is manual cleanup
1828
- });
2601
+ async compileAgents(config) {
2602
+ // Using @clack prompts
2603
+ const { ModuleManager } = require('../modules/manager');
2604
+ const { getSourcePath } = require('../../../lib/project-root');
1829
2605
 
1830
- const inquirer = require('inquirer');
2606
+ const spinner = await prompts.spinner();
2607
+ spinner.start('Recompiling agents with customizations...');
1831
2608
 
1832
- // Show warning for other offending paths FIRST
1833
- if (otherOffenders.length > 0) {
1834
- console.log(chalk.yellow('⚠️ Recommended cleanup:'));
1835
- console.log(chalk.dim('It is recommended to remove the following items before proceeding:\n'));
1836
- for (const p of otherOffenders) console.log(chalk.dim(` - ${p}`));
2609
+ try {
2610
+ const projectDir = path.resolve(config.directory);
2611
+ const { bmadDir } = await this.findBmadDir(projectDir);
1837
2612
 
1838
- console.log(chalk.cyan('\nCleanup commands you can copy/paste:'));
1839
- console.log(chalk.dim('macOS/Linux:'));
1840
- for (const p of otherOffenders) console.log(chalk.dim(` rm -rf '${p}'`));
1841
- console.log(chalk.dim('Windows:'));
1842
- for (const p of otherOffenders) console.log(chalk.dim(` rmdir /S /Q "${p}"`));
2613
+ // Check if bmad directory exists
2614
+ if (!(await fs.pathExists(bmadDir))) {
2615
+ spinner.stop('No BMAD installation found');
2616
+ throw new Error(`BMAD not installed at ${bmadDir}. Use regular install for first-time setup.`);
2617
+ }
1843
2618
 
1844
- const { cleanedUp } = await inquirer.prompt([
1845
- {
1846
- type: 'confirm',
1847
- name: 'cleanedUp',
1848
- message: 'Have you completed the recommended cleanup? (You can proceed without it, but it is recommended)',
1849
- default: false,
1850
- },
1851
- ]);
2619
+ // Detect existing installation
2620
+ const existingInstall = await this.detector.detect(bmadDir);
2621
+ const installedModules = existingInstall.modules.map((m) => m.id);
1852
2622
 
1853
- if (cleanedUp) {
1854
- console.log(chalk.green('✓ Cleanup acknowledged\n'));
1855
- } else {
1856
- console.log(chalk.yellow('⚠️ Proceeding without recommended cleanup\n'));
2623
+ // Initialize module manager
2624
+ const moduleManager = new ModuleManager();
2625
+ moduleManager.setBmadFolderName(path.basename(bmadDir));
2626
+
2627
+ let totalAgentCount = 0;
2628
+
2629
+ // Get custom module sources from cache
2630
+ const customModuleSources = new Map();
2631
+ const cacheDir = path.join(bmadDir, '_config', 'custom');
2632
+ if (await fs.pathExists(cacheDir)) {
2633
+ const cachedModules = await fs.readdir(cacheDir, { withFileTypes: true });
2634
+
2635
+ for (const cachedModule of cachedModules) {
2636
+ if (cachedModule.isDirectory()) {
2637
+ const moduleId = cachedModule.name;
2638
+ const cachedPath = path.join(cacheDir, moduleId);
2639
+ const moduleYamlPath = path.join(cachedPath, 'module.yaml');
2640
+
2641
+ // Check if this is actually a custom module
2642
+ if (await fs.pathExists(moduleYamlPath)) {
2643
+ // Check if this is an external official module - skip cache for those
2644
+ const isExternal = await this.moduleManager.isExternalModule(moduleId);
2645
+ if (isExternal) {
2646
+ // External modules are handled via cloneExternalModule, not from cache
2647
+ continue;
2648
+ }
2649
+ customModuleSources.set(moduleId, cachedPath);
2650
+ }
2651
+ }
2652
+ }
1857
2653
  }
1858
- }
1859
2654
 
1860
- // Handle .bmad* folders with automatic backup
1861
- if (bmadFolders.length > 0) {
1862
- console.log(chalk.cyan('The following legacy folders will be moved to v4-backup:'));
1863
- for (const p of bmadFolders) console.log(chalk.dim(` - ${p}`));
2655
+ // Process each installed module
2656
+ for (const moduleId of installedModules) {
2657
+ spinner.message(`Recompiling agents in ${moduleId}...`);
1864
2658
 
1865
- const { proceed } = await inquirer.prompt([
1866
- {
1867
- type: 'confirm',
1868
- name: 'proceed',
1869
- message: 'Proceed with backing up legacy v4 folders?',
1870
- default: true,
1871
- },
1872
- ]);
2659
+ // Get source path
2660
+ let sourcePath;
2661
+ if (moduleId === 'core') {
2662
+ sourcePath = getSourcePath('core');
2663
+ } else {
2664
+ // First check if it's in the custom cache
2665
+ if (customModuleSources.has(moduleId)) {
2666
+ sourcePath = customModuleSources.get(moduleId);
2667
+ } else {
2668
+ sourcePath = await moduleManager.findModuleSource(moduleId);
2669
+ }
2670
+ }
1873
2671
 
1874
- if (proceed) {
1875
- const backupDir = path.join(projectDir, 'v4-backup');
1876
- await fs.ensureDir(backupDir);
2672
+ if (!sourcePath) {
2673
+ await prompts.log.warn(`Source not found for module ${moduleId}, skipping...`);
2674
+ continue;
2675
+ }
1877
2676
 
1878
- for (const folder of bmadFolders) {
1879
- const folderName = path.basename(folder);
1880
- const backupPath = path.join(backupDir, folderName);
2677
+ const targetPath = path.join(bmadDir, moduleId);
1881
2678
 
1882
- // If backup already exists, add timestamp
1883
- let finalBackupPath = backupPath;
1884
- if (await fs.pathExists(backupPath)) {
1885
- const timestamp = new Date().toISOString().replaceAll(/[:.]/g, '-').split('T')[0];
1886
- finalBackupPath = path.join(backupDir, `${folderName}-${timestamp}`);
1887
- }
2679
+ // Compile agents for this module
2680
+ await moduleManager.compileModuleAgents(sourcePath, targetPath, moduleId, bmadDir, this);
1888
2681
 
1889
- await fs.move(folder, finalBackupPath, { overwrite: false });
1890
- console.log(chalk.green(`✓ Moved ${folderName} to ${path.relative(projectDir, finalBackupPath)}`));
2682
+ // Count agents (rough estimate based on files)
2683
+ const agentsPath = path.join(targetPath, 'agents');
2684
+ if (await fs.pathExists(agentsPath)) {
2685
+ const agentFiles = await fs.readdir(agentsPath);
2686
+ const agentCount = agentFiles.filter((f) => f.endsWith('.md')).length;
2687
+ totalAgentCount += agentCount;
1891
2688
  }
1892
- } else {
1893
- throw new Error('Installation cancelled by user');
1894
2689
  }
2690
+
2691
+ spinner.stop('Agent recompilation complete!');
2692
+
2693
+ return {
2694
+ success: true,
2695
+ agentCount: totalAgentCount,
2696
+ modules: installedModules,
2697
+ };
2698
+ } catch (error) {
2699
+ spinner.error('Agent recompilation failed');
2700
+ throw error;
2701
+ }
2702
+ }
2703
+
2704
+ /**
2705
+ * Private: Prompt for update action
2706
+ */
2707
+ async promptUpdateAction() {
2708
+ const action = await prompts.select({
2709
+ message: 'What would you like to do?',
2710
+ choices: [{ name: 'Update existing installation', value: 'update' }],
2711
+ });
2712
+ return { action };
2713
+ }
2714
+
2715
+ /**
2716
+ * Handle legacy BMAD v4 detection with simple warning
2717
+ * @param {string} _projectDir - Project directory (unused in simplified version)
2718
+ * @param {Object} _legacyV4 - Legacy V4 detection result (unused in simplified version)
2719
+ */
2720
+ async handleLegacyV4Migration(_projectDir, _legacyV4) {
2721
+ await prompts.note(
2722
+ 'Found .bmad-method folder from BMAD v4 installation.\n\n' +
2723
+ 'Before continuing with installation, we recommend:\n' +
2724
+ ' 1. Remove the .bmad-method folder, OR\n' +
2725
+ ' 2. Back it up by renaming it to another name (e.g., bmad-method-backup)\n\n' +
2726
+ 'If your v4 installation set up rules or commands, you should remove those as well.',
2727
+ 'Legacy BMAD v4 detected',
2728
+ );
2729
+
2730
+ const proceed = await prompts.select({
2731
+ message: 'What would you like to do?',
2732
+ choices: [
2733
+ {
2734
+ name: 'Exit and clean up manually (recommended)',
2735
+ value: 'exit',
2736
+ hint: 'Exit installation',
2737
+ },
2738
+ {
2739
+ name: 'Continue with installation anyway',
2740
+ value: 'continue',
2741
+ hint: 'Continue',
2742
+ },
2743
+ ],
2744
+ default: 'exit',
2745
+ });
2746
+
2747
+ if (proceed === 'exit') {
2748
+ await prompts.log.info('Please remove the .bmad-method folder and any v4 rules/commands, then run the installer again.');
2749
+ // Allow event loop to flush pending I/O before exit
2750
+ setImmediate(() => process.exit(0));
2751
+ return;
1895
2752
  }
2753
+
2754
+ await prompts.log.warn('Proceeding with installation despite legacy v4 folder');
1896
2755
  }
1897
2756
 
1898
2757
  /**
@@ -1901,7 +2760,7 @@ class Installer {
1901
2760
  * @returns {Array} Array of file entries from files-manifest.csv
1902
2761
  */
1903
2762
  async readFilesManifest(bmadDir) {
1904
- const filesManifestPath = path.join(bmadDir, '_cfg', 'files-manifest.csv');
2763
+ const filesManifestPath = path.join(bmadDir, '_config', 'files-manifest.csv');
1905
2764
  if (!(await fs.pathExists(filesManifestPath))) {
1906
2765
  return [];
1907
2766
  }
@@ -1946,7 +2805,7 @@ class Installer {
1946
2805
 
1947
2806
  return files;
1948
2807
  } catch (error) {
1949
- console.warn('Warning: Could not read files-manifest.csv:', error.message);
2808
+ await prompts.log.warn('Could not read files-manifest.csv: ' + error.message);
1950
2809
  return [];
1951
2810
  }
1952
2811
  }
@@ -1961,6 +2820,9 @@ class Installer {
1961
2820
  const customFiles = [];
1962
2821
  const modifiedFiles = [];
1963
2822
 
2823
+ // Memory is always in _bmad/_memory
2824
+ const bmadMemoryPath = '_memory';
2825
+
1964
2826
  // Check if the manifest has hashes - if not, we can't detect modifications
1965
2827
  let manifestHasHashes = false;
1966
2828
  if (existingFilesManifest && existingFilesManifest.length > 0) {
@@ -1971,13 +2833,10 @@ class Installer {
1971
2833
  const installedFilesMap = new Map();
1972
2834
  for (const fileEntry of existingFilesManifest) {
1973
2835
  if (fileEntry.path) {
1974
- // Files in manifest are stored as relative paths starting with 'bmad/'
1975
- // Convert to absolute path
1976
- const relativePath = fileEntry.path.startsWith('bmad/') ? fileEntry.path.slice(5) : fileEntry.path;
1977
- const absolutePath = path.join(bmadDir, relativePath);
2836
+ const absolutePath = path.join(bmadDir, fileEntry.path);
1978
2837
  installedFilesMap.set(path.normalize(absolutePath), {
1979
2838
  hash: fileEntry.hash,
1980
- relativePath: relativePath,
2839
+ relativePath: fileEntry.path,
1981
2840
  });
1982
2841
  }
1983
2842
  }
@@ -2003,20 +2862,47 @@ class Installer {
2003
2862
  const relativePath = path.relative(bmadDir, fullPath);
2004
2863
  const fileName = path.basename(fullPath);
2005
2864
 
2006
- // Skip _cfg directory - system files
2007
- if (relativePath.startsWith('_cfg/') || relativePath.startsWith('_cfg\\')) {
2865
+ // Skip _config directory EXCEPT for modified agent customizations
2866
+ if (relativePath.startsWith('_config/') || relativePath.startsWith('_config\\')) {
2867
+ // Special handling for .customize.yaml files - only preserve if modified
2868
+ if (relativePath.includes('/agents/') && fileName.endsWith('.customize.yaml')) {
2869
+ // Check if the customization file has been modified from manifest
2870
+ const manifestPath = path.join(bmadDir, '_config', 'manifest.yaml');
2871
+ if (await fs.pathExists(manifestPath)) {
2872
+ const crypto = require('node:crypto');
2873
+ const currentContent = await fs.readFile(fullPath, 'utf8');
2874
+ const currentHash = crypto.createHash('sha256').update(currentContent).digest('hex');
2875
+
2876
+ const yaml = require('yaml');
2877
+ const manifestContent = await fs.readFile(manifestPath, 'utf8');
2878
+ const manifestData = yaml.parse(manifestContent);
2879
+ const originalHash = manifestData.agentCustomizations?.[relativePath];
2880
+
2881
+ // Only add to customFiles if hash differs (user modified)
2882
+ if (originalHash && currentHash !== originalHash) {
2883
+ customFiles.push(fullPath);
2884
+ }
2885
+ }
2886
+ }
2887
+ continue;
2888
+ }
2889
+
2890
+ if (relativePath.startsWith(bmadMemoryPath + '/') && path.dirname(relativePath).includes('-sidecar')) {
2008
2891
  continue;
2009
2892
  }
2010
2893
 
2011
2894
  // Skip config.yaml files - these are regenerated on each install/update
2012
- // Users should use _cfg/agents/ override files instead
2013
2895
  if (fileName === 'config.yaml') {
2014
2896
  continue;
2015
2897
  }
2016
2898
 
2017
2899
  if (!fileInfo) {
2018
2900
  // File not in manifest = custom file
2019
- customFiles.push(fullPath);
2901
+ // EXCEPT: Agent .md files in module folders are generated files, not custom
2902
+ // Only treat .md files under _config/agents/ as custom
2903
+ if (!(fileName.endsWith('.md') && relativePath.includes('/agents/') && !relativePath.startsWith('_config/'))) {
2904
+ customFiles.push(fullPath);
2905
+ }
2020
2906
  } else if (manifestHasHashes && fileInfo.hash) {
2021
2907
  // File in manifest with hash - check if it was modified
2022
2908
  const currentHash = await this.manifest.calculateFileHash(fullPath);
@@ -2028,8 +2914,6 @@ class Installer {
2028
2914
  });
2029
2915
  }
2030
2916
  }
2031
- // If manifest doesn't have hashes, we can't detect modifications
2032
- // so we just skip files that are in the manifest
2033
2917
  }
2034
2918
  }
2035
2919
  } catch {
@@ -2042,206 +2926,236 @@ class Installer {
2042
2926
  }
2043
2927
 
2044
2928
  /**
2045
- * Private: Create agent configuration files
2046
- * @param {string} bmadDir - BMAD installation directory
2047
- * @param {Object} userInfo - User information including name and language
2929
+ * Handle missing custom module sources interactively
2930
+ * @param {Map} customModuleSources - Map of custom module ID to info
2931
+ * @param {string} bmadDir - BMAD directory
2932
+ * @param {string} projectRoot - Project root directory
2933
+ * @param {string} operation - Current operation ('update', 'compile', etc.)
2934
+ * @param {Array} installedModules - Array of installed module IDs (will be modified)
2935
+ * @param {boolean} [skipPrompts=false] - Skip interactive prompts and keep all modules with missing sources
2936
+ * @returns {Object} Object with validCustomModules array and keptModulesWithoutSources array
2048
2937
  */
2049
- async createAgentConfigs(bmadDir, userInfo = null) {
2050
- const agentConfigDir = path.join(bmadDir, '_cfg', 'agents');
2051
- await fs.ensureDir(agentConfigDir);
2052
-
2053
- // Get all agents from all modules
2054
- const agents = [];
2055
- const agentDetails = []; // For manifest generation
2056
-
2057
- // Check modules for agents (including core)
2058
- const entries = await fs.readdir(bmadDir, { withFileTypes: true });
2059
- for (const entry of entries) {
2060
- if (entry.isDirectory() && entry.name !== '_cfg') {
2061
- const moduleAgentsPath = path.join(bmadDir, entry.name, 'agents');
2062
- if (await fs.pathExists(moduleAgentsPath)) {
2063
- const agentFiles = await fs.readdir(moduleAgentsPath);
2064
- for (const agentFile of agentFiles) {
2065
- if (agentFile.endsWith('.md')) {
2066
- const agentPath = path.join(moduleAgentsPath, agentFile);
2067
- const agentContent = await fs.readFile(agentPath, 'utf8');
2068
-
2069
- // Skip agents with localskip="true"
2070
- const hasLocalSkip = agentContent.match(/<agent[^>]*\slocalskip="true"[^>]*>/);
2071
- if (hasLocalSkip) {
2072
- continue; // Skip this agent - it should not have been installed
2073
- }
2074
-
2075
- const agentName = path.basename(agentFile, '.md');
2076
-
2077
- // Extract any nodes with agentConfig="true"
2078
- const agentConfigNodes = this.extractAgentConfigNodes(agentContent);
2079
-
2080
- agents.push({
2081
- name: agentName,
2082
- module: entry.name,
2083
- agentConfigNodes: agentConfigNodes,
2084
- });
2085
-
2086
- // Use shared AgentPartyGenerator to extract details
2087
- let details = AgentPartyGenerator.extractAgentDetails(agentContent, entry.name, agentName);
2088
-
2089
- // Apply config overrides if they exist
2090
- if (details) {
2091
- const configPath = path.join(agentConfigDir, `${entry.name}-${agentName}.md`);
2092
- if (await fs.pathExists(configPath)) {
2093
- const configContent = await fs.readFile(configPath, 'utf8');
2094
- details = AgentPartyGenerator.applyConfigOverrides(details, configContent);
2095
- }
2096
- agentDetails.push(details);
2097
- }
2098
- }
2099
- }
2938
+ async handleMissingCustomSources(customModuleSources, bmadDir, projectRoot, operation, installedModules, skipPrompts = false) {
2939
+ const validCustomModules = [];
2940
+ const keptModulesWithoutSources = []; // Track modules kept without sources
2941
+ const customModulesWithMissingSources = [];
2942
+
2943
+ // Check which sources exist
2944
+ for (const [moduleId, customInfo] of customModuleSources) {
2945
+ if (await fs.pathExists(customInfo.sourcePath)) {
2946
+ validCustomModules.push({
2947
+ id: moduleId,
2948
+ name: customInfo.name,
2949
+ path: customInfo.sourcePath,
2950
+ info: customInfo,
2951
+ });
2952
+ } else {
2953
+ // For cached modules that are missing, we just skip them without prompting
2954
+ if (customInfo.cached) {
2955
+ // Skip cached modules without prompting
2956
+ keptModulesWithoutSources.push({
2957
+ id: moduleId,
2958
+ name: customInfo.name,
2959
+ cached: true,
2960
+ });
2961
+ } else {
2962
+ customModulesWithMissingSources.push({
2963
+ id: moduleId,
2964
+ name: customInfo.name,
2965
+ sourcePath: customInfo.sourcePath,
2966
+ relativePath: customInfo.relativePath,
2967
+ info: customInfo,
2968
+ });
2100
2969
  }
2101
2970
  }
2102
2971
  }
2103
2972
 
2104
- // Create config file for each agent
2105
- let createdCount = 0;
2106
- let skippedCount = 0;
2107
-
2108
- // Load agent config template
2109
- const templatePath = getSourcePath('utility', 'models', 'agent-config-template.md');
2110
- const templateContent = await fs.readFile(templatePath, 'utf8');
2111
-
2112
- for (const agent of agents) {
2113
- const configPath = path.join(agentConfigDir, `${agent.module}-${agent.name}.md`);
2973
+ // If no missing sources, return immediately
2974
+ if (customModulesWithMissingSources.length === 0) {
2975
+ return {
2976
+ validCustomModules,
2977
+ keptModulesWithoutSources: [],
2978
+ };
2979
+ }
2114
2980
 
2115
- // Skip if config file already exists (preserve custom configurations)
2116
- if (await fs.pathExists(configPath)) {
2117
- skippedCount++;
2118
- continue;
2981
+ // Non-interactive mode: keep all modules with missing sources
2982
+ if (skipPrompts) {
2983
+ for (const missing of customModulesWithMissingSources) {
2984
+ keptModulesWithoutSources.push(missing.id);
2119
2985
  }
2986
+ return { validCustomModules, keptModulesWithoutSources };
2987
+ }
2120
2988
 
2121
- // Build config content header
2122
- let configContent = `# Agent Config: ${agent.name}\n\n`;
2989
+ await prompts.log.warn(`Found ${customModulesWithMissingSources.length} custom module(s) with missing sources:`);
2123
2990
 
2124
- // Process template and add agent-specific config nodes
2125
- let processedTemplate = templateContent;
2991
+ let keptCount = 0;
2992
+ let updatedCount = 0;
2993
+ let removedCount = 0;
2126
2994
 
2127
- // Replace {core:user_name} placeholder with actual user name if available
2128
- if (userInfo && userInfo.userName) {
2129
- processedTemplate = processedTemplate.replaceAll('{core:user_name}', userInfo.userName);
2130
- }
2995
+ for (const missing of customModulesWithMissingSources) {
2996
+ await prompts.log.message(
2997
+ `${missing.name} (${missing.id})\n Original source: ${missing.relativePath}\n Full path: ${missing.sourcePath}`,
2998
+ );
2131
2999
 
2132
- // Replace {core:communication_language} placeholder with actual language if available
2133
- if (userInfo && userInfo.responseLanguage) {
2134
- processedTemplate = processedTemplate.replaceAll('{core:communication_language}', userInfo.responseLanguage);
3000
+ const choices = [
3001
+ {
3002
+ name: 'Keep installed (will not be processed)',
3003
+ value: 'keep',
3004
+ hint: 'Keep',
3005
+ },
3006
+ {
3007
+ name: 'Specify new source location',
3008
+ value: 'update',
3009
+ hint: 'Update',
3010
+ },
3011
+ ];
3012
+
3013
+ // Only add remove option if not just compiling agents
3014
+ if (operation !== 'compile-agents') {
3015
+ choices.push({
3016
+ name: '⚠️ REMOVE module completely (destructive!)',
3017
+ value: 'remove',
3018
+ hint: 'Remove',
3019
+ });
2135
3020
  }
2136
3021
 
2137
- // If this agent has agentConfig nodes, add them after the existing comment
2138
- if (agent.agentConfigNodes && agent.agentConfigNodes.length > 0) {
2139
- // Find the agent-specific configuration nodes comment
2140
- const commentPattern = /(\s*<!-- Agent-specific configuration nodes -->)/;
2141
- const commentMatch = processedTemplate.match(commentPattern);
3022
+ const action = await prompts.select({
3023
+ message: `How would you like to handle "${missing.name}"?`,
3024
+ choices,
3025
+ });
3026
+
3027
+ switch (action) {
3028
+ case 'update': {
3029
+ // Use sync validation because @clack/prompts doesn't support async validate
3030
+ const newSourcePath = await prompts.text({
3031
+ message: 'Enter the new path to the custom module:',
3032
+ default: missing.sourcePath,
3033
+ validate: (input) => {
3034
+ if (!input || input.trim() === '') {
3035
+ return 'Please enter a path';
3036
+ }
3037
+ const expandedPath = path.resolve(input.trim());
3038
+ if (!fs.pathExistsSync(expandedPath)) {
3039
+ return 'Path does not exist';
3040
+ }
3041
+ // Check if it looks like a valid module
3042
+ const moduleYamlPath = path.join(expandedPath, 'module.yaml');
3043
+ const agentsPath = path.join(expandedPath, 'agents');
3044
+ const workflowsPath = path.join(expandedPath, 'workflows');
3045
+
3046
+ if (!fs.pathExistsSync(moduleYamlPath) && !fs.pathExistsSync(agentsPath) && !fs.pathExistsSync(workflowsPath)) {
3047
+ return 'Path does not appear to contain a valid custom module';
3048
+ }
3049
+ return; // clack expects undefined for valid input
3050
+ },
3051
+ });
2142
3052
 
2143
- if (commentMatch) {
2144
- // Add nodes right after the comment
2145
- let agentSpecificNodes = '';
2146
- for (const node of agent.agentConfigNodes) {
2147
- agentSpecificNodes += `\n ${node}`;
3053
+ // Defensive: handleCancel should have exited, but guard against symbol propagation
3054
+ if (typeof newSourcePath !== 'string') {
3055
+ keptCount++;
3056
+ keptModulesWithoutSources.push(missing.id);
3057
+ continue;
2148
3058
  }
2149
3059
 
2150
- processedTemplate = processedTemplate.replace(commentPattern, `$1${agentSpecificNodes}`);
2151
- }
2152
- }
3060
+ // Update the source in manifest
3061
+ const resolvedPath = path.resolve(newSourcePath.trim());
3062
+ missing.info.sourcePath = resolvedPath;
3063
+ // Remove relativePath - we only store absolute sourcePath now
3064
+ delete missing.info.relativePath;
3065
+ await this.manifest.addCustomModule(bmadDir, missing.info);
3066
+
3067
+ validCustomModules.push({
3068
+ id: missing.id,
3069
+ name: missing.name,
3070
+ path: resolvedPath,
3071
+ info: missing.info,
3072
+ });
2153
3073
 
2154
- configContent += processedTemplate;
3074
+ updatedCount++;
3075
+ await prompts.log.success('Updated source location');
2155
3076
 
2156
- // Ensure POSIX-compliant final newline
2157
- if (!configContent.endsWith('\n')) {
2158
- configContent += '\n';
2159
- }
3077
+ break;
3078
+ }
3079
+ case 'remove': {
3080
+ // Extra confirmation for destructive remove
3081
+ await prompts.log.error(
3082
+ `WARNING: This will PERMANENTLY DELETE "${missing.name}" and all its files!\n Module location: ${path.join(bmadDir, missing.id)}`,
3083
+ );
3084
+
3085
+ const confirmDelete = await prompts.confirm({
3086
+ message: 'Are you absolutely sure you want to delete this module?',
3087
+ default: false,
3088
+ });
2160
3089
 
2161
- await fs.writeFile(configPath, configContent, 'utf8');
2162
- this.installedFiles.push(configPath); // Track agent config files
2163
- createdCount++;
2164
- }
3090
+ if (confirmDelete) {
3091
+ const typedConfirm = await prompts.text({
3092
+ message: 'Type "DELETE" to confirm permanent deletion:',
3093
+ validate: (input) => {
3094
+ if (input !== 'DELETE') {
3095
+ return 'You must type "DELETE" exactly to proceed';
3096
+ }
3097
+ return; // clack expects undefined for valid input
3098
+ },
3099
+ });
2165
3100
 
2166
- // Generate agent manifest with overrides applied
2167
- await this.generateAgentManifest(bmadDir, agentDetails);
3101
+ if (typedConfirm === 'DELETE') {
3102
+ // Remove the module from filesystem and manifest
3103
+ const modulePath = path.join(bmadDir, missing.id);
3104
+ if (await fs.pathExists(modulePath)) {
3105
+ const fsExtra = require('fs-extra');
3106
+ await fsExtra.remove(modulePath);
3107
+ await prompts.log.warn(`Deleted module directory: ${path.relative(projectRoot, modulePath)}`);
3108
+ }
2168
3109
 
2169
- return { total: agents.length, created: createdCount, skipped: skippedCount };
2170
- }
3110
+ await this.manifest.removeModule(bmadDir, missing.id);
3111
+ await this.manifest.removeCustomModule(bmadDir, missing.id);
3112
+ await prompts.log.warn('Removed from manifest');
2171
3113
 
2172
- /**
2173
- * Generate agent manifest XML file
2174
- * @param {string} bmadDir - BMAD installation directory
2175
- * @param {Array} agentDetails - Array of agent details
2176
- */
2177
- async generateAgentManifest(bmadDir, agentDetails) {
2178
- const manifestPath = path.join(bmadDir, '_cfg', 'agent-manifest.csv');
2179
- await AgentPartyGenerator.writeAgentParty(manifestPath, agentDetails, { forWeb: false });
2180
- }
3114
+ // Also remove from installedModules list
3115
+ if (installedModules && installedModules.includes(missing.id)) {
3116
+ const index = installedModules.indexOf(missing.id);
3117
+ if (index !== -1) {
3118
+ installedModules.splice(index, 1);
3119
+ }
3120
+ }
2181
3121
 
2182
- /**
2183
- * Extract nodes with agentConfig="true" from agent content
2184
- * @param {string} content - Agent file content
2185
- * @returns {Array} Array of XML nodes that should be added to agent config
2186
- */
2187
- extractAgentConfigNodes(content) {
2188
- const nodes = [];
3122
+ removedCount++;
3123
+ await prompts.log.error(`"${missing.name}" has been permanently removed`);
3124
+ } else {
3125
+ await prompts.log.message('Removal cancelled - module will be kept');
3126
+ keptCount++;
3127
+ }
3128
+ } else {
3129
+ await prompts.log.message('Removal cancelled - module will be kept');
3130
+ keptCount++;
3131
+ }
2189
3132
 
2190
- try {
2191
- // Find all XML nodes with agentConfig="true"
2192
- // Match self-closing tags and tags with content
2193
- const selfClosingPattern = /<([a-zA-Z][a-zA-Z0-9_-]*)\s+[^>]*agentConfig="true"[^>]*\/>/g;
2194
- const withContentPattern = /<([a-zA-Z][a-zA-Z0-9_-]*)\s+[^>]*agentConfig="true"[^>]*>([\s\S]*?)<\/\1>/g;
2195
-
2196
- // Extract self-closing tags
2197
- let match;
2198
- while ((match = selfClosingPattern.exec(content)) !== null) {
2199
- // Extract just the tag without children (structure only)
2200
- const tagMatch = match[0].match(/<([a-zA-Z][a-zA-Z0-9_-]*)([^>]*)\/>/);
2201
- if (tagMatch) {
2202
- const tagName = tagMatch[1];
2203
- const attributes = tagMatch[2].replace(/\s*agentConfig="true"/, ''); // Remove agentConfig attribute
2204
- nodes.push(`<${tagName}${attributes}></${tagName}>`);
3133
+ break;
2205
3134
  }
2206
- }
2207
-
2208
- // Extract tags with content
2209
- while ((match = withContentPattern.exec(content)) !== null) {
2210
- const fullMatch = match[0];
2211
- const tagName = match[1];
3135
+ case 'keep': {
3136
+ keptCount++;
3137
+ keptModulesWithoutSources.push(missing.id);
3138
+ await prompts.log.message('Module will be kept as-is');
2212
3139
 
2213
- // Extract opening tag with attributes (removing agentConfig="true")
2214
- const openingTagMatch = fullMatch.match(new RegExp(`<${tagName}([^>]*)>`));
2215
- if (openingTagMatch) {
2216
- const attributes = openingTagMatch[1].replace(/\s*agentConfig="true"/, '');
2217
- // Add empty node structure (no children)
2218
- nodes.push(`<${tagName}${attributes}></${tagName}>`);
3140
+ break;
2219
3141
  }
3142
+ // No default
2220
3143
  }
2221
- } catch (error) {
2222
- console.error('Error extracting agentConfig nodes:', error);
2223
3144
  }
2224
3145
 
2225
- return nodes;
2226
- }
2227
-
2228
- /**
2229
- * Copy IDE-specific documentation to BMAD docs
2230
- * @param {Array} ides - List of selected IDEs
2231
- * @param {string} bmadDir - BMAD installation directory
2232
- */
2233
- async copyIdeDocumentation(ides, bmadDir) {
2234
- const docsDir = path.join(bmadDir, 'docs');
2235
- await fs.ensureDir(docsDir);
2236
-
2237
- for (const ide of ides) {
2238
- const sourceDocPath = path.join(getProjectRoot(), 'docs', 'ide-info', `${ide}.md`);
2239
- const targetDocPath = path.join(docsDir, `${ide}-instructions.md`);
2240
-
2241
- if (await fs.pathExists(sourceDocPath)) {
2242
- await this.copyFileWithPlaceholderReplacement(sourceDocPath, targetDocPath, this.bmadFolderName || 'bmad');
2243
- }
3146
+ // Show summary
3147
+ if (keptCount > 0 || updatedCount > 0 || removedCount > 0) {
3148
+ let summary = 'Summary for custom modules with missing sources:';
3149
+ if (keptCount > 0) summary += `\n • ${keptCount} module(s) kept as-is`;
3150
+ if (updatedCount > 0) summary += `\n • ${updatedCount} module(s) updated with new sources`;
3151
+ if (removedCount > 0) summary += `\n • ${removedCount} module(s) permanently deleted`;
3152
+ await prompts.log.message(summary);
2244
3153
  }
3154
+
3155
+ return {
3156
+ validCustomModules,
3157
+ keptModulesWithoutSources,
3158
+ };
2245
3159
  }
2246
3160
  }
2247
3161