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,2217 @@
1
+ const path = require('node:path');
2
+ const fs = require('../fs-native');
3
+ const yaml = require('yaml');
4
+ const prompts = require('../prompts');
5
+ const { getProjectRoot, getSourcePath, getModulePath } = require('../project-root');
6
+ const { CLIUtils } = require('../cli-utils');
7
+ const { ExternalModuleManager } = require('./external-manager');
8
+
9
+ class OfficialModules {
10
+ constructor(options = {}) {
11
+ this.externalModuleManager = new ExternalModuleManager();
12
+ // Config collection state (merged from ConfigCollector)
13
+ this.collectedConfig = {};
14
+ this._existingConfig = null;
15
+ // Tracked during interactive config collection so {directory_name}
16
+ // placeholder defaults can be resolved in buildQuestion().
17
+ this.currentProjectDir = null;
18
+ // Install-time channel flag state. Set by Config.build once, then used as
19
+ // the default for every findModuleSource/cloneExternalModule call so that
20
+ // pre-install config collection and the install step agree on which ref
21
+ // to clone.
22
+ this.channelOptions = options.channelOptions || null;
23
+ }
24
+
25
+ /**
26
+ * Module configurations collected during install.
27
+ */
28
+ get moduleConfigs() {
29
+ return this.collectedConfig;
30
+ }
31
+
32
+ /**
33
+ * Existing module configurations read from a previous installation.
34
+ */
35
+ get existingConfig() {
36
+ return this._existingConfig;
37
+ }
38
+
39
+ /**
40
+ * Build a configured OfficialModules instance from install config.
41
+ * @param {Object} config - Clean install config (from Config.build)
42
+ * @param {Object} paths - InstallPaths instance
43
+ * @returns {OfficialModules}
44
+ */
45
+ static async build(config, paths) {
46
+ const instance = new OfficialModules({ channelOptions: config.channelOptions });
47
+
48
+ // Pre-collected by UI or quickUpdate — store and load existing for path-change detection
49
+ if (config.moduleConfigs) {
50
+ instance.collectedConfig = config.moduleConfigs;
51
+ await instance.loadExistingConfig(paths.projectRoot);
52
+ return instance;
53
+ }
54
+
55
+ // Headless collection (--yes flag from CLI without UI, tests)
56
+ if (config.hasCoreConfig()) {
57
+ instance.collectedConfig.core = config.coreConfig;
58
+ instance.allAnswers = {};
59
+ for (const [key, value] of Object.entries(config.coreConfig)) {
60
+ instance.allAnswers[`core_${key}`] = value;
61
+ }
62
+ }
63
+
64
+ const toCollect = config.hasCoreConfig() ? config.modules.filter((m) => m !== 'core') : [...config.modules];
65
+
66
+ await instance.collectAllConfigurations(toCollect, paths.projectRoot, {
67
+ skipPrompts: config.skipPrompts,
68
+ });
69
+
70
+ return instance;
71
+ }
72
+
73
+ /**
74
+ * Copy a file to the target location
75
+ * @param {string} sourcePath - Source file path
76
+ * @param {string} targetPath - Target file path
77
+ * @param {boolean} overwrite - Whether to overwrite existing files (default: true)
78
+ */
79
+ async copyFile(sourcePath, targetPath, overwrite = true) {
80
+ await fs.copy(sourcePath, targetPath, { overwrite });
81
+ }
82
+
83
+ /**
84
+ * Copy a directory recursively
85
+ * @param {string} sourceDir - Source directory path
86
+ * @param {string} targetDir - Target directory path
87
+ * @param {boolean} overwrite - Whether to overwrite existing files (default: true)
88
+ */
89
+ async copyDirectory(sourceDir, targetDir, overwrite = true) {
90
+ await fs.ensureDir(targetDir);
91
+ const entries = await fs.readdir(sourceDir, { withFileTypes: true });
92
+
93
+ for (const entry of entries) {
94
+ const sourcePath = path.join(sourceDir, entry.name);
95
+ const targetPath = path.join(targetDir, entry.name);
96
+
97
+ if (entry.isDirectory()) {
98
+ await this.copyDirectory(sourcePath, targetPath, overwrite);
99
+ } else {
100
+ await this.copyFile(sourcePath, targetPath, overwrite);
101
+ }
102
+ }
103
+ }
104
+
105
+ /**
106
+ * List all available built-in modules (core and bmm).
107
+ * All other modules come from external-official-modules.yaml
108
+ * @returns {Object} Object with modules array
109
+ */
110
+ async listAvailable() {
111
+ const modules = [];
112
+
113
+ // Add built-in core module (directly under src/core-skills)
114
+ const corePath = getSourcePath('core-skills');
115
+ if (await fs.pathExists(corePath)) {
116
+ const coreInfo = await this.getModuleInfo(corePath, 'core', 'src/core-skills');
117
+ if (coreInfo) {
118
+ modules.push(coreInfo);
119
+ }
120
+ }
121
+
122
+ // Add built-in bmm module (directly under src/bmm-skills)
123
+ const bmmPath = getSourcePath('bmm-skills');
124
+ if (await fs.pathExists(bmmPath)) {
125
+ const bmmInfo = await this.getModuleInfo(bmmPath, 'bmm', 'src/bmm-skills');
126
+ if (bmmInfo) {
127
+ modules.push(bmmInfo);
128
+ }
129
+ }
130
+
131
+ return { modules };
132
+ }
133
+
134
+ /**
135
+ * Get module information from a module path
136
+ * @param {string} modulePath - Path to the module directory
137
+ * @param {string} defaultName - Default name for the module
138
+ * @param {string} sourceDescription - Description of where the module was found
139
+ * @returns {Object|null} Module info or null if not a valid module
140
+ */
141
+ async getModuleInfo(modulePath, defaultName, sourceDescription) {
142
+ const moduleConfigPath = path.join(modulePath, 'module.yaml');
143
+
144
+ if (!(await fs.pathExists(moduleConfigPath))) {
145
+ // Check resolution cache for strategy 5 modules (no module.yaml on disk)
146
+ const { CustomModuleManager } = require('./custom-module-manager');
147
+ const customMgr = new CustomModuleManager();
148
+ const resolved = customMgr.getResolution(defaultName);
149
+ if (resolved && resolved.synthesizedModuleYaml) {
150
+ return {
151
+ id: resolved.code,
152
+ path: modulePath,
153
+ name: resolved.name,
154
+ description: resolved.description,
155
+ version: resolved.version || '1.0.0',
156
+ source: sourceDescription,
157
+ dependencies: [],
158
+ defaultSelected: false,
159
+ };
160
+ }
161
+ return null;
162
+ }
163
+
164
+ const moduleInfo = {
165
+ id: defaultName,
166
+ path: modulePath,
167
+ name: defaultName
168
+ .split('-')
169
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
170
+ .join(' '),
171
+ description: 'BMAD Module',
172
+ version: '5.0.0',
173
+ source: sourceDescription,
174
+ };
175
+
176
+ // Read module config for metadata
177
+ try {
178
+ const configContent = await fs.readFile(moduleConfigPath, 'utf8');
179
+ const config = yaml.parse(configContent);
180
+
181
+ // Use the code property as the id if available
182
+ if (config.code) {
183
+ moduleInfo.id = config.code;
184
+ }
185
+
186
+ moduleInfo.name = config.name || moduleInfo.name;
187
+ moduleInfo.description = config.description || moduleInfo.description;
188
+ moduleInfo.version = config.version || moduleInfo.version;
189
+ moduleInfo.dependencies = config.dependencies || [];
190
+ moduleInfo.defaultSelected = config.default_selected === undefined ? false : config.default_selected;
191
+ } catch (error) {
192
+ await prompts.log.warn(`Failed to read config for ${defaultName}: ${error.message}`);
193
+ }
194
+
195
+ return moduleInfo;
196
+ }
197
+
198
+ /**
199
+ * Find the source path for a module by searching all possible locations
200
+ * @param {string} moduleCode - Code of the module to find (from module.yaml)
201
+ * @returns {string|null} Path to the module source or null if not found
202
+ */
203
+ async findModuleSource(moduleCode, options = {}) {
204
+ // Inherit channelOptions from the install-scoped instance when the caller
205
+ // didn't pass one explicitly. Keeps pre-install config collection and the
206
+ // actual install step looking at the same git ref.
207
+ if (options.channelOptions === undefined && this.channelOptions) {
208
+ options = { ...options, channelOptions: this.channelOptions };
209
+ }
210
+ const projectRoot = getProjectRoot();
211
+
212
+ // Check for core module (directly under src/core-skills)
213
+ if (moduleCode === 'core') {
214
+ const corePath = getSourcePath('core-skills');
215
+ if (await fs.pathExists(corePath)) {
216
+ return corePath;
217
+ }
218
+ }
219
+
220
+ // Check for built-in bmm module (directly under src/bmm-skills)
221
+ if (moduleCode === 'bmm') {
222
+ const bmmPath = getSourcePath('bmm-skills');
223
+ if (await fs.pathExists(bmmPath)) {
224
+ return bmmPath;
225
+ }
226
+ }
227
+
228
+ // Check bundled local modules (directly under src/modules/<code>), e.g. wizz.
229
+ // These ship inside this repo and are auto-discovered by list-options.js.
230
+ const bundledPath = getSourcePath('modules', moduleCode);
231
+ if (await fs.pathExists(path.join(bundledPath, 'module.yaml'))) {
232
+ return bundledPath;
233
+ }
234
+
235
+ // Check external official modules (pass channelOptions so channel plan applies)
236
+ const externalSource = await this.externalModuleManager.findExternalModuleSource(moduleCode, options);
237
+ if (externalSource) {
238
+ return externalSource;
239
+ }
240
+
241
+ // Check custom modules (from user-provided URLs, already cloned to cache)
242
+ const { CustomModuleManager } = require('./custom-module-manager');
243
+ const customMgr = new CustomModuleManager();
244
+ const customSource = await customMgr.findModuleSourceByCode(moduleCode, options);
245
+ if (customSource) {
246
+ return customSource;
247
+ }
248
+
249
+ return null;
250
+ }
251
+
252
+ /**
253
+ * Install a module
254
+ * @param {string} moduleName - Code of the module to install (from module.yaml)
255
+ * @param {string} bmadDir - Target bmad directory
256
+ * @param {Function} fileTrackingCallback - Optional callback to track installed files
257
+ * @param {Object} options - Additional installation options
258
+ * @param {Array<string>} options.installedIDEs - Array of IDE codes that were installed
259
+ * @param {Object} options.moduleConfig - Module configuration from config collector
260
+ * @param {Object} options.logger - Logger instance for output
261
+ */
262
+ async install(moduleName, bmadDir, fileTrackingCallback = null, options = {}) {
263
+ // Check if this module has a plugin resolution (custom marketplace install)
264
+ const { CustomModuleManager } = require('./custom-module-manager');
265
+ const customMgr = new CustomModuleManager();
266
+ const resolved = customMgr.getResolution(moduleName);
267
+ if (resolved) {
268
+ return this.installFromResolution(resolved, bmadDir, fileTrackingCallback, options);
269
+ }
270
+
271
+ const sourcePath = await this.findModuleSource(moduleName, {
272
+ silent: options.silent,
273
+ channelOptions: options.channelOptions,
274
+ });
275
+ const targetPath = path.join(bmadDir, moduleName);
276
+
277
+ if (!sourcePath) {
278
+ throw new Error(
279
+ `Source for module '${moduleName}' is not available. It will be retained but cannot be updated without its source files.`,
280
+ );
281
+ }
282
+
283
+ if (await fs.pathExists(targetPath)) {
284
+ await fs.remove(targetPath);
285
+ }
286
+
287
+ await this.copyModuleWithFiltering(sourcePath, targetPath, fileTrackingCallback, options.moduleConfig);
288
+
289
+ if (!options.skipModuleInstaller) {
290
+ await this.createModuleDirectories(moduleName, bmadDir, options);
291
+ }
292
+
293
+ const { Manifest } = require('../core/manifest');
294
+ const manifestObj = new Manifest();
295
+ const versionInfo = await manifestObj.getModuleVersionInfo(moduleName, bmadDir, sourcePath);
296
+
297
+ // Pick up channel resolution recorded by the external manager (the only
298
+ // manager that does pre-clone resolution now that community is retired).
299
+ const resolution = this.externalModuleManager.getResolution(moduleName);
300
+
301
+ await manifestObj.addModule(bmadDir, moduleName, {
302
+ version: resolution?.version || versionInfo.version,
303
+ source: versionInfo.source,
304
+ npmPackage: versionInfo.npmPackage,
305
+ repoUrl: versionInfo.repoUrl,
306
+ channel: resolution?.channel,
307
+ sha: resolution?.sha,
308
+ });
309
+
310
+ return { success: true, module: moduleName, path: targetPath, versionInfo };
311
+ }
312
+
313
+ /**
314
+ * Install a module from a PluginResolver resolution result.
315
+ * Copies specific skill directories and places module-help.csv at the target root.
316
+ * @param {Object} resolved - ResolvedModule from PluginResolver
317
+ * @param {string} bmadDir - Target bmad directory
318
+ * @param {Function} fileTrackingCallback - Optional callback to track installed files
319
+ * @param {Object} options - Installation options
320
+ */
321
+ async installFromResolution(resolved, bmadDir, fileTrackingCallback = null, options = {}) {
322
+ const targetPath = path.join(bmadDir, resolved.code);
323
+
324
+ if (await fs.pathExists(targetPath)) {
325
+ await fs.remove(targetPath);
326
+ }
327
+
328
+ await fs.ensureDir(targetPath);
329
+
330
+ // Copy each skill directory, flattened by leaf name
331
+ for (const skillPath of resolved.skillPaths) {
332
+ const skillDirName = path.basename(skillPath);
333
+ const skillTarget = path.join(targetPath, skillDirName);
334
+ await this.copyModuleWithFiltering(skillPath, skillTarget, fileTrackingCallback, options.moduleConfig);
335
+ }
336
+
337
+ // Place module-help.csv at the module root
338
+ if (resolved.moduleHelpCsvPath) {
339
+ // Strategies 1-4: copy the existing file
340
+ const helpTarget = path.join(targetPath, 'module-help.csv');
341
+ await fs.copy(resolved.moduleHelpCsvPath, helpTarget, { overwrite: true });
342
+ if (fileTrackingCallback) fileTrackingCallback(helpTarget);
343
+ } else if (resolved.synthesizedHelpCsv) {
344
+ // Strategy 5: write synthesized content
345
+ const helpTarget = path.join(targetPath, 'module-help.csv');
346
+ await fs.writeFile(helpTarget, resolved.synthesizedHelpCsv, 'utf8');
347
+ if (fileTrackingCallback) fileTrackingCallback(helpTarget);
348
+ }
349
+
350
+ // Create directories declared in module.yaml (strategies 1-4 may have these)
351
+ if (!options.skipModuleInstaller) {
352
+ await this.createModuleDirectories(resolved.code, bmadDir, options);
353
+ }
354
+
355
+ // Update manifest. For custom-source installs we derive channel from the
356
+ // cloneRef (present → pinned, absent → next; local paths have no channel).
357
+ const { Manifest } = require('../core/manifest');
358
+ const manifestObj = new Manifest();
359
+
360
+ const hasGitClone = !!resolved.repoUrl;
361
+ const manifestEntry = {
362
+ version: resolved.cloneRef || (hasGitClone ? 'main' : resolved.version || null),
363
+ source: 'custom',
364
+ npmPackage: null,
365
+ repoUrl: resolved.repoUrl || null,
366
+ };
367
+ if (hasGitClone) {
368
+ manifestEntry.channel = resolved.cloneRef ? 'pinned' : 'next';
369
+ if (resolved.cloneSha) manifestEntry.sha = resolved.cloneSha;
370
+ if (resolved.rawInput) manifestEntry.rawSource = resolved.rawInput;
371
+ }
372
+ if (resolved.localPath) manifestEntry.localPath = resolved.localPath;
373
+ await manifestObj.addModule(bmadDir, resolved.code, manifestEntry);
374
+
375
+ return {
376
+ success: true,
377
+ module: resolved.code,
378
+ path: targetPath,
379
+ // Mirror the manifestEntry.version precedence above so downstream summary
380
+ // lines show the same string we just wrote to disk (custom git-backed
381
+ // installs show the cloned ref or 'main').
382
+ versionInfo: {
383
+ version: resolved.cloneRef || (hasGitClone ? 'main' : resolved.version || ''),
384
+ },
385
+ };
386
+ }
387
+
388
+ /**
389
+ * Update an existing module
390
+ * @param {string} moduleName - Name of the module to update
391
+ * @param {string} bmadDir - Target bmad directory
392
+ */
393
+ async update(moduleName, bmadDir) {
394
+ const sourcePath = await this.findModuleSource(moduleName);
395
+ const targetPath = path.join(bmadDir, moduleName);
396
+
397
+ if (!sourcePath) {
398
+ throw new Error(`Module '${moduleName}' not found in any source location`);
399
+ }
400
+
401
+ if (!(await fs.pathExists(targetPath))) {
402
+ throw new Error(`Module '${moduleName}' is not installed`);
403
+ }
404
+
405
+ await this.syncModule(sourcePath, targetPath);
406
+
407
+ return {
408
+ success: true,
409
+ module: moduleName,
410
+ path: targetPath,
411
+ };
412
+ }
413
+
414
+ /**
415
+ * Remove a module
416
+ * @param {string} moduleName - Name of the module to remove
417
+ * @param {string} bmadDir - Target bmad directory
418
+ */
419
+ async remove(moduleName, bmadDir) {
420
+ const targetPath = path.join(bmadDir, moduleName);
421
+
422
+ if (!(await fs.pathExists(targetPath))) {
423
+ throw new Error(`Module '${moduleName}' is not installed`);
424
+ }
425
+
426
+ await fs.remove(targetPath);
427
+
428
+ return {
429
+ success: true,
430
+ module: moduleName,
431
+ };
432
+ }
433
+
434
+ /**
435
+ * Check if a module is installed
436
+ * @param {string} moduleName - Name of the module
437
+ * @param {string} bmadDir - Target bmad directory
438
+ * @returns {boolean} True if module is installed
439
+ */
440
+ async isInstalled(moduleName, bmadDir) {
441
+ const targetPath = path.join(bmadDir, moduleName);
442
+ return await fs.pathExists(targetPath);
443
+ }
444
+
445
+ /**
446
+ * Get installed module info
447
+ * @param {string} moduleName - Name of the module
448
+ * @param {string} bmadDir - Target bmad directory
449
+ * @returns {Object|null} Module info or null if not installed
450
+ */
451
+ async getInstalledInfo(moduleName, bmadDir) {
452
+ const targetPath = path.join(bmadDir, moduleName);
453
+
454
+ if (!(await fs.pathExists(targetPath))) {
455
+ return null;
456
+ }
457
+
458
+ const configPath = path.join(targetPath, 'config.yaml');
459
+ const moduleInfo = {
460
+ id: moduleName,
461
+ path: targetPath,
462
+ installed: true,
463
+ };
464
+
465
+ if (await fs.pathExists(configPath)) {
466
+ try {
467
+ const configContent = await fs.readFile(configPath, 'utf8');
468
+ const config = yaml.parse(configContent);
469
+ Object.assign(moduleInfo, config);
470
+ } catch (error) {
471
+ await prompts.log.warn(`Failed to read installed module config: ${error.message}`);
472
+ }
473
+ }
474
+
475
+ return moduleInfo;
476
+ }
477
+
478
+ /**
479
+ * Copy module with filtering for localskip agents and conditional content
480
+ * @param {string} sourcePath - Source module path
481
+ * @param {string} targetPath - Target module path
482
+ * @param {Function} fileTrackingCallback - Optional callback to track installed files
483
+ * @param {Object} moduleConfig - Module configuration with conditional flags
484
+ */
485
+ async copyModuleWithFiltering(sourcePath, targetPath, fileTrackingCallback = null, moduleConfig = {}) {
486
+ // Get all files in source
487
+ const sourceFiles = await this.getFileList(sourcePath);
488
+
489
+ for (const file of sourceFiles) {
490
+ // Skip sub-modules directory - these are IDE-specific and handled separately
491
+ if (file.startsWith('sub-modules/')) {
492
+ continue;
493
+ }
494
+
495
+ // Skip sidecar directories - these contain agent-specific assets not needed at install time
496
+ const isInSidecarDirectory = path
497
+ .dirname(file)
498
+ .split('/')
499
+ .some((dir) => dir.toLowerCase().endsWith('-sidecar'));
500
+
501
+ if (isInSidecarDirectory) {
502
+ continue;
503
+ }
504
+
505
+ // Skip module.yaml at root - it's only needed at install time
506
+ if (file === 'module.yaml') {
507
+ continue;
508
+ }
509
+
510
+ // Skip module root config.yaml only - generated by config collector with actual values
511
+ // Workflow-level config.yaml (e.g. workflows/orchestrate-story/config.yaml) must be copied
512
+ // for custom modules that use workflow-specific configuration
513
+ if (file === 'config.yaml') {
514
+ continue;
515
+ }
516
+
517
+ const sourceFile = path.join(sourcePath, file);
518
+ const targetFile = path.join(targetPath, file);
519
+
520
+ // Check if this is an agent file
521
+ if (file.startsWith('agents/') && file.endsWith('.md')) {
522
+ // Read the file to check for localskip
523
+ const content = await fs.readFile(sourceFile, 'utf8');
524
+
525
+ // Check for localskip="true" in the agent tag
526
+ const agentMatch = content.match(/<agent[^>]*\slocalskip="true"[^>]*>/);
527
+ if (agentMatch) {
528
+ await prompts.log.message(` Skipping web-only agent: ${path.basename(file)}`);
529
+ continue; // Skip this agent
530
+ }
531
+ }
532
+
533
+ // Copy the file with placeholder replacement
534
+ await this.copyFile(sourceFile, targetFile);
535
+
536
+ // Track the file if callback provided
537
+ if (fileTrackingCallback) {
538
+ fileTrackingCallback(targetFile);
539
+ }
540
+ }
541
+ }
542
+
543
+ /**
544
+ * Create directories declared in module.yaml's `directories` key
545
+ * This replaces the security-risky module installer pattern with declarative config
546
+ * During updates, if a directory path changed, moves the old directory to the new path
547
+ * @param {string} moduleName - Name of the module
548
+ * @param {string} bmadDir - Target bmad directory
549
+ * @param {Object} options - Installation options
550
+ * @param {Object} options.moduleConfig - Module configuration from config collector
551
+ * @param {Object} options.existingModuleConfig - Previous module config (for detecting path changes during updates)
552
+ * @param {Object} options.coreConfig - Core configuration
553
+ * @returns {Promise<{createdDirs: string[], movedDirs: string[], createdWdsFolders: string[]}>} Created directories info
554
+ */
555
+ async createModuleDirectories(moduleName, bmadDir, options = {}) {
556
+ const moduleConfig = options.moduleConfig || {};
557
+ const existingModuleConfig = options.existingModuleConfig || {};
558
+ const projectRoot = path.dirname(bmadDir);
559
+ const emptyResult = { createdDirs: [], movedDirs: [], createdWdsFolders: [] };
560
+
561
+ // Special handling for core module - it's in src/core-skills not src/modules
562
+ let sourcePath;
563
+ if (moduleName === 'core') {
564
+ sourcePath = getSourcePath('core-skills');
565
+ } else {
566
+ sourcePath = await this.findModuleSource(moduleName, { silent: true });
567
+ if (!sourcePath) {
568
+ return emptyResult; // No source found, skip
569
+ }
570
+ }
571
+
572
+ // Read module.yaml to find the `directories` key
573
+ const moduleYamlPath = path.join(sourcePath, 'module.yaml');
574
+ if (!(await fs.pathExists(moduleYamlPath))) {
575
+ return emptyResult; // No module.yaml, skip
576
+ }
577
+
578
+ let moduleYaml;
579
+ try {
580
+ const yamlContent = await fs.readFile(moduleYamlPath, 'utf8');
581
+ moduleYaml = yaml.parse(yamlContent);
582
+ } catch (error) {
583
+ await prompts.log.warn(`Invalid module.yaml for ${moduleName}: ${error.message}`);
584
+ return emptyResult;
585
+ }
586
+
587
+ if (!moduleYaml || !moduleYaml.directories) {
588
+ return emptyResult; // No directories declared, skip
589
+ }
590
+
591
+ const directories = moduleYaml.directories;
592
+ const wdsFolders = moduleYaml.wds_folders || [];
593
+ const createdDirs = [];
594
+ const movedDirs = [];
595
+ const createdWdsFolders = [];
596
+
597
+ for (const dirRef of directories) {
598
+ // Parse variable reference like "{design_artifacts}"
599
+ const varMatch = dirRef.match(/^\{([^}]+)\}$/);
600
+ if (!varMatch) {
601
+ // Not a variable reference, skip
602
+ continue;
603
+ }
604
+
605
+ const configKey = varMatch[1];
606
+ const dirValue = moduleConfig[configKey];
607
+ if (!dirValue || typeof dirValue !== 'string') {
608
+ continue; // No value or not a string, skip
609
+ }
610
+
611
+ // Strip {project-root}/ prefix if present
612
+ let dirPath = dirValue.replace(/^\{project-root\}\/?/, '');
613
+
614
+ // Handle remaining {project-root} anywhere in the path
615
+ dirPath = dirPath.replaceAll('{project-root}', '');
616
+
617
+ // Resolve to absolute path
618
+ const fullPath = path.join(projectRoot, dirPath);
619
+
620
+ // Validate path is within project root (prevent directory traversal)
621
+ const normalizedPath = path.normalize(fullPath);
622
+ const normalizedRoot = path.normalize(projectRoot);
623
+ if (!normalizedPath.startsWith(normalizedRoot + path.sep) && normalizedPath !== normalizedRoot) {
624
+ const color = await prompts.getColor();
625
+ await prompts.log.warn(color.yellow(`${configKey} path escapes project root, skipping: ${dirPath}`));
626
+ continue;
627
+ }
628
+
629
+ // Check if directory path changed from previous config (update/modify scenario)
630
+ const oldDirValue = existingModuleConfig[configKey];
631
+ let oldFullPath = null;
632
+ let oldDirPath = null;
633
+ if (oldDirValue && typeof oldDirValue === 'string') {
634
+ // F3: Normalize both values before comparing to avoid false negatives
635
+ // from trailing slashes, separator differences, or prefix format variations
636
+ let normalizedOld = oldDirValue.replace(/^\{project-root\}\/?/, '');
637
+ normalizedOld = path.normalize(normalizedOld.replaceAll('{project-root}', ''));
638
+ const normalizedNew = path.normalize(dirPath);
639
+
640
+ if (normalizedOld !== normalizedNew) {
641
+ oldDirPath = normalizedOld;
642
+ oldFullPath = path.join(projectRoot, oldDirPath);
643
+ const normalizedOldAbsolute = path.normalize(oldFullPath);
644
+ if (!normalizedOldAbsolute.startsWith(normalizedRoot + path.sep) && normalizedOldAbsolute !== normalizedRoot) {
645
+ oldFullPath = null; // Old path escapes project root, ignore it
646
+ }
647
+
648
+ // F13: Prevent parent/child move (e.g. docs/planning → docs/planning/v2)
649
+ if (oldFullPath) {
650
+ const normalizedNewAbsolute = path.normalize(fullPath);
651
+ if (
652
+ normalizedOldAbsolute.startsWith(normalizedNewAbsolute + path.sep) ||
653
+ normalizedNewAbsolute.startsWith(normalizedOldAbsolute + path.sep)
654
+ ) {
655
+ const color = await prompts.getColor();
656
+ await prompts.log.warn(
657
+ color.yellow(
658
+ `${configKey}: cannot move between parent/child paths (${oldDirPath} / ${dirPath}), creating new directory instead`,
659
+ ),
660
+ );
661
+ oldFullPath = null;
662
+ }
663
+ }
664
+ }
665
+ }
666
+
667
+ const dirName = configKey.replaceAll('_', ' ');
668
+
669
+ if (oldFullPath && (await fs.pathExists(oldFullPath)) && !(await fs.pathExists(fullPath))) {
670
+ // Path changed and old dir exists → move old to new location
671
+ // F1: Use fs.move() instead of fs.rename() for cross-device/volume support
672
+ // F2: Wrap in try/catch — fallback to creating new dir on failure
673
+ try {
674
+ await fs.ensureDir(path.dirname(fullPath));
675
+ await fs.move(oldFullPath, fullPath);
676
+ movedDirs.push(`${dirName}: ${oldDirPath} → ${dirPath}`);
677
+ } catch (moveError) {
678
+ const color = await prompts.getColor();
679
+ await prompts.log.warn(
680
+ color.yellow(
681
+ `Failed to move ${oldDirPath} → ${dirPath}: ${moveError.message}\n Creating new directory instead. Please move contents from the old directory manually.`,
682
+ ),
683
+ );
684
+ await fs.ensureDir(fullPath);
685
+ createdDirs.push(`${dirName}: ${dirPath}`);
686
+ }
687
+ } else if (oldFullPath && (await fs.pathExists(oldFullPath)) && (await fs.pathExists(fullPath))) {
688
+ // F5: Both old and new directories exist — warn user about potential orphaned documents
689
+ const color = await prompts.getColor();
690
+ await prompts.log.warn(
691
+ color.yellow(
692
+ `${dirName}: path changed but both directories exist:\n Old: ${oldDirPath}\n New: ${dirPath}\n Old directory may contain orphaned documents — please review and merge manually.`,
693
+ ),
694
+ );
695
+ } else if (!(await fs.pathExists(fullPath))) {
696
+ // New directory doesn't exist yet → create it
697
+ createdDirs.push(`${dirName}: ${dirPath}`);
698
+ await fs.ensureDir(fullPath);
699
+ }
700
+
701
+ // Create WDS subfolders if this is the design_artifacts directory
702
+ if (configKey === 'design_artifacts' && wdsFolders.length > 0) {
703
+ for (const subfolder of wdsFolders) {
704
+ const subPath = path.join(fullPath, subfolder);
705
+ if (!(await fs.pathExists(subPath))) {
706
+ await fs.ensureDir(subPath);
707
+ createdWdsFolders.push(subfolder);
708
+ }
709
+ }
710
+ }
711
+ }
712
+
713
+ return { createdDirs, movedDirs, createdWdsFolders };
714
+ }
715
+
716
+ /**
717
+ * Private: Sync module files (preserving user modifications)
718
+ * @param {string} sourcePath - Source module path
719
+ * @param {string} targetPath - Target module path
720
+ */
721
+ async syncModule(sourcePath, targetPath) {
722
+ // Get list of all source files
723
+ const sourceFiles = await this.getFileList(sourcePath);
724
+
725
+ for (const file of sourceFiles) {
726
+ const sourceFile = path.join(sourcePath, file);
727
+ const targetFile = path.join(targetPath, file);
728
+
729
+ // Check if target file exists and has been modified
730
+ if (await fs.pathExists(targetFile)) {
731
+ const sourceStats = await fs.stat(sourceFile);
732
+ const targetStats = await fs.stat(targetFile);
733
+
734
+ // Skip if target is newer (user modified)
735
+ if (targetStats.mtime > sourceStats.mtime) {
736
+ continue;
737
+ }
738
+ }
739
+
740
+ // Copy file with placeholder replacement
741
+ await this.copyFile(sourceFile, targetFile);
742
+ }
743
+ }
744
+
745
+ /**
746
+ * Private: Get list of all files in a directory
747
+ * @param {string} dir - Directory path
748
+ * @param {string} baseDir - Base directory for relative paths
749
+ * @returns {Array} List of relative file paths
750
+ */
751
+ async getFileList(dir, baseDir = dir) {
752
+ const files = [];
753
+ const entries = await fs.readdir(dir, { withFileTypes: true });
754
+
755
+ for (const entry of entries) {
756
+ const fullPath = path.join(dir, entry.name);
757
+
758
+ if (entry.isDirectory()) {
759
+ const subFiles = await this.getFileList(fullPath, baseDir);
760
+ files.push(...subFiles);
761
+ } else {
762
+ files.push(path.relative(baseDir, fullPath));
763
+ }
764
+ }
765
+
766
+ return files;
767
+ }
768
+
769
+ // ─── Config collection methods (merged from ConfigCollector) ───
770
+
771
+ /**
772
+ * Find the bmad installation directory in a project
773
+ * V6+ installations can use ANY folder name but ALWAYS have _config/manifest.yaml
774
+ * @param {string} projectDir - Project directory
775
+ * @returns {Promise<string>} Path to bmad directory
776
+ */
777
+ async findBmadDir(projectDir) {
778
+ // Check if project directory exists
779
+ if (!(await fs.pathExists(projectDir))) {
780
+ // Project doesn't exist yet, return default
781
+ return path.join(projectDir, 'bmad');
782
+ }
783
+
784
+ // V6+ strategy: Look for ANY directory with _config/manifest.yaml
785
+ // This is the definitive marker of a V6+ installation
786
+ try {
787
+ const entries = await fs.readdir(projectDir, { withFileTypes: true });
788
+ for (const entry of entries) {
789
+ if (entry.isDirectory()) {
790
+ const manifestPath = path.join(projectDir, entry.name, '_config', 'manifest.yaml');
791
+ if (await fs.pathExists(manifestPath)) {
792
+ // Found a V6+ installation
793
+ return path.join(projectDir, entry.name);
794
+ }
795
+ }
796
+ }
797
+ } catch {
798
+ // Ignore errors, fall through to default
799
+ }
800
+
801
+ // No V6+ installation found, return default
802
+ // This will be used for new installations
803
+ return path.join(projectDir, 'bmad');
804
+ }
805
+
806
+ /**
807
+ * Detect the existing BMAD folder name in a project
808
+ * @param {string} projectDir - Project directory
809
+ * @returns {Promise<string|null>} Folder name (just the name, not full path) or null if not found
810
+ */
811
+ async detectExistingBmadFolder(projectDir) {
812
+ // Check if project directory exists
813
+ if (!(await fs.pathExists(projectDir))) {
814
+ return null;
815
+ }
816
+
817
+ // Look for ANY directory with _config/manifest.yaml
818
+ try {
819
+ const entries = await fs.readdir(projectDir, { withFileTypes: true });
820
+ for (const entry of entries) {
821
+ if (entry.isDirectory()) {
822
+ const manifestPath = path.join(projectDir, entry.name, '_config', 'manifest.yaml');
823
+ if (await fs.pathExists(manifestPath)) {
824
+ // Found a V6+ installation, return just the folder name
825
+ return entry.name;
826
+ }
827
+ }
828
+ }
829
+ } catch {
830
+ // Ignore errors
831
+ }
832
+
833
+ return null;
834
+ }
835
+
836
+ /**
837
+ * Load existing config if it exists from module config files
838
+ * @param {string} projectDir - Target project directory
839
+ */
840
+ async loadExistingConfig(projectDir) {
841
+ this._existingConfig = {};
842
+
843
+ // Check if project directory exists first
844
+ if (!(await fs.pathExists(projectDir))) {
845
+ return false;
846
+ }
847
+
848
+ // Find the actual bmad directory (handles custom folder names)
849
+ const bmadDir = await this.findBmadDir(projectDir);
850
+
851
+ // Check if bmad directory exists
852
+ if (!(await fs.pathExists(bmadDir))) {
853
+ return false;
854
+ }
855
+
856
+ // Primary source: installer-written config.toml + config.user.toml (v6+).
857
+ // Both files together hold all install answers; config.user.toml carries
858
+ // user-scoped keys like user_name that would otherwise be re-prompted on
859
+ // every reinstall.
860
+ let foundAny = false;
861
+ for (const fileName of ['config.toml', 'config.user.toml']) {
862
+ const tomlPath = path.join(bmadDir, fileName);
863
+ if (!(await fs.pathExists(tomlPath))) continue;
864
+ try {
865
+ const content = await fs.readFile(tomlPath, 'utf8');
866
+ const parsed = parseCentralToml(content);
867
+ for (const [section, values] of Object.entries(parsed)) {
868
+ if (values && typeof values === 'object' && !Array.isArray(values)) {
869
+ if (!this._existingConfig[section]) this._existingConfig[section] = {};
870
+ Object.assign(this._existingConfig[section], values);
871
+ foundAny = true;
872
+ }
873
+ }
874
+ } catch {
875
+ // Ignore parse errors
876
+ }
877
+ }
878
+
879
+ if (foundAny) {
880
+ return true;
881
+ }
882
+
883
+ // Fallback: legacy per-module config.yaml files (pre-v6 installations).
884
+ const entries = await fs.readdir(bmadDir, { withFileTypes: true });
885
+ const nonModuleDirs = new Set(['_config', '_memory', 'memory', 'docs', 'scripts', 'custom']);
886
+ for (const entry of entries) {
887
+ if (entry.isDirectory()) {
888
+ if (nonModuleDirs.has(entry.name)) {
889
+ continue;
890
+ }
891
+
892
+ const moduleConfigPath = path.join(bmadDir, entry.name, 'config.yaml');
893
+
894
+ if (await fs.pathExists(moduleConfigPath)) {
895
+ try {
896
+ const content = await fs.readFile(moduleConfigPath, 'utf8');
897
+ const moduleConfig = yaml.parse(content);
898
+ // Only keep plain object parses. A corrupt config.yaml that parses
899
+ // to a scalar or array would crash later code that does `key in cfg`
900
+ // / `Object.keys(cfg)`; treat it the same as a parse error.
901
+ if (moduleConfig && typeof moduleConfig === 'object' && !Array.isArray(moduleConfig)) {
902
+ this._existingConfig[entry.name] = moduleConfig;
903
+ foundAny = true;
904
+ }
905
+ } catch {
906
+ // Ignore parse errors for individual modules
907
+ }
908
+ }
909
+ }
910
+ }
911
+
912
+ if (foundAny) {
913
+ await this._hoistCoreKeysFromLegacyModuleConfigs();
914
+ }
915
+
916
+ return foundAny;
917
+ }
918
+
919
+ /**
920
+ * Migrate prior answers when a key has moved from a non-core module to core
921
+ * (e.g. project_name moving from bmm to core in #2279). Without this, the
922
+ * partition logic in writeCentralConfig drops the value from the bmm bucket
923
+ * (because it's now a core key) without re-homing it under [core], so the
924
+ * user's prior answer silently disappears on the next install/quick-update.
925
+ */
926
+ async _hoistCoreKeysFromLegacyModuleConfigs() {
927
+ const coreSchemaPath = path.join(getSourcePath(), 'core-skills', 'module.yaml');
928
+ if (!(await fs.pathExists(coreSchemaPath))) return;
929
+
930
+ let coreSchema;
931
+ try {
932
+ coreSchema = yaml.parse(await fs.readFile(coreSchemaPath, 'utf8'));
933
+ } catch {
934
+ return;
935
+ }
936
+ if (!coreSchema || typeof coreSchema !== 'object') return;
937
+
938
+ const coreKeys = new Set(
939
+ Object.entries(coreSchema)
940
+ .filter(([, v]) => v && typeof v === 'object' && 'prompt' in v)
941
+ .map(([k]) => k),
942
+ );
943
+ if (coreKeys.size === 0) return;
944
+
945
+ // Belt-and-suspenders: loadExistingConfig already filters non-object parses,
946
+ // but anyone calling _hoistCoreKeysFromLegacyModuleConfigs in isolation (or
947
+ // future code paths populating _existingConfig directly) shouldn't be able
948
+ // to crash this with a scalar / array.
949
+ const existingCore = this._existingConfig.core;
950
+ this._existingConfig.core = existingCore && typeof existingCore === 'object' && !Array.isArray(existingCore) ? existingCore : {};
951
+
952
+ for (const [moduleName, cfg] of Object.entries(this._existingConfig)) {
953
+ if (moduleName === 'core' || !cfg || typeof cfg !== 'object' || Array.isArray(cfg)) continue;
954
+ for (const key of Object.keys(cfg)) {
955
+ if (!coreKeys.has(key)) continue;
956
+ if (!(key in this._existingConfig.core)) {
957
+ this._existingConfig.core[key] = cfg[key];
958
+ }
959
+ delete cfg[key];
960
+ }
961
+ }
962
+ }
963
+
964
+ /**
965
+ * Pre-scan module schemas to gather metadata for the configuration gateway prompt.
966
+ * Returns info about which modules have configurable options.
967
+ * @param {Array} modules - List of non-core module names
968
+ * @returns {Promise<Array>} Array of {moduleName, displayName, questionCount, hasFieldsWithoutDefaults}
969
+ */
970
+ async scanModuleSchemas(modules) {
971
+ const metadataFields = new Set(['code', 'name', 'header', 'subheader', 'default_selected']);
972
+ const results = [];
973
+
974
+ for (const moduleName of modules) {
975
+ // Resolve module.yaml path - standard location first, then OfficialModules search
976
+ let moduleConfigPath = null;
977
+ const standardPath = path.join(getModulePath(moduleName), 'module.yaml');
978
+ if (await fs.pathExists(standardPath)) {
979
+ moduleConfigPath = standardPath;
980
+ } else {
981
+ const moduleSourcePath = await this.findModuleSource(moduleName, { silent: true });
982
+ if (moduleSourcePath) {
983
+ moduleConfigPath = path.join(moduleSourcePath, 'module.yaml');
984
+ }
985
+ }
986
+
987
+ if (!moduleConfigPath || !(await fs.pathExists(moduleConfigPath))) {
988
+ continue;
989
+ }
990
+
991
+ try {
992
+ const content = await fs.readFile(moduleConfigPath, 'utf8');
993
+ const moduleConfig = yaml.parse(content);
994
+ if (!moduleConfig) continue;
995
+
996
+ const displayName = moduleConfig.header || `${moduleName.toUpperCase()} Module`;
997
+ const configKeys = Object.keys(moduleConfig).filter((key) => key !== 'prompt');
998
+ const questionKeys = configKeys.filter((key) => {
999
+ if (metadataFields.has(key)) return false;
1000
+ const item = moduleConfig[key];
1001
+ return item && typeof item === 'object' && item.prompt;
1002
+ });
1003
+
1004
+ const hasFieldsWithoutDefaults = questionKeys.some((key) => {
1005
+ const item = moduleConfig[key];
1006
+ return item.default === undefined || item.default === null || item.default === '';
1007
+ });
1008
+
1009
+ results.push({
1010
+ moduleName,
1011
+ displayName,
1012
+ questionCount: questionKeys.length,
1013
+ hasFieldsWithoutDefaults,
1014
+ });
1015
+ } catch (error) {
1016
+ await prompts.log.warn(`Could not read schema for module "${moduleName}": ${error.message}`);
1017
+ }
1018
+ }
1019
+
1020
+ return results;
1021
+ }
1022
+
1023
+ /**
1024
+ * Collect configuration for all modules
1025
+ * @param {Array} modules - List of modules to configure (including 'core')
1026
+ * @param {string} projectDir - Target project directory
1027
+ * @param {Object} options - Additional options
1028
+ * @param {boolean} options.skipPrompts - Skip prompts and use defaults (for --yes flag)
1029
+ */
1030
+ async collectAllConfigurations(modules, projectDir, options = {}) {
1031
+ this.skipPrompts = options.skipPrompts || false;
1032
+ this.modulesToCustomize = undefined;
1033
+ await this.loadExistingConfig(projectDir);
1034
+
1035
+ // Check if core was already collected (e.g., in early collection phase)
1036
+ const coreAlreadyCollected = this.collectedConfig.core && Object.keys(this.collectedConfig.core).length > 0;
1037
+
1038
+ // If core wasn't already collected, include it
1039
+ const allModules = coreAlreadyCollected ? modules.filter((m) => m !== 'core') : ['core', ...modules.filter((m) => m !== 'core')];
1040
+
1041
+ // Store all answers across modules for cross-referencing
1042
+ if (!this.allAnswers) {
1043
+ this.allAnswers = {};
1044
+ }
1045
+
1046
+ // Split processing: core first, then gateway, then remaining modules
1047
+ const coreModules = allModules.filter((m) => m === 'core');
1048
+ const nonCoreModules = allModules.filter((m) => m !== 'core');
1049
+
1050
+ // Collect core config first (always fully prompted)
1051
+ for (const moduleName of coreModules) {
1052
+ await this.collectModuleConfig(moduleName, projectDir);
1053
+ }
1054
+
1055
+ // Show batch configuration gateway for non-core modules
1056
+ // Scan all non-core module schemas for display names and config metadata
1057
+ let scannedModules = [];
1058
+ if (!this.skipPrompts && nonCoreModules.length > 0) {
1059
+ scannedModules = await this.scanModuleSchemas(nonCoreModules);
1060
+ const customizableModules = scannedModules.filter((m) => m.questionCount > 0);
1061
+
1062
+ if (customizableModules.length > 0) {
1063
+ const configMode = await prompts.select({
1064
+ message: 'Module configuration',
1065
+ choices: [
1066
+ { name: 'Express Setup', value: 'express', hint: 'accept all defaults (recommended)' },
1067
+ { name: 'Customize', value: 'customize', hint: 'choose modules to configure' },
1068
+ ],
1069
+ default: 'express',
1070
+ });
1071
+
1072
+ if (configMode === 'customize') {
1073
+ const choices = customizableModules.map((m) => ({
1074
+ name: `${m.displayName} (${m.questionCount} option${m.questionCount === 1 ? '' : 's'})`,
1075
+ value: m.moduleName,
1076
+ hint: m.hasFieldsWithoutDefaults ? 'has fields without defaults' : undefined,
1077
+ checked: m.hasFieldsWithoutDefaults,
1078
+ }));
1079
+ const selected = await prompts.multiselect({
1080
+ message: 'Select modules to customize:',
1081
+ choices,
1082
+ required: false,
1083
+ });
1084
+ this.modulesToCustomize = new Set(selected);
1085
+ } else {
1086
+ // Express mode: no modules to customize
1087
+ this.modulesToCustomize = new Set();
1088
+ }
1089
+ } else {
1090
+ // All non-core modules have zero config - no gateway needed
1091
+ this.modulesToCustomize = new Set();
1092
+ }
1093
+ }
1094
+
1095
+ // Collect remaining non-core modules
1096
+ if (this.modulesToCustomize === undefined) {
1097
+ // No gateway was shown (skipPrompts, no non-core modules, or direct call) - process all normally
1098
+ for (const moduleName of nonCoreModules) {
1099
+ await this.collectModuleConfig(moduleName, projectDir);
1100
+ }
1101
+ } else {
1102
+ // Split into default modules (tasks progress) and customized modules (interactive)
1103
+ const defaultModules = nonCoreModules.filter((m) => !this.modulesToCustomize.has(m));
1104
+ const customizeModules = nonCoreModules.filter((m) => this.modulesToCustomize.has(m));
1105
+
1106
+ // Run default modules with a single spinner
1107
+ if (defaultModules.length > 0) {
1108
+ // Build display name map from all scanned modules for pre-call spinner messages
1109
+ const displayNameMap = new Map();
1110
+ for (const m of scannedModules) {
1111
+ displayNameMap.set(m.moduleName, m.displayName);
1112
+ }
1113
+
1114
+ const configSpinner = await prompts.spinner();
1115
+ configSpinner.start('Configuring modules...');
1116
+ try {
1117
+ for (const moduleName of defaultModules) {
1118
+ const displayName = displayNameMap.get(moduleName) || moduleName.toUpperCase();
1119
+ configSpinner.message(`Configuring ${displayName}...`);
1120
+ try {
1121
+ this._silentConfig = true;
1122
+ await this.collectModuleConfig(moduleName, projectDir);
1123
+ } finally {
1124
+ this._silentConfig = false;
1125
+ }
1126
+ }
1127
+ } finally {
1128
+ configSpinner.stop(customizeModules.length > 0 ? 'Module defaults applied' : 'Module configuration complete');
1129
+ }
1130
+ }
1131
+
1132
+ // Run customized modules individually (may show interactive prompts)
1133
+ for (const moduleName of customizeModules) {
1134
+ await this.collectModuleConfig(moduleName, projectDir);
1135
+ }
1136
+
1137
+ if (customizeModules.length > 0) {
1138
+ await prompts.log.step('Module configuration complete');
1139
+ }
1140
+ }
1141
+
1142
+ // Add metadata
1143
+ this.collectedConfig._meta = {
1144
+ version: require(path.join(getProjectRoot(), 'package.json')).version,
1145
+ installDate: new Date().toISOString(),
1146
+ lastModified: new Date().toISOString(),
1147
+ };
1148
+
1149
+ return this.collectedConfig;
1150
+ }
1151
+
1152
+ /**
1153
+ * Collect configuration for a single module (Quick Update mode - only new fields)
1154
+ * @param {string} moduleName - Module name
1155
+ * @param {string} projectDir - Target project directory
1156
+ * @param {boolean} silentMode - If true, only prompt for new/missing fields
1157
+ * @returns {boolean} True if new fields were prompted, false if all fields existed
1158
+ */
1159
+ async collectModuleConfigQuick(moduleName, projectDir, silentMode = true) {
1160
+ this.currentProjectDir = projectDir;
1161
+ // Load existing config if not already loaded
1162
+ if (!this._existingConfig) {
1163
+ await this.loadExistingConfig(projectDir);
1164
+ }
1165
+
1166
+ // Initialize allAnswers if not already initialized
1167
+ if (!this.allAnswers) {
1168
+ this.allAnswers = {};
1169
+ }
1170
+
1171
+ // Load module's config schema from module.yaml
1172
+ // First, try the standard src/modules location
1173
+ let moduleConfigPath = path.join(getModulePath(moduleName), 'module.yaml');
1174
+
1175
+ // If not found in src/modules, we need to find it by searching the project
1176
+ if (!(await fs.pathExists(moduleConfigPath))) {
1177
+ const moduleSourcePath = await this.findModuleSource(moduleName, { silent: true });
1178
+
1179
+ if (moduleSourcePath) {
1180
+ moduleConfigPath = path.join(moduleSourcePath, 'module.yaml');
1181
+ }
1182
+ }
1183
+
1184
+ if (!(await fs.pathExists(moduleConfigPath))) {
1185
+ // No config schema for this module - use existing values
1186
+ if (this._existingConfig && this._existingConfig[moduleName]) {
1187
+ if (!this.collectedConfig[moduleName]) {
1188
+ this.collectedConfig[moduleName] = {};
1189
+ }
1190
+ this.collectedConfig[moduleName] = { ...this._existingConfig[moduleName] };
1191
+ }
1192
+ return false;
1193
+ }
1194
+
1195
+ const configContent = await fs.readFile(moduleConfigPath, 'utf8');
1196
+ const moduleConfig = yaml.parse(configContent);
1197
+
1198
+ if (!moduleConfig) {
1199
+ return false;
1200
+ }
1201
+
1202
+ // Compare schema with existing config to find new/missing fields
1203
+ const configKeys = Object.keys(moduleConfig).filter((key) => key !== 'prompt');
1204
+ const existingKeys = this._existingConfig && this._existingConfig[moduleName] ? Object.keys(this._existingConfig[moduleName]) : [];
1205
+
1206
+ // Check if this module has no configuration keys at all (like CIS)
1207
+ // Filter out metadata fields and only count actual config objects
1208
+ const metadataFields = new Set(['code', 'name', 'header', 'subheader', 'default_selected']);
1209
+ const actualConfigKeys = configKeys.filter((key) => !metadataFields.has(key));
1210
+ const hasNoConfig = actualConfigKeys.length === 0;
1211
+
1212
+ // If module has no config keys at all, handle it specially
1213
+ if (hasNoConfig && moduleConfig.subheader) {
1214
+ const moduleDisplayName = moduleConfig.header || `${moduleName.toUpperCase()} Module`;
1215
+ await prompts.log.step(moduleDisplayName);
1216
+ await prompts.log.message(` \u2713 ${moduleConfig.subheader}`);
1217
+ return false; // No new fields
1218
+ }
1219
+
1220
+ // Find new interactive fields (with prompt)
1221
+ const newKeys = configKeys.filter((key) => {
1222
+ const item = moduleConfig[key];
1223
+ // Check if it's a config item and doesn't exist in existing config
1224
+ return item && typeof item === 'object' && item.prompt && !existingKeys.includes(key);
1225
+ });
1226
+
1227
+ // Find new static fields (without prompt, just result)
1228
+ const newStaticKeys = configKeys.filter((key) => {
1229
+ const item = moduleConfig[key];
1230
+ return item && typeof item === 'object' && !item.prompt && item.result && !existingKeys.includes(key);
1231
+ });
1232
+
1233
+ // If in silent mode and no new keys (neither interactive nor static), use existing config and skip prompts
1234
+ if (silentMode && newKeys.length === 0 && newStaticKeys.length === 0) {
1235
+ if (this._existingConfig && this._existingConfig[moduleName]) {
1236
+ if (!this.collectedConfig[moduleName]) {
1237
+ this.collectedConfig[moduleName] = {};
1238
+ }
1239
+ this.collectedConfig[moduleName] = { ...this._existingConfig[moduleName] };
1240
+
1241
+ // Special handling for user_name: ensure it has a value
1242
+ if (
1243
+ moduleName === 'core' &&
1244
+ (!this.collectedConfig[moduleName].user_name || this.collectedConfig[moduleName].user_name === '[USER_NAME]')
1245
+ ) {
1246
+ this.collectedConfig[moduleName].user_name = this.getDefaultUsername();
1247
+ }
1248
+
1249
+ // Also populate allAnswers for cross-referencing
1250
+ for (const [key, value] of Object.entries(this._existingConfig[moduleName])) {
1251
+ // Ensure user_name is properly set in allAnswers too
1252
+ let finalValue = value;
1253
+ if (moduleName === 'core' && key === 'user_name' && (!value || value === '[USER_NAME]')) {
1254
+ finalValue = this.getDefaultUsername();
1255
+ }
1256
+ this.allAnswers[`${moduleName}_${key}`] = finalValue;
1257
+ }
1258
+ } else if (moduleName === 'core') {
1259
+ // No existing core config - ensure we at least have user_name
1260
+ if (!this.collectedConfig[moduleName]) {
1261
+ this.collectedConfig[moduleName] = {};
1262
+ }
1263
+ if (!this.collectedConfig[moduleName].user_name) {
1264
+ this.collectedConfig[moduleName].user_name = this.getDefaultUsername();
1265
+ this.allAnswers[`${moduleName}_user_name`] = this.getDefaultUsername();
1266
+ }
1267
+ }
1268
+
1269
+ // Show "no config" message for modules with no new questions (that have config keys)
1270
+ await prompts.log.message(` \u2713 ${moduleName.toUpperCase()} module already up to date`);
1271
+ return false; // No new fields
1272
+ }
1273
+
1274
+ // If we have new fields (interactive or static), process them
1275
+ if (newKeys.length > 0 || newStaticKeys.length > 0) {
1276
+ const questions = [];
1277
+ const staticAnswers = {};
1278
+
1279
+ // Build questions for interactive fields
1280
+ for (const key of newKeys) {
1281
+ const item = moduleConfig[key];
1282
+ const question = await this.buildQuestion(moduleName, key, item, moduleConfig);
1283
+ if (question) {
1284
+ questions.push(question);
1285
+ }
1286
+ }
1287
+
1288
+ // Prepare static answers (no prompt, just result)
1289
+ for (const key of newStaticKeys) {
1290
+ staticAnswers[`${moduleName}_${key}`] = undefined;
1291
+ }
1292
+
1293
+ // Collect all answers (static + prompted)
1294
+ let allAnswers = { ...staticAnswers };
1295
+
1296
+ if (questions.length > 0 && silentMode) {
1297
+ // In silent mode (quick update), use defaults for new fields instead of prompting
1298
+ for (const q of questions) {
1299
+ allAnswers[q.name] = typeof q.default === 'function' ? q.default({}) : q.default;
1300
+ }
1301
+ await prompts.log.message(` \u2713 ${moduleName.toUpperCase()} module configured with defaults`);
1302
+ } else if (questions.length > 0) {
1303
+ // Only show header if we actually have questions
1304
+ await CLIUtils.displayModuleConfigHeader(moduleName, moduleConfig.header, moduleConfig.subheader);
1305
+ await prompts.log.message('');
1306
+ const promptedAnswers = await prompts.prompt(questions);
1307
+
1308
+ // Merge prompted answers with static answers
1309
+ Object.assign(allAnswers, promptedAnswers);
1310
+ } else if (newStaticKeys.length > 0) {
1311
+ // Only static fields, no questions - show no config message
1312
+ await prompts.log.message(` \u2713 ${moduleName.toUpperCase()} module configuration updated`);
1313
+ }
1314
+
1315
+ // Store all answers for cross-referencing
1316
+ Object.assign(this.allAnswers, allAnswers);
1317
+
1318
+ // Process all answers (both static and prompted)
1319
+ // First, copy existing config to preserve values that aren't being updated
1320
+ if (this._existingConfig && this._existingConfig[moduleName]) {
1321
+ this.collectedConfig[moduleName] = { ...this._existingConfig[moduleName] };
1322
+ } else {
1323
+ this.collectedConfig[moduleName] = {};
1324
+ }
1325
+
1326
+ for (const key of Object.keys(allAnswers)) {
1327
+ const originalKey = key.replace(`${moduleName}_`, '');
1328
+ const item = moduleConfig[originalKey];
1329
+ const value = allAnswers[key];
1330
+
1331
+ let result;
1332
+ if (Array.isArray(value)) {
1333
+ result = value;
1334
+ } else if (item.result) {
1335
+ result = this.processResultTemplate(item.result, value);
1336
+ } else {
1337
+ result = value;
1338
+ }
1339
+
1340
+ // Update the collected config with new/updated values
1341
+ this.collectedConfig[moduleName][originalKey] = result;
1342
+ }
1343
+ }
1344
+
1345
+ // Copy over existing values for fields that weren't prompted
1346
+ if (this._existingConfig && this._existingConfig[moduleName]) {
1347
+ if (!this.collectedConfig[moduleName]) {
1348
+ this.collectedConfig[moduleName] = {};
1349
+ }
1350
+ for (const [key, value] of Object.entries(this._existingConfig[moduleName])) {
1351
+ if (!this.collectedConfig[moduleName][key]) {
1352
+ this.collectedConfig[moduleName][key] = value;
1353
+ this.allAnswers[`${moduleName}_${key}`] = value;
1354
+ }
1355
+ }
1356
+ }
1357
+
1358
+ await this.displayModulePostConfigNotes(moduleName, moduleConfig);
1359
+
1360
+ return newKeys.length > 0 || newStaticKeys.length > 0; // Return true if we had any new fields (interactive or static)
1361
+ }
1362
+
1363
+ /**
1364
+ * Process a result template with value substitution
1365
+ * @param {*} resultTemplate - The result template
1366
+ * @param {*} value - The value to substitute
1367
+ * @returns {*} Processed result
1368
+ */
1369
+ processResultTemplate(resultTemplate, value) {
1370
+ let result = resultTemplate;
1371
+
1372
+ if (typeof result === 'string' && value !== undefined) {
1373
+ if (typeof value === 'string') {
1374
+ result = result.replace('{value}', value);
1375
+ } else if (typeof value === 'boolean' || typeof value === 'number') {
1376
+ if (result === '{value}') {
1377
+ result = value;
1378
+ } else {
1379
+ result = result.replace('{value}', value);
1380
+ }
1381
+ } else {
1382
+ result = value;
1383
+ }
1384
+
1385
+ if (typeof result === 'string') {
1386
+ result = result.replaceAll(/{([^}]+)}/g, (match, configKey) => {
1387
+ if (configKey === 'project-root') {
1388
+ return '{project-root}';
1389
+ }
1390
+ if (configKey === 'value') {
1391
+ return match;
1392
+ }
1393
+
1394
+ let configValue = this.allAnswers[configKey] || this.allAnswers[`${configKey}`];
1395
+ if (!configValue) {
1396
+ for (const [answerKey, answerValue] of Object.entries(this.allAnswers)) {
1397
+ if (answerKey.endsWith(`_${configKey}`)) {
1398
+ configValue = answerValue;
1399
+ break;
1400
+ }
1401
+ }
1402
+ }
1403
+
1404
+ if (!configValue) {
1405
+ for (const mod of Object.keys(this.collectedConfig)) {
1406
+ if (mod !== '_meta' && this.collectedConfig[mod] && this.collectedConfig[mod][configKey]) {
1407
+ configValue = this.collectedConfig[mod][configKey];
1408
+ if (typeof configValue === 'string' && configValue.includes('{project-root}/')) {
1409
+ configValue = configValue.replace('{project-root}/', '');
1410
+ }
1411
+ break;
1412
+ }
1413
+ }
1414
+ }
1415
+
1416
+ return configValue || match;
1417
+ });
1418
+ }
1419
+ }
1420
+
1421
+ return result;
1422
+ }
1423
+
1424
+ /**
1425
+ * Get the default username from the system
1426
+ * @returns {string} Capitalized username\
1427
+ */
1428
+ getDefaultUsername() {
1429
+ let result = 'BMad';
1430
+ try {
1431
+ const os = require('node:os');
1432
+ const userInfo = os.userInfo();
1433
+ if (userInfo && userInfo.username) {
1434
+ const username = userInfo.username;
1435
+ result = username.charAt(0).toUpperCase() + username.slice(1);
1436
+ }
1437
+ } catch {
1438
+ // Do nothing, just return 'BMad'
1439
+ }
1440
+ return result;
1441
+ }
1442
+
1443
+ /**
1444
+ * Collect configuration for a single module
1445
+ * @param {string} moduleName - Module name
1446
+ * @param {string} projectDir - Target project directory
1447
+ * @param {boolean} skipLoadExisting - Skip loading existing config (for early core collection)
1448
+ * @param {boolean} skipCompletion - Skip showing completion message (for early core collection)
1449
+ */
1450
+ async collectModuleConfig(moduleName, projectDir, skipLoadExisting = false, skipCompletion = false) {
1451
+ this.currentProjectDir = projectDir;
1452
+ // Load existing config if needed and not already loaded
1453
+ if (!skipLoadExisting && !this._existingConfig) {
1454
+ await this.loadExistingConfig(projectDir);
1455
+ }
1456
+
1457
+ // Initialize allAnswers if not already initialized
1458
+ if (!this.allAnswers) {
1459
+ this.allAnswers = {};
1460
+ }
1461
+ // Load module's config
1462
+ let moduleConfigPath = path.join(getModulePath(moduleName), 'module.yaml');
1463
+
1464
+ // If not found in src/modules or custom paths, search the project
1465
+ if (!(await fs.pathExists(moduleConfigPath))) {
1466
+ const moduleSourcePath = await this.findModuleSource(moduleName, { silent: true });
1467
+
1468
+ if (moduleSourcePath) {
1469
+ moduleConfigPath = path.join(moduleSourcePath, 'module.yaml');
1470
+ }
1471
+ }
1472
+
1473
+ let configPath = null;
1474
+ if (await fs.pathExists(moduleConfigPath)) {
1475
+ configPath = moduleConfigPath;
1476
+ } else {
1477
+ // No config for this module
1478
+ return;
1479
+ }
1480
+
1481
+ const configContent = await fs.readFile(configPath, 'utf8');
1482
+ const moduleConfig = yaml.parse(configContent);
1483
+
1484
+ if (!moduleConfig) {
1485
+ return;
1486
+ }
1487
+
1488
+ // Process each config item
1489
+ const questions = [];
1490
+ const staticAnswers = {};
1491
+ const configKeys = Object.keys(moduleConfig).filter((key) => key !== 'prompt');
1492
+
1493
+ for (const key of configKeys) {
1494
+ const item = moduleConfig[key];
1495
+
1496
+ // Skip if not a config object
1497
+ if (!item || typeof item !== 'object') {
1498
+ continue;
1499
+ }
1500
+
1501
+ // Handle static values (no prompt, just result)
1502
+ if (!item.prompt && item.result) {
1503
+ // Add to static answers with a marker value
1504
+ staticAnswers[`${moduleName}_${key}`] = undefined;
1505
+ continue;
1506
+ }
1507
+
1508
+ // Handle interactive values (with prompt)
1509
+ if (item.prompt) {
1510
+ const question = await this.buildQuestion(moduleName, key, item, moduleConfig);
1511
+ if (question) {
1512
+ questions.push(question);
1513
+ }
1514
+ }
1515
+ }
1516
+
1517
+ // Collect all answers (static + prompted)
1518
+ let allAnswers = { ...staticAnswers };
1519
+
1520
+ // If there are questions to ask, prompt for accepting defaults vs customizing
1521
+ if (questions.length > 0) {
1522
+ const moduleDisplayName = moduleConfig.header || `${moduleName.toUpperCase()} Module`;
1523
+
1524
+ // Skip prompts mode: use all defaults without asking
1525
+ if (this.skipPrompts) {
1526
+ await prompts.log.info(`Using default configuration for ${moduleDisplayName}`);
1527
+ // Use defaults for all questions
1528
+ for (const question of questions) {
1529
+ const hasDefault = question.default !== undefined && question.default !== null && question.default !== '';
1530
+ if (hasDefault && typeof question.default !== 'function') {
1531
+ allAnswers[question.name] = question.default;
1532
+ }
1533
+ }
1534
+ } else {
1535
+ if (!this._silentConfig) await prompts.log.step(`Configuring ${moduleDisplayName}`);
1536
+ let useDefaults = true;
1537
+ if (moduleName === 'core') {
1538
+ useDefaults = false; // Core: always show all questions
1539
+ } else if (this.modulesToCustomize === undefined) {
1540
+ // Fallback: original per-module confirm (backward compat for direct calls)
1541
+ const customizeAnswer = await prompts.prompt([
1542
+ {
1543
+ type: 'confirm',
1544
+ name: 'customize',
1545
+ message: 'Accept Defaults (no to customize)?',
1546
+ default: true,
1547
+ },
1548
+ ]);
1549
+ useDefaults = customizeAnswer.customize;
1550
+ } else {
1551
+ // Batch mode: use defaults unless module was selected for customization
1552
+ useDefaults = !this.modulesToCustomize.has(moduleName);
1553
+ }
1554
+
1555
+ if (useDefaults && moduleName !== 'core') {
1556
+ // Accept defaults - only ask questions that have NO default value
1557
+ const questionsWithoutDefaults = questions.filter((q) => q.default === undefined || q.default === null || q.default === '');
1558
+
1559
+ if (questionsWithoutDefaults.length > 0) {
1560
+ await prompts.log.message(` Asking required questions for ${moduleName.toUpperCase()}...`);
1561
+ const promptedAnswers = await prompts.prompt(questionsWithoutDefaults);
1562
+ Object.assign(allAnswers, promptedAnswers);
1563
+ }
1564
+
1565
+ // For questions with defaults that weren't asked, we need to process them with their default values
1566
+ const questionsWithDefaults = questions.filter((q) => q.default !== undefined && q.default !== null && q.default !== '');
1567
+ for (const question of questionsWithDefaults) {
1568
+ // Skip function defaults - these are dynamic and will be evaluated later
1569
+ if (typeof question.default === 'function') {
1570
+ continue;
1571
+ }
1572
+ allAnswers[question.name] = question.default;
1573
+ }
1574
+ } else {
1575
+ const promptedAnswers = await prompts.prompt(questions);
1576
+ Object.assign(allAnswers, promptedAnswers);
1577
+ }
1578
+ }
1579
+ }
1580
+
1581
+ // Store all answers for cross-referencing
1582
+ Object.assign(this.allAnswers, allAnswers);
1583
+
1584
+ // Process all answers (both static and prompted)
1585
+ // Always process if we have any answers or static answers
1586
+ if (Object.keys(allAnswers).length > 0 || Object.keys(staticAnswers).length > 0) {
1587
+ const answers = allAnswers;
1588
+
1589
+ // Process answers and build result values
1590
+ for (const key of Object.keys(answers)) {
1591
+ const originalKey = key.replace(`${moduleName}_`, '');
1592
+ const item = moduleConfig[originalKey];
1593
+ const value = answers[key];
1594
+
1595
+ // Build the result using the template
1596
+ let result;
1597
+
1598
+ // For arrays (multi-select), handle differently
1599
+ if (Array.isArray(value)) {
1600
+ result = value;
1601
+ } else if (item.result) {
1602
+ result = item.result;
1603
+
1604
+ // Replace placeholders only for strings
1605
+ if (typeof result === 'string' && value !== undefined) {
1606
+ // Replace {value} with the actual value
1607
+ if (typeof value === 'string') {
1608
+ result = result.replace('{value}', value);
1609
+ } else if (typeof value === 'boolean' || typeof value === 'number') {
1610
+ // For boolean and number values, if result is just "{value}", use the raw value
1611
+ if (result === '{value}') {
1612
+ result = value;
1613
+ } else {
1614
+ result = result.replace('{value}', value);
1615
+ }
1616
+ } else {
1617
+ result = value;
1618
+ }
1619
+
1620
+ // Only do further replacements if result is still a string
1621
+ if (typeof result === 'string') {
1622
+ // Replace references to other config values
1623
+ result = result.replaceAll(/{([^}]+)}/g, (match, configKey) => {
1624
+ // Check if it's a special placeholder
1625
+ if (configKey === 'project-root') {
1626
+ return '{project-root}';
1627
+ }
1628
+
1629
+ // Skip if it's the 'value' placeholder we already handled
1630
+ if (configKey === 'value') {
1631
+ return match;
1632
+ }
1633
+
1634
+ // Look for the config value across all modules
1635
+ // First check if it's in the current module's answers
1636
+ let configValue = answers[`${moduleName}_${configKey}`];
1637
+
1638
+ // Then check all answers (for cross-module references like outputFolder)
1639
+ if (!configValue) {
1640
+ // Try with various module prefixes
1641
+ for (const [answerKey, answerValue] of Object.entries(this.allAnswers)) {
1642
+ if (answerKey.endsWith(`_${configKey}`)) {
1643
+ configValue = answerValue;
1644
+ break;
1645
+ }
1646
+ }
1647
+ }
1648
+
1649
+ // Check in already collected config
1650
+ if (!configValue) {
1651
+ for (const mod of Object.keys(this.collectedConfig)) {
1652
+ if (mod !== '_meta' && this.collectedConfig[mod] && this.collectedConfig[mod][configKey]) {
1653
+ configValue = this.collectedConfig[mod][configKey];
1654
+ break;
1655
+ }
1656
+ }
1657
+ }
1658
+
1659
+ return configValue || match;
1660
+ });
1661
+ }
1662
+ }
1663
+ } else {
1664
+ result = value;
1665
+ }
1666
+
1667
+ // Store only the result value (no prompts, defaults, examples, etc.)
1668
+ if (!this.collectedConfig[moduleName]) {
1669
+ this.collectedConfig[moduleName] = {};
1670
+ }
1671
+ this.collectedConfig[moduleName][originalKey] = result;
1672
+ }
1673
+
1674
+ // No longer display completion boxes - keep output clean
1675
+ } else {
1676
+ // No questions for this module - show completion message with header if available
1677
+ const moduleDisplayName = moduleConfig.header || `${moduleName.toUpperCase()} Module`;
1678
+
1679
+ // Check if this module has NO configuration keys at all (like CIS)
1680
+ // Filter out metadata fields and only count actual config objects
1681
+ const metadataFields = new Set(['code', 'name', 'header', 'subheader', 'default_selected']);
1682
+ const actualConfigKeys = configKeys.filter((key) => !metadataFields.has(key));
1683
+ const hasNoConfig = actualConfigKeys.length === 0;
1684
+
1685
+ if (!this._silentConfig) {
1686
+ if (hasNoConfig && (moduleConfig.subheader || moduleConfig.header)) {
1687
+ await prompts.log.step(moduleDisplayName);
1688
+ if (moduleConfig.subheader) {
1689
+ await prompts.log.message(` \u2713 ${moduleConfig.subheader}`);
1690
+ } else {
1691
+ await prompts.log.message(` \u2713 No custom configuration required`);
1692
+ }
1693
+ } else {
1694
+ // Module has config but just no questions to ask
1695
+ await prompts.log.message(` \u2713 ${moduleName.toUpperCase()} module configured`);
1696
+ }
1697
+ }
1698
+ }
1699
+
1700
+ // If we have no collected config for this module, but we have a module schema,
1701
+ // ensure we have at least an empty object
1702
+ if (!this.collectedConfig[moduleName]) {
1703
+ this.collectedConfig[moduleName] = {};
1704
+
1705
+ // If we accepted defaults and have no answers, we still need to check
1706
+ // if there are any static values in the schema that should be applied
1707
+ if (moduleConfig) {
1708
+ for (const key of Object.keys(moduleConfig)) {
1709
+ if (key !== 'prompt' && moduleConfig[key] && typeof moduleConfig[key] === 'object') {
1710
+ const item = moduleConfig[key];
1711
+ // For static items (no prompt, just result), apply the result
1712
+ if (!item.prompt && item.result) {
1713
+ // Apply any placeholder replacements to the result
1714
+ let result = item.result;
1715
+ if (typeof result === 'string') {
1716
+ result = this.replacePlaceholders(result, moduleName, moduleConfig);
1717
+ }
1718
+ this.collectedConfig[moduleName][key] = result;
1719
+ }
1720
+ }
1721
+ }
1722
+ }
1723
+ }
1724
+
1725
+ await this.displayModulePostConfigNotes(moduleName, moduleConfig);
1726
+ }
1727
+
1728
+ /**
1729
+ * Replace placeholders in a string with collected config values
1730
+ * @param {string} str - String with placeholders
1731
+ * @param {string} currentModule - Current module name (to look up defaults in same module)
1732
+ * @param {Object} moduleConfig - Current module's config schema (to look up defaults)
1733
+ * @returns {string} String with placeholders replaced
1734
+ */
1735
+ replacePlaceholders(str, currentModule = null, moduleConfig = null) {
1736
+ if (typeof str !== 'string') {
1737
+ return str;
1738
+ }
1739
+
1740
+ return str.replaceAll(/{([^}]+)}/g, (match, configKey) => {
1741
+ // Preserve special placeholders
1742
+ if (configKey === 'project-root' || configKey === 'value' || configKey === 'directory_name') {
1743
+ return match;
1744
+ }
1745
+
1746
+ const configValue = this.resolveConfigValue(configKey, currentModule, moduleConfig);
1747
+
1748
+ return configValue || match;
1749
+ });
1750
+ }
1751
+
1752
+ /**
1753
+ * Clean a stored path-like value for prompt display/input reuse.
1754
+ * @param {*} value - Stored value
1755
+ * @returns {*} Cleaned value
1756
+ */
1757
+ cleanPromptValue(value) {
1758
+ if (typeof value === 'string' && value.startsWith('{project-root}/')) {
1759
+ return value.replace('{project-root}/', '');
1760
+ }
1761
+
1762
+ return value;
1763
+ }
1764
+
1765
+ /**
1766
+ * Resolve a config key from answers, collected config, existing config, or schema defaults.
1767
+ * @param {string} configKey - Config key to resolve
1768
+ * @param {string} currentModule - Current module name
1769
+ * @param {Object} moduleConfig - Current module config schema
1770
+ * @returns {*} Resolved value
1771
+ */
1772
+ resolveConfigValue(configKey, currentModule = null, moduleConfig = null) {
1773
+ // Look for the config value in allAnswers (already answered questions)
1774
+ let configValue = this.allAnswers?.[configKey] || this.allAnswers?.[`core_${configKey}`];
1775
+
1776
+ if (!configValue && this.allAnswers) {
1777
+ for (const [answerKey, answerValue] of Object.entries(this.allAnswers)) {
1778
+ if (answerKey.endsWith(`_${configKey}`)) {
1779
+ configValue = answerValue;
1780
+ break;
1781
+ }
1782
+ }
1783
+ }
1784
+
1785
+ // Prefer the current module's persisted value when re-prompting an existing install
1786
+ if (!configValue && currentModule && this._existingConfig?.[currentModule]?.[configKey] !== undefined) {
1787
+ configValue = this._existingConfig[currentModule][configKey];
1788
+ }
1789
+
1790
+ // Check in already collected config
1791
+ if (!configValue) {
1792
+ for (const mod of Object.keys(this.collectedConfig)) {
1793
+ if (mod !== '_meta' && this.collectedConfig[mod] && this.collectedConfig[mod][configKey]) {
1794
+ configValue = this.collectedConfig[mod][configKey];
1795
+ break;
1796
+ }
1797
+ }
1798
+ }
1799
+
1800
+ // Fall back to other existing module config values
1801
+ if (!configValue && this._existingConfig) {
1802
+ for (const mod of Object.keys(this._existingConfig)) {
1803
+ if (mod !== '_meta' && this._existingConfig[mod] && this._existingConfig[mod][configKey]) {
1804
+ configValue = this._existingConfig[mod][configKey];
1805
+ break;
1806
+ }
1807
+ }
1808
+ }
1809
+
1810
+ // If still not found and we're in the same module, use the default from the config schema
1811
+ if (!configValue && currentModule && moduleConfig && moduleConfig[configKey]) {
1812
+ const referencedItem = moduleConfig[configKey];
1813
+ if (referencedItem && referencedItem.default !== undefined) {
1814
+ configValue = referencedItem.default;
1815
+ }
1816
+ }
1817
+
1818
+ return this.cleanPromptValue(configValue);
1819
+ }
1820
+
1821
+ /**
1822
+ * Convert an existing stored value back into the prompt-facing value for templated fields.
1823
+ * For example, "{test_artifacts}/{value}" + "_bmad-output/test-artifacts/test-design"
1824
+ * becomes "test-design" so the template is not applied twice on modify.
1825
+ * @param {*} existingValue - Stored config value
1826
+ * @param {string} moduleName - Module name
1827
+ * @param {Object} item - Config item definition
1828
+ * @param {Object} moduleConfig - Current module config schema
1829
+ * @returns {*} Prompt-facing default value
1830
+ */
1831
+ normalizeExistingValueForPrompt(existingValue, moduleName, item, moduleConfig = null) {
1832
+ const cleanedValue = this.cleanPromptValue(existingValue);
1833
+
1834
+ if (typeof cleanedValue !== 'string' || typeof item?.result !== 'string' || !item.result.includes('{value}')) {
1835
+ return cleanedValue;
1836
+ }
1837
+
1838
+ const [prefixTemplate = '', suffixTemplate = ''] = item.result.split('{value}');
1839
+ const prefix = this.cleanPromptValue(this.replacePlaceholders(prefixTemplate, moduleName, moduleConfig));
1840
+ const suffix = this.cleanPromptValue(this.replacePlaceholders(suffixTemplate, moduleName, moduleConfig));
1841
+
1842
+ if ((prefix && !cleanedValue.startsWith(prefix)) || (suffix && !cleanedValue.endsWith(suffix))) {
1843
+ return cleanedValue;
1844
+ }
1845
+
1846
+ const startIndex = prefix.length;
1847
+ const endIndex = suffix ? cleanedValue.length - suffix.length : cleanedValue.length;
1848
+ if (endIndex < startIndex) {
1849
+ return cleanedValue;
1850
+ }
1851
+
1852
+ let promptValue = cleanedValue.slice(startIndex, endIndex);
1853
+ if (promptValue.startsWith('/')) {
1854
+ promptValue = promptValue.slice(1);
1855
+ }
1856
+ if (promptValue.endsWith('/')) {
1857
+ promptValue = promptValue.slice(0, -1);
1858
+ }
1859
+
1860
+ return promptValue || cleanedValue;
1861
+ }
1862
+
1863
+ /**
1864
+ * Build a prompt question from a config item
1865
+ * @param {string} moduleName - Module name
1866
+ * @param {string} key - Config key
1867
+ * @param {Object} item - Config item definition
1868
+ * @param {Object} moduleConfig - Full module config schema (for resolving defaults)
1869
+ */
1870
+ async buildQuestion(moduleName, key, item, moduleConfig = null) {
1871
+ const questionName = `${moduleName}_${key}`;
1872
+
1873
+ // Check for existing value
1874
+ let existingValue = null;
1875
+ if (this._existingConfig && this._existingConfig[moduleName]) {
1876
+ existingValue = this._existingConfig[moduleName][key];
1877
+ existingValue = this.normalizeExistingValueForPrompt(existingValue, moduleName, item, moduleConfig);
1878
+ }
1879
+
1880
+ // Special handling for user_name: default to system user
1881
+ if (moduleName === 'core' && key === 'user_name' && !existingValue) {
1882
+ item.default = this.getDefaultUsername();
1883
+ }
1884
+
1885
+ // Determine question type and default value
1886
+ let questionType = 'input';
1887
+ let defaultValue = item.default;
1888
+ let choices = null;
1889
+
1890
+ // Check if default contains references to other fields in the same module
1891
+ const hasSameModuleReference = typeof defaultValue === 'string' && defaultValue.match(/{([^}]+)}/);
1892
+ let dynamicDefault = false;
1893
+
1894
+ // Replace placeholders in default value with collected config values
1895
+ if (typeof defaultValue === 'string') {
1896
+ if (defaultValue.includes('{directory_name}') && this.currentProjectDir) {
1897
+ const dirName = path.basename(this.currentProjectDir);
1898
+ defaultValue = defaultValue.replaceAll('{directory_name}', dirName);
1899
+ }
1900
+
1901
+ // Check if this references another field in the same module (for dynamic defaults)
1902
+ if (hasSameModuleReference && moduleConfig) {
1903
+ const matches = defaultValue.match(/{([^}]+)}/g);
1904
+ if (matches) {
1905
+ for (const match of matches) {
1906
+ const fieldName = match.slice(1, -1); // Remove { }
1907
+ // Check if this field exists in the same module config
1908
+ if (moduleConfig[fieldName]) {
1909
+ dynamicDefault = true;
1910
+ break;
1911
+ }
1912
+ }
1913
+ }
1914
+ }
1915
+
1916
+ // If not dynamic, replace placeholders now
1917
+ if (!dynamicDefault) {
1918
+ defaultValue = this.replacePlaceholders(defaultValue, moduleName, moduleConfig);
1919
+ }
1920
+
1921
+ // Strip {project-root}/ from defaults since it will be added back by result template
1922
+ // This makes the display cleaner and user input simpler
1923
+ if (defaultValue.includes('{project-root}/')) {
1924
+ defaultValue = defaultValue.replace('{project-root}/', '');
1925
+ }
1926
+ }
1927
+
1928
+ // Handle different question types
1929
+ if (item['single-select']) {
1930
+ questionType = 'list';
1931
+ choices = item['single-select'].map((choice) => {
1932
+ // If choice is an object with label and value
1933
+ if (typeof choice === 'object' && choice.label && choice.value !== undefined) {
1934
+ return {
1935
+ name: choice.label,
1936
+ value: choice.value,
1937
+ };
1938
+ }
1939
+ // Otherwise it's a simple string choice
1940
+ return {
1941
+ name: choice,
1942
+ value: choice,
1943
+ };
1944
+ });
1945
+ if (existingValue) {
1946
+ defaultValue = existingValue;
1947
+ }
1948
+ } else if (item['multi-select']) {
1949
+ questionType = 'checkbox';
1950
+ choices = item['multi-select'].map((choice) => {
1951
+ // If choice is an object with label and value
1952
+ if (typeof choice === 'object' && choice.label && choice.value !== undefined) {
1953
+ return {
1954
+ name: choice.label,
1955
+ value: choice.value,
1956
+ checked: existingValue
1957
+ ? existingValue.includes(choice.value)
1958
+ : item.default && Array.isArray(item.default)
1959
+ ? item.default.includes(choice.value)
1960
+ : false,
1961
+ };
1962
+ }
1963
+ // Otherwise it's a simple string choice
1964
+ return {
1965
+ name: choice,
1966
+ value: choice,
1967
+ checked: existingValue
1968
+ ? existingValue.includes(choice)
1969
+ : item.default && Array.isArray(item.default)
1970
+ ? item.default.includes(choice)
1971
+ : false,
1972
+ };
1973
+ });
1974
+ } else if (typeof defaultValue === 'boolean') {
1975
+ questionType = 'confirm';
1976
+ }
1977
+
1978
+ // Build the prompt message
1979
+ let message = '';
1980
+
1981
+ // Handle array prompts for multi-line messages
1982
+ if (Array.isArray(item.prompt)) {
1983
+ message = item.prompt.join('\n');
1984
+ } else {
1985
+ message = item.prompt;
1986
+ }
1987
+
1988
+ // Replace placeholders in prompt message with collected config values
1989
+ if (typeof message === 'string') {
1990
+ message = this.replacePlaceholders(message, moduleName, moduleConfig);
1991
+ }
1992
+
1993
+ // Add current value indicator for existing configs
1994
+ const color = await prompts.getColor();
1995
+ if (existingValue !== null && existingValue !== undefined) {
1996
+ if (typeof existingValue === 'boolean') {
1997
+ message += color.dim(` (current: ${existingValue ? 'true' : 'false'})`);
1998
+ } else if (Array.isArray(existingValue)) {
1999
+ message += color.dim(` (current: ${existingValue.join(', ')})`);
2000
+ } else if (questionType !== 'list') {
2001
+ // Show the cleaned value (without {project-root}/) for display
2002
+ message += color.dim(` (current: ${existingValue})`);
2003
+ }
2004
+ } else if (item.example && questionType === 'input') {
2005
+ // Show example for input fields
2006
+ let exampleText = typeof item.example === 'string' ? item.example : JSON.stringify(item.example);
2007
+ // Replace placeholders in example
2008
+ if (typeof exampleText === 'string') {
2009
+ exampleText = this.replacePlaceholders(exampleText, moduleName, moduleConfig);
2010
+ exampleText = exampleText.replace('{project-root}/', '');
2011
+ }
2012
+ message += color.dim(` (e.g., ${exampleText})`);
2013
+ }
2014
+
2015
+ // Build the question object
2016
+ const question = {
2017
+ type: questionType,
2018
+ name: questionName,
2019
+ message: message,
2020
+ };
2021
+
2022
+ // Set default - if it's dynamic, use a function that the prompt will evaluate with current answers
2023
+ // But if we have an existing value, always use that instead
2024
+ if (existingValue !== null && existingValue !== undefined && questionType !== 'list') {
2025
+ question.default = existingValue;
2026
+ } else if (dynamicDefault && typeof item.default === 'string') {
2027
+ const originalDefault = item.default;
2028
+ question.default = (answers) => {
2029
+ // Replace placeholders using answers from previous questions in the same batch
2030
+ let resolved = originalDefault;
2031
+ resolved = resolved.replaceAll(/{([^}]+)}/g, (match, fieldName) => {
2032
+ // Look for the answer in the current batch (prefixed with module name)
2033
+ const answerKey = `${moduleName}_${fieldName}`;
2034
+ if (answers[answerKey] !== undefined) {
2035
+ return answers[answerKey];
2036
+ }
2037
+ // Fall back to collected config
2038
+ return this.collectedConfig[moduleName]?.[fieldName] || match;
2039
+ });
2040
+ // Strip {project-root}/ for cleaner display
2041
+ if (resolved.includes('{project-root}/')) {
2042
+ resolved = resolved.replace('{project-root}/', '');
2043
+ }
2044
+ return resolved;
2045
+ };
2046
+ } else {
2047
+ question.default = defaultValue;
2048
+ }
2049
+
2050
+ // Add choices for select types
2051
+ if (choices) {
2052
+ question.choices = choices;
2053
+ }
2054
+
2055
+ // Add validation for input fields
2056
+ if (questionType === 'input') {
2057
+ question.validate = (input) => {
2058
+ if (!input && item.required) {
2059
+ return 'This field is required';
2060
+ }
2061
+ // Validate against regex pattern if provided
2062
+ if (input && item.regex) {
2063
+ const regex = new RegExp(item.regex);
2064
+ if (!regex.test(input)) {
2065
+ return `Invalid format. Must match pattern: ${item.regex}`;
2066
+ }
2067
+ }
2068
+ return true;
2069
+ };
2070
+ }
2071
+
2072
+ // Add validation for checkbox (multi-select) fields
2073
+ if (questionType === 'checkbox' && item.required) {
2074
+ question.validate = (answers) => {
2075
+ if (!answers || answers.length === 0) {
2076
+ return 'At least one option must be selected';
2077
+ }
2078
+ return true;
2079
+ };
2080
+ }
2081
+
2082
+ return question;
2083
+ }
2084
+
2085
+ /**
2086
+ * Display post-configuration notes for a module
2087
+ * Shows prerequisite guidance based on collected config values
2088
+ * Reads notes from the module's `post-install-notes` section in module.yaml
2089
+ * Supports two formats:
2090
+ * - Simple string: always displayed
2091
+ * - Object keyed by config field name, with value-specific messages
2092
+ * @param {string} moduleName - Module name
2093
+ * @param {Object} moduleConfig - Parsed module.yaml content
2094
+ */
2095
+ async displayModulePostConfigNotes(moduleName, moduleConfig) {
2096
+ if (this._silentConfig) return;
2097
+ if (!moduleConfig || !moduleConfig['post-install-notes']) return;
2098
+
2099
+ const notes = moduleConfig['post-install-notes'];
2100
+ const color = await prompts.getColor();
2101
+
2102
+ // Format 1: Simple string - always display
2103
+ if (typeof notes === 'string') {
2104
+ await prompts.log.message('');
2105
+ for (const line of notes.trim().split('\n')) {
2106
+ await prompts.log.message(color.dim(line));
2107
+ }
2108
+ return;
2109
+ }
2110
+
2111
+ // Format 2: Conditional on config values
2112
+ if (typeof notes === 'object') {
2113
+ const config = this.collectedConfig[moduleName];
2114
+ if (!config) return;
2115
+
2116
+ let hasOutput = false;
2117
+ for (const [configKey, valueMessages] of Object.entries(notes)) {
2118
+ const selectedValue = config[configKey];
2119
+ if (!selectedValue || !valueMessages[selectedValue]) continue;
2120
+
2121
+ if (hasOutput) await prompts.log.message('');
2122
+ hasOutput = true;
2123
+
2124
+ const message = valueMessages[selectedValue];
2125
+ for (const line of message.trim().split('\n')) {
2126
+ const trimmedLine = line.trim();
2127
+ if (trimmedLine.endsWith(':') && !trimmedLine.startsWith(' ')) {
2128
+ await prompts.log.info(color.bold(trimmedLine));
2129
+ } else {
2130
+ await prompts.log.message(color.dim(' ' + trimmedLine));
2131
+ }
2132
+ }
2133
+ }
2134
+ }
2135
+ }
2136
+
2137
+ /**
2138
+ * Deep merge two objects
2139
+ * @param {Object} target - Target object
2140
+ * @param {Object} source - Source object
2141
+ */
2142
+ deepMerge(target, source) {
2143
+ const result = { ...target };
2144
+
2145
+ for (const key in source) {
2146
+ if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
2147
+ if (result[key] && typeof result[key] === 'object' && !Array.isArray(result[key])) {
2148
+ result[key] = this.deepMerge(result[key], source[key]);
2149
+ } else {
2150
+ result[key] = source[key];
2151
+ }
2152
+ } else {
2153
+ result[key] = source[key];
2154
+ }
2155
+ }
2156
+
2157
+ return result;
2158
+ }
2159
+ }
2160
+
2161
+ /**
2162
+ * Parse a config.toml or config.user.toml written by writeCentralConfig.
2163
+ * Only handles the subset of TOML the installer produces: [core],
2164
+ * [modules.<code>], string/bool/number scalar values. [agents.*] and other
2165
+ * sections are ignored. Returns a plain object keyed by section name where
2166
+ * module sections use the bare code (e.g. "bmm"), not the full "modules.bmm".
2167
+ */
2168
+ function parseCentralToml(content) {
2169
+ const result = {};
2170
+ let currentSection = null;
2171
+
2172
+ for (const rawLine of content.split('\n')) {
2173
+ const line = rawLine.trim();
2174
+ if (!line || line.startsWith('#')) continue;
2175
+
2176
+ const sectionMatch = line.match(/^\[([^\]]+)\]\s*$/);
2177
+ if (sectionMatch) {
2178
+ const name = sectionMatch[1];
2179
+ if (name === 'core') {
2180
+ currentSection = 'core';
2181
+ } else if (name.startsWith('modules.')) {
2182
+ currentSection = name.slice('modules.'.length);
2183
+ } else {
2184
+ currentSection = null;
2185
+ }
2186
+ if (currentSection && !result[currentSection]) {
2187
+ result[currentSection] = {};
2188
+ }
2189
+ continue;
2190
+ }
2191
+
2192
+ if (!currentSection) continue;
2193
+
2194
+ const kvMatch = line.match(/^([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.+)$/);
2195
+ if (!kvMatch) continue;
2196
+
2197
+ const key = kvMatch[1];
2198
+ const raw = kvMatch[2].trim();
2199
+ let value;
2200
+ if (raw.startsWith('"') && raw.endsWith('"')) {
2201
+ value = raw.slice(1, -1).replaceAll(/\\(["\\nrbt])/g, (_, c) => ({ '"': '"', '\\': '\\', n: '\n', r: '\r', b: '\b', t: '\t' })[c]);
2202
+ } else if (raw === 'true') {
2203
+ value = true;
2204
+ } else if (raw === 'false') {
2205
+ value = false;
2206
+ } else if (raw !== '' && !isNaN(raw)) {
2207
+ value = Number(raw);
2208
+ } else {
2209
+ value = raw;
2210
+ }
2211
+ result[currentSection][key] = value;
2212
+ }
2213
+
2214
+ return result;
2215
+ }
2216
+
2217
+ module.exports = { OfficialModules };