bmad-method 6.0.3 → 6.0.5-next.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 (271) hide show
  1. package/.augment/code_review_guidelines.yaml +2 -42
  2. package/.claude/skills/bmad-os-findings-triage/SKILL.md +6 -0
  3. package/.claude/skills/bmad-os-findings-triage/prompts/agent-prompt.md +104 -0
  4. package/.claude/skills/bmad-os-findings-triage/prompts/instructions.md +286 -0
  5. package/.claude/skills/bmad-os-review-pr/SKILL.md +1 -1
  6. package/.claude/skills/bmad-os-review-pr/prompts/instructions.md +63 -6
  7. package/.claude/skills/bmad-os-review-prompt/SKILL.md +177 -0
  8. package/.claude/skills/bmad-os-root-cause-analysis/SKILL.md +12 -0
  9. package/.claude/skills/bmad-os-root-cause-analysis/prompts/instructions.md +74 -0
  10. package/.github/ISSUE_TEMPLATE/config.yaml +1 -1
  11. package/.github/ISSUE_TEMPLATE/documentation.yaml +1 -1
  12. package/.github/workflows/publish.yaml +243 -0
  13. package/CHANGELOG.md +32 -0
  14. package/CONTRIBUTING.md +1 -1
  15. package/README.md +8 -8
  16. package/README_CN.md +121 -0
  17. package/docs/_STYLE_GUIDE.md +10 -10
  18. package/docs/explanation/brainstorming.md +1 -1
  19. package/docs/explanation/party-mode.md +1 -1
  20. package/docs/explanation/preventing-agent-conflicts.md +1 -1
  21. package/docs/explanation/project-context.md +15 -15
  22. package/docs/explanation/quick-flow.md +9 -9
  23. package/docs/how-to/established-projects.md +7 -7
  24. package/docs/how-to/get-answers-about-bmad.md +2 -2
  25. package/docs/how-to/install-bmad.md +16 -6
  26. package/docs/how-to/project-context.md +2 -2
  27. package/docs/how-to/quick-fixes.md +5 -5
  28. package/docs/how-to/shard-large-documents.md +1 -1
  29. package/docs/how-to/upgrade-to-v6.md +8 -5
  30. package/docs/index.md +2 -2
  31. package/docs/reference/agents.md +14 -14
  32. package/docs/reference/commands.md +64 -70
  33. package/docs/reference/testing.md +1 -1
  34. package/docs/reference/workflow-map.md +19 -19
  35. package/docs/tutorials/getting-started.md +34 -34
  36. package/docs/zh-cn/404.md +9 -0
  37. package/docs/zh-cn/_STYLE_GUIDE.md +370 -0
  38. package/docs/zh-cn/explanation/advanced-elicitation.md +62 -0
  39. package/docs/zh-cn/explanation/adversarial-review.md +71 -0
  40. package/docs/zh-cn/explanation/brainstorming.md +43 -0
  41. package/docs/zh-cn/explanation/established-projects-faq.md +60 -0
  42. package/docs/zh-cn/explanation/party-mode.md +79 -0
  43. package/docs/zh-cn/explanation/preventing-agent-conflicts.md +137 -0
  44. package/docs/zh-cn/explanation/project-context.md +176 -0
  45. package/docs/zh-cn/explanation/quick-flow.md +93 -0
  46. package/docs/zh-cn/explanation/why-solutioning-matters.md +90 -0
  47. package/docs/zh-cn/how-to/customize-bmad.md +182 -0
  48. package/docs/zh-cn/how-to/established-projects.md +134 -0
  49. package/docs/zh-cn/how-to/get-answers-about-bmad.md +144 -0
  50. package/docs/zh-cn/how-to/install-bmad.md +105 -0
  51. package/docs/zh-cn/how-to/non-interactive-installation.md +181 -0
  52. package/docs/zh-cn/how-to/project-context.md +152 -0
  53. package/docs/zh-cn/how-to/quick-fixes.md +140 -0
  54. package/docs/zh-cn/how-to/shard-large-documents.md +86 -0
  55. package/docs/zh-cn/how-to/upgrade-to-v6.md +120 -0
  56. package/docs/zh-cn/index.md +69 -0
  57. package/docs/zh-cn/reference/agents.md +41 -0
  58. package/docs/zh-cn/reference/commands.md +166 -0
  59. package/docs/zh-cn/reference/modules.md +94 -0
  60. package/docs/zh-cn/reference/testing.md +122 -0
  61. package/docs/zh-cn/reference/workflow-map.md +104 -0
  62. package/docs/zh-cn/roadmap.mdx +152 -0
  63. package/docs/zh-cn/tutorials/getting-started.md +300 -0
  64. package/package.json +1 -1
  65. package/src/bmm/agents/analyst.agent.yaml +1 -1
  66. package/src/bmm/agents/bmad-skill-manifest.yaml +39 -0
  67. package/src/bmm/agents/dev.agent.yaml +2 -2
  68. package/src/bmm/agents/pm.agent.yaml +1 -1
  69. package/src/bmm/agents/qa.agent.yaml +1 -1
  70. package/src/bmm/agents/quick-flow-solo-dev.agent.yaml +6 -2
  71. package/src/bmm/agents/sm.agent.yaml +4 -4
  72. package/src/bmm/agents/tech-writer/bmad-skill-manifest.yaml +3 -0
  73. package/src/bmm/agents/tech-writer/tech-writer.agent.yaml +1 -1
  74. package/src/bmm/module-help.csv +11 -10
  75. package/src/bmm/workflows/1-analysis/create-product-brief/bmad-skill-manifest.yaml +3 -0
  76. package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-02-vision.md +1 -1
  77. package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-03-users.md +1 -1
  78. package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-04-metrics.md +1 -1
  79. package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-05-scope.md +1 -1
  80. package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-06-complete.md +1 -1
  81. package/src/bmm/workflows/1-analysis/research/bmad-skill-manifest.yaml +14 -0
  82. package/src/bmm/workflows/2-plan-workflows/create-prd/bmad-skill-manifest.yaml +14 -0
  83. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-02-discovery.md +1 -1
  84. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-02b-vision.md +1 -1
  85. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-02c-executive-summary.md +1 -1
  86. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-03-success.md +1 -1
  87. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-04-journeys.md +1 -1
  88. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-05-domain.md +1 -1
  89. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-06-innovation.md +1 -1
  90. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-07-project-type.md +1 -1
  91. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-08-scoping.md +1 -1
  92. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-09-functional.md +1 -1
  93. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-10-nonfunctional.md +1 -1
  94. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-11-polish.md +1 -1
  95. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-12-complete.md +1 -1
  96. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-e/step-e-01-discovery.md +1 -1
  97. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-e/step-e-02-review.md +1 -1
  98. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-01-discovery.md +1 -1
  99. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-10-smart-validation.md +1 -1
  100. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-11-holistic-quality-validation.md +1 -1
  101. package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-13-report-complete.md +1 -1
  102. package/src/bmm/workflows/2-plan-workflows/create-ux-design/bmad-skill-manifest.yaml +3 -0
  103. package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-02-discovery.md +1 -1
  104. package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-03-core-experience.md +2 -2
  105. package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-04-emotional-response.md +2 -2
  106. package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-05-inspiration.md +2 -2
  107. package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-06-design-system.md +2 -2
  108. package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-07-defining-experience.md +2 -2
  109. package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-08-visual-foundation.md +2 -2
  110. package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-09-design-directions.md +2 -2
  111. package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-10-user-journeys.md +2 -2
  112. package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-11-component-strategy.md +2 -2
  113. package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-12-ux-patterns.md +2 -2
  114. package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-13-responsive-accessibility.md +2 -2
  115. package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-14-complete.md +1 -1
  116. package/src/bmm/workflows/3-solutioning/check-implementation-readiness/bmad-skill-manifest.yaml +3 -0
  117. package/src/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-06-final-assessment.md +1 -1
  118. package/src/bmm/workflows/3-solutioning/create-architecture/bmad-skill-manifest.yaml +3 -0
  119. package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-02-context.md +2 -2
  120. package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-03-starter.md +2 -2
  121. package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-04-decisions.md +2 -2
  122. package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-05-patterns.md +2 -2
  123. package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-06-structure.md +2 -2
  124. package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-07-validation.md +2 -2
  125. package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-08-complete.md +1 -1
  126. package/src/bmm/workflows/3-solutioning/create-epics-and-stories/bmad-skill-manifest.yaml +3 -0
  127. package/src/bmm/workflows/3-solutioning/create-epics-and-stories/steps/step-01-validate-prerequisites.md +1 -1
  128. package/src/bmm/workflows/3-solutioning/create-epics-and-stories/steps/step-02-design-epics.md +1 -1
  129. package/src/bmm/workflows/3-solutioning/create-epics-and-stories/steps/step-03-create-stories.md +1 -1
  130. package/src/bmm/workflows/3-solutioning/create-epics-and-stories/steps/step-04-final-validation.md +2 -2
  131. package/src/bmm/workflows/4-implementation/code-review/bmad-skill-manifest.yaml +3 -0
  132. package/src/bmm/workflows/4-implementation/code-review/discover-inputs.md +88 -0
  133. package/src/bmm/workflows/4-implementation/code-review/workflow.md +271 -0
  134. package/src/bmm/workflows/4-implementation/correct-course/bmad-skill-manifest.yaml +3 -0
  135. package/src/bmm/workflows/4-implementation/correct-course/checklist.md +1 -1
  136. package/src/bmm/workflows/4-implementation/correct-course/{instructions.md → workflow.md} +79 -12
  137. package/src/bmm/workflows/4-implementation/create-story/bmad-skill-manifest.yaml +3 -0
  138. package/src/bmm/workflows/4-implementation/create-story/checklist.md +9 -10
  139. package/src/bmm/workflows/4-implementation/create-story/discover-inputs.md +88 -0
  140. package/src/bmm/workflows/4-implementation/create-story/workflow.md +388 -0
  141. package/src/bmm/workflows/4-implementation/dev-story/bmad-skill-manifest.yaml +3 -0
  142. package/src/bmm/workflows/4-implementation/dev-story/{instructions.xml → workflow.md} +49 -2
  143. package/src/bmm/workflows/4-implementation/retrospective/bmad-skill-manifest.yaml +3 -0
  144. package/src/bmm/workflows/4-implementation/retrospective/{instructions.md → workflow.md} +64 -23
  145. package/src/bmm/workflows/4-implementation/sprint-planning/bmad-skill-manifest.yaml +3 -0
  146. package/src/bmm/workflows/4-implementation/sprint-planning/sprint-status-template.yaml +1 -0
  147. package/src/bmm/workflows/4-implementation/sprint-planning/{instructions.md → workflow.md} +55 -10
  148. package/src/bmm/workflows/4-implementation/sprint-status/bmad-skill-manifest.yaml +3 -0
  149. package/src/bmm/workflows/4-implementation/sprint-status/{instructions.md → workflow.md} +45 -8
  150. package/src/bmm/workflows/bmad-quick-flow/bmad-quick-dev-new-preview/SKILL.md +6 -0
  151. package/src/bmm/workflows/bmad-quick-flow/bmad-quick-dev-new-preview/bmad-skill-manifest.yaml +1 -0
  152. package/src/bmm/workflows/bmad-quick-flow/bmad-quick-dev-new-preview/steps/step-01-clarify-and-route.md +54 -0
  153. package/src/bmm/workflows/bmad-quick-flow/bmad-quick-dev-new-preview/steps/step-02-plan.md +39 -0
  154. package/src/bmm/workflows/bmad-quick-flow/bmad-quick-dev-new-preview/steps/step-03-implement.md +35 -0
  155. package/src/bmm/workflows/bmad-quick-flow/bmad-quick-dev-new-preview/steps/step-04-review.md +55 -0
  156. package/src/bmm/workflows/bmad-quick-flow/bmad-quick-dev-new-preview/steps/step-05-present.md +19 -0
  157. package/src/bmm/workflows/bmad-quick-flow/bmad-quick-dev-new-preview/tech-spec-template.md +90 -0
  158. package/src/bmm/workflows/bmad-quick-flow/bmad-quick-dev-new-preview/workflow.md +84 -0
  159. package/src/bmm/workflows/bmad-quick-flow/quick-dev/bmad-skill-manifest.yaml +3 -0
  160. package/src/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-05-adversarial-review.md +8 -14
  161. package/src/bmm/workflows/bmad-quick-flow/quick-dev/workflow.md +1 -1
  162. package/src/bmm/workflows/bmad-quick-flow/quick-spec/bmad-skill-manifest.yaml +3 -0
  163. package/src/bmm/workflows/bmad-quick-flow/quick-spec/steps/step-04-review.md +4 -6
  164. package/src/bmm/workflows/bmad-quick-flow/quick-spec/workflow.md +1 -1
  165. package/src/bmm/workflows/document-project/bmad-skill-manifest.yaml +3 -0
  166. package/src/bmm/workflows/document-project/instructions.md +5 -7
  167. package/src/bmm/workflows/document-project/workflow.md +39 -0
  168. package/src/bmm/workflows/document-project/workflows/deep-dive-instructions.md +0 -1
  169. package/src/bmm/workflows/document-project/workflows/deep-dive-workflow.md +42 -0
  170. package/src/bmm/workflows/document-project/workflows/full-scan-instructions.md +0 -1
  171. package/src/bmm/workflows/document-project/workflows/full-scan-workflow.md +42 -0
  172. package/src/bmm/workflows/generate-project-context/bmad-skill-manifest.yaml +3 -0
  173. package/src/bmm/workflows/generate-project-context/steps/step-02-generate.md +2 -2
  174. package/src/bmm/workflows/qa-generate-e2e-tests/bmad-skill-manifest.yaml +3 -0
  175. package/src/bmm/workflows/qa-generate-e2e-tests/checklist.md +1 -1
  176. package/src/bmm/workflows/qa-generate-e2e-tests/{instructions.md → workflow.md} +40 -7
  177. package/src/core/agents/bmad-master.agent.yaml +1 -1
  178. package/src/core/agents/bmad-skill-manifest.yaml +3 -0
  179. package/src/core/module-help.csv +3 -2
  180. package/src/core/module.yaml +1 -1
  181. package/src/core/tasks/bmad-help/SKILL.md +6 -0
  182. package/src/core/tasks/bmad-help/bmad-skill-manifest.yaml +1 -0
  183. package/src/core/tasks/{help.md → bmad-help/workflow.md} +6 -4
  184. package/src/core/tasks/bmad-review-adversarial-general/SKILL.md +6 -0
  185. package/src/core/tasks/bmad-review-adversarial-general/bmad-skill-manifest.yaml +1 -0
  186. package/src/core/tasks/bmad-review-adversarial-general/workflow.md +32 -0
  187. package/src/core/tasks/bmad-review-edge-case-hunter/SKILL.md +6 -0
  188. package/src/core/tasks/bmad-review-edge-case-hunter/bmad-skill-manifest.yaml +1 -0
  189. package/src/core/tasks/bmad-review-edge-case-hunter/workflow.md +62 -0
  190. package/src/core/tasks/bmad-skill-manifest.yaml +19 -0
  191. package/src/core/workflows/advanced-elicitation/bmad-skill-manifest.yaml +3 -0
  192. package/src/core/workflows/advanced-elicitation/workflow.md +138 -0
  193. package/src/core/workflows/brainstorming/bmad-skill-manifest.yaml +3 -0
  194. package/src/core/workflows/brainstorming/steps/step-01-session-setup.md +31 -18
  195. package/src/core/workflows/brainstorming/steps/step-01b-continue.md +1 -1
  196. package/src/core/workflows/brainstorming/steps/step-03-technique-execution.md +3 -3
  197. package/src/core/workflows/brainstorming/steps/step-04-idea-organization.md +2 -2
  198. package/src/core/workflows/brainstorming/workflow.md +5 -3
  199. package/src/core/workflows/party-mode/bmad-skill-manifest.yaml +3 -0
  200. package/src/core/workflows/party-mode/workflow.md +1 -1
  201. package/src/utility/agent-components/activation-steps.txt +2 -2
  202. package/src/utility/agent-components/handler-multi.txt +1 -2
  203. package/test/adversarial-review-tests/README.md +3 -3
  204. package/test/adversarial-review-tests/test-cases.yaml +2 -2
  205. package/test/fixtures/agent-schema/valid/menu/multiple-menu-items.agent.yaml +1 -1
  206. package/test/fixtures/agent-schema/valid/menu-commands/all-command-types.agent.yaml +1 -1
  207. package/test/fixtures/agent-schema/valid/menu-commands/multiple-commands.agent.yaml +1 -2
  208. package/test/fixtures/file-refs-csv/valid/bmm-style.csv +1 -1
  209. package/test/test-file-refs-csv.js +1 -1
  210. package/test/test-install-to-bmad.js +154 -0
  211. package/test/test-installation-components.js +1586 -2
  212. package/test/test-workflow-path-regex.js +88 -0
  213. package/tools/cli/installers/install-messages.yaml +1 -1
  214. package/tools/cli/installers/lib/core/installer.js +34 -1
  215. package/tools/cli/installers/lib/core/manifest-generator.js +332 -41
  216. package/tools/cli/installers/lib/ide/_base-ide.js +24 -15
  217. package/tools/cli/installers/lib/ide/_config-driven.js +547 -53
  218. package/tools/cli/installers/lib/ide/manager.js +26 -62
  219. package/tools/cli/installers/lib/ide/platform-codes.yaml +116 -29
  220. package/tools/cli/installers/lib/ide/shared/agent-command-generator.js +1 -0
  221. package/tools/cli/installers/lib/ide/shared/bmad-artifacts.js +7 -0
  222. package/tools/cli/installers/lib/ide/shared/path-utils.js +68 -3
  223. package/tools/cli/installers/lib/ide/shared/skill-manifest.js +90 -0
  224. package/tools/cli/installers/lib/ide/shared/task-tool-command-generator.js +2 -0
  225. package/tools/cli/installers/lib/ide/shared/workflow-command-generator.js +6 -145
  226. package/tools/cli/installers/lib/ide/templates/agent-command-template.md +1 -1
  227. package/tools/cli/installers/lib/ide/templates/combined/default-workflow.md +1 -1
  228. package/tools/cli/installers/lib/ide/templates/combined/gemini-workflow-yaml.toml +1 -1
  229. package/tools/cli/installers/lib/ide/templates/combined/gemini-workflow.toml +1 -1
  230. package/tools/cli/installers/lib/ide/templates/combined/opencode-agent.md +1 -1
  231. package/tools/cli/installers/lib/ide/templates/combined/opencode-task.md +0 -1
  232. package/tools/cli/installers/lib/ide/templates/combined/opencode-tool.md +0 -1
  233. package/tools/cli/installers/lib/ide/templates/combined/opencode-workflow-yaml.md +0 -1
  234. package/tools/cli/installers/lib/ide/templates/combined/opencode-workflow.md +0 -1
  235. package/tools/cli/installers/lib/modules/manager.js +9 -132
  236. package/tools/cli/lib/agent/compiler.js +1 -10
  237. package/tools/cli/lib/agent-analyzer.js +2 -14
  238. package/tools/cli/lib/yaml-xml-builder.js +1 -18
  239. package/tools/docs/native-skills-migration-checklist.md +281 -0
  240. package/tools/platform-codes.yaml +1 -1
  241. package/tools/schema/agent.js +1 -3
  242. package/tools/validate-file-refs.js +2 -0
  243. package/website/astro.config.mjs +24 -3
  244. package/website/src/content/config.ts +2 -1
  245. package/website/src/content/i18n/zh-CN.json +28 -0
  246. package/src/bmm/workflows/4-implementation/code-review/instructions.xml +0 -227
  247. package/src/bmm/workflows/4-implementation/code-review/workflow.yaml +0 -43
  248. package/src/bmm/workflows/4-implementation/correct-course/workflow.yaml +0 -53
  249. package/src/bmm/workflows/4-implementation/create-story/instructions.xml +0 -346
  250. package/src/bmm/workflows/4-implementation/create-story/workflow.yaml +0 -52
  251. package/src/bmm/workflows/4-implementation/dev-story/workflow.yaml +0 -20
  252. package/src/bmm/workflows/4-implementation/retrospective/workflow.yaml +0 -52
  253. package/src/bmm/workflows/4-implementation/sprint-planning/workflow.yaml +0 -47
  254. package/src/bmm/workflows/4-implementation/sprint-status/workflow.yaml +0 -25
  255. package/src/bmm/workflows/document-project/workflow.yaml +0 -22
  256. package/src/bmm/workflows/document-project/workflows/deep-dive.yaml +0 -31
  257. package/src/bmm/workflows/document-project/workflows/full-scan.yaml +0 -31
  258. package/src/bmm/workflows/qa-generate-e2e-tests/workflow.yaml +0 -42
  259. package/src/core/tasks/review-adversarial-general.xml +0 -49
  260. package/src/core/tasks/workflow.xml +0 -235
  261. package/src/core/workflows/advanced-elicitation/workflow.xml +0 -118
  262. package/src/utility/agent-components/handler-validate-workflow.txt +0 -7
  263. package/src/utility/agent-components/handler-workflow.txt +0 -10
  264. package/tools/cli/installers/lib/ide/codex.js +0 -440
  265. package/tools/cli/installers/lib/ide/github-copilot.js +0 -699
  266. package/tools/cli/installers/lib/ide/kilo.js +0 -269
  267. package/tools/cli/installers/lib/ide/rovodev.js +0 -257
  268. package/tools/cli/installers/lib/ide/templates/combined/default-workflow-yaml.md +0 -14
  269. package/tools/cli/installers/lib/ide/templates/combined/kiro-workflow-yaml.md +0 -15
  270. package/tools/cli/installers/lib/ide/templates/workflow-command-template.md +0 -13
  271. package/tools/cli/installers/lib/ide/templates/workflow-commander.md +0 -5
