bmad-method 6.0.0-alpha.13 → 6.0.0-alpha.15

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