wizz-method 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (761) hide show
  1. package/.claude-plugin/marketplace.json +80 -0
  2. package/AGENTS.md +12 -0
  3. package/LICENSE +30 -0
  4. package/README.md +72 -0
  5. package/bmad-modules.yaml +77 -0
  6. package/build/artifacts/llms-full.txt +4049 -0
  7. package/build/artifacts/llms.txt +28 -0
  8. package/build/site/404.html +55 -0
  9. package/build/site/_astro/MobileTableOfContents.astro_astro_type_script_index_0_lang.hwBsy0Mo.js +1 -0
  10. package/build/site/_astro/Search.astro_astro_type_script_index_0_lang.cjYDvRdi.js +1 -0
  11. package/build/site/_astro/TableOfContents.astro_astro_type_script_index_0_lang.FuRcXuRY.js +1 -0
  12. package/build/site/_astro/ec.0vx5m.js +3 -0
  13. package/build/site/_astro/ec.v4551.css +1 -0
  14. package/build/site/_astro/index.DdjI3lF_.css +1 -0
  15. package/build/site/_astro/page.B1D-nYk3.js +1 -0
  16. package/build/site/_astro/print.DNXP8c50.css +1 -0
  17. package/build/site/_astro/ui-core.D_Lfcn_I.js +2 -0
  18. package/build/site/cs/404/index.html +42 -0
  19. package/build/site/cs/explanation/advanced-elicitation/index.html +105 -0
  20. package/build/site/cs/explanation/adversarial-review/index.html +109 -0
  21. package/build/site/cs/explanation/analysis-phase/index.html +129 -0
  22. package/build/site/cs/explanation/brainstorming/index.html +92 -0
  23. package/build/site/cs/explanation/checkpoint-preview/index.html +121 -0
  24. package/build/site/cs/explanation/established-projects-faq/index.html +107 -0
  25. package/build/site/cs/explanation/forensic-investigation/index.html +167 -0
  26. package/build/site/cs/explanation/named-agents/index.html +187 -0
  27. package/build/site/cs/explanation/party-mode/index.html +101 -0
  28. package/build/site/cs/explanation/preventing-agent-conflicts/index.html +185 -0
  29. package/build/site/cs/explanation/project-context/index.html +198 -0
  30. package/build/site/cs/explanation/quick-dev/index.html +113 -0
  31. package/build/site/cs/explanation/web-bundles/index.html +178 -0
  32. package/build/site/cs/explanation/why-solutioning-matters/index.html +168 -0
  33. package/build/site/cs/how-to/customize-bmad/index.html +197 -0
  34. package/build/site/cs/how-to/established-projects/index.html +165 -0
  35. package/build/site/cs/how-to/expand-bmad-for-your-org/index.html +245 -0
  36. package/build/site/cs/how-to/get-answers-about-bmad/index.html +179 -0
  37. package/build/site/cs/how-to/install-bmad/index.html +124 -0
  38. package/build/site/cs/how-to/install-custom-modules/index.html +197 -0
  39. package/build/site/cs/how-to/non-interactive-installation/index.html +255 -0
  40. package/build/site/cs/how-to/project-context/index.html +138 -0
  41. package/build/site/cs/how-to/quick-fixes/index.html +122 -0
  42. package/build/site/cs/how-to/shard-large-documents/index.html +103 -0
  43. package/build/site/cs/how-to/upgrade-to-v6/index.html +185 -0
  44. package/build/site/cs/how-to/use-web-bundles/index.html +99 -0
  45. package/build/site/cs/index.html +129 -0
  46. package/build/site/cs/reference/agents/index.html +173 -0
  47. package/build/site/cs/reference/commands/index.html +250 -0
  48. package/build/site/cs/reference/core-tools/index.html +337 -0
  49. package/build/site/cs/reference/modules/index.html +132 -0
  50. package/build/site/cs/reference/testing/index.html +218 -0
  51. package/build/site/cs/reference/workflow-map/index.html +240 -0
  52. package/build/site/cs/roadmap/index.html +77 -0
  53. package/build/site/cs/tutorials/getting-started/index.html +382 -0
  54. package/build/site/diagrams/checkpoint-preview-diagram-fr.webp +0 -0
  55. package/build/site/diagrams/checkpoint-preview-diagram.png +0 -0
  56. package/build/site/diagrams/quick-dev-diagram-fr.webp +0 -0
  57. package/build/site/diagrams/quick-dev-diagram.png +0 -0
  58. package/build/site/explanation/advanced-elicitation/index.html +105 -0
  59. package/build/site/explanation/adversarial-review/index.html +109 -0
  60. package/build/site/explanation/analysis-phase/index.html +129 -0
  61. package/build/site/explanation/brainstorming/index.html +92 -0
  62. package/build/site/explanation/checkpoint-preview/index.html +121 -0
  63. package/build/site/explanation/established-projects-faq/index.html +107 -0
  64. package/build/site/explanation/forensic-investigation/index.html +167 -0
  65. package/build/site/explanation/named-agents/index.html +187 -0
  66. package/build/site/explanation/party-mode/index.html +256 -0
  67. package/build/site/explanation/preventing-agent-conflicts/index.html +185 -0
  68. package/build/site/explanation/project-context/index.html +198 -0
  69. package/build/site/explanation/quick-dev/index.html +113 -0
  70. package/build/site/explanation/web-bundles/index.html +178 -0
  71. package/build/site/explanation/why-solutioning-matters/index.html +168 -0
  72. package/build/site/fr/404/index.html +42 -0
  73. package/build/site/fr/explanation/advanced-elicitation/index.html +105 -0
  74. package/build/site/fr/explanation/adversarial-review/index.html +126 -0
  75. package/build/site/fr/explanation/analysis-phase/index.html +137 -0
  76. package/build/site/fr/explanation/brainstorming/index.html +92 -0
  77. package/build/site/fr/explanation/checkpoint-preview/index.html +121 -0
  78. package/build/site/fr/explanation/established-projects-faq/index.html +107 -0
  79. package/build/site/fr/explanation/forensic-investigation/index.html +194 -0
  80. package/build/site/fr/explanation/named-agents/index.html +187 -0
  81. package/build/site/fr/explanation/party-mode/index.html +112 -0
  82. package/build/site/fr/explanation/preventing-agent-conflicts/index.html +196 -0
  83. package/build/site/fr/explanation/project-context/index.html +198 -0
  84. package/build/site/fr/explanation/quick-dev/index.html +127 -0
  85. package/build/site/fr/explanation/web-bundles/index.html +198 -0
  86. package/build/site/fr/explanation/why-solutioning-matters/index.html +191 -0
  87. package/build/site/fr/how-to/customize-bmad/index.html +277 -0
  88. package/build/site/fr/how-to/established-projects/index.html +176 -0
  89. package/build/site/fr/how-to/expand-bmad-for-your-org/index.html +245 -0
  90. package/build/site/fr/how-to/get-answers-about-bmad/index.html +129 -0
  91. package/build/site/fr/how-to/install-bmad/index.html +330 -0
  92. package/build/site/fr/how-to/install-custom-modules/index.html +197 -0
  93. package/build/site/fr/how-to/non-interactive-installation/index.html +73 -0
  94. package/build/site/fr/how-to/project-context/index.html +138 -0
  95. package/build/site/fr/how-to/quick-fixes/index.html +130 -0
  96. package/build/site/fr/how-to/shard-large-documents/index.html +103 -0
  97. package/build/site/fr/how-to/upgrade-to-v6/index.html +202 -0
  98. package/build/site/fr/how-to/use-web-bundles/index.html +99 -0
  99. package/build/site/fr/index.html +144 -0
  100. package/build/site/fr/reference/agents/index.html +179 -0
  101. package/build/site/fr/reference/commands/index.html +264 -0
  102. package/build/site/fr/reference/core-tools/index.html +392 -0
  103. package/build/site/fr/reference/modules/index.html +146 -0
  104. package/build/site/fr/reference/testing/index.html +229 -0
  105. package/build/site/fr/reference/workflow-map/index.html +282 -0
  106. package/build/site/fr/roadmap/index.html +77 -0
  107. package/build/site/fr/tutorials/getting-started/index.html +406 -0
  108. package/build/site/how-to/customize-bmad/index.html +277 -0
  109. package/build/site/how-to/established-projects/index.html +165 -0
  110. package/build/site/how-to/expand-bmad-for-your-org/index.html +245 -0
  111. package/build/site/how-to/get-answers-about-bmad/index.html +129 -0
  112. package/build/site/how-to/install-bmad/index.html +330 -0
  113. package/build/site/how-to/install-custom-modules/index.html +197 -0
  114. package/build/site/how-to/non-interactive-installation/index.html +73 -0
  115. package/build/site/how-to/project-context/index.html +141 -0
  116. package/build/site/how-to/quick-fixes/index.html +122 -0
  117. package/build/site/how-to/shard-large-documents/index.html +103 -0
  118. package/build/site/how-to/upgrade-to-v6/index.html +185 -0
  119. package/build/site/how-to/use-web-bundles/index.html +99 -0
  120. package/build/site/img/bmad-dark.png +0 -0
  121. package/build/site/img/bmad-light.png +0 -0
  122. package/build/site/index.html +128 -0
  123. package/build/site/llms-full.txt +4049 -0
  124. package/build/site/llms.txt +28 -0
  125. package/build/site/pagefind/fragment/cs-cz_178b946.pf_fragment +0 -0
  126. package/build/site/pagefind/fragment/cs-cz_19e1744.pf_fragment +0 -0
  127. package/build/site/pagefind/fragment/cs-cz_1b81518.pf_fragment +0 -0
  128. package/build/site/pagefind/fragment/cs-cz_24fd94a.pf_fragment +0 -0
  129. package/build/site/pagefind/fragment/cs-cz_25c5ff1.pf_fragment +0 -0
  130. package/build/site/pagefind/fragment/cs-cz_2a7d8c4.pf_fragment +0 -0
  131. package/build/site/pagefind/fragment/cs-cz_2b14d1f.pf_fragment +0 -0
  132. package/build/site/pagefind/fragment/cs-cz_34d3775.pf_fragment +0 -0
  133. package/build/site/pagefind/fragment/cs-cz_366f93d.pf_fragment +0 -0
  134. package/build/site/pagefind/fragment/cs-cz_4761b65.pf_fragment +0 -0
  135. package/build/site/pagefind/fragment/cs-cz_4d1a40a.pf_fragment +0 -0
  136. package/build/site/pagefind/fragment/cs-cz_57dfabb.pf_fragment +0 -0
  137. package/build/site/pagefind/fragment/cs-cz_5c68f33.pf_fragment +0 -0
  138. package/build/site/pagefind/fragment/cs-cz_5efaba9.pf_fragment +0 -0
  139. package/build/site/pagefind/fragment/cs-cz_5f63a83.pf_fragment +0 -0
  140. package/build/site/pagefind/fragment/cs-cz_73bbdce.pf_fragment +0 -0
  141. package/build/site/pagefind/fragment/cs-cz_7b6af37.pf_fragment +0 -0
  142. package/build/site/pagefind/fragment/cs-cz_7d51fe5.pf_fragment +0 -0
  143. package/build/site/pagefind/fragment/cs-cz_7db5922.pf_fragment +0 -0
  144. package/build/site/pagefind/fragment/cs-cz_83e7116.pf_fragment +0 -0
  145. package/build/site/pagefind/fragment/cs-cz_83fec4e.pf_fragment +0 -0
  146. package/build/site/pagefind/fragment/cs-cz_84972ee.pf_fragment +0 -0
  147. package/build/site/pagefind/fragment/cs-cz_94e9289.pf_fragment +0 -0
  148. package/build/site/pagefind/fragment/cs-cz_a0a122c.pf_fragment +0 -0
  149. package/build/site/pagefind/fragment/cs-cz_a3e7fad.pf_fragment +0 -0
  150. package/build/site/pagefind/fragment/cs-cz_af43e66.pf_fragment +0 -0
  151. package/build/site/pagefind/fragment/cs-cz_b4f4e0a.pf_fragment +0 -0
  152. package/build/site/pagefind/fragment/cs-cz_b51b8b3.pf_fragment +0 -0
  153. package/build/site/pagefind/fragment/cs-cz_ba705d5.pf_fragment +0 -0
  154. package/build/site/pagefind/fragment/cs-cz_c315625.pf_fragment +0 -0
  155. package/build/site/pagefind/fragment/cs-cz_cf5138a.pf_fragment +0 -0
  156. package/build/site/pagefind/fragment/cs-cz_df8bdfe.pf_fragment +0 -0
  157. package/build/site/pagefind/fragment/cs-cz_e1b56f8.pf_fragment +0 -0
  158. package/build/site/pagefind/fragment/cs-cz_e3e33bb.pf_fragment +0 -0
  159. package/build/site/pagefind/fragment/cs-cz_f9d07b3.pf_fragment +0 -0
  160. package/build/site/pagefind/fragment/en_026af96.pf_fragment +0 -0
  161. package/build/site/pagefind/fragment/en_132ea1f.pf_fragment +0 -0
  162. package/build/site/pagefind/fragment/en_15ee460.pf_fragment +0 -0
  163. package/build/site/pagefind/fragment/en_171773c.pf_fragment +0 -0
  164. package/build/site/pagefind/fragment/en_1a7ec42.pf_fragment +0 -0
  165. package/build/site/pagefind/fragment/en_1bb5ebb.pf_fragment +0 -0
  166. package/build/site/pagefind/fragment/en_229434b.pf_fragment +0 -0
  167. package/build/site/pagefind/fragment/en_22b33a0.pf_fragment +0 -0
  168. package/build/site/pagefind/fragment/en_38efcf3.pf_fragment +0 -0
  169. package/build/site/pagefind/fragment/en_3d17cef.pf_fragment +0 -0
  170. package/build/site/pagefind/fragment/en_426a8fd.pf_fragment +0 -0
  171. package/build/site/pagefind/fragment/en_446b27a.pf_fragment +0 -0
  172. package/build/site/pagefind/fragment/en_582ecad.pf_fragment +0 -0
  173. package/build/site/pagefind/fragment/en_5d6b1f1.pf_fragment +0 -0
  174. package/build/site/pagefind/fragment/en_6465dfb.pf_fragment +0 -0
  175. package/build/site/pagefind/fragment/en_66f3f7a.pf_fragment +0 -0
  176. package/build/site/pagefind/fragment/en_68872f8.pf_fragment +0 -0
  177. package/build/site/pagefind/fragment/en_6e9deef.pf_fragment +0 -0
  178. package/build/site/pagefind/fragment/en_70a6a7e.pf_fragment +0 -0
  179. package/build/site/pagefind/fragment/en_73f0b1e.pf_fragment +0 -0
  180. package/build/site/pagefind/fragment/en_768fd99.pf_fragment +0 -0
  181. package/build/site/pagefind/fragment/en_7e52baa.pf_fragment +0 -0
  182. package/build/site/pagefind/fragment/en_8068cdc.pf_fragment +0 -0
  183. package/build/site/pagefind/fragment/en_9385c94.pf_fragment +0 -0
  184. package/build/site/pagefind/fragment/en_95b9c04.pf_fragment +0 -0
  185. package/build/site/pagefind/fragment/en_9a95e82.pf_fragment +0 -0
  186. package/build/site/pagefind/fragment/en_a66c4ef.pf_fragment +0 -0
  187. package/build/site/pagefind/fragment/en_ac6318c.pf_fragment +0 -0
  188. package/build/site/pagefind/fragment/en_bba8f66.pf_fragment +0 -0
  189. package/build/site/pagefind/fragment/en_d9f7193.pf_fragment +0 -0
  190. package/build/site/pagefind/fragment/en_e0e52b1.pf_fragment +0 -0
  191. package/build/site/pagefind/fragment/en_ece4cbe.pf_fragment +0 -0
  192. package/build/site/pagefind/fragment/en_ed6969c.pf_fragment +0 -0
  193. package/build/site/pagefind/fragment/en_f12187d.pf_fragment +0 -0
  194. package/build/site/pagefind/fragment/en_f643df1.pf_fragment +0 -0
  195. package/build/site/pagefind/fragment/fr-fr_10fdc25.pf_fragment +0 -0
  196. package/build/site/pagefind/fragment/fr-fr_132e2e0.pf_fragment +0 -0
  197. package/build/site/pagefind/fragment/fr-fr_1529d31.pf_fragment +0 -0
  198. package/build/site/pagefind/fragment/fr-fr_1531e2c.pf_fragment +0 -0
  199. package/build/site/pagefind/fragment/fr-fr_1d51fb7.pf_fragment +0 -0
  200. package/build/site/pagefind/fragment/fr-fr_27d0aaf.pf_fragment +0 -0
  201. package/build/site/pagefind/fragment/fr-fr_583aea8.pf_fragment +0 -0
  202. package/build/site/pagefind/fragment/fr-fr_5c4f14f.pf_fragment +0 -0
  203. package/build/site/pagefind/fragment/fr-fr_654bb96.pf_fragment +0 -0
  204. package/build/site/pagefind/fragment/fr-fr_65e37ae.pf_fragment +0 -0
  205. package/build/site/pagefind/fragment/fr-fr_6898afa.pf_fragment +0 -0
  206. package/build/site/pagefind/fragment/fr-fr_68d0a0b.pf_fragment +0 -0
  207. package/build/site/pagefind/fragment/fr-fr_6ce4bc1.pf_fragment +0 -0
  208. package/build/site/pagefind/fragment/fr-fr_7ddd126.pf_fragment +0 -0
  209. package/build/site/pagefind/fragment/fr-fr_7f50a2e.pf_fragment +0 -0
  210. package/build/site/pagefind/fragment/fr-fr_8d2df1b.pf_fragment +0 -0
  211. package/build/site/pagefind/fragment/fr-fr_9b8d33d.pf_fragment +0 -0
  212. package/build/site/pagefind/fragment/fr-fr_9c7f851.pf_fragment +0 -0
  213. package/build/site/pagefind/fragment/fr-fr_9cf6892.pf_fragment +0 -0
  214. package/build/site/pagefind/fragment/fr-fr_a512e53.pf_fragment +0 -0
  215. package/build/site/pagefind/fragment/fr-fr_a7ad3f6.pf_fragment +0 -0
  216. package/build/site/pagefind/fragment/fr-fr_afeac81.pf_fragment +0 -0
  217. package/build/site/pagefind/fragment/fr-fr_b364826.pf_fragment +0 -0
  218. package/build/site/pagefind/fragment/fr-fr_bdb3e1e.pf_fragment +0 -0
  219. package/build/site/pagefind/fragment/fr-fr_c4884c3.pf_fragment +0 -0
  220. package/build/site/pagefind/fragment/fr-fr_c54c827.pf_fragment +0 -0
  221. package/build/site/pagefind/fragment/fr-fr_ca664c2.pf_fragment +0 -0
  222. package/build/site/pagefind/fragment/fr-fr_ce3562d.pf_fragment +0 -0
  223. package/build/site/pagefind/fragment/fr-fr_d086476.pf_fragment +0 -0
  224. package/build/site/pagefind/fragment/fr-fr_d0b4e62.pf_fragment +0 -0
  225. package/build/site/pagefind/fragment/fr-fr_df4c57f.pf_fragment +0 -0
  226. package/build/site/pagefind/fragment/fr-fr_e1c2fa8.pf_fragment +0 -0
  227. package/build/site/pagefind/fragment/fr-fr_eaa1363.pf_fragment +0 -0
  228. package/build/site/pagefind/fragment/fr-fr_f456c69.pf_fragment +0 -0
  229. package/build/site/pagefind/fragment/fr-fr_fe8d783.pf_fragment +0 -0
  230. package/build/site/pagefind/fragment/vi-vn_1e5b1da.pf_fragment +0 -0
  231. package/build/site/pagefind/fragment/vi-vn_3268948.pf_fragment +0 -0
  232. package/build/site/pagefind/fragment/vi-vn_37f0bf6.pf_fragment +0 -0
  233. package/build/site/pagefind/fragment/vi-vn_485232c.pf_fragment +0 -0
  234. package/build/site/pagefind/fragment/vi-vn_4d3bd13.pf_fragment +0 -0
  235. package/build/site/pagefind/fragment/vi-vn_4e578d2.pf_fragment +0 -0
  236. package/build/site/pagefind/fragment/vi-vn_4ed2533.pf_fragment +0 -0
  237. package/build/site/pagefind/fragment/vi-vn_533743c.pf_fragment +0 -0
  238. package/build/site/pagefind/fragment/vi-vn_5ffd258.pf_fragment +0 -0
  239. package/build/site/pagefind/fragment/vi-vn_6bfed03.pf_fragment +0 -0
  240. package/build/site/pagefind/fragment/vi-vn_6f2ed6d.pf_fragment +0 -0
  241. package/build/site/pagefind/fragment/vi-vn_71aa305.pf_fragment +0 -0
  242. package/build/site/pagefind/fragment/vi-vn_7577c2c.pf_fragment +0 -0
  243. package/build/site/pagefind/fragment/vi-vn_7883435.pf_fragment +0 -0
  244. package/build/site/pagefind/fragment/vi-vn_7f2e496.pf_fragment +0 -0
  245. package/build/site/pagefind/fragment/vi-vn_7fce614.pf_fragment +0 -0
  246. package/build/site/pagefind/fragment/vi-vn_86274a6.pf_fragment +0 -0
  247. package/build/site/pagefind/fragment/vi-vn_8c4fd62.pf_fragment +0 -0
  248. package/build/site/pagefind/fragment/vi-vn_a06c773.pf_fragment +0 -0
  249. package/build/site/pagefind/fragment/vi-vn_a2e535b.pf_fragment +0 -0
  250. package/build/site/pagefind/fragment/vi-vn_a4b9dbc.pf_fragment +0 -0
  251. package/build/site/pagefind/fragment/vi-vn_b2d8e01.pf_fragment +0 -0
  252. package/build/site/pagefind/fragment/vi-vn_b631cf6.pf_fragment +0 -0
  253. package/build/site/pagefind/fragment/vi-vn_c970ebb.pf_fragment +0 -0
  254. package/build/site/pagefind/fragment/vi-vn_d229f2a.pf_fragment +0 -0
  255. package/build/site/pagefind/fragment/vi-vn_d685e88.pf_fragment +0 -0
  256. package/build/site/pagefind/fragment/vi-vn_dab2c52.pf_fragment +0 -0
  257. package/build/site/pagefind/fragment/vi-vn_df5a829.pf_fragment +0 -0
  258. package/build/site/pagefind/fragment/vi-vn_dfcf280.pf_fragment +0 -0
  259. package/build/site/pagefind/fragment/vi-vn_e8ec8cf.pf_fragment +0 -0
  260. package/build/site/pagefind/fragment/vi-vn_e9f3439.pf_fragment +0 -0
  261. package/build/site/pagefind/fragment/vi-vn_f0bfe83.pf_fragment +0 -0
  262. package/build/site/pagefind/fragment/vi-vn_f5462f7.pf_fragment +0 -0
  263. package/build/site/pagefind/fragment/vi-vn_fcc9bb7.pf_fragment +0 -0
  264. package/build/site/pagefind/fragment/vi-vn_fd55264.pf_fragment +0 -0
  265. package/build/site/pagefind/fragment/vi-vn_ffd39df.pf_fragment +0 -0
  266. package/build/site/pagefind/fragment/zh-cn_119c5bb.pf_fragment +0 -0
  267. package/build/site/pagefind/fragment/zh-cn_198e8ce.pf_fragment +0 -0
  268. package/build/site/pagefind/fragment/zh-cn_19b87a8.pf_fragment +0 -0
  269. package/build/site/pagefind/fragment/zh-cn_1edc3a5.pf_fragment +0 -0
  270. package/build/site/pagefind/fragment/zh-cn_36ae67a.pf_fragment +0 -0
  271. package/build/site/pagefind/fragment/zh-cn_37ab313.pf_fragment +0 -0
  272. package/build/site/pagefind/fragment/zh-cn_3b56778.pf_fragment +0 -0
  273. package/build/site/pagefind/fragment/zh-cn_5735cd4.pf_fragment +0 -0
  274. package/build/site/pagefind/fragment/zh-cn_6110769.pf_fragment +0 -0
  275. package/build/site/pagefind/fragment/zh-cn_6d66d9e.pf_fragment +0 -0
  276. package/build/site/pagefind/fragment/zh-cn_731b3f5.pf_fragment +0 -0
  277. package/build/site/pagefind/fragment/zh-cn_82885ff.pf_fragment +0 -0
  278. package/build/site/pagefind/fragment/zh-cn_8be7aac.pf_fragment +0 -0
  279. package/build/site/pagefind/fragment/zh-cn_8c3fa9e.pf_fragment +0 -0
  280. package/build/site/pagefind/fragment/zh-cn_8f452f7.pf_fragment +0 -0
  281. package/build/site/pagefind/fragment/zh-cn_96ccc61.pf_fragment +0 -0
  282. package/build/site/pagefind/fragment/zh-cn_9f29932.pf_fragment +0 -0
  283. package/build/site/pagefind/fragment/zh-cn_a24d786.pf_fragment +0 -0
  284. package/build/site/pagefind/fragment/zh-cn_a598178.pf_fragment +0 -0
  285. package/build/site/pagefind/fragment/zh-cn_a603f17.pf_fragment +0 -0
  286. package/build/site/pagefind/fragment/zh-cn_a9cffcf.pf_fragment +0 -0
  287. package/build/site/pagefind/fragment/zh-cn_c1bebbd.pf_fragment +0 -0
  288. package/build/site/pagefind/fragment/zh-cn_c38cf84.pf_fragment +0 -0
  289. package/build/site/pagefind/fragment/zh-cn_c42a851.pf_fragment +0 -0
  290. package/build/site/pagefind/fragment/zh-cn_ce57c6e.pf_fragment +0 -0
  291. package/build/site/pagefind/fragment/zh-cn_d4b5cd5.pf_fragment +0 -0
  292. package/build/site/pagefind/fragment/zh-cn_da40e6b.pf_fragment +0 -0
  293. package/build/site/pagefind/fragment/zh-cn_da777ec.pf_fragment +0 -0
  294. package/build/site/pagefind/fragment/zh-cn_de145f9.pf_fragment +0 -0
  295. package/build/site/pagefind/fragment/zh-cn_e376bf7.pf_fragment +0 -0
  296. package/build/site/pagefind/fragment/zh-cn_e663978.pf_fragment +0 -0
  297. package/build/site/pagefind/fragment/zh-cn_e97a13b.pf_fragment +0 -0
  298. package/build/site/pagefind/fragment/zh-cn_f13873d.pf_fragment +0 -0
  299. package/build/site/pagefind/fragment/zh-cn_f25fad4.pf_fragment +0 -0
  300. package/build/site/pagefind/fragment/zh-cn_f7edf48.pf_fragment +0 -0
  301. package/build/site/pagefind/index/cs-cz_24147b6.pf_index +0 -0
  302. package/build/site/pagefind/index/cs-cz_82fc657.pf_index +0 -0
  303. package/build/site/pagefind/index/cs-cz_8f6a83c.pf_index +0 -0
  304. package/build/site/pagefind/index/en_432f933.pf_index +0 -0
  305. package/build/site/pagefind/index/en_70c744a.pf_index +0 -0
  306. package/build/site/pagefind/index/en_fd84991.pf_index +0 -0
  307. package/build/site/pagefind/index/fr-fr_7275484.pf_index +0 -0
  308. package/build/site/pagefind/index/fr-fr_8096dd3.pf_index +0 -0
  309. package/build/site/pagefind/index/fr-fr_b34df12.pf_index +0 -0
  310. package/build/site/pagefind/index/fr-fr_e6ad665.pf_index +0 -0
  311. package/build/site/pagefind/index/vi-vn_129cf7e.pf_index +0 -0
  312. package/build/site/pagefind/index/vi-vn_7f54472.pf_index +0 -0
  313. package/build/site/pagefind/index/vi-vn_c1f31ce.pf_index +0 -0
  314. package/build/site/pagefind/index/vi-vn_c56f634.pf_index +0 -0
  315. package/build/site/pagefind/index/zh-cn_5ed0486.pf_index +0 -0
  316. package/build/site/pagefind/index/zh-cn_ad4d724.pf_index +0 -0
  317. package/build/site/pagefind/index/zh-cn_bc9f7b6.pf_index +0 -0
  318. package/build/site/pagefind/pagefind-entry.json +1 -0
  319. package/build/site/pagefind/pagefind-highlight.js +1064 -0
  320. package/build/site/pagefind/pagefind-modular-ui.css +214 -0
  321. package/build/site/pagefind/pagefind-modular-ui.js +8 -0
  322. package/build/site/pagefind/pagefind-ui.css +1 -0
  323. package/build/site/pagefind/pagefind-ui.js +2 -0
  324. package/build/site/pagefind/pagefind.cs-cz_cd39a8505eabf.pf_meta +0 -0
  325. package/build/site/pagefind/pagefind.en_8ae84a1b9c.pf_meta +0 -0
  326. package/build/site/pagefind/pagefind.fr-fr_20fca6f342d9a.pf_meta +0 -0
  327. package/build/site/pagefind/pagefind.js +6 -0
  328. package/build/site/pagefind/pagefind.vi-vn_6a45d08c2bbc8.pf_meta +0 -0
  329. package/build/site/pagefind/pagefind.zh-cn_ae911ac338eae.pf_meta +0 -0
  330. package/build/site/pagefind/wasm.en.pagefind +0 -0
  331. package/build/site/pagefind/wasm.fr-fr.pagefind +0 -0
  332. package/build/site/pagefind/wasm.unknown.pagefind +0 -0
  333. package/build/site/reference/agents/index.html +168 -0
  334. package/build/site/reference/commands/index.html +254 -0
  335. package/build/site/reference/core-tools/index.html +384 -0
  336. package/build/site/reference/modules/index.html +132 -0
  337. package/build/site/reference/testing/index.html +218 -0
  338. package/build/site/reference/workflow-map/index.html +266 -0
  339. package/build/site/roadmap/index.html +77 -0
  340. package/build/site/robots.txt +37 -0
  341. package/build/site/sitemap-0.xml +1 -0
  342. package/build/site/sitemap-index.xml +1 -0
  343. package/build/site/tutorials/getting-started/index.html +386 -0
  344. package/build/site/vi-vn/404/index.html +42 -0
  345. package/build/site/vi-vn/bmad-developer-guide/index.html +794 -0
  346. package/build/site/vi-vn/explanation/advanced-elicitation/index.html +105 -0
  347. package/build/site/vi-vn/explanation/adversarial-review/index.html +109 -0
  348. package/build/site/vi-vn/explanation/analysis-phase/index.html +129 -0
  349. package/build/site/vi-vn/explanation/brainstorming/index.html +92 -0
  350. package/build/site/vi-vn/explanation/checkpoint-preview/index.html +121 -0
  351. package/build/site/vi-vn/explanation/established-projects-faq/index.html +107 -0
  352. package/build/site/vi-vn/explanation/forensic-investigation/index.html +167 -0
  353. package/build/site/vi-vn/explanation/named-agents/index.html +187 -0
  354. package/build/site/vi-vn/explanation/party-mode/index.html +101 -0
  355. package/build/site/vi-vn/explanation/preventing-agent-conflicts/index.html +185 -0
  356. package/build/site/vi-vn/explanation/project-context/index.html +198 -0
  357. package/build/site/vi-vn/explanation/quick-dev/index.html +113 -0
  358. package/build/site/vi-vn/explanation/web-bundles/index.html +178 -0
  359. package/build/site/vi-vn/explanation/why-solutioning-matters/index.html +168 -0
  360. package/build/site/vi-vn/how-to/customize-bmad/index.html +277 -0
  361. package/build/site/vi-vn/how-to/established-projects/index.html +165 -0
  362. package/build/site/vi-vn/how-to/expand-bmad-for-your-org/index.html +228 -0
  363. package/build/site/vi-vn/how-to/get-answers-about-bmad/index.html +129 -0
  364. package/build/site/vi-vn/how-to/install-bmad/index.html +124 -0
  365. package/build/site/vi-vn/how-to/install-custom-modules/index.html +197 -0
  366. package/build/site/vi-vn/how-to/non-interactive-installation/index.html +300 -0
  367. package/build/site/vi-vn/how-to/project-context/index.html +138 -0
  368. package/build/site/vi-vn/how-to/quick-fixes/index.html +122 -0
  369. package/build/site/vi-vn/how-to/shard-large-documents/index.html +103 -0
  370. package/build/site/vi-vn/how-to/upgrade-to-v6/index.html +185 -0
  371. package/build/site/vi-vn/how-to/use-web-bundles/index.html +99 -0
  372. package/build/site/vi-vn/index.html +129 -0
  373. package/build/site/vi-vn/reference/agents/index.html +173 -0
  374. package/build/site/vi-vn/reference/commands/index.html +250 -0
  375. package/build/site/vi-vn/reference/core-tools/index.html +337 -0
  376. package/build/site/vi-vn/reference/modules/index.html +132 -0
  377. package/build/site/vi-vn/reference/testing/index.html +218 -0
  378. package/build/site/vi-vn/reference/workflow-map/index.html +240 -0
  379. package/build/site/vi-vn/roadmap/index.html +77 -0
  380. package/build/site/vi-vn/tutorials/getting-started/index.html +382 -0
  381. package/build/site/wizz-icon-orange.svg +7 -0
  382. package/build/site/workflow-map-diagram-fr.html +375 -0
  383. package/build/site/workflow-map-diagram.html +375 -0
  384. package/build/site/zh-cn/404/index.html +42 -0
  385. package/build/site/zh-cn/explanation/advanced-elicitation/index.html +142 -0
  386. package/build/site/zh-cn/explanation/adversarial-review/index.html +132 -0
  387. package/build/site/zh-cn/explanation/analysis-phase/index.html +129 -0
  388. package/build/site/zh-cn/explanation/brainstorming/index.html +147 -0
  389. package/build/site/zh-cn/explanation/checkpoint-preview/index.html +121 -0
  390. package/build/site/zh-cn/explanation/established-projects-faq/index.html +122 -0
  391. package/build/site/zh-cn/explanation/forensic-investigation/index.html +167 -0
  392. package/build/site/zh-cn/explanation/named-agents/index.html +187 -0
  393. package/build/site/zh-cn/explanation/party-mode/index.html +144 -0
  394. package/build/site/zh-cn/explanation/preventing-agent-conflicts/index.html +191 -0
  395. package/build/site/zh-cn/explanation/project-context/index.html +153 -0
  396. package/build/site/zh-cn/explanation/quick-dev/index.html +139 -0
  397. package/build/site/zh-cn/explanation/web-bundles/index.html +178 -0
  398. package/build/site/zh-cn/explanation/why-solutioning-matters/index.html +175 -0
  399. package/build/site/zh-cn/how-to/customize-bmad/index.html +202 -0
  400. package/build/site/zh-cn/how-to/established-projects/index.html +166 -0
  401. package/build/site/zh-cn/how-to/expand-bmad-for-your-org/index.html +223 -0
  402. package/build/site/zh-cn/how-to/get-answers-about-bmad/index.html +198 -0
  403. package/build/site/zh-cn/how-to/install-bmad/index.html +126 -0
  404. package/build/site/zh-cn/how-to/install-custom-modules/index.html +197 -0
  405. package/build/site/zh-cn/how-to/non-interactive-installation/index.html +254 -0
  406. package/build/site/zh-cn/how-to/project-context/index.html +143 -0
  407. package/build/site/zh-cn/how-to/quick-fixes/index.html +122 -0
  408. package/build/site/zh-cn/how-to/shard-large-documents/index.html +110 -0
  409. package/build/site/zh-cn/how-to/upgrade-to-v6/index.html +198 -0
  410. package/build/site/zh-cn/how-to/use-web-bundles/index.html +99 -0
  411. package/build/site/zh-cn/index.html +129 -0
  412. package/build/site/zh-cn/reference/agents/index.html +178 -0
  413. package/build/site/zh-cn/reference/commands/index.html +245 -0
  414. package/build/site/zh-cn/reference/core-tools/index.html +313 -0
  415. package/build/site/zh-cn/reference/modules/index.html +181 -0
  416. package/build/site/zh-cn/reference/testing/index.html +226 -0
  417. package/build/site/zh-cn/reference/workflow-map/index.html +238 -0
  418. package/build/site/zh-cn/roadmap/index.html +77 -0
  419. package/build/site/zh-cn/tutorials/getting-started/index.html +381 -0
  420. package/evals/bmm-skills/bmad-product-brief/evals.json +237 -0
  421. package/evals/bmm-skills/bmad-product-brief/files/branfield-memo.md +46 -0
  422. package/evals/bmm-skills/bmad-product-brief/files/forkbird-brief/addendum.md +40 -0
  423. package/evals/bmm-skills/bmad-product-brief/files/forkbird-brief/brief.md +56 -0
  424. package/evals/bmm-skills/bmad-product-brief/files/forkbird-brief/decision-log.md +27 -0
  425. package/evals/bmm-skills/bmad-product-brief/files/meridian-mobility-report.md +116 -0
  426. package/evals/bmm-skills/bmad-product-brief/files/mossridge-brief/addendum.md +41 -0
  427. package/evals/bmm-skills/bmad-product-brief/files/mossridge-brief/brief.md +57 -0
  428. package/evals/bmm-skills/bmad-product-brief/files/mossridge-brief/decision-log.md +29 -0
  429. package/evals/bmm-skills/bmad-product-brief/files/pantry-bridge-interviews.md +90 -0
  430. package/evals/bmm-skills/bmad-product-brief/files/q2-brainstorm.md +101 -0
  431. package/evals/bmm-skills/bmad-product-brief/triggers.json +18 -0
  432. package/package.json +114 -0
  433. package/removals.txt +62 -0
  434. package/src/bmm-skills/1-analysis/bmad-agent-analyst/SKILL.md +76 -0
  435. package/src/bmm-skills/1-analysis/bmad-agent-analyst/customize.toml +90 -0
  436. package/src/bmm-skills/1-analysis/bmad-agent-tech-writer/SKILL.md +76 -0
  437. package/src/bmm-skills/1-analysis/bmad-agent-tech-writer/customize.toml +81 -0
  438. package/src/bmm-skills/1-analysis/bmad-agent-tech-writer/explain-concept.md +20 -0
  439. package/src/bmm-skills/1-analysis/bmad-agent-tech-writer/mermaid-gen.md +20 -0
  440. package/src/bmm-skills/1-analysis/bmad-agent-tech-writer/validate-doc.md +19 -0
  441. package/src/bmm-skills/1-analysis/bmad-agent-tech-writer/write-document.md +20 -0
  442. package/src/bmm-skills/1-analysis/bmad-document-project/SKILL.md +62 -0
  443. package/src/bmm-skills/1-analysis/bmad-document-project/checklist.md +245 -0
  444. package/src/bmm-skills/1-analysis/bmad-document-project/customize.toml +41 -0
  445. package/src/bmm-skills/1-analysis/bmad-document-project/documentation-requirements.csv +12 -0
  446. package/src/bmm-skills/1-analysis/bmad-document-project/instructions.md +128 -0
  447. package/src/bmm-skills/1-analysis/bmad-document-project/templates/deep-dive-template.md +345 -0
  448. package/src/bmm-skills/1-analysis/bmad-document-project/templates/index-template.md +169 -0
  449. package/src/bmm-skills/1-analysis/bmad-document-project/templates/project-overview-template.md +103 -0
  450. package/src/bmm-skills/1-analysis/bmad-document-project/templates/project-scan-report-schema.json +160 -0
  451. package/src/bmm-skills/1-analysis/bmad-document-project/templates/source-tree-template.md +135 -0
  452. package/src/bmm-skills/1-analysis/bmad-document-project/workflows/deep-dive-instructions.md +300 -0
  453. package/src/bmm-skills/1-analysis/bmad-document-project/workflows/deep-dive-workflow.md +34 -0
  454. package/src/bmm-skills/1-analysis/bmad-document-project/workflows/full-scan-instructions.md +1108 -0
  455. package/src/bmm-skills/1-analysis/bmad-document-project/workflows/full-scan-workflow.md +34 -0
  456. package/src/bmm-skills/1-analysis/bmad-prfaq/SKILL.md +135 -0
  457. package/src/bmm-skills/1-analysis/bmad-prfaq/agents/artifact-analyzer.md +60 -0
  458. package/src/bmm-skills/1-analysis/bmad-prfaq/agents/web-researcher.md +49 -0
  459. package/src/bmm-skills/1-analysis/bmad-prfaq/assets/prfaq-template.md +62 -0
  460. package/src/bmm-skills/1-analysis/bmad-prfaq/bmad-manifest.json +16 -0
  461. package/src/bmm-skills/1-analysis/bmad-prfaq/customize.toml +41 -0
  462. package/src/bmm-skills/1-analysis/bmad-prfaq/references/customer-faq.md +55 -0
  463. package/src/bmm-skills/1-analysis/bmad-prfaq/references/internal-faq.md +51 -0
  464. package/src/bmm-skills/1-analysis/bmad-prfaq/references/press-release.md +60 -0
  465. package/src/bmm-skills/1-analysis/bmad-prfaq/references/verdict.md +83 -0
  466. package/src/bmm-skills/1-analysis/bmad-product-brief/SKILL.md +91 -0
  467. package/src/bmm-skills/1-analysis/bmad-product-brief/assets/brief-template.md +41 -0
  468. package/src/bmm-skills/1-analysis/bmad-product-brief/customize.toml +99 -0
  469. package/src/bmm-skills/1-analysis/research/bmad-domain-research/SKILL.md +96 -0
  470. package/src/bmm-skills/1-analysis/research/bmad-domain-research/customize.toml +41 -0
  471. package/src/bmm-skills/1-analysis/research/bmad-domain-research/domain-steps/step-01-init.md +137 -0
  472. package/src/bmm-skills/1-analysis/research/bmad-domain-research/domain-steps/step-02-domain-analysis.md +229 -0
  473. package/src/bmm-skills/1-analysis/research/bmad-domain-research/domain-steps/step-03-competitive-landscape.md +238 -0
  474. package/src/bmm-skills/1-analysis/research/bmad-domain-research/domain-steps/step-04-regulatory-focus.md +206 -0
  475. package/src/bmm-skills/1-analysis/research/bmad-domain-research/domain-steps/step-05-technical-trends.md +234 -0
  476. package/src/bmm-skills/1-analysis/research/bmad-domain-research/domain-steps/step-06-research-synthesis.md +450 -0
  477. package/src/bmm-skills/1-analysis/research/bmad-domain-research/research.template.md +29 -0
  478. package/src/bmm-skills/1-analysis/research/bmad-market-research/SKILL.md +96 -0
  479. package/src/bmm-skills/1-analysis/research/bmad-market-research/customize.toml +41 -0
  480. package/src/bmm-skills/1-analysis/research/bmad-market-research/research.template.md +29 -0
  481. package/src/bmm-skills/1-analysis/research/bmad-market-research/steps/step-01-init.md +184 -0
  482. package/src/bmm-skills/1-analysis/research/bmad-market-research/steps/step-02-customer-behavior.md +239 -0
  483. package/src/bmm-skills/1-analysis/research/bmad-market-research/steps/step-03-customer-pain-points.md +251 -0
  484. package/src/bmm-skills/1-analysis/research/bmad-market-research/steps/step-04-customer-decisions.md +261 -0
  485. package/src/bmm-skills/1-analysis/research/bmad-market-research/steps/step-05-competitive-analysis.md +173 -0
  486. package/src/bmm-skills/1-analysis/research/bmad-market-research/steps/step-06-research-completion.md +484 -0
  487. package/src/bmm-skills/1-analysis/research/bmad-technical-research/SKILL.md +96 -0
  488. package/src/bmm-skills/1-analysis/research/bmad-technical-research/customize.toml +41 -0
  489. package/src/bmm-skills/1-analysis/research/bmad-technical-research/research.template.md +29 -0
  490. package/src/bmm-skills/1-analysis/research/bmad-technical-research/technical-steps/step-01-init.md +137 -0
  491. package/src/bmm-skills/1-analysis/research/bmad-technical-research/technical-steps/step-02-technical-overview.md +239 -0
  492. package/src/bmm-skills/1-analysis/research/bmad-technical-research/technical-steps/step-03-integration-patterns.md +248 -0
  493. package/src/bmm-skills/1-analysis/research/bmad-technical-research/technical-steps/step-04-architectural-patterns.md +202 -0
  494. package/src/bmm-skills/1-analysis/research/bmad-technical-research/technical-steps/step-05-implementation-research.md +233 -0
  495. package/src/bmm-skills/1-analysis/research/bmad-technical-research/technical-steps/step-06-research-synthesis.md +493 -0
  496. package/src/bmm-skills/2-plan-workflows/bmad-agent-pm/SKILL.md +76 -0
  497. package/src/bmm-skills/2-plan-workflows/bmad-agent-pm/customize.toml +75 -0
  498. package/src/bmm-skills/2-plan-workflows/bmad-agent-ux-designer/SKILL.md +76 -0
  499. package/src/bmm-skills/2-plan-workflows/bmad-agent-ux-designer/customize.toml +60 -0
  500. package/src/bmm-skills/2-plan-workflows/bmad-create-prd/SKILL.md +30 -0
  501. package/src/bmm-skills/2-plan-workflows/bmad-create-prd/customize.toml +41 -0
  502. package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/SKILL.md +30 -0
  503. package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/customize.toml +42 -0
  504. package/src/bmm-skills/2-plan-workflows/bmad-prd/SKILL.md +92 -0
  505. package/src/bmm-skills/2-plan-workflows/bmad-prd/assets/headless-schemas.md +76 -0
  506. package/src/bmm-skills/2-plan-workflows/bmad-prd/assets/prd-template.md +165 -0
  507. package/src/bmm-skills/2-plan-workflows/bmad-prd/assets/prd-validation-checklist.md +135 -0
  508. package/src/bmm-skills/2-plan-workflows/bmad-prd/assets/validation-report-template.html +325 -0
  509. package/src/bmm-skills/2-plan-workflows/bmad-prd/customize.toml +147 -0
  510. package/src/bmm-skills/2-plan-workflows/bmad-prd/references/headless.md +39 -0
  511. package/src/bmm-skills/2-plan-workflows/bmad-prd/references/validate.md +97 -0
  512. package/src/bmm-skills/2-plan-workflows/bmad-ux/SKILL.md +90 -0
  513. package/src/bmm-skills/2-plan-workflows/bmad-ux/assets/color-themes.md +9 -0
  514. package/src/bmm-skills/2-plan-workflows/bmad-ux/assets/design-directions.md +9 -0
  515. package/src/bmm-skills/2-plan-workflows/bmad-ux/assets/design-example-editorial.md +158 -0
  516. package/src/bmm-skills/2-plan-workflows/bmad-ux/assets/design-example-mobile.md +93 -0
  517. package/src/bmm-skills/2-plan-workflows/bmad-ux/assets/design-example-shadcn.md +109 -0
  518. package/src/bmm-skills/2-plan-workflows/bmad-ux/assets/excalidraw-wireframe.md +19 -0
  519. package/src/bmm-skills/2-plan-workflows/bmad-ux/assets/experience-example-mobile.md +112 -0
  520. package/src/bmm-skills/2-plan-workflows/bmad-ux/assets/experience-example-shadcn.md +133 -0
  521. package/src/bmm-skills/2-plan-workflows/bmad-ux/assets/headless-schemas.md +84 -0
  522. package/src/bmm-skills/2-plan-workflows/bmad-ux/assets/key-screens.md +29 -0
  523. package/src/bmm-skills/2-plan-workflows/bmad-ux/assets/validation-report-template.html +319 -0
  524. package/src/bmm-skills/2-plan-workflows/bmad-ux/customize.toml +100 -0
  525. package/src/bmm-skills/2-plan-workflows/bmad-ux/references/creative-tools.md +19 -0
  526. package/src/bmm-skills/2-plan-workflows/bmad-ux/references/design-md-spec.md +50 -0
  527. package/src/bmm-skills/2-plan-workflows/bmad-ux/references/headless.md +37 -0
  528. package/src/bmm-skills/2-plan-workflows/bmad-ux/references/validate.md +115 -0
  529. package/src/bmm-skills/2-plan-workflows/bmad-validate-prd/SKILL.md +30 -0
  530. package/src/bmm-skills/2-plan-workflows/bmad-validate-prd/customize.toml +42 -0
  531. package/src/bmm-skills/3-solutioning/bmad-agent-architect/SKILL.md +76 -0
  532. package/src/bmm-skills/3-solutioning/bmad-agent-architect/customize.toml +65 -0
  533. package/src/bmm-skills/3-solutioning/bmad-architecture/SKILL.md +85 -0
  534. package/src/bmm-skills/3-solutioning/bmad-architecture/assets/spine-template.md +79 -0
  535. package/src/bmm-skills/3-solutioning/bmad-architecture/customize.toml +100 -0
  536. package/src/bmm-skills/3-solutioning/bmad-architecture/references/headless.md +26 -0
  537. package/src/bmm-skills/3-solutioning/bmad-architecture/references/reviewer-gate.md +13 -0
  538. package/src/bmm-skills/3-solutioning/bmad-architecture/scripts/lint_spine.py +257 -0
  539. package/src/bmm-skills/3-solutioning/bmad-architecture/scripts/tests/test_lint_spine.py +270 -0
  540. package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/SKILL.md +91 -0
  541. package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/customize.toml +41 -0
  542. package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/steps/step-01-document-discovery.md +179 -0
  543. package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/steps/step-02-prd-analysis.md +168 -0
  544. package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/steps/step-03-epic-coverage-validation.md +169 -0
  545. package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/steps/step-04-ux-alignment.md +129 -0
  546. package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/steps/step-05-epic-quality-review.md +241 -0
  547. package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/steps/step-06-final-assessment.md +132 -0
  548. package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/templates/readiness-report-template.md +4 -0
  549. package/src/bmm-skills/3-solutioning/bmad-create-architecture/SKILL.md +30 -0
  550. package/src/bmm-skills/3-solutioning/bmad-create-architecture/customize.toml +41 -0
  551. package/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/SKILL.md +93 -0
  552. package/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/customize.toml +41 -0
  553. package/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/steps/step-01-validate-prerequisites.md +263 -0
  554. package/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/steps/step-02-design-epics.md +242 -0
  555. package/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/steps/step-03-create-stories.md +255 -0
  556. package/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/steps/step-04-final-validation.md +143 -0
  557. package/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/templates/epics-template.md +61 -0
  558. package/src/bmm-skills/3-solutioning/bmad-generate-project-context/SKILL.md +81 -0
  559. package/src/bmm-skills/3-solutioning/bmad-generate-project-context/customize.toml +41 -0
  560. package/src/bmm-skills/3-solutioning/bmad-generate-project-context/project-context-template.md +21 -0
  561. package/src/bmm-skills/3-solutioning/bmad-generate-project-context/steps/step-01-discover.md +186 -0
  562. package/src/bmm-skills/3-solutioning/bmad-generate-project-context/steps/step-02-generate.md +321 -0
  563. package/src/bmm-skills/3-solutioning/bmad-generate-project-context/steps/step-03-complete.md +284 -0
  564. package/src/bmm-skills/4-implementation/bmad-agent-dev/SKILL.md +76 -0
  565. package/src/bmm-skills/4-implementation/bmad-agent-dev/customize.toml +95 -0
  566. package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/SKILL.md +68 -0
  567. package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/customize.toml +41 -0
  568. package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/generate-trail.md +38 -0
  569. package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/step-01-orientation.md +105 -0
  570. package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/step-02-walkthrough.md +89 -0
  571. package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/step-03-detail-pass.md +106 -0
  572. package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/step-04-testing.md +74 -0
  573. package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/step-05-wrapup.md +30 -0
  574. package/src/bmm-skills/4-implementation/bmad-code-review/SKILL.md +93 -0
  575. package/src/bmm-skills/4-implementation/bmad-code-review/customize.toml +41 -0
  576. package/src/bmm-skills/4-implementation/bmad-code-review/steps/step-01-gather-context.md +85 -0
  577. package/src/bmm-skills/4-implementation/bmad-code-review/steps/step-02-review.md +35 -0
  578. package/src/bmm-skills/4-implementation/bmad-code-review/steps/step-03-triage.md +49 -0
  579. package/src/bmm-skills/4-implementation/bmad-code-review/steps/step-04-present.md +132 -0
  580. package/src/bmm-skills/4-implementation/bmad-correct-course/SKILL.md +301 -0
  581. package/src/bmm-skills/4-implementation/bmad-correct-course/checklist.md +288 -0
  582. package/src/bmm-skills/4-implementation/bmad-correct-course/customize.toml +41 -0
  583. package/src/bmm-skills/4-implementation/bmad-create-story/SKILL.md +432 -0
  584. package/src/bmm-skills/4-implementation/bmad-create-story/checklist.md +357 -0
  585. package/src/bmm-skills/4-implementation/bmad-create-story/customize.toml +41 -0
  586. package/src/bmm-skills/4-implementation/bmad-create-story/discover-inputs.md +88 -0
  587. package/src/bmm-skills/4-implementation/bmad-create-story/template.md +49 -0
  588. package/src/bmm-skills/4-implementation/bmad-dev-story/SKILL.md +500 -0
  589. package/src/bmm-skills/4-implementation/bmad-dev-story/checklist.md +80 -0
  590. package/src/bmm-skills/4-implementation/bmad-dev-story/customize.toml +41 -0
  591. package/src/bmm-skills/4-implementation/bmad-investigate/SKILL.md +196 -0
  592. package/src/bmm-skills/4-implementation/bmad-investigate/customize.toml +62 -0
  593. package/src/bmm-skills/4-implementation/bmad-investigate/references/case-file-template.md +127 -0
  594. package/src/bmm-skills/4-implementation/bmad-qa-generate-e2e-tests/SKILL.md +176 -0
  595. package/src/bmm-skills/4-implementation/bmad-qa-generate-e2e-tests/checklist.md +33 -0
  596. package/src/bmm-skills/4-implementation/bmad-qa-generate-e2e-tests/customize.toml +41 -0
  597. package/src/bmm-skills/4-implementation/bmad-quick-dev/SKILL.md +114 -0
  598. package/src/bmm-skills/4-implementation/bmad-quick-dev/compile-epic-context.md +62 -0
  599. package/src/bmm-skills/4-implementation/bmad-quick-dev/customize.toml +41 -0
  600. package/src/bmm-skills/4-implementation/bmad-quick-dev/spec-template.md +88 -0
  601. package/src/bmm-skills/4-implementation/bmad-quick-dev/step-01-clarify-and-route.md +100 -0
  602. package/src/bmm-skills/4-implementation/bmad-quick-dev/step-02-plan.md +47 -0
  603. package/src/bmm-skills/4-implementation/bmad-quick-dev/step-03-implement.md +41 -0
  604. package/src/bmm-skills/4-implementation/bmad-quick-dev/step-04-review.md +50 -0
  605. package/src/bmm-skills/4-implementation/bmad-quick-dev/step-05-present.md +78 -0
  606. package/src/bmm-skills/4-implementation/bmad-quick-dev/step-oneshot.md +71 -0
  607. package/src/bmm-skills/4-implementation/bmad-quick-dev/sync-sprint-status.md +19 -0
  608. package/src/bmm-skills/4-implementation/bmad-retrospective/SKILL.md +1527 -0
  609. package/src/bmm-skills/4-implementation/bmad-retrospective/customize.toml +41 -0
  610. package/src/bmm-skills/4-implementation/bmad-sprint-planning/SKILL.md +319 -0
  611. package/src/bmm-skills/4-implementation/bmad-sprint-planning/checklist.md +34 -0
  612. package/src/bmm-skills/4-implementation/bmad-sprint-planning/customize.toml +41 -0
  613. package/src/bmm-skills/4-implementation/bmad-sprint-planning/sprint-status-template.yaml +69 -0
  614. package/src/bmm-skills/4-implementation/bmad-sprint-status/SKILL.md +311 -0
  615. package/src/bmm-skills/4-implementation/bmad-sprint-status/customize.toml +41 -0
  616. package/src/bmm-skills/module-help.csv +32 -0
  617. package/src/bmm-skills/module.yaml +95 -0
  618. package/src/core-skills/bmad-advanced-elicitation/SKILL.md +142 -0
  619. package/src/core-skills/bmad-advanced-elicitation/methods.csv +70 -0
  620. package/src/core-skills/bmad-brainstorming/SKILL.md +82 -0
  621. package/src/core-skills/bmad-brainstorming/analysis/catalog-analysis.md +239 -0
  622. package/src/core-skills/bmad-brainstorming/analysis/method-matrix.csv +109 -0
  623. package/src/core-skills/bmad-brainstorming/assets/brain-icons.json +166 -0
  624. package/src/core-skills/bmad-brainstorming/assets/brain-methods.csv +109 -0
  625. package/src/core-skills/bmad-brainstorming/assets/brain-selector.html +326 -0
  626. package/src/core-skills/bmad-brainstorming/customize.toml +84 -0
  627. package/src/core-skills/bmad-brainstorming/references/converge.md +24 -0
  628. package/src/core-skills/bmad-brainstorming/references/finalize.md +26 -0
  629. package/src/core-skills/bmad-brainstorming/references/headless.md +54 -0
  630. package/src/core-skills/bmad-brainstorming/references/in-chat-techniques.md +18 -0
  631. package/src/core-skills/bmad-brainstorming/references/mode-autonomous.md +10 -0
  632. package/src/core-skills/bmad-brainstorming/references/mode-facilitator.md +11 -0
  633. package/src/core-skills/bmad-brainstorming/references/mode-partner.md +16 -0
  634. package/src/core-skills/bmad-brainstorming/references/resume.md +5 -0
  635. package/src/core-skills/bmad-brainstorming/scripts/brain.py +740 -0
  636. package/src/core-skills/bmad-brainstorming/scripts/memlog.py +202 -0
  637. package/src/core-skills/bmad-brainstorming/scripts/tests/test_brain.py +217 -0
  638. package/src/core-skills/bmad-brainstorming/scripts/tests/test_memlog.py +265 -0
  639. package/src/core-skills/bmad-customize/SKILL.md +111 -0
  640. package/src/core-skills/bmad-customize/scripts/list_customizable_skills.py +231 -0
  641. package/src/core-skills/bmad-customize/scripts/tests/test_list_customizable_skills.py +249 -0
  642. package/src/core-skills/bmad-editorial-review-prose/SKILL.md +86 -0
  643. package/src/core-skills/bmad-editorial-review-structure/SKILL.md +179 -0
  644. package/src/core-skills/bmad-help/SKILL.md +75 -0
  645. package/src/core-skills/bmad-index-docs/SKILL.md +66 -0
  646. package/src/core-skills/bmad-party-mode/SKILL.md +77 -0
  647. package/src/core-skills/bmad-party-mode/customize.toml +150 -0
  648. package/src/core-skills/bmad-party-mode/references/create-party.md +65 -0
  649. package/src/core-skills/bmad-party-mode/references/mode-agent-team.md +11 -0
  650. package/src/core-skills/bmad-party-mode/references/mode-auto.md +13 -0
  651. package/src/core-skills/bmad-party-mode/references/mode-subagent.md +19 -0
  652. package/src/core-skills/bmad-party-mode/scripts/resolve_party.py +267 -0
  653. package/src/core-skills/bmad-party-mode/scripts/tests/test-resolve_party.py +138 -0
  654. package/src/core-skills/bmad-review-adversarial-general/SKILL.md +37 -0
  655. package/src/core-skills/bmad-review-edge-case-hunter/SKILL.md +67 -0
  656. package/src/core-skills/bmad-shard-doc/SKILL.md +105 -0
  657. package/src/core-skills/bmad-spec/SKILL.md +145 -0
  658. package/src/core-skills/bmad-spec/assets/headless-schemas.md +33 -0
  659. package/src/core-skills/bmad-spec/assets/spec-template.md +49 -0
  660. package/src/core-skills/bmad-spec/customize.toml +53 -0
  661. package/src/core-skills/module-help.csv +13 -0
  662. package/src/core-skills/module.yaml +33 -0
  663. package/src/modules/wizz/README.md +39 -0
  664. package/src/modules/wizz/_shared/cerebro.md +24 -0
  665. package/src/modules/wizz/_shared/encerramento.md +36 -0
  666. package/src/modules/wizz/_shared/token-economy.md +28 -0
  667. package/src/modules/wizz/agents/wizz-ads/SKILL.md +30 -0
  668. package/src/modules/wizz/agents/wizz-ads/customize.toml +53 -0
  669. package/src/modules/wizz/agents/wizz-copy/SKILL.md +30 -0
  670. package/src/modules/wizz/agents/wizz-copy/customize.toml +53 -0
  671. package/src/modules/wizz/agents/wizz-designer/SKILL.md +38 -0
  672. package/src/modules/wizz/agents/wizz-designer/customize.toml +60 -0
  673. package/src/modules/wizz/agents/wizz-growth/SKILL.md +31 -0
  674. package/src/modules/wizz/agents/wizz-growth/customize.toml +58 -0
  675. package/src/modules/wizz/agents/wizz-maestro/SKILL.md +77 -0
  676. package/src/modules/wizz/agents/wizz-maestro/customize.toml +78 -0
  677. package/src/modules/wizz/agents/wizz-memoria/SKILL.md +31 -0
  678. package/src/modules/wizz/agents/wizz-memoria/customize.toml +57 -0
  679. package/src/modules/wizz/agents/wizz-qa/SKILL.md +31 -0
  680. package/src/modules/wizz/agents/wizz-qa/customize.toml +63 -0
  681. package/src/modules/wizz/agents/wizz-seo/SKILL.md +31 -0
  682. package/src/modules/wizz/agents/wizz-seo/customize.toml +58 -0
  683. package/src/modules/wizz/module.yaml +117 -0
  684. package/src/modules/wizz/overrides/bmad-agent-analyst.toml +35 -0
  685. package/src/modules/wizz/overrides/bmad-agent-architect.toml +39 -0
  686. package/src/modules/wizz/overrides/bmad-agent-dev.toml +40 -0
  687. package/src/modules/wizz/overrides/bmad-agent-pm.toml +23 -0
  688. package/src/modules/wizz/overrides/bmad-agent-tech-writer.toml +30 -0
  689. package/src/modules/wizz/overrides/bmad-agent-ux-designer.toml +26 -0
  690. package/src/modules/wizz/scripts/wizz-init.mjs +163 -0
  691. package/src/scripts/memlog.py +224 -0
  692. package/src/scripts/resolve_config.py +176 -0
  693. package/src/scripts/resolve_customization.py +238 -0
  694. package/src/scripts/tests/test_memlog.py +306 -0
  695. package/src/scripts/tests/test_resolve_customization.py +50 -0
  696. package/tools/bundle-web-bundles.js +117 -0
  697. package/tools/format-workflow-md.js +263 -0
  698. package/tools/installer/README.md +60 -0
  699. package/tools/installer/bmad-cli.js +105 -0
  700. package/tools/installer/cli-utils.js +53 -0
  701. package/tools/installer/commands/install.js +149 -0
  702. package/tools/installer/commands/status.js +65 -0
  703. package/tools/installer/commands/uninstall.js +167 -0
  704. package/tools/installer/core/config.js +73 -0
  705. package/tools/installer/core/existing-install.js +121 -0
  706. package/tools/installer/core/install-paths.js +132 -0
  707. package/tools/installer/core/installer.js +1653 -0
  708. package/tools/installer/core/legacy-warnings.js +151 -0
  709. package/tools/installer/core/manifest-generator.js +859 -0
  710. package/tools/installer/core/manifest.js +433 -0
  711. package/tools/installer/core/python-check.js +199 -0
  712. package/tools/installer/core/wsl-node-check.js +109 -0
  713. package/tools/installer/file-ops.js +204 -0
  714. package/tools/installer/fs-native.js +116 -0
  715. package/tools/installer/ide/_config-driven.js +972 -0
  716. package/tools/installer/ide/manager.js +324 -0
  717. package/tools/installer/ide/platform-codes.js +80 -0
  718. package/tools/installer/ide/platform-codes.yaml +329 -0
  719. package/tools/installer/ide/shared/installed-skills.js +50 -0
  720. package/tools/installer/ide/shared/path-utils.js +219 -0
  721. package/tools/installer/ide/shared/skill-manifest.js +57 -0
  722. package/tools/installer/install-messages.yaml +32 -0
  723. package/tools/installer/list-options.js +210 -0
  724. package/tools/installer/message-loader.js +83 -0
  725. package/tools/installer/modules/channel-plan.js +203 -0
  726. package/tools/installer/modules/channel-resolver.js +241 -0
  727. package/tools/installer/modules/custom-module-manager.js +912 -0
  728. package/tools/installer/modules/external-manager.js +527 -0
  729. package/tools/installer/modules/module-help-schema.js +13 -0
  730. package/tools/installer/modules/official-modules.js +2217 -0
  731. package/tools/installer/modules/plugin-resolver.js +398 -0
  732. package/tools/installer/modules/version-resolver.js +336 -0
  733. package/tools/installer/project-root.js +224 -0
  734. package/tools/installer/prompts.js +852 -0
  735. package/tools/installer/set-overrides.js +330 -0
  736. package/tools/installer/ui.js +2077 -0
  737. package/tools/installer/yaml-format.js +245 -0
  738. package/tools/javascript-conventions.md +5 -0
  739. package/tools/migrate-custom-module-paths.js +124 -0
  740. package/tools/skill-validator.md +368 -0
  741. package/tools/validate-sidebar-order.js +388 -0
  742. package/tools/validate-skills.js +698 -0
  743. package/tools/validate-svg-changes.sh +356 -0
  744. package/web-bundles/README.md +46 -0
  745. package/web-bundles/brainstorming-coach/INSTRUCTIONS.md +86 -0
  746. package/web-bundles/brainstorming-coach/SKILL.md +83 -0
  747. package/web-bundles/brainstorming-coach/brain-methods.csv +62 -0
  748. package/web-bundles/bundles.json +139 -0
  749. package/web-bundles/market-and-industry-research/INSTRUCTIONS.md +88 -0
  750. package/web-bundles/market-and-industry-research/SKILL.md +59 -0
  751. package/web-bundles/prd-coach/INSTRUCTIONS.md +86 -0
  752. package/web-bundles/prd-coach/SKILL.md +101 -0
  753. package/web-bundles/prd-coach/prd-template.md +165 -0
  754. package/web-bundles/prd-coach/prd-validation-checklist.md +135 -0
  755. package/web-bundles/prfaq-coach/INSTRUCTIONS.md +86 -0
  756. package/web-bundles/prfaq-coach/SKILL.md +139 -0
  757. package/web-bundles/product-brief-coach/INSTRUCTIONS.md +86 -0
  758. package/web-bundles/product-brief-coach/SKILL.md +113 -0
  759. package/web-bundles/ux-coach/INSTRUCTIONS.md +92 -0
  760. package/web-bundles/ux-coach/SKILL.md +187 -0
  761. package/web-bundles/ux-coach/ux-validation.md +100 -0