@@ -1,10 +1,13 @@
1
+ const os = require('node:os');
1
2
  const path = require('node:path');
2
3
  const fs = require('fs-extra');
4
+ const yaml = require('yaml');
3
5
  const { BaseIdeSetup } = require('./_base-ide');
4
6
  const prompts = require('../../../lib/prompts');
5
7
  const { AgentCommandGenerator } = require('./shared/agent-command-generator');
6
8
  const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');
7
9
  const { TaskToolCommandGenerator } = require('./shared/task-tool-command-generator');
10
+ const csv = require('csv-parse/sync');
8
11
 
9
12
  /**
10
13
  * Config-driven IDE setup handler
@@ -24,6 +27,34 @@ class ConfigDrivenIdeSetup extends BaseIdeSetup {
24
27
  super(platformCode, platformConfig.name, platformConfig.preferred);
25
28
  this.platformConfig = platformConfig;
26
29
  this.installerConfig = platformConfig.installer || null;
30
+
31
+ // Set configDir from target_dir so base-class detect() works
32
+ if (this.installerConfig?.target_dir) {
33
+ this.configDir = this.installerConfig.target_dir;
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Detect whether this IDE already has configuration in the project.
39
+ * For skill_format platforms, checks for bmad-prefixed entries in target_dir
40
+ * (matching old codex.js behavior) instead of just checking directory existence.
41
+ * @param {string} projectDir - Project directory
42
+ * @returns {Promise<boolean>}
43
+ */
44
+ async detect(projectDir) {
45
+ if (this.installerConfig?.skill_format && this.configDir) {
46
+ const dir = path.join(projectDir || process.cwd(), this.configDir);
47
+ if (await fs.pathExists(dir)) {
48
+ try {
49
+ const entries = await fs.readdir(dir);
50
+ return entries.some((e) => typeof e === 'string' && e.startsWith('bmad'));
51
+ } catch {
52
+ return false;
53
+ }
54
+ }
55
+ return false;
56
+ }
57
+ return super.detect(projectDir);
27
58
  }
