bmad-method-quarkus 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 (380) hide show
  1. package/.claude-plugin/marketplace.json +53 -0
  2. package/AGENTS.md +12 -0
  3. package/LICENSE +30 -0
  4. package/README.md +94 -0
  5. package/README_CN.md +108 -0
  6. package/README_VN.md +109 -0
  7. package/bmad-modules.yaml +133 -0
  8. package/package.json +123 -0
  9. package/removals.txt +87 -0
  10. package/src/bmm-skills/agents/bmad-agent-analyst/SKILL.md +76 -0
  11. package/src/bmm-skills/agents/bmad-agent-analyst/customize.toml +105 -0
  12. package/src/bmm-skills/agents/bmad-agent-architect/SKILL.md +76 -0
  13. package/src/bmm-skills/agents/bmad-agent-architect/customize.toml +65 -0
  14. package/src/bmm-skills/agents/bmad-agent-dev/SKILL.md +76 -0
  15. package/src/bmm-skills/agents/bmad-agent-dev/customize.toml +83 -0
  16. package/src/bmm-skills/agents/bmad-agent-pm/SKILL.md +76 -0
  17. package/src/bmm-skills/agents/bmad-agent-pm/customize.toml +75 -0
  18. package/src/bmm-skills/agents/bmad-agent-ux-designer/SKILL.md +76 -0
  19. package/src/bmm-skills/agents/bmad-agent-ux-designer/customize.toml +60 -0
  20. package/src/bmm-skills/agents/bmad-quarkus-architect/.memlog.md +12 -0
  21. package/src/bmm-skills/agents/bmad-quarkus-architect/.memlog.md:Zone.Identifier +0 -0
  22. package/src/bmm-skills/agents/bmad-quarkus-architect/SKILL.md +86 -0
  23. package/src/bmm-skills/agents/bmad-quarkus-architect/SKILL.md:Zone.Identifier +0 -0
  24. package/src/bmm-skills/agents/bmad-quarkus-architect/customize.toml +37 -0
  25. package/src/bmm-skills/agents/bmad-quarkus-architect/customize.toml:Zone.Identifier +0 -0
  26. package/src/bmm-skills/agents/bmad-quarkus-architect/references/enrich-stories.md +19 -0
  27. package/src/bmm-skills/agents/bmad-quarkus-architect/references/enrich-stories.md:Zone.Identifier +0 -0
  28. package/src/bmm-skills/agents/bmad-quarkus-architect/references/prompt-quality-canon.md +79 -0
  29. package/src/bmm-skills/agents/bmad-quarkus-architect/references/prompt-quality-canon.md:Zone.Identifier +0 -0
  30. package/src/bmm-skills/agents/bmad-quarkus-architect/skills/bqa-setup/SKILL.md +80 -0
  31. package/src/bmm-skills/agents/bmad-quarkus-architect/skills/bqa-setup/SKILL.md:Zone.Identifier +0 -0
  32. package/src/bmm-skills/agents/bmad-quarkus-architect/skills/bqa-setup/assets/module-help.csv +9 -0
  33. package/src/bmm-skills/agents/bmad-quarkus-architect/skills/bqa-setup/assets/module-help.csv:Zone.Identifier +0 -0
  34. package/src/bmm-skills/agents/bmad-quarkus-architect/skills/bqa-setup/assets/module.yaml +16 -0
  35. package/src/bmm-skills/agents/bmad-quarkus-architect/skills/bqa-setup/assets/module.yaml:Zone.Identifier +0 -0
  36. package/src/bmm-skills/agents/bmad-quarkus-architect/skills/bqa-setup/scripts/cleanup-legacy.py +287 -0
  37. package/src/bmm-skills/agents/bmad-quarkus-architect/skills/bqa-setup/scripts/cleanup-legacy.py:Zone.Identifier +0 -0
  38. package/src/bmm-skills/agents/bmad-quarkus-architect/skills/bqa-setup/scripts/merge-config.py +441 -0
  39. package/src/bmm-skills/agents/bmad-quarkus-architect/skills/bqa-setup/scripts/merge-config.py:Zone.Identifier +0 -0
  40. package/src/bmm-skills/agents/bmad-quarkus-architect/skills/bqa-setup/scripts/merge-help-csv.py +246 -0
  41. package/src/bmm-skills/agents/bmad-quarkus-architect/skills/bqa-setup/scripts/merge-help-csv.py:Zone.Identifier +0 -0
  42. package/src/bmm-skills/agents/bmad-quarkus-architect/skills/quarkus-error-handling-i18n/SKILL.md +181 -0
  43. package/src/bmm-skills/agents/bmad-quarkus-architect/skills/quarkus-error-handling-i18n/SKILL.md:Zone.Identifier +0 -0
  44. package/src/bmm-skills/agents/bmad-quarkus-architect/skills/quarkus-grpc-services/SKILL.md +141 -0
  45. package/src/bmm-skills/agents/bmad-quarkus-architect/skills/quarkus-grpc-services/SKILL.md:Zone.Identifier +0 -0
  46. package/src/bmm-skills/agents/bmad-quarkus-architect/skills/quarkus-hexagonal-core/SKILL.md +392 -0
  47. package/src/bmm-skills/agents/bmad-quarkus-architect/skills/quarkus-hexagonal-core/SKILL.md:Zone.Identifier +0 -0
  48. package/src/bmm-skills/agents/bmad-quarkus-architect/skills/quarkus-kafka-messaging/SKILL.md +148 -0
  49. package/src/bmm-skills/agents/bmad-quarkus-architect/skills/quarkus-kafka-messaging/SKILL.md:Zone.Identifier +0 -0
  50. package/src/bmm-skills/agents/bmad-quarkus-architect/skills/quarkus-observability-otel/SKILL.md +180 -0
  51. package/src/bmm-skills/agents/bmad-quarkus-architect/skills/quarkus-observability-otel/SKILL.md:Zone.Identifier +0 -0
  52. package/src/bmm-skills/agents/bmad-quarkus-architect/skills/quarkus-openapi-tmforum/SKILL.md +128 -0
  53. package/src/bmm-skills/agents/bmad-quarkus-architect/skills/quarkus-openapi-tmforum/SKILL.md:Zone.Identifier +0 -0
  54. package/src/bmm-skills/agents/bmad-quarkus-architect/skills/quarkus-sql-jdbc-agroal/SKILL.md +245 -0
  55. package/src/bmm-skills/agents/bmad-quarkus-architect/skills/quarkus-sql-jdbc-agroal/SKILL.md:Zone.Identifier +0 -0
  56. package/src/bmm-skills/module-help.csv +19 -0
  57. package/src/bmm-skills/module.yaml +92 -0
  58. package/src/bmm-skills/plan/bmad-architecture/SKILL.md +85 -0
  59. package/src/bmm-skills/plan/bmad-architecture/assets/spine-template.md +79 -0
  60. package/src/bmm-skills/plan/bmad-architecture/customize.toml +102 -0
  61. package/src/bmm-skills/plan/bmad-architecture/references/headless.md +26 -0
  62. package/src/bmm-skills/plan/bmad-architecture/references/reviewer-gate.md +13 -0
  63. package/src/bmm-skills/plan/bmad-architecture/scripts/lint_spine.py +257 -0
  64. package/src/bmm-skills/plan/bmad-architecture/scripts/tests/test_lint_spine.py +270 -0
  65. package/src/bmm-skills/plan/bmad-create-epics-and-stories/SKILL.md +93 -0
  66. package/src/bmm-skills/plan/bmad-create-epics-and-stories/customize.toml +41 -0
  67. package/src/bmm-skills/plan/bmad-create-epics-and-stories/steps/step-01-validate-prerequisites.md +263 -0
  68. package/src/bmm-skills/plan/bmad-create-epics-and-stories/steps/step-02-design-epics.md +242 -0
  69. package/src/bmm-skills/plan/bmad-create-epics-and-stories/steps/step-03-create-stories.md +255 -0
  70. package/src/bmm-skills/plan/bmad-create-epics-and-stories/steps/step-04-final-validation.md +143 -0
  71. package/src/bmm-skills/plan/bmad-create-epics-and-stories/templates/epics-template.md +61 -0
  72. package/src/bmm-skills/plan/bmad-generate-project-context/SKILL.md +10 -0
  73. package/src/bmm-skills/plan/bmad-prd/SKILL.md +94 -0
  74. package/src/bmm-skills/plan/bmad-prd/assets/headless-schemas.md +76 -0
  75. package/src/bmm-skills/plan/bmad-prd/assets/prd-template.md +165 -0
  76. package/src/bmm-skills/plan/bmad-prd/assets/prd-validation-checklist.md +135 -0
  77. package/src/bmm-skills/plan/bmad-prd/assets/validation-report-template.html +325 -0
  78. package/src/bmm-skills/plan/bmad-prd/customize.toml +149 -0
  79. package/src/bmm-skills/plan/bmad-prd/references/headless.md +39 -0
  80. package/src/bmm-skills/plan/bmad-prd/references/validate.md +97 -0
  81. package/src/bmm-skills/plan/bmad-prfaq/SKILL.md +135 -0
  82. package/src/bmm-skills/plan/bmad-prfaq/agents/artifact-analyzer.md +60 -0
  83. package/src/bmm-skills/plan/bmad-prfaq/agents/web-researcher.md +49 -0
  84. package/src/bmm-skills/plan/bmad-prfaq/assets/prfaq-template.md +62 -0
  85. package/src/bmm-skills/plan/bmad-prfaq/bmad-manifest.json +16 -0
  86. package/src/bmm-skills/plan/bmad-prfaq/customize.toml +41 -0
  87. package/src/bmm-skills/plan/bmad-prfaq/references/customer-faq.md +55 -0
  88. package/src/bmm-skills/plan/bmad-prfaq/references/internal-faq.md +51 -0
  89. package/src/bmm-skills/plan/bmad-prfaq/references/press-release.md +60 -0
  90. package/src/bmm-skills/plan/bmad-prfaq/references/verdict.md +83 -0
  91. package/src/bmm-skills/plan/bmad-product-brief/SKILL.md +91 -0
  92. package/src/bmm-skills/plan/bmad-product-brief/assets/brief-template.md +41 -0
  93. package/src/bmm-skills/plan/bmad-product-brief/customize.toml +101 -0
  94. package/src/bmm-skills/plan/bmad-project-context/SKILL.md +110 -0
  95. package/src/bmm-skills/plan/bmad-project-context/customize.toml +24 -0
  96. package/src/bmm-skills/plan/bmad-project-context/references/best-practices.md +65 -0
  97. package/src/bmm-skills/plan/bmad-project-context/references/template.md +55 -0
  98. package/src/bmm-skills/plan/bmad-spec/SKILL.md +160 -0
  99. package/src/bmm-skills/plan/bmad-spec/assets/headless-schemas.md +33 -0
  100. package/src/bmm-skills/plan/bmad-spec/assets/spec-template.md +49 -0
  101. package/src/bmm-skills/plan/bmad-spec/assets/stories-schema.md +44 -0
  102. package/src/bmm-skills/plan/bmad-spec/customize.toml +52 -0
  103. package/src/bmm-skills/plan/bmad-sprint-planning/SKILL.md +62 -0
  104. package/src/bmm-skills/plan/bmad-sprint-planning/customize.toml +41 -0
  105. package/src/bmm-skills/plan/bmad-sprint-planning/references/fix-sprint-status.md +30 -0
  106. package/src/bmm-skills/plan/bmad-sprint-planning/references/generate-tracking.md +25 -0
  107. package/src/bmm-skills/plan/bmad-sprint-planning/references/readiness-gate.md +20 -0
  108. package/src/bmm-skills/plan/bmad-sprint-planning/references/status-view.md +14 -0
  109. package/src/bmm-skills/plan/bmad-sprint-planning/references/validate.md +10 -0
  110. package/src/bmm-skills/plan/bmad-sprint-planning/scripts/__pycache__/sprint_plan.cpython-311.pyc +0 -0
  111. package/src/bmm-skills/plan/bmad-sprint-planning/scripts/sprint_plan.py +697 -0
  112. package/src/bmm-skills/plan/bmad-sprint-planning/scripts/tests/__pycache__/test_sprint_plan.cpython-311-pytest-9.1.1.pyc +0 -0
  113. package/src/bmm-skills/plan/bmad-sprint-planning/scripts/tests/test_sprint_plan.py +524 -0
  114. package/src/bmm-skills/plan/bmad-sprint-planning/sprint-status-template.yaml +71 -0
  115. package/src/bmm-skills/plan/bmad-ux/SKILL.md +90 -0
  116. package/src/bmm-skills/plan/bmad-ux/assets/color-themes.md +9 -0
  117. package/src/bmm-skills/plan/bmad-ux/assets/design-directions.md +9 -0
  118. package/src/bmm-skills/plan/bmad-ux/assets/design-example-editorial.md +158 -0
  119. package/src/bmm-skills/plan/bmad-ux/assets/design-example-mobile.md +93 -0
  120. package/src/bmm-skills/plan/bmad-ux/assets/design-example-shadcn.md +109 -0
  121. package/src/bmm-skills/plan/bmad-ux/assets/excalidraw-wireframe.md +19 -0
  122. package/src/bmm-skills/plan/bmad-ux/assets/experience-example-mobile.md +112 -0
  123. package/src/bmm-skills/plan/bmad-ux/assets/experience-example-shadcn.md +133 -0
  124. package/src/bmm-skills/plan/bmad-ux/assets/headless-schemas.md +84 -0
  125. package/src/bmm-skills/plan/bmad-ux/assets/key-screens.md +29 -0
  126. package/src/bmm-skills/plan/bmad-ux/assets/validation-report-template.html +319 -0
  127. package/src/bmm-skills/plan/bmad-ux/customize.toml +102 -0
  128. package/src/bmm-skills/plan/bmad-ux/references/creative-tools.md +19 -0
  129. package/src/bmm-skills/plan/bmad-ux/references/design-md-spec.md +50 -0
  130. package/src/bmm-skills/plan/bmad-ux/references/headless.md +37 -0
  131. package/src/bmm-skills/plan/bmad-ux/references/validate.md +115 -0
  132. package/src/bmm-skills/ship/bmad-build/SKILL.md +13 -0
  133. package/src/bmm-skills/ship/bmad-build/compile-epic-context.md +62 -0
  134. package/src/bmm-skills/ship/bmad-build/customize.toml +164 -0
  135. package/src/bmm-skills/ship/bmad-build/references/deletion-check.md +14 -0
  136. package/src/bmm-skills/ship/bmad-build/review-prompts/edge-case-hunter.md +88 -0
  137. package/src/bmm-skills/ship/bmad-build/review-prompts/verification-gap.md +113 -0
  138. package/src/bmm-skills/ship/bmad-build/spec-template.md +89 -0
  139. package/src/bmm-skills/ship/bmad-build/step-01-clarify-and-route.md +105 -0
  140. package/src/bmm-skills/ship/bmad-build/step-02-plan.md +47 -0
  141. package/src/bmm-skills/ship/bmad-build/step-03-implement.md +49 -0
  142. package/src/bmm-skills/ship/bmad-build/step-04-review.md +55 -0
  143. package/src/bmm-skills/ship/bmad-build/step-05-present.md +75 -0
  144. package/src/bmm-skills/ship/bmad-build/step-oneshot.md +77 -0
  145. package/src/bmm-skills/ship/bmad-build/sync-sprint-status.md +19 -0
  146. package/src/bmm-skills/ship/bmad-build/workflow.md +84 -0
  147. package/src/bmm-skills/ship/bmad-build-auto/SKILL.md +13 -0
  148. package/src/bmm-skills/ship/bmad-build-auto/compile-epic-context.md +62 -0
  149. package/src/bmm-skills/ship/bmad-build-auto/customize.toml +121 -0
  150. package/src/bmm-skills/ship/bmad-build-auto/references/deletion-check.md +14 -0
  151. package/src/bmm-skills/ship/bmad-build-auto/review-prompts/edge-case-hunter.md +88 -0
  152. package/src/bmm-skills/ship/bmad-build-auto/review-prompts/verification-gap.md +113 -0
  153. package/src/bmm-skills/ship/bmad-build-auto/spec-template.md +99 -0
  154. package/src/bmm-skills/ship/bmad-build-auto/step-01-clarify-and-route.md +83 -0
  155. package/src/bmm-skills/ship/bmad-build-auto/step-02-plan.md +27 -0
  156. package/src/bmm-skills/ship/bmad-build-auto/step-03-implement.md +44 -0
  157. package/src/bmm-skills/ship/bmad-build-auto/step-04-review.md +96 -0
  158. package/src/bmm-skills/ship/bmad-build-auto/workflow.md +104 -0
  159. package/src/bmm-skills/ship/bmad-checkpoint-preview/SKILL.md +68 -0
  160. package/src/bmm-skills/ship/bmad-checkpoint-preview/customize.toml +41 -0
  161. package/src/bmm-skills/ship/bmad-checkpoint-preview/generate-trail.md +38 -0
  162. package/src/bmm-skills/ship/bmad-checkpoint-preview/step-01-orientation.md +105 -0
  163. package/src/bmm-skills/ship/bmad-checkpoint-preview/step-02-walkthrough.md +89 -0
  164. package/src/bmm-skills/ship/bmad-checkpoint-preview/step-03-detail-pass.md +106 -0
  165. package/src/bmm-skills/ship/bmad-checkpoint-preview/step-04-testing.md +74 -0
  166. package/src/bmm-skills/ship/bmad-checkpoint-preview/step-05-wrapup.md +30 -0
  167. package/src/bmm-skills/ship/bmad-code-review/SKILL.md +90 -0
  168. package/src/bmm-skills/ship/bmad-code-review/customize.toml +113 -0
  169. package/src/bmm-skills/ship/bmad-code-review/references/deletion-check.md +14 -0
  170. package/src/bmm-skills/ship/bmad-code-review/review-prompts/edge-case-hunter.md +88 -0
  171. package/src/bmm-skills/ship/bmad-code-review/review-prompts/verification-gap.md +113 -0
  172. package/src/bmm-skills/ship/bmad-code-review/steps/step-01-gather-context.md +87 -0
  173. package/src/bmm-skills/ship/bmad-code-review/steps/step-02-review.md +32 -0
  174. package/src/bmm-skills/ship/bmad-code-review/steps/step-03-triage.md +50 -0
  175. package/src/bmm-skills/ship/bmad-code-review/steps/step-04-present.md +132 -0
  176. package/src/bmm-skills/ship/bmad-correct-course/SKILL.md +300 -0
  177. package/src/bmm-skills/ship/bmad-correct-course/checklist.md +288 -0
  178. package/src/bmm-skills/ship/bmad-correct-course/customize.toml +41 -0
  179. package/src/bmm-skills/ship/bmad-qa-generate-e2e-tests/SKILL.md +176 -0
  180. package/src/bmm-skills/ship/bmad-qa-generate-e2e-tests/checklist.md +33 -0
  181. package/src/bmm-skills/ship/bmad-qa-generate-e2e-tests/customize.toml +41 -0
  182. package/src/bmm-skills/ship/bmad-retrospective/SKILL.md +94 -0
  183. package/src/bmm-skills/ship/bmad-retrospective/customize.toml +41 -0
  184. package/src/bmm-skills/ship/bmad-retrospective/references/acceptance-verdict.md +55 -0
  185. package/src/bmm-skills/ship/bmad-retrospective/references/aggregate-views.md +17 -0
  186. package/src/bmm-skills/ship/bmad-retrospective/references/evidence-gathering.md +30 -0
  187. package/src/bmm-skills/ship/bmad-retrospective/references/retro-document.md +84 -0
  188. package/src/bmm-skills/ship/bmad-retrospective/references/team-discussion.md +22 -0
  189. package/src/bmm-skills/ship/bmad-retrospective/scripts/__pycache__/sprint_status.cpython-311.pyc +0 -0
  190. package/src/bmm-skills/ship/bmad-retrospective/scripts/git_evidence.py +304 -0
  191. package/src/bmm-skills/ship/bmad-retrospective/scripts/sprint_status.py +746 -0
  192. package/src/bmm-skills/ship/bmad-retrospective/scripts/tests/__pycache__/test_git_evidence.cpython-311-pytest-9.1.1.pyc +0 -0
  193. package/src/bmm-skills/ship/bmad-retrospective/scripts/tests/__pycache__/test_sprint_status.cpython-311-pytest-9.1.1.pyc +0 -0
  194. package/src/bmm-skills/ship/bmad-retrospective/scripts/tests/fixtures/sprint-status-template.yaml +71 -0
  195. package/src/bmm-skills/ship/bmad-retrospective/scripts/tests/test_git_evidence.py +750 -0
  196. package/src/bmm-skills/ship/bmad-retrospective/scripts/tests/test_sprint_status.py +1579 -0
  197. package/src/bmm-skills/v6-shims/README.md +28 -0
  198. package/src/bmm-skills/v6-shims/bmad-create-architecture/SKILL.md +30 -0
  199. package/src/bmm-skills/v6-shims/bmad-create-architecture/customize.toml +41 -0
  200. package/src/bmm-skills/v6-shims/bmad-create-prd/SKILL.md +30 -0
  201. package/src/bmm-skills/v6-shims/bmad-create-prd/customize.toml +41 -0
  202. package/src/bmm-skills/v6-shims/bmad-create-story/SKILL.md +434 -0
  203. package/src/bmm-skills/v6-shims/bmad-create-story/checklist.md +357 -0
  204. package/src/bmm-skills/v6-shims/bmad-create-story/customize.toml +41 -0
  205. package/src/bmm-skills/v6-shims/bmad-create-story/discover-inputs.md +88 -0
  206. package/src/bmm-skills/v6-shims/bmad-create-story/template.md +49 -0
  207. package/src/bmm-skills/v6-shims/bmad-dev-auto/SKILL.md +19 -0
  208. package/src/bmm-skills/v6-shims/bmad-dev-story/SKILL.md +502 -0
  209. package/src/bmm-skills/v6-shims/bmad-dev-story/checklist.md +80 -0
  210. package/src/bmm-skills/v6-shims/bmad-dev-story/customize.toml +44 -0
  211. package/src/bmm-skills/v6-shims/bmad-document-project/SKILL.md +14 -0
  212. package/src/bmm-skills/v6-shims/bmad-domain-research/SKILL.md +14 -0
  213. package/src/bmm-skills/v6-shims/bmad-edit-prd/SKILL.md +30 -0
  214. package/src/bmm-skills/v6-shims/bmad-edit-prd/customize.toml +42 -0
  215. package/src/bmm-skills/v6-shims/bmad-market-research/SKILL.md +14 -0
  216. package/src/bmm-skills/v6-shims/bmad-quick-dev/SKILL.md +19 -0
  217. package/src/bmm-skills/v6-shims/bmad-sprint-status/SKILL.md +26 -0
  218. package/src/bmm-skills/v6-shims/bmad-sprint-status/customize.toml +41 -0
  219. package/src/bmm-skills/v6-shims/bmad-technical-research/SKILL.md +14 -0
  220. package/src/bmm-skills/v6-shims/bmad-validate-prd/SKILL.md +30 -0
  221. package/src/bmm-skills/v6-shims/bmad-validate-prd/customize.toml +42 -0
  222. package/src/core-skills/bmad-advanced-elicitation/SKILL.md +65 -0
  223. package/src/core-skills/bmad-advanced-elicitation/assets/methods.csv +72 -0
  224. package/src/core-skills/bmad-advanced-elicitation/customize.toml +54 -0
  225. package/src/core-skills/bmad-advanced-elicitation/scripts/pick_methods.py +233 -0
  226. package/src/core-skills/bmad-advanced-elicitation/scripts/tests/test_pick_methods.py +228 -0
  227. package/src/core-skills/bmad-brainstorming/SKILL.md +80 -0
  228. package/src/core-skills/bmad-brainstorming/assets/brain-icons.json +166 -0
  229. package/src/core-skills/bmad-brainstorming/assets/brain-methods.csv +109 -0
  230. package/src/core-skills/bmad-brainstorming/assets/brain-selector.html +328 -0
  231. package/src/core-skills/bmad-brainstorming/customize.toml +84 -0
  232. package/src/core-skills/bmad-brainstorming/references/converge.md +24 -0
  233. package/src/core-skills/bmad-brainstorming/references/finalize.md +26 -0
  234. package/src/core-skills/bmad-brainstorming/references/headless.md +54 -0
  235. package/src/core-skills/bmad-brainstorming/references/in-chat-techniques.md +18 -0
  236. package/src/core-skills/bmad-brainstorming/references/mode-autonomous.md +10 -0
  237. package/src/core-skills/bmad-brainstorming/references/mode-facilitator.md +11 -0
  238. package/src/core-skills/bmad-brainstorming/references/mode-partner.md +16 -0
  239. package/src/core-skills/bmad-brainstorming/references/resume.md +5 -0
  240. package/src/core-skills/bmad-brainstorming/scripts/brain.py +770 -0
  241. package/src/core-skills/bmad-brainstorming/scripts/tests/test_brain.py +239 -0
  242. package/src/core-skills/bmad-customize/SKILL.md +111 -0
  243. package/src/core-skills/bmad-customize/scripts/list_customizable_skills.py +231 -0
  244. package/src/core-skills/bmad-customize/scripts/tests/test_list_customizable_skills.py +249 -0
  245. package/src/core-skills/bmad-deep-recon/SKILL.md +82 -0
  246. package/src/core-skills/bmad-deep-recon/assets/research.template.md +18 -0
  247. package/src/core-skills/bmad-deep-recon/customize.toml +212 -0
  248. package/src/core-skills/bmad-deep-recon/references/draft.md +8 -0
  249. package/src/core-skills/bmad-deep-recon/references/finalize.md +11 -0
  250. package/src/core-skills/bmad-deep-recon/references/html-briefing.md +16 -0
  251. package/src/core-skills/bmad-deep-recon/references/lifecycle.md +11 -0
  252. package/src/core-skills/bmad-deep-recon/references/process.md +10 -0
  253. package/src/core-skills/bmad-deep-recon/references/run.md +73 -0
  254. package/src/core-skills/bmad-deep-recon/references/selection.md +13 -0
  255. package/src/core-skills/bmad-deep-recon/references/synthesis.md +16 -0
  256. package/src/core-skills/bmad-deep-recon/references/verification.md +29 -0
  257. package/src/core-skills/bmad-deep-recon/scripts/recon_kit.py +322 -0
  258. package/src/core-skills/bmad-deep-recon/scripts/tests/test_recon_kit.py +144 -0
  259. package/src/core-skills/bmad-deep-recon/types/academic-lit.md +19 -0
  260. package/src/core-skills/bmad-deep-recon/types/competitive.md +19 -0
  261. package/src/core-skills/bmad-deep-recon/types/domain.md +19 -0
  262. package/src/core-skills/bmad-deep-recon/types/market.md +19 -0
  263. package/src/core-skills/bmad-deep-recon/types/technical.md +19 -0
  264. package/src/core-skills/bmad-deep-recon/types/user-voice.md +19 -0
  265. package/src/core-skills/bmad-forge-idea/SKILL.md +107 -0
  266. package/src/core-skills/bmad-forge-idea/customize.toml +42 -0
  267. package/src/core-skills/bmad-forge-idea/scripts/resolve_personas.py +275 -0
  268. package/src/core-skills/bmad-forge-idea/scripts/tests/test_resolve_personas.py +138 -0
  269. package/src/core-skills/bmad-help/SKILL.md +75 -0
  270. package/src/core-skills/bmad-party-mode/SKILL.md +60 -0
  271. package/src/core-skills/bmad-party-mode/customize.toml +211 -0
  272. package/src/core-skills/bmad-party-mode/references/create-party.md +70 -0
  273. package/src/core-skills/bmad-party-mode/references/mode-agent-team.md +13 -0
  274. package/src/core-skills/bmad-party-mode/references/mode-auto.md +13 -0
  275. package/src/core-skills/bmad-party-mode/references/mode-subagent.md +31 -0
  276. package/src/core-skills/bmad-party-mode/references/party-memory.md +51 -0
  277. package/src/core-skills/bmad-party-mode/scripts/resolve_party.py +282 -0
  278. package/src/core-skills/bmad-party-mode/scripts/tests/test_resolve_party.py +146 -0
  279. package/src/core-skills/bmad-review/SKILL.md +49 -0
  280. package/src/core-skills/bmad-review/customize.toml +141 -0
  281. package/src/core-skills/bmad-review/references/editorial-common.md +56 -0
  282. package/src/core-skills/bmad-review/references/lens-adversarial.md +19 -0
  283. package/src/core-skills/bmad-review/references/lens-edge-case-hunter.md +54 -0
  284. package/src/core-skills/bmad-review/references/lens-prose.md +7 -0
  285. package/src/core-skills/bmad-review/references/lens-structure.md +9 -0
  286. package/src/core-skills/bmad-review/references/lens-verification-gap.md +92 -0
  287. package/src/core-skills/bmad-review/references/structure-models.md +44 -0
  288. package/src/core-skills/bmad-review/scripts/tests/test_word_metrics.py +62 -0
  289. package/src/core-skills/bmad-review/scripts/word_metrics.py +102 -0
  290. package/src/core-skills/module-help.csv +10 -0
  291. package/src/core-skills/module.yaml +38 -0
  292. package/src/core-skills/v6-shims/README.md +25 -0
  293. package/src/core-skills/v6-shims/bmad-editorial-review/SKILL.md +6 -0
  294. package/src/core-skills/v6-shims/bmad-editorial-review/customize.toml +31 -0
  295. package/src/core-skills/v6-shims/bmad-editorial-review-prose/SKILL.md +6 -0
  296. package/src/core-skills/v6-shims/bmad-editorial-review-structure/SKILL.md +6 -0
  297. package/src/core-skills/v6-shims/bmad-review-adversarial-general/SKILL.md +6 -0
  298. package/src/core-skills/v6-shims/bmad-review-edge-case-hunter/SKILL.md +6 -0
  299. package/src/core-skills/v6-shims/bmad-review-verification-gap/SKILL.md +6 -0
  300. package/src/scripts/__pycache__/config_utils.cpython-311.pyc +0 -0
  301. package/src/scripts/config_utils.py +119 -0
  302. package/src/scripts/memlog.py +224 -0
  303. package/src/scripts/render_skill.py +401 -0
  304. package/src/scripts/resolve_config.py +74 -0
  305. package/src/scripts/resolve_customization.py +99 -0
  306. package/src/scripts/tests/__pycache__/test_config_utils.cpython-311.pyc +0 -0
  307. package/src/scripts/tests/__pycache__/test_resolve_config.cpython-311.pyc +0 -0
  308. package/src/scripts/tests/__pycache__/test_resolve_customization.cpython-311.pyc +0 -0
  309. package/src/scripts/tests/test_config_utils.py +85 -0
  310. package/src/scripts/tests/test_memlog.py +306 -0
  311. package/src/scripts/tests/test_resolve_config.py +89 -0
  312. package/src/scripts/tests/test_resolve_customization.py +77 -0
  313. package/tools/bundle-web-bundles.js +117 -0
  314. package/tools/format-workflow-md.js +263 -0
  315. package/tools/installer/README.md +60 -0
  316. package/tools/installer/bmad-cli.js +105 -0
  317. package/tools/installer/cli-utils.js +57 -0
  318. package/tools/installer/commands/install.js +149 -0
  319. package/tools/installer/commands/status.js +65 -0
  320. package/tools/installer/commands/uninstall.js +167 -0
  321. package/tools/installer/core/config.js +73 -0
  322. package/tools/installer/core/existing-install.js +121 -0
  323. package/tools/installer/core/install-paths.js +132 -0
  324. package/tools/installer/core/installer.js +1767 -0
  325. package/tools/installer/core/legacy-warnings.js +151 -0
  326. package/tools/installer/core/manifest-generator.js +859 -0
  327. package/tools/installer/core/manifest.js +433 -0
  328. package/tools/installer/core/uv-check.js +195 -0
  329. package/tools/installer/core/wsl-node-check.js +109 -0
  330. package/tools/installer/file-ops.js +204 -0
  331. package/tools/installer/fs-native.js +116 -0
  332. package/tools/installer/ide/_config-driven.js +972 -0
  333. package/tools/installer/ide/manager.js +324 -0
  334. package/tools/installer/ide/platform-codes.js +80 -0
  335. package/tools/installer/ide/platform-codes.yaml +343 -0
  336. package/tools/installer/ide/shared/installed-skills.js +50 -0
  337. package/tools/installer/ide/shared/path-utils.js +219 -0
  338. package/tools/installer/ide/shared/skill-manifest.js +57 -0
  339. package/tools/installer/install-messages.yaml +37 -0
  340. package/tools/installer/list-options.js +210 -0
  341. package/tools/installer/message-loader.js +83 -0
  342. package/tools/installer/modules/channel-plan.js +203 -0
  343. package/tools/installer/modules/channel-resolver.js +241 -0
  344. package/tools/installer/modules/custom-module-manager.js +924 -0
  345. package/tools/installer/modules/external-manager.js +671 -0
  346. package/tools/installer/modules/git-env.js +47 -0
  347. package/tools/installer/modules/module-help-schema.js +13 -0
  348. package/tools/installer/modules/official-modules.js +2257 -0
  349. package/tools/installer/modules/plugin-resolver.js +398 -0
  350. package/tools/installer/modules/version-resolver.js +336 -0
  351. package/tools/installer/project-root.js +224 -0
  352. package/tools/installer/prompts.js +791 -0
  353. package/tools/installer/set-overrides.js +330 -0
  354. package/tools/installer/ui.js +2167 -0
  355. package/tools/installer/yaml-format.js +245 -0
  356. package/tools/javascript-conventions.md +5 -0
  357. package/tools/migrate-custom-module-paths.js +124 -0
  358. package/tools/skill-validator.md +378 -0
  359. package/tools/validate-published-implementation-model.mjs +68 -0
  360. package/tools/validate-sidebar-order.js +388 -0
  361. package/tools/validate-skills.js +735 -0
  362. package/tools/validate-svg-changes.sh +356 -0
  363. package/web-bundles/README.md +46 -0
  364. package/web-bundles/brainstorming-coach/INSTRUCTIONS.md +86 -0
  365. package/web-bundles/brainstorming-coach/SKILL.md +83 -0
  366. package/web-bundles/brainstorming-coach/brain-methods.csv +62 -0
  367. package/web-bundles/bundles.json +139 -0
  368. package/web-bundles/market-and-industry-research/INSTRUCTIONS.md +88 -0
  369. package/web-bundles/market-and-industry-research/SKILL.md +59 -0
  370. package/web-bundles/prd-coach/INSTRUCTIONS.md +86 -0
  371. package/web-bundles/prd-coach/SKILL.md +101 -0
  372. package/web-bundles/prd-coach/prd-template.md +165 -0
  373. package/web-bundles/prd-coach/prd-validation-checklist.md +135 -0
  374. package/web-bundles/prfaq-coach/INSTRUCTIONS.md +86 -0
  375. package/web-bundles/prfaq-coach/SKILL.md +139 -0
  376. package/web-bundles/product-brief-coach/INSTRUCTIONS.md +86 -0
  377. package/web-bundles/product-brief-coach/SKILL.md +113 -0
  378. package/web-bundles/ux-coach/INSTRUCTIONS.md +92 -0
  379. package/web-bundles/ux-coach/SKILL.md +187 -0
  380. package/web-bundles/ux-coach/ux-validation.md +100 -0