@@ -0,0 +1,2077 @@
1
+ const path = require('node:path');
2
+ const os = require('node:os');
3
+ const semver = require('semver');
4
+ const fs = require('./fs-native');
5
+ const installerPackageJson = require('../../package.json');
6
+ const { CLIUtils } = require('./cli-utils');
7
+ const { ExternalModuleManager } = require('./modules/external-manager');
8
+ const { resolveModuleVersion } = require('./modules/version-resolver');
9
+ const { Manifest } = require('./core/manifest');
10
+ const {
11
+ parseChannelOptions,
12
+ buildPlan,
13
+ decideChannelForModule,
14
+ orphanPinWarnings,
15
+ bundledTargetWarnings,
16
+ } = require('./modules/channel-plan');
17
+ const channelResolver = require('./modules/channel-resolver');
18
+ const prompts = require('./prompts');
19
+ const { parseSetEntries } = require('./set-overrides');
20
+
21
+ const manifest = new Manifest();
22
+
23
+ /**
24
+ * Format a resolved version for display in installer labels.
25
+ * Semver-like values are normalized to a single leading "v".
26
+ * @param {string|null|undefined} version
27
+ * @returns {string}
28
+ */
29
+ function formatDisplayVersion(version) {
30
+ const trimmed = typeof version === 'string' ? version.trim() : '';
31
+ if (!trimmed) return '';
32
+
33
+ const normalized = semver.valid(semver.coerce(trimmed));
34
+ if (normalized) {
35
+ return `v${normalized}`;
36
+ }
37
+
38
+ return trimmed;
39
+ }
40
+
41
+ /**
42
+ * Build the display label for a module, showing an upgrade arrow when an
43
+ * installed semver differs from the latest resolvable semver.
44
+ * @param {string} name
45
+ * @param {string} latestVersion
46
+ * @param {string} installedVersion
47
+ * @returns {string}
48
+ */
49
+ function buildModuleLabel(name, latestVersion, installedVersion = '') {
50
+ const latestDisplay = formatDisplayVersion(latestVersion);
51
+ if (!latestDisplay) return name;
52
+
53
+ const installedDisplay = formatDisplayVersion(installedVersion);
54
+ const latestSemver = semver.valid(semver.coerce(latestVersion || ''));
55
+ const installedSemver = semver.valid(semver.coerce(installedVersion || ''));
56
+
57
+ if (installedDisplay && latestSemver && installedSemver && semver.neq(installedSemver, latestSemver)) {
58
+ return `${name} (${installedDisplay} → ${latestDisplay})`;
59
+ }
60
+
61
+ return `${name} (${latestDisplay})`;
62
+ }
63
+
64
+ /**
65
+ * Resolve the version to show for a module picker entry. External modules use
66
+ * the same channel/tag resolver as installs; bundled modules fall back to local
67
+ * source metadata.
68
+ * @param {string} moduleCode - Module code (e.g., 'core', 'bmm', 'cis')
69
+ * @param {Object} options
70
+ * @param {string|null} [options.repoUrl] - Module repository URL for tag resolution
71
+ * @param {string|null} [options.registryDefault] - Registry default channel
72
+ * @param {Object|null} [options.channelOptions] - Parsed installer channel options
73
+ * @returns {Promise<{version: string, lookupAttempted: boolean, lookupSucceeded: boolean}>}
74
+ */
75
+ async function getModuleVersion(moduleCode, { repoUrl = null, registryDefault = null, channelOptions = null } = {}) {
76
+ if (repoUrl) {
77
+ const plan = decideChannelForModule({
78
+ code: moduleCode,
79
+ channelOptions,
80
+ registryDefault,
81
+ });
82
+
83
+ try {
84
+ const resolved = await channelResolver.resolveChannel({
85
+ channel: plan.channel,
86
+ pin: plan.pin,
87
+ repoUrl,
88
+ });
89
+ if (resolved?.version) {
90
+ return {
91
+ version: resolved.version,
92
+ lookupAttempted: plan.channel === 'stable',
93
+ lookupSucceeded: true,
94
+ };
95
+ }
96
+ } catch {
97
+ // Fall back to local metadata when tag resolution is unavailable.
98
+ }
99
+ }
100
+
101
+ const versionInfo = await resolveModuleVersion(moduleCode);
102
+ return {
103
+ version: versionInfo.version || '',
104
+ lookupAttempted: !!repoUrl,
105
+ lookupSucceeded: false,
106
+ };
107
+ }
108
+
109
+ /**
110
+ * UI utilities for the installer
111
+ */
112
+ class UI {
113
+ async _retainUnavailableInstalledModules(selectedModules, installedModuleIds, bmadDir, options = {}) {
114
+ const { OfficialModules } = require('./modules/official-modules');
115
+ const officialCodes = new Set(['core']);
116
+
117
+ const builtInModules = (await new OfficialModules().listAvailable()).modules || [];
118
+ for (const mod of builtInModules) {
119
+ officialCodes.add(mod.id);
120
+ }
121
+
122
+ const externalManager = new ExternalModuleManager();
123
+ const registryModules = await externalManager.listAvailable();
124
+ for (const mod of registryModules) {
125
+ officialCodes.add(mod.code);
126
+ }
127
+
128
+ const { CustomModuleManager } = require('./modules/custom-module-manager');
129
+ const customMgr = new CustomModuleManager();
130
+ const selectedSet = new Set(selectedModules);
131
+ const preserveModules = [];
132
+
133
+ for (const moduleId of installedModuleIds) {
134
+ if (moduleId === 'core') continue;
135
+ if (!selectedSet.has(moduleId) && !options.preserveUnselected) continue;
136
+ if (officialCodes.has(moduleId)) continue;
137
+
138
+ const customSource = await customMgr.findModuleSourceByCode(moduleId, { bmadDir });
139
+ if (!customSource) {
140
+ preserveModules.push(moduleId);
141
+ }
142
+ }
143
+
144
+ const preservedSet = new Set(preserveModules);
145
+ return {
146
+ selectedModules: selectedModules.filter((moduleId) => !preservedSet.has(moduleId)),
147
+ preserveModules,
148
+ };
149
+ }
150
+
151
+ /**
152
+ * Prompt for installation configuration
153
+ * @param {Object} options - Command-line options from install command
154
+ * @returns {Object} Installation configuration
155
+ */
156
+ async promptInstall(options = {}) {
157
+ await CLIUtils.displayLogo();
158
+
159
+ // Display version-specific start message from install-messages.yaml
160
+ const { MessageLoader } = require('./message-loader');
161
+ const messageLoader = new MessageLoader();
162
+ await messageLoader.displayStartMessage();
163
+
164
+ // Probe the local Python before any other prompts: several BMAD features
165
+ // (memlog session memory, TOML config resolution) need Python 3.11+ at
166
+ // runtime. Warn-don't-block, but require an explicit ack so the warning
167
+ // can't scroll past unseen. The installer runs in the destination
168
+ // environment, so probing PATH here tests the right machine.
169
+ // Skip the ack when stdin isn't a TTY (CI/Docker/piped): clack's select
170
+ // on closed stdin resolves to cancel, which would silently exit 0.
171
+ const { checkPythonEnvironment } = require('./core/python-check');
172
+ await checkPythonEnvironment({ nonInteractive: !!options.yes || !process.stdin.isTTY });
173
+
174
+ // Parse channel flags (--channel/--all-*/--next=/--pin) once. Warnings
175
+ // are surfaced immediately so the user sees them before any git ops run.
176
+ const channelOptions = parseChannelOptions(options);
177
+ for (const warning of channelOptions.warnings) {
178
+ await prompts.log.warn(warning);
179
+ }
180
+
181
+ // When the user launched the installer from a prerelease (npx bmad-method@next),
182
+ // mirror that intent for external modules: seed the global channel to 'next' so
183
+ // the module picker's version labels resolve from main HEAD (matching what
184
+ // actually gets installed) and the interactive channel gate skips — the user
185
+ // already declared "next" intent by typing @next. Explicit channel flags
186
+ // override this seed.
187
+ if (
188
+ semver.prerelease(installerPackageJson.version) !== null &&
189
+ !channelOptions.global &&
190
+ channelOptions.nextSet.size === 0 &&
191
+ channelOptions.pins.size === 0
192
+ ) {
193
+ channelOptions.global = 'next';
194
+ await prompts.log.info(
195
+ 'Launched from a prerelease — installing all external modules from main HEAD (next channel). Pass --all-stable or --pin to override.',
196
+ );
197
+ }
198
+
199
+ // Get directory from options or prompt
200
+ let confirmedDirectory;
201
+ if (options.directory) {
202
+ // Use provided directory from command-line
203
+ const expandedDir = this.expandUserPath(options.directory);
204
+ const validation = this.validateDirectorySync(expandedDir);
205
+ if (validation) {
206
+ throw new Error(`Invalid directory: ${validation}`);
207
+ }
208
+ confirmedDirectory = expandedDir;
209
+ await prompts.log.info(`Using directory from command-line: ${confirmedDirectory}`);
210
+ } else {
211
+ confirmedDirectory = await this.getConfirmedDirectory();
212
+ }
213
+
214
+ const { Installer } = require('./core/installer');
215
+ const installer = new Installer();
216
+ const { bmadDir } = await installer.findBmadDir(confirmedDirectory);
217
+
218
+ // Check if there's an existing BMAD installation
219
+ const hasExistingInstall = await fs.pathExists(bmadDir);
220
+
221
+ // Track action type (only set if there's an existing installation)
222
+ let actionType;
223
+
224
+ // Only show action menu if there's an existing installation
225
+ if (hasExistingInstall) {
226
+ // Get version information
227
+ const { existingInstall, bmadDir } = await this.getExistingInstallation(confirmedDirectory);
228
+
229
+ // Build menu choices dynamically
230
+ const choices = [];
231
+
232
+ // Always show Quick Update first (allows refreshing installation even on same version)
233
+ if (existingInstall.installed) {
234
+ choices.push({
235
+ name: 'Quick Update',
236
+ value: 'quick-update',
237
+ });
238
+ }
239
+
240
+ // Common actions
241
+ choices.push({ name: 'Modify BMAD Installation', value: 'update' });
242
+
243
+ // Check if action is provided via command-line
244
+ if (options.action) {
245
+ const validActions = choices.map((c) => c.value);
246
+ if (!validActions.includes(options.action)) {
247
+ throw new Error(`Invalid action: ${options.action}. Valid actions: ${validActions.join(', ')}`);
248
+ }
249
+ actionType = options.action;
250
+ await prompts.log.info(`Using action from command-line: ${actionType}`);
251
+ } else if (options.yes) {
252
+ // Default to quick-update if available, unless flags that require the
253
+ // full update path are present (e.g. --custom-source which re-clones
254
+ // modules at a new version — quick-update skips that entirely).
255
+ if (choices.length === 0) {
256
+ throw new Error('No valid actions available for this installation');
257
+ }
258
+ const hasQuickUpdate = choices.some((c) => c.value === 'quick-update');
259
+ const needsFullUpdate = !!options.customSource;
260
+ actionType = hasQuickUpdate && !needsFullUpdate ? 'quick-update' : (choices.find((c) => c.value === 'update') || choices[0]).value;
261
+ await prompts.log.info(`Non-interactive mode (--yes): defaulting to ${actionType}`);
262
+ } else {
263
+ actionType = await prompts.select({
264
+ message: 'How would you like to proceed?',
265
+ choices: choices,
266
+ default: choices[0].value,
267
+ });
268
+ }
269
+
270
+ // Handle quick update separately
271
+ if (actionType === 'quick-update') {
272
+ return {
273
+ actionType: 'quick-update',
274
+ directory: confirmedDirectory,
275
+ skipPrompts: options.yes || false,
276
+ };
277
+ }
278
+
279
+ // If actionType === 'update', handle it with the new flow
280
+ // Return early with modify configuration
281
+ if (actionType === 'update') {
282
+ // Get existing installation info
283
+ const { installedModuleIds, installedModuleVersions } = await this.getExistingInstallation(confirmedDirectory);
284
+
285
+ await prompts.log.message(`Found existing modules: ${[...installedModuleIds].join(', ')}`);
286
+
287
+ // Unified module selection - all modules in one grouped multiselect
288
+ let selectedModules;
289
+ if (options.modules) {
290
+ // Use modules from command-line
291
+ selectedModules = options.modules
292
+ .split(',')
293
+ .map((m) => m.trim())
294
+ .filter(Boolean);
295
+ await prompts.log.info(`Using modules from command-line: ${selectedModules.join(', ')}`);
296
+ } else if (options.customSource && !options.yes) {
297
+ // Custom source without --modules or --yes: start with empty list
298
+ // (only custom source modules + core will be installed).
299
+ // When --yes is also set, fall through to the --yes branch so all
300
+ // installed modules are included alongside the custom source modules.
301
+ selectedModules = [];
302
+ } else if (options.yes) {
303
+ selectedModules = await this.getDefaultModules(installedModuleIds);
304
+ await prompts.log.info(
305
+ `Non-interactive mode (--yes): using default modules (installed + defaults): ${selectedModules.join(', ')}`,
306
+ );
307
+ } else {
308
+ selectedModules = await this.selectAllModules(installedModuleIds, installedModuleVersions, channelOptions);
309
+ }
310
+
311
+ // Resolve custom sources from --custom-source flag
312
+ if (options.customSource) {
313
+ const customCodes = await this._resolveCustomSourcesCli(options.customSource);
314
+ for (const code of customCodes) {
315
+ if (!selectedModules.includes(code)) selectedModules.push(code);
316
+ }
317
+ }
318
+
319
+ // Ensure core is in the modules list
320
+ if (!selectedModules.includes('core')) {
321
+ selectedModules.unshift('core');
322
+ }
323
+
324
+ const retainedModuleResult = await this._retainUnavailableInstalledModules(selectedModules, installedModuleIds, bmadDir, {
325
+ preserveUnselected: options.yes && !options.modules,
326
+ });
327
+ selectedModules = retainedModuleResult.selectedModules;
328
+ const preservedModules = retainedModuleResult.preserveModules;
329
+
330
+ if (preservedModules.length > 0) {
331
+ await prompts.log.warn(
332
+ `Retaining ${preservedModules.length} installed module(s) with no available source: ${preservedModules.join(', ')}`,
333
+ );
334
+ }
335
+
336
+ // For existing installs, resolve per-module update decisions BEFORE
337
+ // we clone anything. Reads the existing manifest's recorded channel
338
+ // per module and prompts the user on available upgrades (patch/minor
339
+ // default Y, major default N). Legacy entries with no channel are
340
+ // migrated here too. Mutates channelOptions.pins to lock rejections.
341
+ await this._resolveUpdateChannels({
342
+ bmadDir,
343
+ selectedModules,
344
+ channelOptions,
345
+ yes: options.yes || false,
346
+ });
347
+
348
+ // Get tool selection
349
+ const toolSelection = await this.promptToolSelection(confirmedDirectory, options);
350
+
351
+ const { moduleConfigs, setOverrides } = await this.collectModuleConfigs(confirmedDirectory, selectedModules, {
352
+ ...options,
353
+ channelOptions,
354
+ });
355
+
356
+ // Warn about --pin/--next flags that refer to modules the user didn't
357
+ // select, or that target bundled modules (core/bmm) where channel
358
+ // flags don't apply.
359
+ {
360
+ const bundledCodes = await this._bundledModuleCodes();
361
+ for (const warning of [
362
+ ...orphanPinWarnings(channelOptions, selectedModules),
363
+ ...bundledTargetWarnings(channelOptions, bundledCodes),
364
+ ]) {
365
+ await prompts.log.warn(warning);
366
+ }
367
+ }
368
+
369
+ return {
370
+ actionType: 'update',
371
+ directory: confirmedDirectory,
372
+ modules: selectedModules,
373
+ ides: toolSelection.ides,
374
+ skipIde: toolSelection.skipIde,
375
+ coreConfig: moduleConfigs.core || {},
376
+ moduleConfigs: moduleConfigs,
377
+ setOverrides,
378
+ skipPrompts: options.yes || false,
379
+ channelOptions,
380
+ _preserveModules: preservedModules,
381
+ };
382
+ }
383
+ }
384
+
385
+ // This section is only for new installations (update returns early above)
386
+ const { installedModuleIds, installedModuleVersions } = await this.getExistingInstallation(confirmedDirectory);
387
+
388
+ // Unified module selection - all modules in one grouped multiselect
389
+ let selectedModules;
390
+ if (options.modules) {
391
+ // Use modules from command-line
392
+ selectedModules = options.modules
393
+ .split(',')
394
+ .map((m) => m.trim())
395
+ .filter(Boolean);
396
+ await prompts.log.info(`Using modules from command-line: ${selectedModules.join(', ')}`);
397
+ } else if (options.customSource) {
398
+ // Custom source without --modules: start with empty list (core added below)
399
+ selectedModules = [];
400
+ } else if (options.yes) {
401
+ // Use default modules when --yes flag is set
402
+ selectedModules = await this.getDefaultModules(installedModuleIds);
403
+ await prompts.log.info(`Using default modules (--yes flag): ${selectedModules.join(', ')}`);
404
+ } else {
405
+ selectedModules = await this.selectAllModules(installedModuleIds, installedModuleVersions, channelOptions);
406
+ }
407
+
408
+ // Resolve custom sources from --custom-source flag
409
+ if (options.customSource) {
410
+ const customCodes = await this._resolveCustomSourcesCli(options.customSource);
411
+ for (const code of customCodes) {
412
+ if (!selectedModules.includes(code)) selectedModules.push(code);
413
+ }
414
+ }
415
+
416
+ // Ensure core is in the modules list
417
+ if (!selectedModules.includes('core')) {
418
+ selectedModules.unshift('core');
419
+ }
420
+
421
+ // Interactive channel gate: "Ready to install (all stable)? [Y/n]"
422
+ // Only shown for fresh installs with no channel flags and an external module
423
+ // selected. Skipped for prerelease launches because channelOptions.global
424
+ // was already seeded to 'next' upstream. Non-interactive installs skip this
425
+ // and fall through to the registry default (stable) or whatever flags were
426
+ // supplied.
427
+ await this._interactiveChannelGate({ options, channelOptions, selectedModules });
428
+
429
+ let toolSelection = await this.promptToolSelection(confirmedDirectory, options);
430
+ const { moduleConfigs, setOverrides } = await this.collectModuleConfigs(confirmedDirectory, selectedModules, {
431
+ ...options,
432
+ channelOptions,
433
+ });
434
+
435
+ // Warn about --pin/--next flags that refer to modules the user didn't
436
+ // select, or that target bundled modules (core/bmm) where channel
437
+ // flags don't apply.
438
+ {
439
+ const bundledCodes = await this._bundledModuleCodes();
440
+ for (const warning of [
441
+ ...orphanPinWarnings(channelOptions, selectedModules),
442
+ ...bundledTargetWarnings(channelOptions, bundledCodes),
443
+ ]) {
444
+ await prompts.log.warn(warning);
445
+ }
446
+ }
447
+
448
+ return {
449
+ actionType: 'install',
450
+ directory: confirmedDirectory,
451
+ modules: selectedModules,
452
+ ides: toolSelection.ides,
453
+ skipIde: toolSelection.skipIde,
454
+ coreConfig: moduleConfigs.core || {},
455
+ moduleConfigs: moduleConfigs,
456
+ setOverrides,
457
+ skipPrompts: options.yes || false,
458
+ channelOptions,
459
+ };
460
+ }
461
+
462
+ /**
463
+ * Prompt for tool/IDE selection (called after module configuration)
464
+ * Uses a split prompt approach:
465
+ * 1. Recommended tools - standard multiselect for preferred tools
466
+ * 2. Additional tools - autocompleteMultiselect with search capability
467
+ * @param {string} projectDir - Project directory to check for existing IDEs
468
+ * @param {Object} options - Command-line options
469
+ * @returns {Object} Tool configuration
470
+ */
471
+ _parseToolsFlag(toolsArg, allKnownValues) {
472
+ const selectedIdes = toolsArg
473
+ .split(',')
474
+ .map((t) => t.trim())
475
+ .filter(Boolean);
476
+
477
+ if (selectedIdes.length === 0) {
478
+ const err = new Error(
479
+ '--tools was passed empty. Provide at least one tool ID (e.g. --tools claude-code) or run with --list-tools to see valid IDs.',
480
+ );
481
+ err.expected = true;
482
+ throw err;
483
+ }
484
+
485
+ const unknown = selectedIdes.filter((id) => !allKnownValues.has(id));
486
+ if (unknown.length > 0) {
487
+ const err = new Error(
488
+ [
489
+ `Unknown tool ID${unknown.length === 1 ? '' : 's'}: ${unknown.join(', ')}`,
490
+ '',
491
+ 'Run with --list-tools to see all valid IDs.',
492
+ 'Common: claude-code, cursor, copilot, windsurf, cline',
493
+ ].join('\n'),
494
+ );
495
+ err.expected = true;
496
+ throw err;
497
+ }
498
+
499
+ return selectedIdes;
500
+ }
501
+
502
+ async promptToolSelection(projectDir, options = {}) {
503
+ const { ExistingInstall } = require('./core/existing-install');
504
+ const { Installer } = require('./core/installer');
505
+ const installer = new Installer();
506
+ const { bmadDir } = await installer.findBmadDir(projectDir || process.cwd());
507
+ const existingInstall = await ExistingInstall.detect(bmadDir);
508
+ const configuredIdes = existingInstall.ides;
509
+
510
+ // Get IDE manager to fetch available IDEs dynamically
511
+ const { IdeManager } = require('./ide/manager');
512
+ const ideManager = new IdeManager();
513
+ await ideManager.ensureInitialized(); // IMPORTANT: Must initialize before getting IDEs
514
+
515
+ const preferredIdes = ideManager.getPreferredIdes();
516
+ const otherIdes = ideManager.getOtherIdes();
517
+
518
+ // Determine which configured IDEs are in "preferred" vs "other" categories
519
+ const configuredPreferred = configuredIdes.filter((id) => preferredIdes.some((ide) => ide.value === id));
520
+ const configuredOther = configuredIdes.filter((id) => otherIdes.some((ide) => ide.value === id));
521
+
522
+ // Warn about previously configured tools that are no longer available
523
+ const allKnownValues = new Set([...preferredIdes, ...otherIdes].map((ide) => ide.value));
524
+ const unknownTools = configuredIdes.filter((id) => id && typeof id === 'string' && !allKnownValues.has(id));
525
+ if (unknownTools.length > 0) {
526
+ await prompts.log.warn(`Previously configured tools are no longer available: ${unknownTools.join(', ')}`);
527
+ }
528
+
529
+ // ─────────────────────────────────────────────────────────────────────────────
530
+ // UPGRADE PATH: If tools already configured, show all tools with configured at top
531
+ // ─────────────────────────────────────────────────────────────────────────────
532
+ if (configuredIdes.length > 0) {
533
+ const allTools = [...preferredIdes, ...otherIdes];
534
+
535
+ // Non-interactive: handle --tools and --yes flags before interactive prompt
536
+ // Use !== undefined so an explicit --tools "" falls through to _parseToolsFlag and
537
+ // gets a specific "passed empty" error instead of being silently ignored.
538
+ if (options.tools !== undefined) {
539
+ const selectedIdes = this._parseToolsFlag(options.tools, allKnownValues);
540
+ await prompts.log.info(`Using tools from command-line: ${selectedIdes.join(', ')}`);
541
+ await this.displaySelectedTools(selectedIdes, preferredIdes, allTools);
542
+ return { ides: selectedIdes, skipIde: false };
543
+ }
544
+
545
+ if (options.yes) {
546
+ await prompts.log.info(`Non-interactive mode (--yes): keeping configured tools: ${configuredIdes.join(', ')}`);
547
+ await this.displaySelectedTools(configuredIdes, preferredIdes, allTools);
548
+ return { ides: configuredIdes, skipIde: false };
549
+ }
550
+
551
+ // Sort: configured tools first, then preferred, then others
552
+ const sortedTools = [
553
+ ...allTools.filter((ide) => configuredIdes.includes(ide.value)),
554
+ ...allTools.filter((ide) => !configuredIdes.includes(ide.value)),
555
+ ];
556
+
557
+ const upgradeOptions = sortedTools.map((ide) => {
558
+ const isConfigured = configuredIdes.includes(ide.value);
559
+ const isPreferred = preferredIdes.some((p) => p.value === ide.value);
560
+ let label = ide.name;
561
+ if (isPreferred) label += ' ⭐';
562
+ if (isConfigured) label += ' ✅';
563
+ return { label, value: ide.value };
564
+ });
565
+
566
+ // Sort initialValues to match display order
567
+ const sortedInitialValues = sortedTools.filter((ide) => configuredIdes.includes(ide.value)).map((ide) => ide.value);
568
+
569
+ const upgradeSelected = await prompts.autocompleteMultiselect({
570
+ message: 'Integrate with',
571
+ options: upgradeOptions,
572
+ initialValues: sortedInitialValues,
573
+ required: false,
574
+ maxItems: 8,
575
+ });
576
+
577
+ const selectedIdes = upgradeSelected || [];
578
+
579
+ if (selectedIdes.length === 0) {
580
+ const confirmNoTools = await prompts.confirm({
581
+ message: 'No tools selected. Continue without installing any tools?',
582
+ default: false,
583
+ });
584
+
585
+ if (!confirmNoTools) {
586
+ return this.promptToolSelection(projectDir, options);
587
+ }
588
+
589
+ return { ides: [], skipIde: true };
590
+ }
591
+
592
+ // Display selected tools
593
+ await this.displaySelectedTools(selectedIdes, preferredIdes, allTools);
594
+
595
+ return { ides: selectedIdes, skipIde: false };
596
+ }
597
+
598
+ // ─────────────────────────────────────────────────────────────────────────────
599
+ // NEW INSTALL: Show all tools with search
600
+ // ─────────────────────────────────────────────────────────────────────────────
601
+ const allTools = [...preferredIdes, ...otherIdes];
602
+
603
+ const allToolOptions = allTools.map((ide) => {
604
+ const isPreferred = preferredIdes.some((p) => p.value === ide.value);
605
+ let label = ide.name;
606
+ if (isPreferred) label += ' ⭐';
607
+ return {
608
+ label,
609
+ value: ide.value,
610
+ };
611
+ });
612
+
613
+ let selectedIdes = [];
614
+
615
+ // Check if tools are provided via command-line.
616
+ // Use !== undefined so an explicit --tools "" still hits _parseToolsFlag's empty-value error.
617
+ if (options.tools !== undefined) {
618
+ selectedIdes = this._parseToolsFlag(options.tools, allKnownValues);
619
+ await prompts.log.info(`Using tools from command-line: ${selectedIdes.join(', ')}`);
620
+ await this.displaySelectedTools(selectedIdes, preferredIdes, allTools);
621
+ return { ides: selectedIdes, skipIde: false };
622
+ } else if (options.yes) {
623
+ // If --yes flag is set, skip tool prompt and use previously configured tools or empty
624
+ if (configuredIdes.length > 0) {
625
+ await prompts.log.info(`Using previously configured tools (--yes flag): ${configuredIdes.join(', ')}`);
626
+ await this.displaySelectedTools(configuredIdes, preferredIdes, allTools);
627
+ return { ides: configuredIdes, skipIde: false };
628
+ } else {
629
+ const err = new Error(
630
+ [
631
+ '--tools is required for non-interactive install (--yes / -y) when no tools are previously configured.',
632
+ '',
633
+ 'Common: claude-code, cursor, copilot, windsurf, cline',
634
+ 'See all supported tools: bmad-method install --list-tools',
635
+ '',
636
+ 'Example: bmad-method install --modules bmm --tools claude-code -y',
637
+ ].join('\n'),
638
+ );
639
+ err.expected = true;
640
+ throw err;
641
+ }
642
+ }
643
+
644
+ // Interactive mode
645
+ const interactiveSelectedIdes = await prompts.autocompleteMultiselect({
646
+ message: 'Integrate with:',
647
+ options: allToolOptions,
648
+ initialValues: configuredIdes.length > 0 ? configuredIdes : undefined,
649
+ required: false,
650
+ maxItems: 8,
651
+ });
652
+
653
+ selectedIdes = interactiveSelectedIdes || [];
654
+
655
+ // ─────────────────────────────────────────────────────────────────────────────
656
+ // STEP 3: Confirm if no tools selected
657
+ // ─────────────────────────────────────────────────────────────────────────────
658
+ if (selectedIdes.length === 0) {
659
+ const confirmNoTools = await prompts.confirm({
660
+ message: 'No tools selected. Continue without installing any tools?',
661
+ default: false,
662
+ });
663
+
664
+ if (!confirmNoTools) {
665
+ // User wants to select tools - recurse
666
+ return this.promptToolSelection(projectDir, options);
667
+ }
668
+
669
+ return {
670
+ ides: [],
671
+ skipIde: true,
672
+ };
673
+ }
674
+
675
+ // Display selected tools
676
+ await this.displaySelectedTools(selectedIdes, preferredIdes, allTools);
677
+
678
+ return {
679
+ ides: selectedIdes,
680
+ skipIde: selectedIdes.length === 0,
681
+ };
682
+ }
683
+
684
+ /**
685
+ * Prompt for update configuration
686
+ * @returns {Object} Update configuration
687
+ */
688
+ async promptUpdate() {
689
+ const backupFirst = await prompts.confirm({
690
+ message: 'Create backup before updating?',
691
+ default: true,
692
+ });
693
+
694
+ const preserveCustomizations = await prompts.confirm({
695
+ message: 'Preserve local customizations?',
696
+ default: true,
697
+ });
698
+
699
+ return { backupFirst, preserveCustomizations };
700
+ }
701
+
702
+ /**
703
+ * Confirm action
704
+ * @param {string} message - Confirmation message
705
+ * @param {boolean} defaultValue - Default value
706
+ * @returns {boolean} User confirmation
707
+ */
708
+ async confirm(message, defaultValue = false) {
709
+ return await prompts.confirm({
710
+ message,
711
+ default: defaultValue,
712
+ });
713
+ }
714
+
715
+ /**
716
+ * Get confirmed directory from user
717
+ * @returns {string} Confirmed directory path
718
+ */
719
+ async getConfirmedDirectory() {
720
+ let confirmedDirectory = null;
721
+ while (!confirmedDirectory) {
722
+ const directoryAnswer = await this.promptForDirectory();
723
+ await this.displayDirectoryInfo(directoryAnswer.directory);
724
+
725
+ if (await this.confirmDirectory(directoryAnswer.directory)) {
726
+ confirmedDirectory = directoryAnswer.directory;
727
+ }
728
+ }
729
+ return confirmedDirectory;
730
+ }
731
+
732
+ /**
733
+ * Get existing installation info and installed modules
734
+ * @param {string} directory - Installation directory
735
+ * @returns {Object} Object with existingInstall, installedModuleIds, installedModuleVersions, and bmadDir
736
+ */
737
+ async getExistingInstallation(directory) {
738
+ const { ExistingInstall } = require('./core/existing-install');
739
+ const { Installer } = require('./core/installer');
740
+ const installer = new Installer();
741
+ const { bmadDir } = await installer.findBmadDir(directory);
742
+ const existingInstall = await ExistingInstall.detect(bmadDir);
743
+ const installedModuleIds = new Set(existingInstall.moduleIds);
744
+ const installedModuleVersions = new Map();
745
+ const manifestModules = await manifest.getAllModuleVersions(bmadDir);
746
+
747
+ for (const module of manifestModules) {
748
+ if (module?.name && module.version) {
749
+ installedModuleVersions.set(module.name, module.version);
750
+ }
751
+ }
752
+
753
+ for (const module of existingInstall.modules) {
754
+ if (module?.id && module.version && module.version !== 'unknown' && !installedModuleVersions.has(module.id)) {
755
+ installedModuleVersions.set(module.id, module.version);
756
+ }
757
+ }
758
+
759
+ if (existingInstall.hasCore && existingInstall.version && !installedModuleVersions.has('core')) {
760
+ installedModuleVersions.set('core', existingInstall.version);
761
+ }
762
+
763
+ return { existingInstall, installedModuleIds, installedModuleVersions, bmadDir };
764
+ }
765
+
766
+ /**
767
+ * Collect all module configurations (core + selected modules).
768
+ * All interactive prompting happens here in the UI layer.
769
+ * @param {string} directory - Installation directory
770
+ * @param {string[]} modules - Modules to configure (including 'core')
771
+ * @param {Object} options - Command-line options
772
+ * @returns {Object} Collected module configurations keyed by module name
773
+ */
774
+ async collectModuleConfigs(directory, modules, options = {}) {
775
+ const { OfficialModules } = require('./modules/official-modules');
776
+
777
+ // Parse --set up front purely to surface user-error before the install
778
+ // burns time on the network / filesystem. The actual application happens
779
+ // in installer.install() as a post-write TOML patch — see
780
+ // `tools/installer/set-overrides.js`. We also warn about overrides
781
+ // targeting modules the user didn't include, since those will silently
782
+ // miss the file the patch step looks for.
783
+ let setOverrides = {};
784
+ try {
785
+ setOverrides = parseSetEntries(options.set || []);
786
+ } catch (error) {
787
+ // install.js validated already; rethrow as-is for the user.
788
+ throw error;
789
+ }
790
+ // Drop overrides for modules that aren't in the install set so the
791
+ // post-install patch step doesn't create orphan sections in config.toml
792
+ // for modules that were never installed.
793
+ const selectedModuleSet = new Set(['core', ...modules]);
794
+ for (const moduleCode of Object.keys(setOverrides)) {
795
+ if (!selectedModuleSet.has(moduleCode)) {
796
+ await prompts.log.warn(
797
+ `--set ${moduleCode}.* — module '${moduleCode}' is not in the install set; values will be ignored. Add it to --modules to apply.`,
798
+ );
799
+ delete setOverrides[moduleCode];
800
+ }
801
+ }
802
+
803
+ const configCollector = new OfficialModules({ channelOptions: options.channelOptions });
804
+
805
+ // Seed core config from CLI options if provided
806
+ if (options.userName || options.communicationLanguage || options.documentOutputLanguage || options.outputFolder) {
807
+ const coreConfig = {};
808
+ if (options.userName) {
809
+ coreConfig.user_name = options.userName;
810
+ await prompts.log.info(`Using user name from command-line: ${options.userName}`);
811
+ }
812
+ if (options.communicationLanguage) {
813
+ coreConfig.communication_language = options.communicationLanguage;
814
+ await prompts.log.info(`Using communication language from command-line: ${options.communicationLanguage}`);
815
+ }
816
+ if (options.documentOutputLanguage) {
817
+ coreConfig.document_output_language = options.documentOutputLanguage;
818
+ await prompts.log.info(`Using document output language from command-line: ${options.documentOutputLanguage}`);
819
+ }
820
+ if (options.outputFolder) {
821
+ coreConfig.output_folder = options.outputFolder;
822
+ await prompts.log.info(`Using output folder from command-line: ${options.outputFolder}`);
823
+ }
824
+
825
+ // Load existing config to merge with provided options
826
+ await configCollector.loadExistingConfig(directory);
827
+ const existingConfig = configCollector.collectedConfig.core || {};
828
+ configCollector.collectedConfig.core = { ...existingConfig, ...coreConfig };
829
+
830
+ // If not all options are provided, collect the missing ones interactively (unless --yes flag)
831
+ if (
832
+ !options.yes &&
833
+ (!options.userName || !options.communicationLanguage || !options.documentOutputLanguage || !options.outputFolder)
834
+ ) {
835
+ await configCollector.collectModuleConfig('core', directory, false, true);
836
+ }
837
+ } else if (options.yes) {
838
+ // Use all defaults when --yes flag is set
839
+ await configCollector.loadExistingConfig(directory);
840
+ const existingConfig = configCollector.collectedConfig.core || {};
841
+
842
+ if (Object.keys(existingConfig).length === 0) {
843
+ let safeUsername;
844
+ try {
845
+ safeUsername = os.userInfo().username;
846
+ } catch {
847
+ safeUsername = process.env.USER || process.env.USERNAME || 'User';
848
+ }
849
+ const defaultUsername = safeUsername.charAt(0).toUpperCase() + safeUsername.slice(1);
850
+ configCollector.collectedConfig.core = {
851
+ user_name: defaultUsername,
852
+ // {directory_name} default per src/core-skills/module.yaml — matches what the
853
+ // interactive flow resolves via buildQuestion()'s {directory_name} placeholder.
854
+ project_name: path.basename(directory),
855
+ communication_language: 'English',
856
+ document_output_language: 'English',
857
+ output_folder: '_bmad-output',
858
+ };
859
+ await prompts.log.info('Using default configuration (--yes flag)');
860
+ }
861
+ }
862
+
863
+ // Collect all module configs — core is skipped if already seeded above
864
+ await configCollector.collectAllConfigurations(modules, directory, {
865
+ skipPrompts: options.yes || false,
866
+ });
867
+
868
+ return { moduleConfigs: configCollector.collectedConfig, setOverrides };
869
+ }
870
+
871
+ /**
872
+ * Select all modules across three tiers: official, community, and custom URL.
873
+ * @param {Set} installedModuleIds - Currently installed module IDs
874
+ * @param {Map<string, string>} installedModuleVersions - Installed module versions from the local manifest
875
+ * @param {Object|null} channelOptions - Parsed installer channel options
876
+ * @returns {Array} Selected module codes (excluding core)
877
+ */
878
+ async selectAllModules(installedModuleIds = new Set(), installedModuleVersions = new Map(), channelOptions = null) {
879
+ // Phase 1: Official modules
880
+ const officialSelected = await this._selectOfficialModules(installedModuleIds, installedModuleVersions, channelOptions);
881
+
882
+ // Identify installed modules that aren't official (previously installed
883
+ // community modules or custom-source modules). Preserve them on update;
884
+ // they can be managed via --custom-source, uninstall, or a dedicated installer.
885
+ const externalManager = new ExternalModuleManager();
886
+ const registryModules = await externalManager.listAvailable();
887
+ const officialRegistryCodes = new Set(['core', 'bmm', ...registryModules.map((m) => m.code)]);
888
+ const installedNonOfficial = [...installedModuleIds].filter((id) => !officialRegistryCodes.has(id));
889
+
890
+ // Phase 2: Custom URL modules
891
+ const customSelected = await this._addCustomUrlModules(installedModuleIds);
892
+
893
+ const allSelected = new Set([...officialSelected, ...customSelected, ...installedNonOfficial]);
894
+
895
+ return [...allSelected];
896
+ }
897
+
898
+ /**
899
+ * Select official modules using autocompleteMultiselect.
900
+ * Extracted from the original selectAllModules - unchanged behavior.
901
+ * @param {Set} installedModuleIds - Currently installed module IDs
902
+ * @param {Map<string, string>} installedModuleVersions - Installed module versions from the local manifest
903
+ * @param {Object|null} channelOptions - Parsed installer channel options
904
+ * @returns {Array} Selected official module codes
905
+ */
906
+ async _selectOfficialModules(installedModuleIds = new Set(), installedModuleVersions = new Map(), channelOptions = null) {
907
+ // Built-in modules (core, bmm) come from local source, not the registry
908
+ const { OfficialModules } = require('./modules/official-modules');
909
+ const builtInModules = (await new OfficialModules().listAvailable()).modules || [];
910
+
911
+ // External modules come from the registry (with fallback)
912
+ const externalManager = new ExternalModuleManager();
913
+ const registryModules = await externalManager.listAvailable();
914
+
915
+ const allOptions = [];
916
+ const initialValues = [];
917
+ const lockedValues = ['core'];
918
+
919
+ const buildModuleEntry = async (code, name, description, isDefault, repoUrl = null, registryDefault = null) => {
920
+ const isInstalled = installedModuleIds.has(code);
921
+ const installedVersion = installedModuleVersions.get(code) || '';
922
+ const versionState = await getModuleVersion(code, { repoUrl, registryDefault, channelOptions });
923
+ const label = buildModuleLabel(name, versionState.version, installedVersion);
924
+ return {
925
+ label,
926
+ value: code,
927
+ hint: description,
928
+ selected: isInstalled || isDefault,
929
+ lookupAttempted: versionState.lookupAttempted,
930
+ lookupSucceeded: versionState.lookupSucceeded,
931
+ };
932
+ };
933
+
934
+ // Add built-in modules first (always available regardless of network)
935
+ const builtInCodes = new Set();
936
+ for (const mod of builtInModules) {
937
+ const code = mod.id;
938
+ builtInCodes.add(code);
939
+ const entry = await buildModuleEntry(code, mod.name, mod.description, mod.defaultSelected);
940
+ allOptions.push({ label: entry.label, value: entry.value, hint: entry.hint });
941
+ if (entry.selected) {
942
+ initialValues.push(code);
943
+ }
944
+ }
945
+
946
+ // Add external registry modules (skip built-in duplicates)
947
+ const externalRegistryModules = registryModules.filter((mod) => !mod.builtIn && !builtInCodes.has(mod.code));
948
+ let externalRegistryEntries = [];
949
+ if (externalRegistryModules.length > 0) {
950
+ const spinner = await prompts.spinner();
951
+ spinner.start('Checking latest module versions...');
952
+
953
+ externalRegistryEntries = await Promise.all(
954
+ externalRegistryModules.map(async (mod) => ({
955
+ code: mod.code,
956
+ entry: await buildModuleEntry(
957
+ mod.code,
958
+ mod.name,
959
+ mod.description,
960
+ mod.defaultSelected,
961
+ mod.url || null,
962
+ mod.defaultChannel || null,
963
+ ),
964
+ })),
965
+ );
966
+
967
+ spinner.stop('Checked latest module versions.');
968
+
969
+ const attemptedLookups = externalRegistryEntries.filter(({ entry }) => entry.lookupAttempted).length;
970
+ const successfulLookups = externalRegistryEntries.filter(({ entry }) => entry.lookupSucceeded).length;
971
+ if (attemptedLookups > 0 && successfulLookups === 0) {
972
+ await prompts.log.warn('Could not check latest module versions; showing cached/local versions.');
973
+ }
974
+ }
975
+ for (const { code, entry } of externalRegistryEntries) {
976
+ allOptions.push({ label: entry.label, value: entry.value, hint: entry.hint });
977
+ if (entry.selected) {
978
+ initialValues.push(code);
979
+ }
980
+ }
981
+
982
+ const selected = await prompts.autocompleteMultiselect({
983
+ message: 'Select official modules to install:',
984
+ options: allOptions,
985
+ initialValues: initialValues.length > 0 ? initialValues : undefined,
986
+ lockedValues,
987
+ required: true,
988
+ maxItems: allOptions.length,
989
+ });
990
+
991
+ const result = selected ? [...selected] : [];
992
+
993
+ if (result.length > 0) {
994
+ const moduleLines = result.map((moduleId) => {
995
+ const opt = allOptions.find((o) => o.value === moduleId);
996
+ return ` \u2022 ${opt?.label || moduleId}`;
997
+ });
998
+ await prompts.log.message('Selected official modules:\n' + moduleLines.join('\n'));
999
+ }
1000
+
1001
+ return result;
1002
+ }
1003
+
1004
+ /**
1005
+ * Prompt user to install modules from custom sources (Git URLs or local paths).
1006
+ * @param {Set} installedModuleIds - Currently installed module IDs
1007
+ * @returns {Array} Selected custom module code strings
1008
+ */
1009
+ async _addCustomUrlModules(installedModuleIds = new Set()) {
1010
+ const addCustom = await prompts.confirm({
1011
+ message: 'Do you want to install custom or community modules (Git URL or local path)?',
1012
+ default: false,
1013
+ });
1014
+ if (!addCustom) return [];
1015
+
1016
+ const { CustomModuleManager } = require('./modules/custom-module-manager');
1017
+ const customMgr = new CustomModuleManager();
1018
+ const selectedModules = [];
1019
+
1020
+ let addMore = true;
1021
+ while (addMore) {
1022
+ const sourceInput = await prompts.text({
1023
+ message: 'Git URL or local path:',
1024
+ placeholder: 'https://github.com/owner/repo or /path/to/module',
1025
+ validate: (input) => {
1026
+ if (!input || input.trim() === '') return 'Source is required';
1027
+ const result = customMgr.parseSource(input.trim());
1028
+ return result.isValid ? undefined : result.error;
1029
+ },
1030
+ });
1031
+
1032
+ const s = await prompts.spinner();
1033
+ s.start('Resolving source...');
1034
+
1035
+ let sourceResult;
1036
+ try {
1037
+ sourceResult = await customMgr.resolveSource(sourceInput.trim(), { skipInstall: true, silent: true });
1038
+ s.stop(sourceResult.parsed.type === 'local' ? 'Local source resolved' : 'Repository cloned');
1039
+ } catch (error) {
1040
+ s.error('Failed to resolve source');
1041
+ await prompts.log.error(` ${error.message}`);
1042
+ addMore = await prompts.confirm({ message: 'Try another source?', default: false });
1043
+ continue;
1044
+ }
1045
+
1046
+ if (sourceResult.parsed.type === 'local') {
1047
+ await prompts.log.info('LOCAL MODULE: Pointing directly at local source (changes take effect on reinstall).');
1048
+ } else {
1049
+ await prompts.log.warn(
1050
+ 'UNVERIFIED MODULE: This module has not been reviewed by the BMad team.\n' + ' Only install modules from sources you trust.',
1051
+ );
1052
+ }
1053
+
1054
+ // Resolve plugins based on discovery mode vs direct mode
1055
+ s.start('Analyzing plugin structure...');
1056
+ const allResolved = [];
1057
+ const localPath = sourceResult.parsed.type === 'local' ? sourceResult.rootDir : null;
1058
+
1059
+ if (sourceResult.mode === 'discovery') {
1060
+ // Discovery mode: marketplace.json found, list available plugins
1061
+ let plugins;
1062
+ try {
1063
+ plugins = await customMgr.discoverModules(sourceResult.marketplace, sourceResult.sourceUrl);
1064
+ } catch (discoverError) {
1065
+ s.error('Failed to discover modules');
1066
+ await prompts.log.error(` ${discoverError.message}`);
1067
+ addMore = await prompts.confirm({ message: 'Try another source?', default: false });
1068
+ continue;
1069
+ }
1070
+
1071
+ const effectiveRepoPath = sourceResult.repoPath || sourceResult.rootDir;
1072
+ for (const plugin of plugins) {
1073
+ try {
1074
+ const resolved = await customMgr.resolvePlugin(effectiveRepoPath, plugin.rawPlugin, sourceResult.sourceUrl, localPath);
1075
+ if (resolved.length > 0) {
1076
+ allResolved.push(...resolved);
1077
+ } else {
1078
+ // No skills array or empty - use plugin metadata as-is (legacy)
1079
+ allResolved.push({
1080
+ code: plugin.code,
1081
+ name: plugin.displayName || plugin.name,
1082
+ version: plugin.version,
1083
+ description: plugin.description,
1084
+ strategy: 0,
1085
+ pluginName: plugin.name,
1086
+ skillPaths: [],
1087
+ });
1088
+ }
1089
+ } catch (resolveError) {
1090
+ await prompts.log.warn(` Could not resolve ${plugin.name}: ${resolveError.message}`);
1091
+ }
1092
+ }
1093
+ } else {
1094
+ // Direct mode: no marketplace.json, scan directory for skills and resolve
1095
+ const directPlugin = {
1096
+ name: sourceResult.parsed.displayName || path.basename(sourceResult.rootDir),
1097
+ source: '.',
1098
+ skills: [],
1099
+ };
1100
+
1101
+ // Scan for SKILL.md directories to populate skills array
1102
+ try {
1103
+ const entries = await fs.readdir(sourceResult.rootDir, { withFileTypes: true });
1104
+ for (const entry of entries) {
1105
+ if (entry.isDirectory()) {
1106
+ const skillMd = path.join(sourceResult.rootDir, entry.name, 'SKILL.md');
1107
+ if (await fs.pathExists(skillMd)) {
1108
+ directPlugin.skills.push(entry.name);
1109
+ }
1110
+ }
1111
+ }
1112
+ } catch (scanError) {
1113
+ s.error('Failed to scan directory');
1114
+ await prompts.log.error(` ${scanError.message}`);
1115
+ addMore = await prompts.confirm({ message: 'Try another source?', default: false });
1116
+ continue;
1117
+ }
1118
+
1119
+ if (directPlugin.skills.length > 0) {
1120
+ try {
1121
+ const resolved = await customMgr.resolvePlugin(sourceResult.rootDir, directPlugin, sourceResult.sourceUrl, localPath);
1122
+ allResolved.push(...resolved);
1123
+ } catch (resolveError) {
1124
+ await prompts.log.warn(` Could not resolve: ${resolveError.message}`);
1125
+ }
1126
+ }
1127
+ }
1128
+ s.stop(`Found ${allResolved.length} installable module${allResolved.length === 1 ? '' : 's'}`);
1129
+
1130
+ if (allResolved.length === 0) {
1131
+ await prompts.log.warn('No installable modules found in this source.');
1132
+ addMore = await prompts.confirm({ message: 'Try another source?', default: false });
1133
+ continue;
1134
+ }
1135
+
1136
+ // Build multiselect choices
1137
+ // Already-installed modules are pre-checked (update). New modules are unchecked (opt-in).
1138
+ // Unchecking an installed module means "skip update" - removal is handled elsewhere.
1139
+ const choices = allResolved.map((mod) => {
1140
+ const versionStr = mod.version ? ` v${mod.version}` : '';
1141
+ const skillCount = mod.skillPaths ? mod.skillPaths.length : 0;
1142
+ const skillStr = skillCount > 0 ? ` (${skillCount} skill${skillCount === 1 ? '' : 's'})` : '';
1143
+ const alreadyInstalled = installedModuleIds.has(mod.code);
1144
+ const hint = alreadyInstalled ? 'update' : undefined;
1145
+
1146
+ return {
1147
+ name: `${mod.name}${versionStr}${skillStr}`,
1148
+ value: mod.code,
1149
+ hint,
1150
+ checked: alreadyInstalled,
1151
+ };
1152
+ });
1153
+
1154
+ // Show descriptions before the multiselect
1155
+ for (const mod of allResolved) {
1156
+ const versionStr = mod.version ? ` v${mod.version}` : '';
1157
+ await prompts.log.info(` ${mod.name}${versionStr}\n ${mod.description}`);
1158
+ }
1159
+
1160
+ const selected = await prompts.multiselect({
1161
+ message: 'Select modules to install:',
1162
+ choices,
1163
+ required: false,
1164
+ });
1165
+
1166
+ if (selected && selected.length > 0) {
1167
+ for (const code of selected) {
1168
+ selectedModules.push(code);
1169
+ }
1170
+ }
1171
+
1172
+ addMore = await prompts.confirm({
1173
+ message: 'Add another custom source?',
1174
+ default: false,
1175
+ });
1176
+ }
1177
+
1178
+ if (selectedModules.length > 0) {
1179
+ await prompts.log.message('Selected custom modules:\n' + selectedModules.map((c) => ` \u2022 ${c}`).join('\n'));
1180
+ }
1181
+
1182
+ return selectedModules;
1183
+ }
1184
+
1185
+ /**
1186
+ * Resolve custom sources from --custom-source CLI flag (non-interactive).
1187
+ * Auto-selects all discovered modules from each source.
1188
+ * @param {string} sourcesArg - Comma-separated Git URLs or local paths
1189
+ * @returns {Array} Module codes from all resolved sources
1190
+ */
1191
+ async _resolveCustomSourcesCli(sourcesArg) {
1192
+ const { CustomModuleManager } = require('./modules/custom-module-manager');
1193
+ const customMgr = new CustomModuleManager();
1194
+ const allCodes = [];
1195
+
1196
+ const sources = sourcesArg
1197
+ .split(',')
1198
+ .map((s) => s.trim())
1199
+ .filter(Boolean);
1200
+
1201
+ for (const source of sources) {
1202
+ const s = await prompts.spinner();
1203
+ s.start(`Resolving ${source}...`);
1204
+
1205
+ let sourceResult;
1206
+ try {
1207
+ sourceResult = await customMgr.resolveSource(source, { skipInstall: true, silent: true });
1208
+ s.stop(sourceResult.parsed.type === 'local' ? 'Local source resolved' : 'Repository cloned');
1209
+ } catch (error) {
1210
+ s.error(`Failed to resolve ${source}`);
1211
+ await prompts.log.error(` ${error.message}`);
1212
+ continue;
1213
+ }
1214
+
1215
+ const s2 = await prompts.spinner();
1216
+ s2.start('Analyzing plugin structure...');
1217
+ const allResolved = [];
1218
+ const localPath = sourceResult.parsed.type === 'local' ? sourceResult.rootDir : null;
1219
+
1220
+ if (sourceResult.mode === 'discovery') {
1221
+ try {
1222
+ const plugins = await customMgr.discoverModules(sourceResult.marketplace, sourceResult.sourceUrl);
1223
+ const effectiveRepoPath = sourceResult.repoPath || sourceResult.rootDir;
1224
+ for (const plugin of plugins) {
1225
+ try {
1226
+ const resolved = await customMgr.resolvePlugin(effectiveRepoPath, plugin.rawPlugin, sourceResult.sourceUrl, localPath);
1227
+ if (resolved.length > 0) {
1228
+ allResolved.push(...resolved);
1229
+ }
1230
+ } catch {
1231
+ // Skip unresolvable plugins
1232
+ }
1233
+ }
1234
+ } catch (discoverError) {
1235
+ s2.error('Failed to discover modules');
1236
+ await prompts.log.error(` ${discoverError.message}`);
1237
+ continue;
1238
+ }
1239
+ } else {
1240
+ // Direct mode: scan for SKILL.md directories
1241
+ const directPlugin = {
1242
+ name: sourceResult.parsed.displayName || path.basename(sourceResult.rootDir),
1243
+ source: '.',
1244
+ skills: [],
1245
+ };
1246
+ try {
1247
+ const entries = await fs.readdir(sourceResult.rootDir, { withFileTypes: true });
1248
+ for (const entry of entries) {
1249
+ if (entry.isDirectory()) {
1250
+ const skillMd = path.join(sourceResult.rootDir, entry.name, 'SKILL.md');
1251
+ if (await fs.pathExists(skillMd)) {
1252
+ directPlugin.skills.push(entry.name);
1253
+ }
1254
+ }
1255
+ }
1256
+ } catch {
1257
+ // Skip unreadable directories
1258
+ }
1259
+
1260
+ if (directPlugin.skills.length > 0) {
1261
+ try {
1262
+ const resolved = await customMgr.resolvePlugin(sourceResult.rootDir, directPlugin, sourceResult.sourceUrl, localPath);
1263
+ allResolved.push(...resolved);
1264
+ } catch {
1265
+ // Skip unresolvable
1266
+ }
1267
+ }
1268
+ }
1269
+ s2.stop(`Found ${allResolved.length} module${allResolved.length === 1 ? '' : 's'}`);
1270
+
1271
+ for (const mod of allResolved) {
1272
+ allCodes.push(mod.code);
1273
+ const versionStr = mod.version ? ` v${mod.version}` : '';
1274
+ await prompts.log.info(` Custom module: ${mod.name}${versionStr}`);
1275
+ }
1276
+ }
1277
+
1278
+ return allCodes;
1279
+ }
1280
+
1281
+ /**
1282
+ * Get default modules for non-interactive mode
1283
+ * @param {Set} installedModuleIds - Already installed module IDs
1284
+ * @returns {Array} Default module codes
1285
+ */
1286
+ async getDefaultModules(installedModuleIds = new Set()) {
1287
+ // Built-in modules with default_selected come from local source
1288
+ const { OfficialModules } = require('./modules/official-modules');
1289
+ const builtInModules = (await new OfficialModules().listAvailable()).modules || [];
1290
+
1291
+ const defaultModules = [];
1292
+ const seen = new Set();
1293
+
1294
+ for (const mod of builtInModules) {
1295
+ if (mod.defaultSelected || installedModuleIds.has(mod.id)) {
1296
+ defaultModules.push(mod.id);
1297
+ seen.add(mod.id);
1298
+ }
1299
+ }
1300
+
1301
+ // Add external registry defaults
1302
+ const externalManager = new ExternalModuleManager();
1303
+ const registryModules = await externalManager.listAvailable();
1304
+
1305
+ for (const mod of registryModules) {
1306
+ if (mod.builtIn || seen.has(mod.code)) continue;
1307
+ if (mod.defaultSelected || installedModuleIds.has(mod.code)) {
1308
+ defaultModules.push(mod.code);
1309
+ }
1310
+ }
1311
+
1312
+ // If no defaults found, use 'bmm' as the fallback default
1313
+ if (defaultModules.length === 0) {
1314
+ defaultModules.push('bmm');
1315
+ }
1316
+
1317
+ return defaultModules;
1318
+ }
1319
+
1320
+ /**
1321
+ * Prompt for directory selection
1322
+ * @returns {Object} Directory answer from prompt
1323
+ */
1324
+ async promptForDirectory() {
1325
+ // Use sync validation because @clack/prompts doesn't support async validate
1326
+ const directory = await prompts.directory({
1327
+ message: 'Installation directory:',
1328
+ default: process.cwd(),
1329
+ placeholder: process.cwd(),
1330
+ validate: (input) => this.validateDirectorySync(input),
1331
+ });
1332
+
1333
+ // Apply filter logic
1334
+ let filteredDir = directory;
1335
+ if (!filteredDir || filteredDir.trim() === '') {
1336
+ filteredDir = process.cwd();
1337
+ } else {
1338
+ filteredDir = this.expandUserPath(filteredDir);
1339
+ }
1340
+
1341
+ return { directory: filteredDir };
1342
+ }
1343
+
1344
+ /**
1345
+ * Display directory information
1346
+ * @param {string} directory - The directory path
1347
+ */
1348
+ async displayDirectoryInfo(directory) {
1349
+ await prompts.log.info(`Resolved installation path: ${directory}`);
1350
+
1351
+ const dirExists = await fs.pathExists(directory);
1352
+ if (dirExists) {
1353
+ // Show helpful context about the existing path
1354
+ const stats = await fs.stat(directory);
1355
+ if (stats.isDirectory()) {
1356
+ const files = await fs.readdir(directory);
1357
+ if (files.length > 0) {
1358
+ // Check for any bmad installation (any folder with _config/manifest.yaml)
1359
+ const { Installer } = require('./core/installer');
1360
+ const installer = new Installer();
1361
+ const bmadResult = await installer.findBmadDir(directory);
1362
+ const hasBmadInstall =
1363
+ (await fs.pathExists(bmadResult.bmadDir)) && (await fs.pathExists(path.join(bmadResult.bmadDir, '_config', 'manifest.yaml')));
1364
+
1365
+ const bmadNote = hasBmadInstall ? ` including existing BMAD installation (${path.basename(bmadResult.bmadDir)})` : '';
1366
+ await prompts.log.message(`Directory exists and contains ${files.length} item(s)${bmadNote}`);
1367
+ } else {
1368
+ await prompts.log.message('Directory exists and is empty');
1369
+ }
1370
+ }
1371
+ }
1372
+ }
1373
+
1374
+ /**
1375
+ * Confirm directory selection
1376
+ * @param {string} directory - The directory path
1377
+ * @returns {boolean} Whether user confirmed
1378
+ */
1379
+ async confirmDirectory(directory) {
1380
+ const dirExists = await fs.pathExists(directory);
1381
+
1382
+ if (dirExists) {
1383
+ const proceed = await prompts.confirm({
1384
+ message: 'Install to this directory?',
1385
+ default: true,
1386
+ });
1387
+
1388
+ if (!proceed) {
1389
+ await prompts.log.warn("Let's try again with a different path.");
1390
+ }
1391
+
1392
+ return proceed;
1393
+ } else {
1394
+ // Ask for confirmation to create the directory
1395
+ const create = await prompts.confirm({
1396
+ message: `Create directory: ${directory}?`,
1397
+ default: false,
1398
+ });
1399
+
1400
+ if (!create) {
1401
+ await prompts.log.warn("Let's try again with a different path.");
1402
+ }
1403
+
1404
+ return create;
1405
+ }
1406
+ }
1407
+
1408
+ /**
1409
+ * Validate directory path for installation (sync version for clack prompts)
1410
+ * @param {string} input - User input path
1411
+ * @returns {string|undefined} Error message or undefined if valid
1412
+ */
1413
+ validateDirectorySync(input) {
1414
+ // Allow empty input to use the default
1415
+ if (!input || input.trim() === '') {
1416
+ return; // Empty means use default, undefined = valid for clack
1417
+ }
1418
+
1419
+ let expandedPath;
1420
+ try {
1421
+ expandedPath = this.expandUserPath(input.trim());
1422
+ } catch (error) {
1423
+ return error.message;
1424
+ }
1425
+
1426
+ // Check if the path exists
1427
+ const pathExists = fs.pathExistsSync(expandedPath);
1428
+
1429
+ if (!pathExists) {
1430
+ // Find the first existing parent directory
1431
+ const existingParent = this.findExistingParentSync(expandedPath);
1432
+
1433
+ if (!existingParent) {
1434
+ return 'Cannot create directory: no existing parent directory found';
1435
+ }
1436
+
1437
+ // Check if the existing parent is writable
1438
+ try {
1439
+ fs.accessSync(existingParent, fs.constants.W_OK);
1440
+ // Path doesn't exist but can be created - will prompt for confirmation later
1441
+ return;
1442
+ } catch {
1443
+ // Provide a detailed error message explaining both issues
1444
+ return `Directory '${expandedPath}' does not exist and cannot be created: parent directory '${existingParent}' is not writable`;
1445
+ }
1446
+ }
1447
+
1448
+ // If it exists, validate it's a directory and writable
1449
+ const stat = fs.statSync(expandedPath);
1450
+ if (!stat.isDirectory()) {
1451
+ return `Path exists but is not a directory: ${expandedPath}`;
1452
+ }
1453
+
1454
+ // Check write permissions
1455
+ try {
1456
+ fs.accessSync(expandedPath, fs.constants.W_OK);
1457
+ } catch {
1458
+ return `Directory is not writable: ${expandedPath}`;
1459
+ }
1460
+
1461
+ return;
1462
+ }
1463
+
1464
+ /**
1465
+ * Validate directory path for installation (async version)
1466
+ * @param {string} input - User input path
1467
+ * @returns {string|true} Error message or true if valid
1468
+ */
1469
+ async validateDirectory(input) {
1470
+ // Allow empty input to use the default
1471
+ if (!input || input.trim() === '') {
1472
+ return true; // Empty means use default
1473
+ }
1474
+
1475
+ let expandedPath;
1476
+ try {
1477
+ expandedPath = this.expandUserPath(input.trim());
1478
+ } catch (error) {
1479
+ return error.message;
1480
+ }
1481
+
1482
+ // Check if the path exists
1483
+ const pathExists = await fs.pathExists(expandedPath);
1484
+
1485
+ if (!pathExists) {
1486
+ // Find the first existing parent directory
1487
+ const existingParent = await this.findExistingParent(expandedPath);
1488
+
1489
+ if (!existingParent) {
1490
+ return 'Cannot create directory: no existing parent directory found';
1491
+ }
1492
+
1493
+ // Check if the existing parent is writable
1494
+ try {
1495
+ await fs.access(existingParent, fs.constants.W_OK);
1496
+ // Path doesn't exist but can be created - will prompt for confirmation later
1497
+ return true;
1498
+ } catch {
1499
+ // Provide a detailed error message explaining both issues
1500
+ return `Directory '${expandedPath}' does not exist and cannot be created: parent directory '${existingParent}' is not writable`;
1501
+ }
1502
+ }
1503
+
1504
+ // If it exists, validate it's a directory and writable
1505
+ const stat = await fs.stat(expandedPath);
1506
+ if (!stat.isDirectory()) {
1507
+ return `Path exists but is not a directory: ${expandedPath}`;
1508
+ }
1509
+
1510
+ // Check write permissions
1511
+ try {
1512
+ await fs.access(expandedPath, fs.constants.W_OK);
1513
+ } catch {
1514
+ return `Directory is not writable: ${expandedPath}`;
1515
+ }
1516
+
1517
+ return true;
1518
+ }
1519
+
1520
+ /**
1521
+ * Find the first existing parent directory (sync version)
1522
+ * @param {string} targetPath - The path to check
1523
+ * @returns {string|null} The first existing parent directory, or null if none found
1524
+ */
1525
+ findExistingParentSync(targetPath) {
1526
+ let currentPath = path.resolve(targetPath);
1527
+
1528
+ // Walk up the directory tree until we find an existing directory
1529
+ while (currentPath !== path.dirname(currentPath)) {
1530
+ // Stop at root
1531
+ const parent = path.dirname(currentPath);
1532
+ if (fs.pathExistsSync(parent)) {
1533
+ return parent;
1534
+ }
1535
+ currentPath = parent;
1536
+ }
1537
+
1538
+ return null; // No existing parent found (shouldn't happen in practice)
1539
+ }
1540
+
1541
+ /**
1542
+ * Find the first existing parent directory (async version)
1543
+ * @param {string} targetPath - The path to check
1544
+ * @returns {string|null} The first existing parent directory, or null if none found
1545
+ */
1546
+ async findExistingParent(targetPath) {
1547
+ let currentPath = path.resolve(targetPath);
1548
+
1549
+ // Walk up the directory tree until we find an existing directory
1550
+ while (currentPath !== path.dirname(currentPath)) {
1551
+ // Stop at root
1552
+ const parent = path.dirname(currentPath);
1553
+ if (await fs.pathExists(parent)) {
1554
+ return parent;
1555
+ }
1556
+ currentPath = parent;
1557
+ }
1558
+
1559
+ return null; // No existing parent found (shouldn't happen in practice)
1560
+ }
1561
+
1562
+ /**
1563
+ * Expands the user-provided path: handles ~ and resolves to absolute.
1564
+ * @param {string} inputPath - User input path.
1565
+ * @returns {string} Absolute expanded path.
1566
+ */
1567
+ expandUserPath(inputPath) {
1568
+ if (typeof inputPath !== 'string') {
1569
+ throw new TypeError('Path must be a string.');
1570
+ }
1571
+
1572
+ let expanded = inputPath.trim();
1573
+
1574
+ // Handle tilde expansion
1575
+ if (expanded.startsWith('~')) {
1576
+ if (expanded === '~') {
1577
+ expanded = os.homedir();
1578
+ } else if (expanded.startsWith('~' + path.sep)) {
1579
+ const pathAfterHome = expanded.slice(2); // Remove ~/ or ~\
1580
+ expanded = path.join(os.homedir(), pathAfterHome);
1581
+ } else {
1582
+ const restOfPath = expanded.slice(1);
1583
+ const separatorIndex = restOfPath.indexOf(path.sep);
1584
+ const username = separatorIndex === -1 ? restOfPath : restOfPath.slice(0, separatorIndex);
1585
+ if (username) {
1586
+ throw new Error(`Path expansion for ~${username} is not supported. Please use an absolute path or ~${path.sep}`);
1587
+ }
1588
+ }
1589
+ }
1590
+
1591
+ // Resolve to the absolute path relative to the current working directory
1592
+ return path.resolve(expanded);
1593
+ }
1594
+
1595
+ /**
1596
+ * Get configured IDEs from existing installation
1597
+ * @param {string} directory - Installation directory
1598
+ * @returns {Array} List of configured IDEs
1599
+ */
1600
+ async getConfiguredIdes(directory) {
1601
+ const { ExistingInstall } = require('./core/existing-install');
1602
+ const { Installer } = require('./core/installer');
1603
+ const installer = new Installer();
1604
+ const { bmadDir } = await installer.findBmadDir(directory);
1605
+ const existingInstall = await ExistingInstall.detect(bmadDir);
1606
+ return existingInstall.ides;
1607
+ }
1608
+
1609
+ /**
1610
+ * Display module versions with update availability
1611
+ * @param {Array} modules - Array of module info objects with version info
1612
+ * @param {Array} availableUpdates - Array of available updates
1613
+ */
1614
+ async displayModuleVersions(modules, availableUpdates = []) {
1615
+ // Group modules by source
1616
+ const builtIn = modules.filter((m) => m.source === 'built-in');
1617
+ const external = modules.filter((m) => m.source === 'external');
1618
+ const community = modules.filter((m) => m.source === 'community');
1619
+ const custom = modules.filter((m) => m.source === 'custom');
1620
+ const unknown = modules.filter((m) => m.source === 'unknown');
1621
+
1622
+ const lines = [];
1623
+ const formatGroup = (group, title) => {
1624
+ if (group.length === 0) return;
1625
+ lines.push(title);
1626
+ for (const mod of group) {
1627
+ const updateInfo = availableUpdates.find((u) => u.name === mod.name);
1628
+ const versionDisplay = mod.version || 'unknown';
1629
+ if (updateInfo) {
1630
+ lines.push(` ${mod.name.padEnd(20)} ${versionDisplay} \u2192 ${updateInfo.latestVersion} \u2191`);
1631
+ } else {
1632
+ lines.push(` ${mod.name.padEnd(20)} ${versionDisplay} \u2713`);
1633
+ }
1634
+ }
1635
+ };
1636
+
1637
+ formatGroup(builtIn, 'Built-in Modules');
1638
+ formatGroup(external, 'External Modules (Official)');
1639
+ formatGroup(community, 'Community Modules');
1640
+ formatGroup(custom, 'Custom Modules');
1641
+ formatGroup(unknown, 'Other Modules');
1642
+
1643
+ await prompts.note(lines.join('\n'), 'Module Versions');
1644
+ }
1645
+
1646
+ /**
1647
+ * Prompt user to select which modules to update
1648
+ * @param {Array} availableUpdates - Array of available updates
1649
+ * @returns {Array} Selected module names to update
1650
+ */
1651
+ async promptUpdateSelection(availableUpdates) {
1652
+ if (availableUpdates.length === 0) {
1653
+ return [];
1654
+ }
1655
+
1656
+ await prompts.log.info('Available Updates');
1657
+
1658
+ const choices = availableUpdates.map((update) => ({
1659
+ name: `${update.name} (v${update.installedVersion} \u2192 v${update.latestVersion})`,
1660
+ value: update.name,
1661
+ checked: true, // Default to selecting all updates
1662
+ }));
1663
+
1664
+ // Add "Update All" and "Cancel" options
1665
+ const action = await prompts.select({
1666
+ message: 'How would you like to proceed?',
1667
+ choices: [
1668
+ { name: 'Update all available modules', value: 'all' },
1669
+ { name: 'Select specific modules to update', value: 'select' },
1670
+ { name: 'Skip updates for now', value: 'skip' },
1671
+ ],
1672
+ default: 'all',
1673
+ });
1674
+
1675
+ if (action === 'all') {
1676
+ return availableUpdates.map((u) => u.name);
1677
+ }
1678
+
1679
+ if (action === 'skip') {
1680
+ return [];
1681
+ }
1682
+
1683
+ // Allow specific selection
1684
+ const selected = await prompts.multiselect({
1685
+ message: 'Select modules to update (use arrow keys, space to toggle):',
1686
+ choices: choices,
1687
+ required: true,
1688
+ });
1689
+
1690
+ return selected || [];
1691
+ }
1692
+
1693
+ /**
1694
+ * Display status of all installed modules
1695
+ * @param {Object} statusData - Status data with modules, installation info, and available updates
1696
+ */
1697
+ async displayStatus(statusData) {
1698
+ const { installation, modules, availableUpdates, bmadDir } = statusData;
1699
+
1700
+ // Installation info
1701
+ const infoLines = [
1702
+ `Version: ${installation.version || 'unknown'}`,
1703
+ `Location: ${bmadDir}`,
1704
+ `Installed: ${new Date(installation.installDate).toLocaleDateString()}`,
1705
+ `Last Updated: ${installation.lastUpdated ? new Date(installation.lastUpdated).toLocaleDateString() : 'unknown'}`,
1706
+ ];
1707
+
1708
+ await prompts.note(infoLines.join('\n'), 'BMAD Status');
1709
+
1710
+ // Module versions
1711
+ await this.displayModuleVersions(modules, availableUpdates);
1712
+
1713
+ // Update summary
1714
+ if (availableUpdates.length > 0) {
1715
+ await prompts.log.warn(`${availableUpdates.length} update(s) available`);
1716
+ await prompts.log.message('Run \'bmad install\' and select "Quick Update" to update');
1717
+ } else {
1718
+ await prompts.log.success('All modules are up to date');
1719
+ }
1720
+ }
1721
+
1722
+ /**
1723
+ * Display list of selected tools after IDE selection
1724
+ * @param {Array} selectedIdes - Array of selected IDE values
1725
+ * @param {Array} preferredIdes - Array of preferred IDE objects
1726
+ * @param {Array} allTools - Array of all tool objects
1727
+ */
1728
+ async displaySelectedTools(selectedIdes, preferredIdes, allTools) {
1729
+ if (selectedIdes.length === 0) return;
1730
+
1731
+ const preferredValues = new Set(preferredIdes.map((ide) => ide.value));
1732
+ const toolLines = selectedIdes.map((ideValue) => {
1733
+ const tool = allTools.find((t) => t.value === ideValue);
1734
+ const name = tool?.name || ideValue;
1735
+ const marker = preferredValues.has(ideValue) ? ' \u2B50' : '';
1736
+ return ` \u2022 ${name}${marker}`;
1737
+ });
1738
+ await prompts.log.message('Selected tools:\n' + toolLines.join('\n'));
1739
+ }
1740
+
1741
+ /**
1742
+ * Return the set of module codes the registry marks as built-in (core, bmm).
1743
+ * These ship with the installer binary and have no per-module channel.
1744
+ */
1745
+ async _bundledModuleCodes() {
1746
+ const externalManager = new ExternalModuleManager();
1747
+ try {
1748
+ const modules = await externalManager.listAvailable();
1749
+ return modules.filter((m) => m.builtIn).map((m) => m.code);
1750
+ } catch {
1751
+ // Registry unreachable — fall back to the known bundled codes.
1752
+ return ['core', 'bmm'];
1753
+ }
1754
+ }
1755
+
1756
+ /**
1757
+ * Fast-path channel gate: confirm "all stable" or open the per-module picker.
1758
+ *
1759
+ * Skipped when:
1760
+ * - running non-interactively (--yes)
1761
+ * - the user already passed channel flags (--channel / --pin / --next), OR
1762
+ * the installer was launched from a prerelease (which seeds
1763
+ * channelOptions.global = 'next' upstream in promptInstall)
1764
+ * - no externals/community modules are selected
1765
+ *
1766
+ * Mutates channelOptions.pins and channelOptions.nextSet to reflect picker choices.
1767
+ */
1768
+ async _interactiveChannelGate({ options, channelOptions, selectedModules }) {
1769
+ if (options.yes) return;
1770
+ // If the user already declared their channel intent via flags, trust them
1771
+ // and skip the gate.
1772
+ const haveFlagIntent = channelOptions.global || channelOptions.nextSet.size > 0 || channelOptions.pins.size > 0;
1773
+ if (haveFlagIntent) return;
1774
+
1775
+ // Figure out which selected modules actually get a channel (externals only).
1776
+ // Bundled core/bmm and custom modules skip the picker.
1777
+ const externalManager = new ExternalModuleManager();
1778
+ const externals = await externalManager.listAvailable();
1779
+ const externalByCode = new Map(externals.map((m) => [m.code, m]));
1780
+
1781
+ const channelSelectable = selectedModules.filter((code) => {
1782
+ const info = externalByCode.get(code);
1783
+ return info && !info.builtIn;
1784
+ });
1785
+ if (channelSelectable.length === 0) return;
1786
+
1787
+ const fastPath = await prompts.confirm({
1788
+ message: `Ready to install (all stable)? Pick "n" to customize channels or pin versions.`,
1789
+ default: true,
1790
+ });
1791
+ if (fastPath) return; // stable for all, registry default applies
1792
+
1793
+ // Customize path: per-module picker.
1794
+ const { fetchStableTags, parseGitHubRepo } = require('./modules/channel-resolver');
1795
+
1796
+ for (const code of channelSelectable) {
1797
+ const info = externalByCode.get(code);
1798
+ const repoUrl = info.url;
1799
+
1800
+ // Try to pre-resolve the top stable tag so we can surface it in the picker.
1801
+ let stableLabel = 'stable (released version)';
1802
+ try {
1803
+ const parsed = repoUrl ? parseGitHubRepo(repoUrl) : null;
1804
+ if (parsed) {
1805
+ const tags = await fetchStableTags(parsed.owner, parsed.repo);
1806
+ if (tags.length > 0) {
1807
+ stableLabel = `stable ${tags[0].tag} (released version)`;
1808
+ }
1809
+ }
1810
+ } catch {
1811
+ // fall through with the generic label
1812
+ }
1813
+
1814
+ const choice = await prompts.select({
1815
+ message: `${code}: choose a channel`,
1816
+ choices: [
1817
+ { name: stableLabel, value: 'stable' },
1818
+ { name: 'next (main HEAD \u2014 current development)', value: 'next' },
1819
+ { name: 'pin (specific version)', value: 'pin' },
1820
+ ],
1821
+ default: 'stable',
1822
+ });
1823
+
1824
+ if (choice === 'next') {
1825
+ channelOptions.nextSet.add(code);
1826
+ } else if (choice === 'pin') {
1827
+ const pinValue = await prompts.text({
1828
+ message: `Enter a version tag for '${code}' (e.g. v1.6.0):`,
1829
+ validate: (value) => {
1830
+ if (!value || !/^[\w.\-+/]+$/.test(String(value).trim())) {
1831
+ return 'Must be a non-empty tag name (letters, digits, dots, hyphens).';
1832
+ }
1833
+ },
1834
+ });
1835
+ channelOptions.pins.set(code, String(pinValue).trim());
1836
+ }
1837
+ // 'stable' is the default; nothing to record.
1838
+ }
1839
+ }
1840
+
1841
+ /**
1842
+ * Resolve channel decisions for an update over an existing install.
1843
+ *
1844
+ * For each selected external/community module:
1845
+ * - Read the recorded channel from the existing manifest.
1846
+ * - On `stable`: query tags; if a newer stable exists, classify the diff
1847
+ * and prompt. Patch/minor default Y; major defaults N. `--yes` accepts
1848
+ * defaults (patches/minors) but NOT majors — a major under --yes stays
1849
+ * frozen unless the user also passes `--pin CODE=NEW_TAG`.
1850
+ * - On `next`: no prompt (pull HEAD).
1851
+ * - On `pinned`: no prompt (stays pinned).
1852
+ * - No channel recorded and `version: null`: one-time migration prompt
1853
+ * ("Switch to stable / Keep on next").
1854
+ *
1855
+ * Decisions that freeze the current version are applied by adding a pin to
1856
+ * `channelOptions.pins` so downstream clone logic honors them.
1857
+ */
1858
+ async _resolveUpdateChannels({ bmadDir, selectedModules, channelOptions, yes }) {
1859
+ const { Manifest } = require('./core/manifest');
1860
+ const manifestObj = new Manifest();
1861
+ const manifest = await manifestObj.read(bmadDir);
1862
+ const existingByName = new Map();
1863
+ for (const m of manifest?.modulesDetailed || []) {
1864
+ if (m?.name) existingByName.set(m.name, m);
1865
+ }
1866
+ if (existingByName.size === 0) return;
1867
+
1868
+ const externalManager = new ExternalModuleManager();
1869
+ const externals = await externalManager.listAvailable();
1870
+ const externalByCode = new Map(externals.map((m) => [m.code, m]));
1871
+
1872
+ const { fetchStableTags, classifyUpgrade, releaseNotesUrl } = require('./modules/channel-resolver');
1873
+ const { parseGitHubRepo } = require('./modules/channel-resolver');
1874
+
1875
+ // Interactive-only: offer a one-time gate to review / switch channels for
1876
+ // selected modules that are already installed. Default N so normal Modify
1877
+ // flows (add/remove modules) aren't interrupted.
1878
+ let reviewChannels = false;
1879
+ if (!yes) {
1880
+ const existingWithChannel = selectedModules.filter((code) => {
1881
+ const prev = existingByName.get(code);
1882
+ if (!prev) return false;
1883
+ const info = externalByCode.get(code);
1884
+ return info && !info.builtIn;
1885
+ });
1886
+ if (existingWithChannel.length > 0) {
1887
+ reviewChannels = await prompts.confirm({
1888
+ message: 'Review channel assignments (stable / next / pin) for your existing modules?',
1889
+ default: false,
1890
+ });
1891
+ }
1892
+ }
1893
+
1894
+ for (const code of selectedModules) {
1895
+ const prev = existingByName.get(code);
1896
+ if (!prev) continue;
1897
+
1898
+ const info = externalByCode.get(code);
1899
+ if (!info) continue;
1900
+ // Bundled modules (core/bmm) ship with the installer binary itself —
1901
+ // their version is stapled to the CLI version, not a git tag. Skip
1902
+ // tag-API lookups for them; the "upgrade" mechanism is `npx bmad@X install`.
1903
+ if (info.builtIn) continue;
1904
+
1905
+ const repoUrl = info.url;
1906
+ const parsed = repoUrl ? parseGitHubRepo(repoUrl) : null;
1907
+
1908
+ // Legacy migration: manifest carries no channel and a null/empty
1909
+ // version. Offer the one-time pick between stable and next.
1910
+ const recordedChannel = prev.channel || null;
1911
+ const needsMigration = !recordedChannel && (prev.version == null || prev.version === '');
1912
+ if (needsMigration) {
1913
+ if (yes) {
1914
+ // Conservative headless default: stable.
1915
+ continue;
1916
+ }
1917
+ const chosen = await prompts.select({
1918
+ message: `${code}: your existing install tracks the main branch. Switch to stable releases (recommended for production), or keep on main?`,
1919
+ choices: [
1920
+ { name: 'Switch to stable', value: 'stable' },
1921
+ { name: 'Keep on main (next)', value: 'next' },
1922
+ ],
1923
+ default: 'stable',
1924
+ });
1925
+ if (chosen === 'next') channelOptions.nextSet.add(code);
1926
+ continue;
1927
+ }
1928
+
1929
+ // Optional channel-switch offer. Fires only when the user opted in via
1930
+ // the gate above. 'keep' falls through to the existing per-channel
1931
+ // logic (which runs upgrade classification for stable). Any switch
1932
+ // records the new intent into channelOptions and skips upgrade prompts.
1933
+ if (reviewChannels && recordedChannel) {
1934
+ const switchChoices = [
1935
+ {
1936
+ name: `Keep on '${recordedChannel}'${prev.version ? ` @ ${prev.version}` : ''}`,
1937
+ value: 'keep',
1938
+ },
1939
+ ];
1940
+ if (recordedChannel !== 'stable') {
1941
+ switchChoices.push({ name: 'Switch to stable (released version)', value: 'stable' });
1942
+ }
1943
+ if (recordedChannel !== 'next') {
1944
+ switchChoices.push({ name: 'Switch to next (main HEAD)', value: 'next' });
1945
+ }
1946
+ switchChoices.push({ name: 'Pin to a specific version tag', value: 'pin' });
1947
+
1948
+ const choice = await prompts.select({
1949
+ message: `${code} channel:`,
1950
+ choices: switchChoices,
1951
+ default: 'keep',
1952
+ });
1953
+
1954
+ if (choice === 'next') {
1955
+ channelOptions.nextSet.add(code);
1956
+ continue;
1957
+ }
1958
+ if (choice === 'pin') {
1959
+ const pinValue = await prompts.text({
1960
+ message: `Enter a version tag for '${code}' (e.g. v1.6.0):`,
1961
+ validate: (value) => {
1962
+ if (!value || !/^[\w.\-+/]+$/.test(String(value).trim())) {
1963
+ return 'Must be a non-empty tag name (letters, digits, dots, hyphens).';
1964
+ }
1965
+ },
1966
+ });
1967
+ channelOptions.pins.set(code, String(pinValue).trim());
1968
+ continue;
1969
+ }
1970
+ if (choice === 'stable') {
1971
+ // Switch to stable: install at the top stable tag without an
1972
+ // upgrade-classification prompt (the user explicitly opted in).
1973
+ // Also warm the tag cache here so the actual clone step doesn't
1974
+ // need a second GitHub API call (can hit rate limits).
1975
+ if (parsed) {
1976
+ try {
1977
+ await fetchStableTags(parsed.owner, parsed.repo);
1978
+ } catch {
1979
+ // best effort; clone step will surface any failure
1980
+ }
1981
+ }
1982
+ continue;
1983
+ }
1984
+ // 'keep' → fall through with recordedChannel below.
1985
+ }
1986
+
1987
+ if (recordedChannel === 'pinned' || recordedChannel === 'next') {
1988
+ // Respect any explicit channel intent the user already expressed via
1989
+ // CLI flags (--channel / --all-* / --next=CODE / --pin CODE=TAG) or
1990
+ // via the interactive review gate above. Only auto-re-assert the
1991
+ // recorded channel when the user hasn't opted into anything else —
1992
+ // otherwise --all-stable (or a review "switch to stable") would be
1993
+ // silently clobbered by the prior channel.
1994
+ const alreadyDecided = channelOptions.global || channelOptions.nextSet.has(code) || channelOptions.pins.has(code);
1995
+ if (!alreadyDecided) {
1996
+ if (recordedChannel === 'pinned' && prev.version) {
1997
+ channelOptions.pins.set(code, prev.version);
1998
+ } else if (recordedChannel === 'next') {
1999
+ channelOptions.nextSet.add(code);
2000
+ }
2001
+ }
2002
+ continue;
2003
+ }
2004
+
2005
+ // Stable channel: check for a newer released tag.
2006
+ if (!parsed) continue;
2007
+ // Respect explicit CLI intent (--pin / --next=CODE / --all-*) and any
2008
+ // choice the user already made in the earlier review gate. Without this
2009
+ // guard the upgrade classifier below would unconditionally call
2010
+ // `channelOptions.pins.set(code, prev.version)` on decline/major-refuse/
2011
+ // fetch-error, silently clobbering the user's override.
2012
+ const alreadyDecided = channelOptions.global || channelOptions.nextSet.has(code) || channelOptions.pins.has(code);
2013
+ if (alreadyDecided) continue;
2014
+ let tags;
2015
+ try {
2016
+ tags = await fetchStableTags(parsed.owner, parsed.repo);
2017
+ } catch (error) {
2018
+ await prompts.log.warn(`Could not check for updates on ${code} (${error.message}). Leaving at ${prev.version}.`);
2019
+ if (prev.version) channelOptions.pins.set(code, prev.version);
2020
+ continue;
2021
+ }
2022
+ if (!tags || tags.length === 0) continue;
2023
+ const topTag = tags[0].tag; // e.g. "v1.7.0"
2024
+ const currentTag = prev.version || '';
2025
+ const diffClass = classifyUpgrade(currentTag, topTag);
2026
+
2027
+ if (diffClass === 'none') continue; // already at or above top tag
2028
+
2029
+ const notes = releaseNotesUrl(repoUrl, topTag);
2030
+ let accept;
2031
+ if (diffClass === 'major') {
2032
+ if (yes) {
2033
+ // Major under --yes is refused by design.
2034
+ await prompts.log.warn(
2035
+ `${code} ${currentTag} → ${topTag} is a new major release; staying on ${currentTag}. ` +
2036
+ `To accept, rerun with --pin ${code}=${topTag}.`,
2037
+ );
2038
+ channelOptions.pins.set(code, currentTag);
2039
+ continue;
2040
+ }
2041
+ accept = await prompts.confirm({
2042
+ message:
2043
+ `${code} ${topTag} available — new major release (may change behavior).` +
2044
+ (notes ? ` Release notes: ${notes}.` : '') +
2045
+ ' Upgrade?',
2046
+ default: false,
2047
+ });
2048
+ } else if (diffClass === 'minor') {
2049
+ if (yes) {
2050
+ accept = true;
2051
+ } else {
2052
+ accept = await prompts.confirm({
2053
+ message: `${code} ${topTag} available (new features).` + (notes ? ` Release notes: ${notes}.` : '') + ' Upgrade?',
2054
+ default: true,
2055
+ });
2056
+ }
2057
+ } else {
2058
+ // patch
2059
+ if (yes) {
2060
+ accept = true;
2061
+ } else {
2062
+ accept = await prompts.confirm({
2063
+ message: `${code} ${topTag} available. Upgrade?`,
2064
+ default: true,
2065
+ });
2066
+ }
2067
+ }
2068
+
2069
+ if (!accept && currentTag) {
2070
+ // Freeze the current version by pinning it for this run.
2071
+ channelOptions.pins.set(code, currentTag);
2072
+ }
2073
+ }
2074
+ }
2075
+ }
2076
+
2077
+ module.exports = { UI };