28
59
 
29
60
  /**
@@ -34,6 +65,25 @@ class ConfigDrivenIdeSetup extends BaseIdeSetup {
34
65
  * @returns {Promise<Object>} Setup result
35
66
  */
36
67
  async setup(projectDir, bmadDir, options = {}) {
68
+ // Check for BMAD files in ancestor directories that would cause duplicates
69
+ if (this.installerConfig?.ancestor_conflict_check) {
70
+ const conflict = await this.findAncestorConflict(projectDir);
71
+ if (conflict) {
72
+ await prompts.log.error(
73
+ `Found existing BMAD skills in ancestor installation: ${conflict}\n` +
74
+ ` ${this.name} inherits skills from parent directories, so this would cause duplicates.\n` +
75
+ ` Please remove the BMAD files from that directory first:\n` +
76
+ ` rm -rf "${conflict}"/bmad*`,
77
+ );
78
+ return {
79
+ success: false,
80
+ reason: 'ancestor-conflict',
81
+ error: `Ancestor conflict: ${conflict}`,
82
+ conflictDir: conflict,
83
+ };
84
+ }
85
+ }
86
+
37
87
  if (!options.silent) await prompts.log.info(`Setting up ${this.name}...`);
38
88
 
39
89
  // Clean up any old BMAD installation first
@@ -67,39 +117,48 @@ class ConfigDrivenIdeSetup extends BaseIdeSetup {
67
117
  async installToTarget(projectDir, bmadDir, config, options) {
68
118
  const { target_dir, template_type, artifact_types } = config;
69
119
 
70
- // Skip targets with explicitly empty artifact_types array
120
+ // Skip targets with explicitly empty artifact_types and no verbatim skills
71
121
  // This prevents creating empty directories when no artifacts will be written
72
- if (Array.isArray(artifact_types) && artifact_types.length === 0) {
73
- return { success: true, results: { agents: 0, workflows: 0, tasks: 0, tools: 0 } };
122
+ const skipStandardArtifacts = Array.isArray(artifact_types) && artifact_types.length === 0;
123
+ if (skipStandardArtifacts && !config.skill_format) {
124
+ return { success: true, results: { agents: 0, workflows: 0, tasks: 0, tools: 0, skills: 0 } };
74
125
  }
75
126
 
76
127
  const targetPath = path.join(projectDir, target_dir);
77
128
  await this.ensureDir(targetPath);
78
129
 
79
130
  const selectedModules = options.selectedModules || [];
80
- const results = { agents: 0, workflows: 0, tasks: 0, tools: 0 };
131
+ const results = { agents: 0, workflows: 0, tasks: 0, tools: 0, skills: 0 };
132
+
133
+ // Install standard artifacts (agents, workflows, tasks, tools)
134
+ if (!skipStandardArtifacts) {
135
+ // Install agents
136
+ if (!artifact_types || artifact_types.includes('agents')) {
137
+ const agentGen = new AgentCommandGenerator(this.bmadFolderName);
138
+ const { artifacts } = await agentGen.collectAgentArtifacts(bmadDir, selectedModules);
139
+ results.agents = await this.writeAgentArtifacts(targetPath, artifacts, template_type, config);
140
+ }
81
141
 
82
- // Install agents
83
- if (!artifact_types || artifact_types.includes('agents')) {
84
- const agentGen = new AgentCommandGenerator(this.bmadFolderName);
85
- const { artifacts } = await agentGen.collectAgentArtifacts(bmadDir, selectedModules);
86
- results.agents = await this.writeAgentArtifacts(targetPath, artifacts, template_type, config);
87
- }
142
+ // Install workflows
143
+ if (!artifact_types || artifact_types.includes('workflows')) {
144
+ const workflowGen = new WorkflowCommandGenerator(this.bmadFolderName);
145
+ const { artifacts } = await workflowGen.collectWorkflowArtifacts(bmadDir);
146
+ results.workflows = await this.writeWorkflowArtifacts(targetPath, artifacts, template_type, config);
147
+ }
88
148
 
89
- // Install workflows
90
- if (!artifact_types || artifact_types.includes('workflows')) {
91
- const workflowGen = new WorkflowCommandGenerator(this.bmadFolderName);
92
- const { artifacts } = await workflowGen.collectWorkflowArtifacts(bmadDir);
93
- results.workflows = await this.writeWorkflowArtifacts(targetPath, artifacts, template_type, config);
149
+ // Install tasks and tools using template system (supports TOML for Gemini, MD for others)
150
+ if (!artifact_types || artifact_types.includes('tasks') || artifact_types.includes('tools')) {
151
+ const taskToolGen = new TaskToolCommandGenerator(this.bmadFolderName);
152
+ const { artifacts } = await taskToolGen.collectTaskToolArtifacts(bmadDir);
153
+ const taskToolResult = await this.writeTaskToolArtifacts(targetPath, artifacts, template_type, config);
154
+ results.tasks = taskToolResult.tasks || 0;
155
+ results.tools = taskToolResult.tools || 0;
156
+ }
94
157
  }
95
158
 
96
- // Install tasks and tools using template system (supports TOML for Gemini, MD for others)
97
- if (!artifact_types || artifact_types.includes('tasks') || artifact_types.includes('tools')) {
98
- const taskToolGen = new TaskToolCommandGenerator(this.bmadFolderName);
99
- const { artifacts } = await taskToolGen.collectTaskToolArtifacts(bmadDir);
100
- const taskToolResult = await this.writeTaskToolArtifacts(targetPath, artifacts, template_type, config);
101
- results.tasks = taskToolResult.tasks || 0;
102
- results.tools = taskToolResult.tools || 0;
159
+ // Install verbatim skills (type: skill)
160
+ if (config.skill_format) {
161
+ results.skills = await this.installVerbatimSkills(projectDir, bmadDir, targetPath, config);
103
162
  }
104
163
 
105
164
  await this.printSummary(results, target_dir, options);
@@ -115,7 +174,7 @@ class ConfigDrivenIdeSetup extends BaseIdeSetup {
115
174
  * @returns {Promise<Object>} Installation result
116
175
  */
117
176
  async installToMultipleTargets(projectDir, bmadDir, targets, options) {
118
- const allResults = { agents: 0, workflows: 0, tasks: 0, tools: 0 };
177
+ const allResults = { agents: 0, workflows: 0, tasks: 0, tools: 0, skills: 0 };
119
178
 
120
179
  for (const target of targets) {
121
180
  const result = await this.installToTarget(projectDir, bmadDir, target, options);
@@ -124,6 +183,7 @@ class ConfigDrivenIdeSetup extends BaseIdeSetup {
124
183
  allResults.workflows += result.results.workflows || 0;
125
184
  allResults.tasks += result.results.tasks || 0;
126
185
  allResults.tools += result.results.tools || 0;
186
+ allResults.skills += result.results.skills || 0;
127
187
  }
128
188
  }
129
189
 
@@ -146,8 +206,13 @@ class ConfigDrivenIdeSetup extends BaseIdeSetup {
146
206
  for (const artifact of artifacts) {
147
207
  const content = this.renderTemplate(template, artifact);
148
208
  const filename = this.generateFilename(artifact, 'agent', extension);
149
- const filePath = path.join(targetPath, filename);
150
- await this.writeFile(filePath, content);
209
+
210
+ if (config.skill_format) {
211
+ await this.writeSkillFile(targetPath, artifact, content);
212
+ } else {
213
+ const filePath = path.join(targetPath, filename);
214
+ await this.writeFile(filePath, content);
215
+ }
151
216
  count++;
152
217
  }
153
218
 
@@ -167,20 +232,17 @@ class ConfigDrivenIdeSetup extends BaseIdeSetup {
167
232
 
168
233
  for (const artifact of artifacts) {
169
234
  if (artifact.type === 'workflow-command') {
170
- // Use different template based on workflow type (YAML vs MD)
171
- // Default to 'default' template type, but allow override via config
172
- const workflowTemplateType = artifact.isYamlWorkflow
173
- ? config.yaml_workflow_template || `${templateType}-workflow-yaml`
174
- : config.md_workflow_template || `${templateType}-workflow`;
175
-
176
- // Fall back to default templates if specific ones don't exist
177
- const finalTemplateType = artifact.isYamlWorkflow ? 'default-workflow-yaml' : 'default-workflow';
178
- // workflowTemplateType already contains full name (e.g., 'gemini-workflow-yaml'), so pass empty artifactType
179
- const { content: template, extension } = await this.loadTemplate(workflowTemplateType, '', config, finalTemplateType);
235
+ const workflowTemplateType = config.md_workflow_template || `${templateType}-workflow`;
236
+ const { content: template, extension } = await this.loadTemplate(workflowTemplateType, '', config, 'default-workflow');
180
237
  const content = this.renderTemplate(template, artifact);
181
238
  const filename = this.generateFilename(artifact, 'workflow', extension);
182
- const filePath = path.join(targetPath, filename);
183
- await this.writeFile(filePath, content);
239
+
240
+ if (config.skill_format) {
241
+ await this.writeSkillFile(targetPath, artifact, content);
242
+ } else {
243
+ const filePath = path.join(targetPath, filename);
244
+ await this.writeFile(filePath, content);
245
+ }
184
246
  count++;
185
247
  }
186
248
  }
@@ -222,8 +284,13 @@ class ConfigDrivenIdeSetup extends BaseIdeSetup {
222
284
 
223
285
  const content = this.renderTemplate(template, artifact);
224
286
  const filename = this.generateFilename(artifact, artifact.type, extension);
225
- const filePath = path.join(targetPath, filename);
226
- await this.writeFile(filePath, content);
287
+
288
+ if (config.skill_format) {
289
+ await this.writeSkillFile(targetPath, artifact, content);
290
+ } else {
291
+ const filePath = path.join(targetPath, filename);
292
+ await this.writeFile(filePath, content);
293
+ }
227
294
 
228
295
  if (artifact.type === 'task') {
229
296
  taskCount++;
@@ -390,20 +457,144 @@ LOAD and execute from: {project-root}/{{bmadFolderName}}/{{path}}
390
457
  // No default
391
458
  }
392
459
 
393
- let rendered = template
460
+ // Replace _bmad placeholder with actual folder name BEFORE inserting paths,
461
+ // so that paths containing '_bmad' are not corrupted by the blanket replacement.
462
+ let rendered = template.replaceAll('_bmad', this.bmadFolderName);
463
+
464
+ // Replace {{bmadFolderName}} placeholder if present
465
+ rendered = rendered.replaceAll('{{bmadFolderName}}', this.bmadFolderName);
466
+
467
+ rendered = rendered
394
468
  .replaceAll('{{name}}', artifact.name || '')
395
469
  .replaceAll('{{module}}', artifact.module || 'core')
396
470
  .replaceAll('{{path}}', pathToUse)
397
471
  .replaceAll('{{description}}', artifact.description || `${artifact.name} ${artifact.type || ''}`)
398
472
  .replaceAll('{{workflow_path}}', pathToUse);
399
473
 
400
- // Replace _bmad placeholder with actual folder name
401
- rendered = rendered.replaceAll('_bmad', this.bmadFolderName);
474
+ return rendered;
475
+ }
402
476
 
403
- // Replace {{bmadFolderName}} placeholder if present
404
- rendered = rendered.replaceAll('{{bmadFolderName}}', this.bmadFolderName);
477
+ /**
478
+ * Write artifact as a skill directory with SKILL.md inside.
479
+ * Writes artifact as a skill directory with SKILL.md inside.
480
+ * @param {string} targetPath - Base skills directory
481
+ * @param {Object} artifact - Artifact data
482
+ * @param {string} content - Rendered template content
483
+ */
484
+ async writeSkillFile(targetPath, artifact, content) {
485
+ const { resolveSkillName } = require('./shared/path-utils');
405
486
 
406
- return rendered;
487
+ // Get the skill name (prefers canonicalId, falls back to path-derived) and remove .md
488
+ const flatName = resolveSkillName(artifact);
489
+ const skillName = path.basename(flatName.replace(/\.md$/, ''));
490
+
491
+ if (!skillName) {
492
+ throw new Error(`Cannot derive skill name for artifact: ${artifact.relativePath || JSON.stringify(artifact)}`);
493
+ }
494
+
495
+ // Create skill directory
496
+ const skillDir = path.join(targetPath, skillName);
497
+ await this.ensureDir(skillDir);
498
+
499
+ // Transform content: rewrite frontmatter for skills format
500
+ const skillContent = this.transformToSkillFormat(content, skillName);
501
+
502
+ await this.writeFile(path.join(skillDir, 'SKILL.md'), skillContent);
503
+ }
504
+
505
+ /**
506
+ * Transform artifact content to Agent Skills format.
507
+ * Rewrites frontmatter to contain only unquoted name and description.
508
+ * @param {string} content - Original content with YAML frontmatter
509
+ * @param {string} skillName - Skill name (must match directory name)
510
+ * @returns {string} Transformed content
511
+ */
512
+ transformToSkillFormat(content, skillName) {
513
+ // Normalize line endings
514
+ content = content.replaceAll('\r\n', '\n').replaceAll('\r', '\n');
515
+
516
+ // Parse frontmatter
517
+ const fmMatch = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
518
+ if (!fmMatch) {
519
+ // No frontmatter -- wrap with minimal frontmatter
520
+ const fm = yaml.stringify({ name: skillName, description: skillName }).trimEnd();
521
+ return `---\n${fm}\n---\n\n${content}`;
522
+ }
523
+
524
+ const frontmatter = fmMatch[1];
525
+ const body = fmMatch[2];
526
+
527
+ // Parse frontmatter with yaml library to extract description
528
+ let description;
529
+ try {
530
+ const parsed = yaml.parse(frontmatter);
531
+ const rawDesc = parsed?.description;
532
+ description = typeof rawDesc === 'string' && rawDesc ? rawDesc : `${skillName} skill`;
533
+ } catch {
534
+ description = `${skillName} skill`;
535
+ }
536
+
537
+ // Build new frontmatter with only name and description, unquoted
538
+ const newFrontmatter = yaml.stringify({ name: skillName, description: String(description) }, { lineWidth: 0 }).trimEnd();
539
+ return `---\n${newFrontmatter}\n---\n${body}`;
540
+ }
541
+
542
+ /**
543
+ * Install a custom agent launcher.
544
+ * For skill_format platforms, produces <skillDir>/SKILL.md.
545
+ * For flat platforms, produces a single file in target_dir.
546
+ * @param {string} projectDir - Project directory
547
+ * @param {string} agentName - Agent name (e.g., "fred-commit-poet")
548
+ * @param {string} agentPath - Path to compiled agent (relative to project root)
549
+ * @param {Object} metadata - Agent metadata
550
+ * @returns {Object|null} Info about created file/skill
551
+ */
552
+ async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) {
553
+ if (!this.installerConfig?.target_dir) return null;
554
+
555
+ const { customAgentDashName } = require('./shared/path-utils');
556
+ const targetPath = path.join(projectDir, this.installerConfig.target_dir);
557
+ await this.ensureDir(targetPath);
558
+
559
+ // Build artifact to reuse existing template rendering.
560
+ // The default-agent template already includes the _bmad/ prefix before {{path}},
561
+ // but agentPath is relative to project root (e.g. "_bmad/custom/agents/fred.md").
562
+ // Strip the bmadFolderName prefix so the template doesn't produce a double path.
563
+ const bmadPrefix = this.bmadFolderName + '/';
564
+ const normalizedPath = agentPath.startsWith(bmadPrefix) ? agentPath.slice(bmadPrefix.length) : agentPath;
565
+
566
+ const artifact = {
567
+ type: 'agent-launcher',
568
+ name: agentName,
569
+ description: metadata?.description || `${agentName} agent`,
570
+ agentPath: normalizedPath,
571
+ relativePath: normalizedPath,
572
+ module: 'custom',
573
+ };
574
+
575
+ const { content: template } = await this.loadTemplate(
576
+ this.installerConfig.template_type || 'default',
577
+ 'agent',
578
+ this.installerConfig,
579
+ 'default-agent',
580
+ );
581
+ const content = this.renderTemplate(template, artifact);
582
+
583
+ if (this.installerConfig.skill_format) {
584
+ const skillName = customAgentDashName(agentName).replace(/\.md$/, '');
585
+ const skillDir = path.join(targetPath, skillName);
586
+ await this.ensureDir(skillDir);
587
+ const skillContent = this.transformToSkillFormat(content, skillName);
588
+ const skillPath = path.join(skillDir, 'SKILL.md');
589
+ await this.writeFile(skillPath, skillContent);
590
+ return { path: path.relative(projectDir, skillPath), command: `$${skillName}` };
591
+ }
592
+
593
+ // Flat file output
594
+ const filename = customAgentDashName(agentName);
595
+ const filePath = path.join(targetPath, filename);
596
+ await this.writeFile(filePath, content);
597
+ return { path: path.relative(projectDir, filePath), command: agentName };
407
598
  }
408
599
 
409
600
  /**
@@ -414,10 +605,11 @@ LOAD and execute from: {project-root}/{{bmadFolderName}}/{{path}}
414
605
  * @returns {string} Generated filename
415
606
  */
416
607
  generateFilename(artifact, artifactType, extension = '.md') {
417
- const { toDashPath } = require('./shared/path-utils');
608
+ const { resolveSkillName } = require('./shared/path-utils');
418
609
 
419
610
  // Reuse central logic to ensure consistent naming conventions
420
- const standardName = toDashPath(artifact.relativePath);
611
+ // Prefers canonicalId from manifest when available, falls back to path-derived name
612
+ const standardName = resolveSkillName(artifact);
421
613
 
422
614
  // Clean up potential double extensions from source files (e.g. .yaml.md, .xml.md -> .md)
423
615
  // This handles any extensions that might slip through toDashPath()
@@ -433,6 +625,80 @@ LOAD and execute from: {project-root}/{{bmadFolderName}}/{{path}}
433
625
  return baseName.replace(/\.md$/, extension);
434
626
  }
435
627
 
628
+ /**
629
+ * Install verbatim skill directories (type: skill entries from skill-manifest.csv).
630
+ * Copies the entire source directory as-is into the IDE skill directory.
631
+ * The source SKILL.md is used directly — no frontmatter transformation or file generation.
632
+ * @param {string} projectDir - Project directory
633
+ * @param {string} bmadDir - BMAD installation directory
634
+ * @param {string} targetPath - Target skills directory
635
+ * @param {Object} config - Installation configuration
636
+ * @returns {Promise<number>} Count of skills installed
637
+ */
638
+ async installVerbatimSkills(projectDir, bmadDir, targetPath, config) {
639
+ const bmadFolderName = path.basename(bmadDir);
640
+ const bmadPrefix = bmadFolderName + '/';
641
+ const csvPath = path.join(bmadDir, '_config', 'skill-manifest.csv');
642
+
643
+ if (!(await fs.pathExists(csvPath))) return 0;
644
+
645
+ const csvContent = await fs.readFile(csvPath, 'utf8');
646
+ const records = csv.parse(csvContent, {
647
+ columns: true,
648
+ skip_empty_lines: true,
649
+ });
650
+
651
+ let count = 0;
652
+
653
+ for (const record of records) {
654
+ const canonicalId = record.canonicalId;
655
+ if (!canonicalId) continue;
656
+
657
+ // Derive source directory from path column
658
+ // path is like "_bmad/bmm/workflows/bmad-quick-flow/bmad-quick-dev-new-preview/SKILL.md"
659
+ // Strip bmadFolderName prefix and join with bmadDir, then get dirname
660
+ const relativePath = record.path.startsWith(bmadPrefix) ? record.path.slice(bmadPrefix.length) : record.path;
661
+ const sourceFile = path.join(bmadDir, relativePath);
662
+ const sourceDir = path.dirname(sourceFile);
663
+
664
+ if (!(await fs.pathExists(sourceDir))) continue;
665
+
666
+ // Clean target before copy to prevent stale files
667
+ const skillDir = path.join(targetPath, canonicalId);
668
+ await fs.remove(skillDir);
669
+ await fs.ensureDir(skillDir);
670
+
671
+ // Copy all skill files, filtering OS/editor artifacts recursively
672
+ const skipPatterns = new Set(['.DS_Store', 'Thumbs.db', 'desktop.ini']);
673
+ const skipSuffixes = ['~', '.swp', '.swo', '.bak'];
674
+ const filter = (src) => {
675
+ const name = path.basename(src);
676
+ if (src === sourceDir) return true;
677
+ if (skipPatterns.has(name)) return false;
678
+ if (name.startsWith('.') && name !== '.gitkeep') return false;
679
+ if (skipSuffixes.some((s) => name.endsWith(s))) return false;
680
+ return true;
681
+ };
682
+ await fs.copy(sourceDir, skillDir, { filter });
683
+
684
+ count++;
685
+ }
686
+
687
+ // Post-install cleanup: remove _bmad/ directories for skills with install_to_bmad === "false"
688
+ for (const record of records) {
689
+ if (record.install_to_bmad === 'false') {
690
+ const relativePath = record.path.startsWith(bmadPrefix) ? record.path.slice(bmadPrefix.length) : record.path;
691
+ const sourceFile = path.join(bmadDir, relativePath);
692
+ const sourceDir = path.dirname(sourceFile);
693
+ if (await fs.pathExists(sourceDir)) {
694
+ await fs.remove(sourceDir);
695
+ }
696
+ }
697
+ }
698
+
699
+ return count;
700
+ }
701
+
436
702
  /**
437
703
  * Print installation summary
438
704
  * @param {Object} results - Installation results
@@ -445,6 +711,7 @@ LOAD and execute from: {project-root}/{{bmadFolderName}}/{{path}}
445
711
  if (results.workflows > 0) parts.push(`${results.workflows} workflows`);
446
712
  if (results.tasks > 0) parts.push(`${results.tasks} tasks`);
447
713
  if (results.tools > 0) parts.push(`${results.tools} tools`);
714
+ if (results.skills > 0) parts.push(`${results.skills} skills`);
448
715
  await prompts.log.success(`${this.name} configured: ${parts.join(', ')} → ${targetDir}`);
449
716
  }
450
717
 
@@ -453,6 +720,34 @@ LOAD and execute from: {project-root}/{{bmadFolderName}}/{{path}}
453
720
  * @param {string} projectDir - Project directory
454
721
  */
455
722
  async cleanup(projectDir, options = {}) {
723
+ // Migrate legacy target directories (e.g. .opencode/agent → .opencode/agents)
724
+ if (this.installerConfig?.legacy_targets) {
725
+ if (!options.silent) await prompts.log.message(' Migrating legacy directories...');
726
+ for (const legacyDir of this.installerConfig.legacy_targets) {
727
+ if (this.isGlobalPath(legacyDir)) {
728
+ await this.warnGlobalLegacy(legacyDir, options);
729
+ } else {
730
+ await this.cleanupTarget(projectDir, legacyDir, options);
731
+ await this.removeEmptyParents(projectDir, legacyDir);
732
+ }
733
+ }
734
+ }
735
+
736
+ // Strip BMAD markers from copilot-instructions.md if present
737
+ if (this.name === 'github-copilot') {
738
+ await this.cleanupCopilotInstructions(projectDir, options);
739
+ }
740
+
741
+ // Strip BMAD modes from .kilocodemodes if present
742
+ if (this.name === 'kilo') {
743
+ await this.cleanupKiloModes(projectDir, options);
744
+ }
745
+
746
+ // Strip BMAD entries from .rovodev/prompts.yml if present
747
+ if (this.name === 'rovo-dev') {
748
+ await this.cleanupRovoDevPrompts(projectDir, options);
749
+ }
750
+
456
751
  // Clean all target directories
457
752
  if (this.installerConfig?.targets) {
458
753
  const parentDirs = new Set();
@@ -473,6 +768,41 @@ LOAD and execute from: {project-root}/{{bmadFolderName}}/{{path}}
473
768
  }
474
769
  }
475
770
 
771
+ /**
772
+ * Check if a path is global (starts with ~ or is absolute)
773
+ * @param {string} p - Path to check
774
+ * @returns {boolean}
775
+ */
776
+ isGlobalPath(p) {
777
+ return p.startsWith('~') || path.isAbsolute(p);
778
+ }
779
+
780
+ /**
781
+ * Warn about stale BMAD files in a global legacy directory (never auto-deletes)
782
+ * @param {string} legacyDir - Legacy directory path (may start with ~)
783
+ * @param {Object} options - Options (silent, etc.)
784
+ */
785
+ async warnGlobalLegacy(legacyDir, options = {}) {
786
+ try {
787
+ const expanded = legacyDir.startsWith('~/')
788
+ ? path.join(os.homedir(), legacyDir.slice(2))
789
+ : legacyDir === '~'
790
+ ? os.homedir()
791
+ : legacyDir;
792
+
793
+ if (!(await fs.pathExists(expanded))) return;
794
+
795
+ const entries = await fs.readdir(expanded);
796
+ const bmadFiles = entries.filter((e) => typeof e === 'string' && e.startsWith('bmad'));
797
+
798
+ if (bmadFiles.length > 0 && !options.silent) {
799
+ await prompts.log.warn(`Found ${bmadFiles.length} stale BMAD file(s) in ${expanded}. Remove manually: rm ${expanded}/bmad-*`);
800
+ }
801
+ } catch {
802
+ // Errors reading global paths are silently ignored
803
+ }
804
+ }
805
+
476
806
  /**
477
807
  * Cleanup a specific target directory
478
808
  * @param {string} projectDir - Project directory
@@ -504,7 +834,7 @@ LOAD and execute from: {project-root}/{{bmadFolderName}}/{{path}}
504
834
  if (!entry || typeof entry !== 'string') {
505
835
  continue;
506
836
  }
507
- if (entry.startsWith('bmad')) {
837
+ if (entry.startsWith('bmad') && !entry.startsWith('bmad-os-')) {
508
838
  const entryPath = path.join(targetPath, entry);
509
839
  try {
510
840
  await fs.remove(entryPath);
@@ -532,24 +862,188 @@ LOAD and execute from: {project-root}/{{bmadFolderName}}/{{path}}
532
862
  }
533
863
  }
534
864
  /**
535
- * Recursively remove empty directories walking up from dir toward projectDir
865
+ * Strip BMAD-owned content from .github/copilot-instructions.md.
866
+ * The old custom installer injected content between <!-- BMAD:START --> and <!-- BMAD:END --> markers.
867
+ * Deletes the file if nothing remains. Restores .bak backup if one exists.
868
+ */
869
+ async cleanupCopilotInstructions(projectDir, options = {}) {
870
+ const filePath = path.join(projectDir, '.github', 'copilot-instructions.md');
871
+
872
+ if (!(await fs.pathExists(filePath))) return;
873
+
874
+ try {
875
+ const content = await fs.readFile(filePath, 'utf8');
876
+ const startIdx = content.indexOf('<!-- BMAD:START -->');
877
+ const endIdx = content.indexOf('<!-- BMAD:END -->');
878
+
879
+ if (startIdx === -1 || endIdx === -1 || endIdx <= startIdx) return;
880
+
881
+ const cleaned = content.slice(0, startIdx) + content.slice(endIdx + '<!-- BMAD:END -->'.length);
882
+
883
+ if (cleaned.trim().length === 0) {
884
+ await fs.remove(filePath);
885
+ const backupPath = `${filePath}.bak`;
886
+ if (await fs.pathExists(backupPath)) {
887
+ await fs.rename(backupPath, filePath);
888
+ if (!options.silent) await prompts.log.message(' Restored copilot-instructions.md from backup');
889
+ }
890
+ } else {
891
+ await fs.writeFile(filePath, cleaned, 'utf8');
892
+ const backupPath = `${filePath}.bak`;
893
+ if (await fs.pathExists(backupPath)) await fs.remove(backupPath);
894
+ }
895
+
896
+ if (!options.silent) await prompts.log.message(' Cleaned BMAD markers from copilot-instructions.md');
897
+ } catch {
898
+ if (!options.silent) await prompts.log.warn(' Warning: Could not clean BMAD markers from copilot-instructions.md');
899
+ }
900
+ }
901
+
902
+ /**
903
+ * Strip BMAD-owned modes from .kilocodemodes.
904
+ * The old custom kilo.js installer added modes with slug starting with 'bmad-'.
905
+ * Parses YAML, filters out BMAD modes, rewrites. Leaves file as-is on parse failure.
906
+ */
907
+ async cleanupKiloModes(projectDir, options = {}) {
908
+ const kiloModesPath = path.join(projectDir, '.kilocodemodes');
909
+
910
+ if (!(await fs.pathExists(kiloModesPath))) return;
911
+
912
+ const content = await fs.readFile(kiloModesPath, 'utf8');
913
+
914
+ let config;
915
+ try {
916
+ config = yaml.parse(content) || {};
917
+ } catch {
918
+ if (!options.silent) await prompts.log.warn(' Warning: Could not parse .kilocodemodes for cleanup');
919
+ return;
920
+ }
921
+
922
+ if (!Array.isArray(config.customModes)) return;
923
+
924
+ const originalCount = config.customModes.length;
925
+ config.customModes = config.customModes.filter((mode) => mode && (!mode.slug || !mode.slug.startsWith('bmad-')));
926
+ const removedCount = originalCount - config.customModes.length;
927
+
928
+ if (removedCount > 0) {
929
+ try {
930
+ await fs.writeFile(kiloModesPath, yaml.stringify(config, { lineWidth: 0 }));
931
+ if (!options.silent) await prompts.log.message(` Removed ${removedCount} BMAD modes from .kilocodemodes`);
932
+ } catch {
933
+ if (!options.silent) await prompts.log.warn(' Warning: Could not write .kilocodemodes during cleanup');
934
+ }
935
+ }
936
+ }
937
+
938
+ /**
939
+ * Strip BMAD-owned entries from .rovodev/prompts.yml.
940
+ * The old custom rovodev.js installer registered workflows in prompts.yml.
941
+ * Parses YAML, filters out entries with name starting with 'bmad-', rewrites.
942
+ * Removes the file if no entries remain.
943
+ */
944
+ async cleanupRovoDevPrompts(projectDir, options = {}) {
945
+ const promptsPath = path.join(projectDir, '.rovodev', 'prompts.yml');
946
+
947
+ if (!(await fs.pathExists(promptsPath))) return;
948
+
949
+ const content = await fs.readFile(promptsPath, 'utf8');
950
+
951
+ let config;
952
+ try {
953
+ config = yaml.parse(content) || {};
954
+ } catch {
955
+ if (!options.silent) await prompts.log.warn(' Warning: Could not parse prompts.yml for cleanup');
956
+ return;
957
+ }
958
+
959
+ if (!Array.isArray(config.prompts)) return;
960
+
961
+ const originalCount = config.prompts.length;
962
+ config.prompts = config.prompts.filter((entry) => entry && (!entry.name || !entry.name.startsWith('bmad-')));
963
+ const removedCount = originalCount - config.prompts.length;
964
+
965
+ if (removedCount > 0) {
966
+ try {
967
+ if (config.prompts.length === 0) {
968
+ await fs.remove(promptsPath);
969
+ } else {
970
+ await fs.writeFile(promptsPath, yaml.stringify(config, { lineWidth: 0 }));
971
+ }
972
+ if (!options.silent) await prompts.log.message(` Removed ${removedCount} BMAD entries from prompts.yml`);
973
+ } catch {
974
+ if (!options.silent) await prompts.log.warn(' Warning: Could not write prompts.yml during cleanup');
975
+ }
976
+ }
977
+ }
978
+
979
+ /**
980
+ * Check ancestor directories for existing BMAD files in the same target_dir.
981
+ * IDEs like Claude Code inherit commands from parent directories, so an existing
982
+ * installation in an ancestor would cause duplicate commands.
983
+ * @param {string} projectDir - Project directory being installed to
984
+ * @returns {Promise<string|null>} Path to conflicting directory, or null if clean
985
+ */
986
+ async findAncestorConflict(projectDir) {
987
+ const targetDir = this.installerConfig?.target_dir;
988
+ if (!targetDir) return null;
989
+
990
+ const resolvedProject = await fs.realpath(path.resolve(projectDir));
991
+ let current = path.dirname(resolvedProject);
992
+ const root = path.parse(current).root;
993
+
994
+ while (current !== root && current.length > root.length) {
995
+ const candidatePath = path.join(current, targetDir);
996
+ try {
997
+ if (await fs.pathExists(candidatePath)) {
998
+ const entries = await fs.readdir(candidatePath);
999
+ const hasBmad = entries.some(
1000
+ (e) => typeof e === 'string' && e.toLowerCase().startsWith('bmad') && !e.toLowerCase().startsWith('bmad-os-'),
1001
+ );
1002
+ if (hasBmad) {
1003
+ return candidatePath;
1004
+ }
1005
+ }
1006
+ } catch {
1007
+ // Can't read directory — skip
1008
+ }
1009
+ current = path.dirname(current);
1010
+ }
1011
+
1012
+ return null;
1013
+ }
1014
+
1015
+ /**
1016
+ * Walk up ancestor directories from relativeDir toward projectDir, removing each if empty
536
1017
  * Stops at projectDir boundary — never removes projectDir itself
537
1018
  * @param {string} projectDir - Project root (boundary)
538
1019
  * @param {string} relativeDir - Relative directory to start from
539
1020
  */
540
1021
  async removeEmptyParents(projectDir, relativeDir) {
1022
+ const resolvedProject = path.resolve(projectDir);
541
1023
  let current = relativeDir;
542
1024
  let last = null;
543
1025
  while (current && current !== '.' && current !== last) {
544
1026
  last = current;
545
- const fullPath = path.join(projectDir, current);
1027
+ const fullPath = path.resolve(projectDir, current);
1028
+ // Boundary guard: never traverse outside projectDir
1029
+ if (!fullPath.startsWith(resolvedProject + path.sep) && fullPath !== resolvedProject) break;
546
1030
  try {
547
- if (!(await fs.pathExists(fullPath))) break;
1031
+ if (!(await fs.pathExists(fullPath))) {
1032
+ // Dir already gone — advance current; last is reset at top of next iteration
1033
+ current = path.dirname(current);
1034
+ continue;
1035
+ }
548
1036
  const remaining = await fs.readdir(fullPath);
549
1037
  if (remaining.length > 0) break;
550
1038
  await fs.rmdir(fullPath);
551
- } catch {
552
- break;
1039
+ } catch (error) {
1040
+ // ENOTEMPTY: TOCTOU race (file added between readdir and rmdir) — skip level, continue upward
1041
+ // ENOENT: dir removed by another process between pathExists and rmdir — skip level, continue upward
1042
+ if (error.code === 'ENOTEMPTY' || error.code === 'ENOENT') {
1043
+ current = path.dirname(current);
1044
+ continue;
1045
+ }
1046
+ break; // fatal error (e.g. EACCES) — stop upward walk
553
1047
  }
554
1048
  current = path.dirname(current);
555
1049
  }