@@ -0,0 +1,671 @@
1
+ const fs = require('../fs-native');
2
+ const os = require('node:os');
3
+ const path = require('node:path');
4
+ const { execSync } = require('node:child_process');
5
+ const yaml = require('yaml');
6
+ const prompts = require('../prompts');
7
+ const { resolveChannel, tagExists, parseGitHubRepo } = require('./channel-resolver');
8
+ const { gitEnv } = require('./git-env');
9
+ const { decideChannelForModule } = require('./channel-plan');
10
+ const { getProjectRoot } = require('../project-root');
11
+
12
+ const VALID_CHANNELS = new Set(['stable', 'next', 'pinned']);
13
+
14
+ function normalizeChannelName(raw) {
15
+ if (typeof raw !== 'string') return null;
16
+ const lower = raw.trim().toLowerCase();
17
+ return VALID_CHANNELS.has(lower) ? lower : null;
18
+ }
19
+
20
+ /**
21
+ * Conservative quoting for tag names passed to git commands. Tags are
22
+ * user-typed (--pin) or come from the GitHub API. Only allow the semver
23
+ * character class we use to tag BMad releases; anything else throws.
24
+ */
25
+ function quoteShell(ref) {
26
+ if (typeof ref !== 'string' || !/^[\w.\-+/]+$/.test(ref)) {
27
+ throw new Error(`Unsafe ref name: ${JSON.stringify(ref)}`);
28
+ }
29
+ return `"${ref}"`;
30
+ }
31
+
32
+ async function readChannelMarker(markerPath) {
33
+ try {
34
+ if (!(await fs.pathExists(markerPath))) return null;
35
+ const content = await fs.readFile(markerPath, 'utf8');
36
+ return JSON.parse(content);
37
+ } catch {
38
+ return null;
39
+ }
40
+ }
41
+
42
+ async function writeChannelMarker(markerPath, data) {
43
+ try {
44
+ await fs.writeFile(markerPath, JSON.stringify({ ...data, writtenAt: new Date().toISOString() }, null, 2));
45
+ } catch {
46
+ // Best-effort: marker is an optimization, not a correctness requirement.
47
+ }
48
+ }
49
+
50
+ const REGISTRY_CONFIG_PATH = path.join(getProjectRoot(), 'bmad-modules.yaml');
51
+
52
+ /**
53
+ * Manages official modules from the bundled registry file. The remote
54
+ * marketplace fetch has been retired; this repo is the single source of truth
55
+ * for which official modules exist and how they are displayed.
56
+ *
57
+ * @class ExternalModuleManager
58
+ */
59
+ class ExternalModuleManager {
60
+ // moduleCode → { channel, version, ref, sha, repoUrl, resolvedFallback }
61
+ // Populated when cloneExternalModule resolves a channel. Shared across all
62
+ // instances so the manifest writer (which often instantiates a fresh
63
+ // ExternalModuleManager) sees resolutions made during install.
64
+ static _resolutions = new Map();
65
+
66
+ // moduleCode → ResolvedModule (from PluginResolver). Populated for registry
67
+ // modules flagged `marketplace-plugin: true`, whose installable skills live
68
+ // outside a single module.yaml directory and must be resolved from
69
+ // .claude-plugin/marketplace.json. Shared across instances so install() can
70
+ // pick up the resolution computed during findExternalModuleSource.
71
+ static _pluginResolutions = new Map();
72
+
73
+ constructor() {}
74
+
75
+ /**
76
+ * Get the most recent channel resolution for a module (if any).
77
+ * @param {string} moduleCode
78
+ * @returns {Object|null}
79
+ */
80
+ getResolution(moduleCode) {
81
+ return ExternalModuleManager._resolutions.get(moduleCode) || null;
82
+ }
83
+
84
+ /**
85
+ * Get the cached marketplace-plugin resolution for a module (if any).
86
+ * @param {string} moduleCode
87
+ * @returns {Object|null} ResolvedModule from PluginResolver, or null
88
+ */
89
+ getPluginResolution(moduleCode) {
90
+ return ExternalModuleManager._pluginResolutions.get(moduleCode) || null;
91
+ }
92
+
93
+ /**
94
+ * Load the official modules registry from the bundled YAML file.
95
+ * @returns {Object} Parsed YAML content with modules array
96
+ */
97
+ async loadExternalModulesConfig() {
98
+ if (this.cachedModules) {
99
+ return this.cachedModules;
100
+ }
101
+
102
+ try {
103
+ const content = await fs.readFile(REGISTRY_CONFIG_PATH, 'utf8');
104
+ const config = yaml.parse(content);
105
+ this.cachedModules = config;
106
+ return config;
107
+ } catch (error) {
108
+ await prompts.log.warn(`Failed to load modules config: ${error.message}`);
109
+ return { modules: [] };
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Normalize a module entry from either the remote registry format
115
+ * (snake_case, array) or the legacy bundled format (kebab-case, object map).
116
+ * @param {Object} mod - Raw module config from YAML
117
+ * @param {string} [key] - Key name (only for legacy map format)
118
+ * @returns {Object} Normalized module info
119
+ */
120
+ _normalizeModule(mod, key) {
121
+ return {
122
+ key: key || mod.name,
123
+ url: mod.repository || mod.url,
124
+ moduleDefinition: mod.module_definition || mod['module-definition'],
125
+ code: mod.code,
126
+ name: mod.display_name || mod.name,
127
+ description: mod.description || '',
128
+ defaultSelected: mod.default_selected === true || mod.defaultSelected === true,
129
+ type: mod.type || 'bmad-org',
130
+ npmPackage: mod.npm_package || mod.npmPackage || null,
131
+ pluginName: mod.plugin_name || mod.pluginName || null,
132
+ defaultChannel: normalizeChannelName(mod.default_channel || mod.defaultChannel) || 'stable',
133
+ deprecated: mod.deprecated === true,
134
+ deprecationMessage: mod.deprecation_message || mod['deprecation-message'] || mod.deprecationMessage || null,
135
+ marketplacePlugin: mod.marketplace_plugin === true || mod['marketplace-plugin'] === true || mod.marketplacePlugin === true,
136
+ postInstallMessage: mod.post_install_message || mod['post-install-message'] || mod.postInstallMessage || null,
137
+ builtIn: mod.built_in === true,
138
+ isExternal: mod.built_in !== true,
139
+ // Prior codes this module was registered under (e.g. `bmad-loop` was
140
+ // `bauto`). Lets a renamed module keep resolving existing installs
141
+ // instead of orphaning them — see getModuleByCode().
142
+ aliases: Array.isArray(mod.aliases) ? mod.aliases : [],
143
+ };
144
+ }
145
+
146
+ /**
147
+ * Get list of available modules from the registry
148
+ * @returns {Array<Object>} Array of module info objects
149
+ */
150
+ async listAvailable() {
151
+ const config = await this.loadExternalModulesConfig();
152
+
153
+ // Remote format: modules is an array
154
+ if (Array.isArray(config.modules)) {
155
+ return config.modules.map((mod) => this._normalizeModule(mod));
156
+ }
157
+
158
+ // Legacy bundled format: modules is an object map
159
+ const modules = [];
160
+ for (const [key, mod] of Object.entries(config.modules || {})) {
161
+ modules.push(this._normalizeModule(mod, key));
162
+ }
163
+ return modules;
164
+ }
165
+
166
+ /**
167
+ * Get module info by code. Falls back to matching a registry entry's
168
+ * `aliases` list, so a module that was renamed (its `code` changed) still
169
+ * resolves for installs recorded under the prior code.
170
+ * @param {string} code - The module code (e.g., 'cis'), current or aliased
171
+ * @returns {Object|null} Module info or null if not found
172
+ */
173
+ async getModuleByCode(code) {
174
+ const modules = await this.listAvailable();
175
+ return modules.find((m) => m.code === code) || modules.find((m) => m.aliases.includes(code)) || null;
176
+ }
177
+
178
+ /**
179
+ * Resolve a possibly-legacy module code to its current canonical code.
180
+ * Returns the input unchanged if it doesn't match any registry entry or
181
+ * alias (e.g. a custom/unknown module).
182
+ * @param {string} code
183
+ * @returns {Promise<string>}
184
+ */
185
+ async resolveCanonicalCode(code) {
186
+ const info = await this.getModuleByCode(code);
187
+ return info ? info.code : code;
188
+ }
189
+
190
+ /**
191
+ * Get the cache directory for external modules
192
+ * @returns {string} Path to the external modules cache directory
193
+ */
194
+ getExternalCacheDir() {
195
+ const cacheDir = path.join(os.homedir(), '.bmad', 'cache', 'external-modules');
196
+ return cacheDir;
197
+ }
198
+
199
+ /**
200
+ * Clone an external module repository to cache, resolving the requested
201
+ * channel (stable / next / pinned) to a concrete git ref.
202
+ *
203
+ * @param {string} moduleCode - Code of the external module
204
+ * @param {Object} options - Clone options
205
+ * @param {boolean} [options.silent] - Suppress spinner output
206
+ * @param {Object} [options.channelOptions] - Parsed channel flags. See
207
+ * modules/channel-plan.js. When absent, the module installs on its
208
+ * registry-declared default channel (typically 'stable').
209
+ * @returns {string} Path to the cloned repository
210
+ */
211
+ async cloneExternalModule(moduleCode, options = {}) {
212
+ const moduleInfo = await this.getModuleByCode(moduleCode);
213
+
214
+ if (!moduleInfo) {
215
+ throw new Error(`External module '${moduleCode}' not found in the BMad registry`);
216
+ }
217
+
218
+ // Normalize to the canonical code so cache dir, in-memory resolutions,
219
+ // and log/error text stay consistent even when called with a renamed
220
+ // module's prior alias (getModuleByCode resolves aliases above).
221
+ moduleCode = moduleInfo.code;
222
+
223
+ const cacheDir = this.getExternalCacheDir();
224
+ const moduleCacheDir = path.join(cacheDir, moduleCode);
225
+ const silent = options.silent || false;
226
+
227
+ // Create cache directory if it doesn't exist
228
+ await fs.ensureDir(cacheDir);
229
+
230
+ // Helper to create a spinner or a no-op when silent
231
+ const createSpinner = async () => {
232
+ if (silent) {
233
+ return {
234
+ start() {},
235
+ stop() {},
236
+ error() {},
237
+ message() {},
238
+ cancel() {},
239
+ clear() {},
240
+ get isSpinning() {
241
+ return false;
242
+ },
243
+ get isCancelled() {
244
+ return false;
245
+ },
246
+ };
247
+ }
248
+ return await prompts.spinner();
249
+ };
250
+
251
+ // ─── Resolve channel plan ─────────────────────────────────────────────
252
+ // Post-install callers (config generation, directory setup, help catalog
253
+ // rebuild) invoke findModuleSource/cloneExternalModule without
254
+ // channelOptions just to locate the module's files. Those calls must not
255
+ // redecide the channel — the install step already chose one, cloned the
256
+ // right ref, and recorded a resolution. If we re-resolve without flags,
257
+ // we'd snap back to stable and overwrite a pinned install.
258
+ const hasExplicitChannelInput =
259
+ options.channelOptions &&
260
+ (options.channelOptions.global ||
261
+ (options.channelOptions.nextSet && options.channelOptions.nextSet.size > 0) ||
262
+ (options.channelOptions.pins && options.channelOptions.pins.size > 0));
263
+ const existingResolution = ExternalModuleManager._resolutions.get(moduleCode);
264
+ const haveUsableCache = await fs.pathExists(moduleCacheDir);
265
+
266
+ if (!hasExplicitChannelInput && existingResolution && haveUsableCache) {
267
+ // This is a look-up only; the module is already installed at its chosen
268
+ // ref. Skip cloning and return the cached path unchanged.
269
+ return moduleCacheDir;
270
+ }
271
+
272
+ const planEntry = decideChannelForModule({
273
+ code: moduleCode,
274
+ channelOptions: options.channelOptions,
275
+ registryDefault: moduleInfo.defaultChannel,
276
+ });
277
+
278
+ // Same-plan short-circuit: a single install calls cloneExternalModule
279
+ // several times (config collection, directory setup, help-catalog rebuild)
280
+ // with the same channelOptions. The first call resolves + clones; later
281
+ // calls with an identical plan and a valid cache should return immediately
282
+ // instead of re-running resolveChannel() and `git fetch` (slow; can fail
283
+ // on flaky networks even though the tagCache dedupes the GitHub API hit).
284
+ if (existingResolution && haveUsableCache && existingResolution.channel === planEntry.channel) {
285
+ const samePin = planEntry.channel !== 'pinned' || existingResolution.version === planEntry.pin;
286
+ if (samePin) return moduleCacheDir;
287
+ }
288
+
289
+ let resolved;
290
+ try {
291
+ resolved = await resolveChannel({
292
+ channel: planEntry.channel,
293
+ pin: planEntry.pin,
294
+ repoUrl: moduleInfo.url,
295
+ });
296
+ } catch (error) {
297
+ // Tag-API failure (rate limit, transient network). If we already have
298
+ // a usable cache at a recorded ref, treat this as "couldn't check for
299
+ // updates" and re-use the cached version silently — that's the right
300
+ // call for an update/quick-update, since the semantics don't change
301
+ // and the user isn't worse off than before they ran this command.
302
+ const cachedMarker = await readChannelMarker(path.join(moduleCacheDir, '.bmad-channel.json'));
303
+ if (cachedMarker?.channel && (await fs.pathExists(moduleCacheDir))) {
304
+ if (!silent) {
305
+ await prompts.log.warn(
306
+ `Could not check for updates to ${moduleInfo.name} (${error.message}); using cached ${cachedMarker.version || cachedMarker.channel}.`,
307
+ );
308
+ }
309
+ ExternalModuleManager._resolutions.set(moduleCode, {
310
+ channel: cachedMarker.channel,
311
+ version: cachedMarker.version || 'main',
312
+ ref: cachedMarker.version && cachedMarker.version !== 'main' ? cachedMarker.version : null,
313
+ sha: cachedMarker.sha,
314
+ repoUrl: moduleInfo.url,
315
+ resolvedFallback: false,
316
+ planSource: 'cached',
317
+ });
318
+ return moduleCacheDir;
319
+ }
320
+ // No cache to fall back on — this is effectively a fresh install with
321
+ // no offline safety net. Surface a clear error with actionable guidance.
322
+ const isRateLimited = /rate limit/i.test(error.message);
323
+ const hint = isRateLimited
324
+ ? process.env.GITHUB_TOKEN
325
+ ? 'Your GITHUB_TOKEN may have expired or been rate-limited on its own budget. Try a different token or wait for the reset.'
326
+ : 'Set a GITHUB_TOKEN env var (any personal access token with public-repo read) to raise the 60-req/hour anonymous limit.'
327
+ : `Check your network connection, or rerun with \`--next=${moduleCode}\` / \`--pin ${moduleCode}=<tag>\` to skip the tag lookup.`;
328
+ throw new Error(`Could not resolve stable tag for '${moduleCode}' (${error.message}). ${hint}`);
329
+ }
330
+
331
+ if (resolved.resolvedFallback && !silent) {
332
+ if (resolved.reason === 'no-stable-tags') {
333
+ await prompts.log.warn(`No stable releases found for ${moduleInfo.name}; installing from main.`);
334
+ } else if (resolved.reason === 'not-a-github-url') {
335
+ await prompts.log.warn(`Cannot determine stable tags for ${moduleInfo.name} (non-GitHub URL); installing from main.`);
336
+ }
337
+ }
338
+
339
+ // Validate pin before we burn time cloning. Best-effort: skip on non-GitHub URLs.
340
+ if (planEntry.channel === 'pinned') {
341
+ const parsed = parseGitHubRepo(moduleInfo.url);
342
+ if (parsed) {
343
+ try {
344
+ const exists = await tagExists(parsed.owner, parsed.repo, planEntry.pin);
345
+ if (!exists) {
346
+ throw new Error(`Tag '${planEntry.pin}' not found in ${parsed.owner}/${parsed.repo}.`);
347
+ }
348
+ } catch (error) {
349
+ if (error.message?.includes('not found')) throw error;
350
+ // Network hiccup on tag verification — let the clone attempt fail clearly.
351
+ }
352
+ }
353
+ }
354
+
355
+ // ─── Clone or update cache by resolved channel ────────────────────────
356
+ const markerPath = path.join(moduleCacheDir, '.bmad-channel.json');
357
+ const currentMarker = await readChannelMarker(markerPath);
358
+ const needsChannelReset = currentMarker && currentMarker.channel !== resolved.channel;
359
+
360
+ let needsDependencyInstall = false;
361
+ let wasNewClone = false;
362
+
363
+ if (needsChannelReset && (await fs.pathExists(moduleCacheDir))) {
364
+ // Channel changed (e.g. user switched stable→next). Blow away and re-clone
365
+ // to avoid tangling shallow clones of different refs.
366
+ await fs.remove(moduleCacheDir);
367
+ }
368
+
369
+ if (await fs.pathExists(moduleCacheDir)) {
370
+ // Cache exists on the right channel. Refresh the ref.
371
+ const fetchSpinner = await createSpinner();
372
+ fetchSpinner.start(`Fetching ${moduleInfo.name}...`);
373
+ try {
374
+ const currentSha = execSync('git rev-parse HEAD', { cwd: moduleCacheDir, stdio: 'pipe', env: gitEnv() }).toString().trim();
375
+
376
+ if (resolved.channel === 'next') {
377
+ execSync('git fetch origin --depth 1', {
378
+ cwd: moduleCacheDir,
379
+ stdio: ['ignore', 'pipe', 'pipe'],
380
+ env: gitEnv({ GIT_TERMINAL_PROMPT: '0' }),
381
+ });
382
+ execSync('git reset --hard origin/HEAD', {
383
+ cwd: moduleCacheDir,
384
+ stdio: ['ignore', 'pipe', 'pipe'],
385
+ env: gitEnv({ GIT_TERMINAL_PROMPT: '0' }),
386
+ });
387
+ } else {
388
+ // stable or pinned — fetch the specific tag and check it out.
389
+ execSync(`git fetch --depth 1 origin tag ${quoteShell(resolved.ref)} --no-tags`, {
390
+ cwd: moduleCacheDir,
391
+ stdio: ['ignore', 'pipe', 'pipe'],
392
+ env: gitEnv({ GIT_TERMINAL_PROMPT: '0' }),
393
+ });
394
+ execSync(`git checkout --quiet FETCH_HEAD`, {
395
+ cwd: moduleCacheDir,
396
+ stdio: ['ignore', 'pipe', 'pipe'],
397
+ env: gitEnv(),
398
+ });
399
+ }
400
+
401
+ const newSha = execSync('git rev-parse HEAD', { cwd: moduleCacheDir, stdio: 'pipe', env: gitEnv() }).toString().trim();
402
+ fetchSpinner.stop(`Fetched ${moduleInfo.name}`);
403
+ if (currentSha !== newSha) needsDependencyInstall = true;
404
+ } catch {
405
+ fetchSpinner.error(`Fetch failed, re-downloading ${moduleInfo.name}`);
406
+ await fs.remove(moduleCacheDir);
407
+ wasNewClone = true;
408
+ }
409
+ } else {
410
+ wasNewClone = true;
411
+ }
412
+
413
+ if (wasNewClone) {
414
+ const fetchSpinner = await createSpinner();
415
+ fetchSpinner.start(`Fetching ${moduleInfo.name}...`);
416
+ try {
417
+ if (resolved.channel === 'next') {
418
+ execSync(`git clone --depth 1 "${moduleInfo.url}" "${moduleCacheDir}"`, {
419
+ stdio: ['ignore', 'pipe', 'pipe'],
420
+ env: gitEnv({ GIT_TERMINAL_PROMPT: '0' }),
421
+ });
422
+ } else {
423
+ execSync(`git clone --depth 1 --branch ${quoteShell(resolved.ref)} "${moduleInfo.url}" "${moduleCacheDir}"`, {
424
+ stdio: ['ignore', 'pipe', 'pipe'],
425
+ env: gitEnv({ GIT_TERMINAL_PROMPT: '0' }),
426
+ });
427
+ }
428
+ fetchSpinner.stop(`Fetched ${moduleInfo.name}`);
429
+ } catch (error) {
430
+ fetchSpinner.error(`Failed to fetch ${moduleInfo.name}`);
431
+ throw new Error(`Failed to clone external module '${moduleCode}' at ${resolved.version}: ${error.message}`);
432
+ }
433
+ }
434
+
435
+ // Record resolution (channel + tag + SHA) for the manifest writer to pick up.
436
+ const sha = execSync('git rev-parse HEAD', { cwd: moduleCacheDir, stdio: 'pipe', env: gitEnv() }).toString().trim();
437
+ ExternalModuleManager._resolutions.set(moduleCode, {
438
+ channel: resolved.channel,
439
+ version: resolved.version,
440
+ ref: resolved.ref,
441
+ sha,
442
+ repoUrl: moduleInfo.url,
443
+ resolvedFallback: !!resolved.resolvedFallback,
444
+ planSource: planEntry.source,
445
+ });
446
+ await writeChannelMarker(markerPath, { channel: resolved.channel, version: resolved.version, sha });
447
+
448
+ // Install dependencies if package.json exists
449
+ const packageJsonPath = path.join(moduleCacheDir, 'package.json');
450
+ const nodeModulesPath = path.join(moduleCacheDir, 'node_modules');
451
+ if (await fs.pathExists(packageJsonPath)) {
452
+ // Install if node_modules doesn't exist, or if package.json is newer (dependencies changed)
453
+ const nodeModulesMissing = !(await fs.pathExists(nodeModulesPath));
454
+
455
+ // Force install if we updated or cloned new
456
+ if (needsDependencyInstall || wasNewClone || nodeModulesMissing) {
457
+ const installSpinner = await createSpinner();
458
+ installSpinner.start(`Installing dependencies for ${moduleInfo.name}...`);
459
+ try {
460
+ execSync('npm install --omit=dev --no-audit --no-fund --no-progress --legacy-peer-deps', {
461
+ cwd: moduleCacheDir,
462
+ stdio: ['ignore', 'pipe', 'pipe'],
463
+ timeout: 120_000, // 2 minute timeout
464
+ env: gitEnv(), // npm shells out to git for git-URL deps; keep hook GIT_* vars away from it
465
+ });
466
+ installSpinner.stop(`Installed dependencies for ${moduleInfo.name}`);
467
+ } catch (error) {
468
+ installSpinner.error(`Failed to install dependencies for ${moduleInfo.name}`);
469
+ if (!silent) await prompts.log.warn(` ${error.message}`);
470
+ }
471
+ } else {
472
+ // Check if package.json is newer than node_modules
473
+ let packageJsonNewer = false;
474
+ try {
475
+ const packageStats = await fs.stat(packageJsonPath);
476
+ const nodeModulesStats = await fs.stat(nodeModulesPath);
477
+ packageJsonNewer = packageStats.mtime > nodeModulesStats.mtime;
478
+ } catch {
479
+ // If stat fails, assume we need to install
480
+ packageJsonNewer = true;
481
+ }
482
+
483
+ if (packageJsonNewer) {
484
+ const installSpinner = await createSpinner();
485
+ installSpinner.start(`Installing dependencies for ${moduleInfo.name}...`);
486
+ try {
487
+ execSync('npm install --omit=dev --no-audit --no-fund --no-progress --legacy-peer-deps', {
488
+ cwd: moduleCacheDir,
489
+ stdio: ['ignore', 'pipe', 'pipe'],
490
+ timeout: 120_000, // 2 minute timeout
491
+ env: gitEnv(), // npm shells out to git for git-URL deps; keep hook GIT_* vars away from it
492
+ });
493
+ installSpinner.stop(`Installed dependencies for ${moduleInfo.name}`);
494
+ } catch (error) {
495
+ installSpinner.error(`Failed to install dependencies for ${moduleInfo.name}`);
496
+ if (!silent) await prompts.log.warn(` ${error.message}`);
497
+ }
498
+ }
499
+ }
500
+ }
501
+
502
+ return moduleCacheDir;
503
+ }
504
+
505
+ /**
506
+ * Find the source path for an external module
507
+ * @param {string} moduleCode - Code of the external module
508
+ * @param {Object} options - Options passed to cloneExternalModule
509
+ * @returns {string|null} Path to the module source or null if not found
510
+ */
511
+ async findExternalModuleSource(moduleCode, options = {}) {
512
+ const moduleInfo = await this.getModuleByCode(moduleCode);
513
+
514
+ if (!moduleInfo || moduleInfo.builtIn) {
515
+ return null;
516
+ }
517
+
518
+ // Normalize to the canonical code — see cloneExternalModule for why.
519
+ moduleCode = moduleInfo.code;
520
+
521
+ // Marketplace-plugin modules (registry entries flagged `marketplace-plugin`)
522
+ // ship a .claude-plugin/marketplace.json and keep their module.yaml inside a
523
+ // skill's assets/ rather than in one directory alongside the skills. Resolve
524
+ // them through the PluginResolver (the same machinery custom-URL installs
525
+ // use) and return the directory that holds module.yaml so config/version/
526
+ // directory callers work; install() copies the resolved skill dirs.
527
+ if (moduleInfo.marketplacePlugin) {
528
+ const pluginMod = await this.resolvePluginModule(moduleCode, options);
529
+ if (pluginMod && pluginMod.moduleYamlPath) {
530
+ return path.dirname(pluginMod.moduleYamlPath);
531
+ }
532
+ }
533
+
534
+ // Clone the external module repo
535
+ const cloneDir = await this.cloneExternalModule(moduleCode, options);
536
+
537
+ // The module-definition specifies the path to module.yaml relative to repo root
538
+ // We need to return the directory containing module.yaml
539
+ const moduleDefinitionPath = moduleInfo.moduleDefinition; // e.g., 'skills/module.yaml'
540
+ const configuredPath = path.join(cloneDir, moduleDefinitionPath);
541
+
542
+ if (await fs.pathExists(configuredPath)) {
543
+ return path.dirname(configuredPath);
544
+ }
545
+
546
+ // Fallback: search skills/ and src/ (root level and one level deep for subfolders)
547
+ for (const dir of ['skills', 'src']) {
548
+ const rootCandidate = path.join(cloneDir, dir, 'module.yaml');
549
+ if (await fs.pathExists(rootCandidate)) {
550
+ return path.dirname(rootCandidate);
551
+ }
552
+ const dirPath = path.join(cloneDir, dir);
553
+ if (await fs.pathExists(dirPath)) {
554
+ const entries = await fs.readdir(dirPath, { withFileTypes: true });
555
+ for (const entry of entries) {
556
+ if (entry.isDirectory()) {
557
+ const subCandidate = path.join(dirPath, entry.name, 'module.yaml');
558
+ if (await fs.pathExists(subCandidate)) {
559
+ return path.dirname(subCandidate);
560
+ }
561
+ }
562
+ }
563
+ }
564
+ }
565
+
566
+ // Check repo root as last fallback
567
+ const rootCandidate = path.join(cloneDir, 'module.yaml');
568
+ if (await fs.pathExists(rootCandidate)) {
569
+ return path.dirname(rootCandidate);
570
+ }
571
+
572
+ // Nothing found: the cloned ref does not contain a recognizable module structure.
573
+ // This happens when a stable tag predates a module restructure (e.g. the repo
574
+ // moved files from payload/ to skills/ after the tag was cut). Returning a
575
+ // non-existent path silently causes a confusing ENOENT deep inside copyModuleWithFiltering;
576
+ // throw a descriptive error here instead so the user knows what happened and how to recover.
577
+ const resolution = ExternalModuleManager._resolutions.get(moduleCode);
578
+ const versionHint = resolution?.version ? `version ${resolution.version}` : 'the cloned version';
579
+ const channelHint =
580
+ resolution?.channel === 'stable' ? ` Try reinstalling with \`--next=${moduleCode}\` to use the latest main branch instead.` : '';
581
+ throw new Error(
582
+ `Module '${moduleCode}' was downloaded but its module definition was not found. ` +
583
+ `Expected '${moduleDefinitionPath}' to exist in ${versionHint}, but it is missing. ` +
584
+ `The repository may have been restructured after this release was tagged.${channelHint}`,
585
+ );
586
+ }
587
+
588
+ /**
589
+ * Resolve a marketplace-plugin registry module to an installable plugin
590
+ * definition. Clones the repo (respecting the channel plan), reads its
591
+ * .claude-plugin/marketplace.json, and runs the PluginResolver against the
592
+ * plugin matching this module. The result (skillPaths + module.yaml +
593
+ * module-help.csv) is cached so install() can copy the resolved skill dirs.
594
+ *
595
+ * @param {string} moduleCode - Code of the external module
596
+ * @param {Object} options - Options passed to cloneExternalModule
597
+ * @returns {Promise<Object|null>} ResolvedModule from PluginResolver, or null
598
+ * when the module is not a marketplace plugin or cannot be resolved.
599
+ */
600
+ async resolvePluginModule(moduleCode, options = {}) {
601
+ const moduleInfo = await this.getModuleByCode(moduleCode);
602
+ if (!moduleInfo || moduleInfo.builtIn || !moduleInfo.marketplacePlugin) {
603
+ return null;
604
+ }
605
+
606
+ // Normalize to the canonical code — see cloneExternalModule for why.
607
+ moduleCode = moduleInfo.code;
608
+
609
+ const cloneDir = await this.cloneExternalModule(moduleCode, options);
610
+
611
+ const marketplacePath = path.join(cloneDir, '.claude-plugin', 'marketplace.json');
612
+ if (!(await fs.pathExists(marketplacePath))) {
613
+ return null;
614
+ }
615
+
616
+ let marketplace;
617
+ try {
618
+ marketplace = JSON.parse(await fs.readFile(marketplacePath, 'utf8'));
619
+ } catch {
620
+ return null;
621
+ }
622
+
623
+ const plugins = Array.isArray(marketplace.plugins) ? marketplace.plugins : [];
624
+ if (plugins.length === 0) {
625
+ return null;
626
+ }
627
+
628
+ // Prefer the plugin whose name matches the registry's plugin_name (or code),
629
+ // falling back to any plugin that declares skills.
630
+ const preferredName = moduleInfo.pluginName || moduleInfo.code;
631
+ const ordered = [...plugins].sort((a, b) => {
632
+ const am = a?.name === preferredName ? 0 : 1;
633
+ const bm = b?.name === preferredName ? 0 : 1;
634
+ return am - bm;
635
+ });
636
+
637
+ const { PluginResolver } = require('./plugin-resolver');
638
+ const resolver = new PluginResolver();
639
+
640
+ for (const plugin of ordered) {
641
+ if (!plugin || !Array.isArray(plugin.skills) || plugin.skills.length === 0) {
642
+ continue;
643
+ }
644
+ let resolvedMods;
645
+ try {
646
+ resolvedMods = await resolver.resolve(cloneDir, plugin);
647
+ } catch {
648
+ continue;
649
+ }
650
+ if (!resolvedMods || resolvedMods.length === 0) {
651
+ continue;
652
+ }
653
+ // Match the registry code, then the preferred plugin name, then accept a
654
+ // lone resolved module.
655
+ const match =
656
+ resolvedMods.find((mod) => mod.code === moduleCode) ||
657
+ resolvedMods.find((mod) => mod.code === preferredName) ||
658
+ (resolvedMods.length === 1 ? resolvedMods[0] : null);
659
+ if (match) {
660
+ match.repoUrl = moduleInfo.url;
661
+ ExternalModuleManager._pluginResolutions.set(moduleCode, match);
662
+ return match;
663
+ }
664
+ }
665
+
666
+ return null;
667
+ }
668
+ cachedModules = null;
669
+ }
670
+
671
+ module.exports = { ExternalModuleManager };