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,401 @@
1
+ #!/usr/bin/env python3
2
+ # /// script
3
+ # requires-python = ">=3.11"
4
+ # ///
5
+ """Render a skill's Markdown sources into an immutable project snapshot."""
6
+
7
+ from __future__ import annotations
8
+
9
+ import argparse
10
+ import hashlib
11
+ import json
12
+ import os
13
+ import re
14
+ import shutil
15
+ import sys
16
+ import tempfile
17
+ from pathlib import Path
18
+ from typing import Any
19
+
20
+ # Installed scripts are consumer files, not a location for interpreter caches.
21
+ sys.dont_write_bytecode = True
22
+
23
+ from config_utils import ConfigError, load_central_config, load_customization, load_toml
24
+
25
+
26
+ class RenderError(ValueError):
27
+ """Raised when rendering cannot safely publish a snapshot."""
28
+
29
+
30
+ _CONFIG_TOKEN = re.compile(r"\{\{config\.([A-Za-z0-9_.-]+)\}\}")
31
+ _SHORT_CONFIG_TOKEN = re.compile(r"\{\{\.([A-Za-z0-9_]+)\}\}")
32
+ _CUSTOM_TOKEN = re.compile(r"\{workflow\.([A-Za-z0-9_.-]+)\}")
33
+ _SNAPSHOT_TOKEN = re.compile(r"\[\[bmad-snapshot:([A-Za-z0-9_./-]+\.md)\]\]")
34
+
35
+
36
+ def _hash_bytes(content: bytes) -> str:
37
+ return hashlib.sha256(content).hexdigest()
38
+
39
+
40
+ def _canonical_json(value: Any) -> bytes:
41
+ return json.dumps(
42
+ value, ensure_ascii=False, sort_keys=True, separators=(",", ":")
43
+ ).encode("utf-8")
44
+
45
+
46
+ def _lookup(data: dict[str, Any], dotted_path: str, label: str) -> Any:
47
+ current: Any = data
48
+ for part in dotted_path.split("."):
49
+ if not isinstance(current, dict) or part not in current:
50
+ raise RenderError(f"missing {label} `{dotted_path}`")
51
+ current = current[part]
52
+ return current
53
+
54
+
55
+ def _require_string(value: Any, label: str, *, allow_empty: bool = False) -> str:
56
+ if not isinstance(value, str):
57
+ raise RenderError(f"{label} must be a string, got {type(value).__name__}")
58
+ if not allow_empty and not value.strip():
59
+ raise RenderError(f"{label} must not be empty")
60
+ return value
61
+
62
+
63
+ def _require_string_list(value: Any, label: str) -> list[str]:
64
+ if not isinstance(value, list):
65
+ raise RenderError(f"{label} must be a list, got {type(value).__name__}")
66
+ result = []
67
+ for index, item in enumerate(value):
68
+ result.append(_require_string(item, f"{label}[{index}]"))
69
+ return result
70
+
71
+
72
+ def _require_review_layers(value: Any, label: str) -> list[dict[str, str]]:
73
+ if not isinstance(value, list):
74
+ raise RenderError(f"{label} must be a list of tables")
75
+ result: list[dict[str, str]] = []
76
+ seen: set[str] = set()
77
+ for index, item in enumerate(value):
78
+ item_label = f"{label}[{index}]"
79
+ if not isinstance(item, dict):
80
+ raise RenderError(f"{item_label} must be a table")
81
+ identifier = _require_string(item.get("id"), f"{item_label}.id")
82
+ if identifier in seen:
83
+ raise RenderError(f"duplicate review layer id `{identifier}`")
84
+ seen.add(identifier)
85
+ layer = {
86
+ "id": identifier,
87
+ "name": _require_string(item.get("name", identifier), f"{item_label}.name"),
88
+ "instruction": _require_string(
89
+ item.get("instruction"), f"{item_label}.instruction", allow_empty=True
90
+ ),
91
+ }
92
+ if "when" in item:
93
+ layer["when"] = _require_string(item["when"], f"{item_label}.when")
94
+ result.append(layer)
95
+ return result
96
+
97
+
98
+ def _load_sources(skill_dir: Path) -> dict[str, str]:
99
+ sources: dict[str, str] = {}
100
+ for candidate in sorted(skill_dir.rglob("*.md")):
101
+ if candidate.name == "SKILL.md":
102
+ continue
103
+ name = candidate.relative_to(skill_dir).as_posix()
104
+ path = candidate.resolve(strict=True)
105
+ if not path.is_relative_to(skill_dir):
106
+ raise RenderError(f"render source escapes skill directory: {name}")
107
+ if not path.is_file():
108
+ raise RenderError(f"render source is missing or not a file: {path}")
109
+ try:
110
+ sources[name] = path.read_text(encoding="utf-8")
111
+ except (OSError, UnicodeError) as error:
112
+ raise RenderError(f"failed to read render source {path}: {error}") from error
113
+ if "workflow.md" not in sources:
114
+ raise RenderError(f"render entry is missing: {skill_dir / 'workflow.md'}")
115
+ return sources
116
+
117
+
118
+ def _resolve_config_value(value: Any, label: str, project_root: Path) -> str:
119
+ text = _require_string(value, label)
120
+ if "{project-root}" not in text:
121
+ return text
122
+ resolved = text.replace("{project-root}", str(project_root))
123
+ if not Path(resolved).is_absolute():
124
+ raise RenderError(f"{label} must resolve to an absolute path: {resolved}")
125
+ return resolved
126
+
127
+
128
+ def _find_config_values(data: Any, key: str, prefix: str = "") -> list[tuple[str, Any]]:
129
+ matches: list[tuple[str, Any]] = []
130
+ if not isinstance(data, dict):
131
+ return matches
132
+ for name, value in data.items():
133
+ path = f"{prefix}.{name}" if prefix else name
134
+ if name == key and not isinstance(value, (dict, list)):
135
+ matches.append((path, value))
136
+ matches.extend(_find_config_values(value, key, path))
137
+ return matches
138
+
139
+
140
+ def _resolve_short_config(
141
+ central: dict[str, Any], key: str, project_root: Path
142
+ ) -> tuple[str, str]:
143
+ matches = _find_config_values(central, key)
144
+ if not matches:
145
+ raise RenderError(f"missing config value `{key}`")
146
+ if len(matches) > 1:
147
+ paths = ", ".join(path for path, _ in matches)
148
+ raise RenderError(f"ambiguous config value `{key}` found at: {paths}")
149
+ path, value = matches[0]
150
+ return path, _resolve_config_value(value, f"config.{path}", project_root)
151
+
152
+
153
+ def _format_markdown_list(items: list[str]) -> str:
154
+ if not items:
155
+ return "_None._"
156
+ rendered = []
157
+ for item in items:
158
+ lines = item.splitlines() or [""]
159
+ rendered.append("- " + lines[0])
160
+ rendered.extend(" " + line for line in lines[1:])
161
+ return "\n".join(rendered)
162
+
163
+
164
+ def _format_review_layers(layers: list[dict[str, str]]) -> str:
165
+ active = [layer for layer in layers if layer["instruction"].strip()]
166
+ if not active:
167
+ return "No active review layers. HALT with blocking condition `no active review layers`."
168
+ sections = []
169
+ for layer in active:
170
+ section = [f"#### {layer['name']} (`{layer['id']}`)"]
171
+ if layer.get("when"):
172
+ section.extend(["", f"Run only when: {layer['when']}"])
173
+ section.extend(["", layer["instruction"].strip()])
174
+ sections.append("\n".join(section))
175
+ return "\n\n".join(sections)
176
+
177
+
178
+ def _resolve_customization_value(value: Any, default: Any, label: str) -> tuple[Any, str]:
179
+ if isinstance(default, str):
180
+ allow_empty = not default.strip() or label == "customization.workflow.open_spec"
181
+ resolved = _require_string(value, label, allow_empty=allow_empty)
182
+ return resolved, resolved
183
+ if isinstance(default, list):
184
+ if default and all(isinstance(item, dict) for item in default):
185
+ resolved = _require_review_layers(value, label)
186
+ return resolved, _format_review_layers(resolved)
187
+ resolved = _require_string_list(value, label)
188
+ return resolved, _format_markdown_list(resolved)
189
+ raise RenderError(f"{label} has unsupported default type {type(default).__name__}")
190
+
191
+
192
+ def _resolve_replacements(
193
+ sources: dict[str, str],
194
+ central: dict[str, Any],
195
+ customization: dict[str, Any],
196
+ defaults: dict[str, Any] | None,
197
+ project_root: Path,
198
+ ) -> tuple[dict[str, str], dict[str, Any]]:
199
+ replacements: dict[str, str] = {}
200
+ input_values: dict[str, Any] = {}
201
+ for content in sources.values():
202
+ for match in _SHORT_CONFIG_TOKEN.finditer(content):
203
+ token, key = match.group(0), match.group(1)
204
+ path, resolved = _resolve_short_config(central, key, project_root)
205
+ source = f"config.{path}"
206
+ replacements[token] = resolved
207
+ input_values[source] = resolved
208
+ for match in _CONFIG_TOKEN.finditer(content):
209
+ token, path = match.group(0), match.group(1)
210
+ source = f"config.{path}"
211
+ resolved = _resolve_config_value(
212
+ _lookup(central, path, "config value"), source, project_root
213
+ )
214
+ replacements[token] = resolved
215
+ input_values[source] = resolved
216
+ for match in _CUSTOM_TOKEN.finditer(content):
217
+ if defaults is None:
218
+ raise RenderError("customization tokens require customize.toml")
219
+ token, relative_path = match.group(0), match.group(1)
220
+ path = f"workflow.{relative_path}"
221
+ source = f"customization.{path}"
222
+ resolved, rendered = _resolve_customization_value(
223
+ _lookup(customization, path, "customization value"),
224
+ _lookup(defaults, path, "customization default"),
225
+ source,
226
+ )
227
+ replacements[token] = rendered
228
+ input_values[source] = resolved
229
+ return replacements, input_values
230
+
231
+
232
+ def _render_sources(
233
+ sources: dict[str, str], replacements: dict[str, str], destination: Path
234
+ ) -> dict[str, str]:
235
+ """Resolve only tokens authored in installed sources in one opaque pass."""
236
+ # Workflow customization may reference installed skill files; bind those
237
+ # references to the immutable generation before inserting the prose.
238
+ replacements = {
239
+ token: value.replace("{skill-root}", str(destination))
240
+ if token.startswith("{workflow.")
241
+ else value
242
+ for token, value in replacements.items()
243
+ }
244
+ source_names = set(sources)
245
+ patterns = [
246
+ *(re.escape(token) for token in sorted(replacements, key=len, reverse=True)),
247
+ _SNAPSHOT_TOKEN.pattern,
248
+ ]
249
+ token_pattern = re.compile("|".join(patterns))
250
+
251
+ def replace(match: re.Match[str]) -> str:
252
+ token = match.group(0)
253
+ if token in replacements:
254
+ return replacements[token]
255
+ snapshot = _SNAPSHOT_TOKEN.fullmatch(token)
256
+ if snapshot is None:
257
+ raise RenderError(f"unsupported render token: {token}")
258
+ target = snapshot.group(1)
259
+ if target not in source_names:
260
+ raise RenderError(f"snapshot reference targets undeclared source: {target}")
261
+ return str(destination / target)
262
+
263
+ rendered: dict[str, str] = {}
264
+ for name, content in sources.items():
265
+ # Inserted paths and customization prose are never scanned as source tokens.
266
+ rendered[name] = token_pattern.sub(replace, content)
267
+ return rendered
268
+
269
+
270
+ def _verify_existing(destination: Path, manifest: dict[str, Any]) -> None:
271
+ manifest_path = destination / "manifest.json"
272
+ try:
273
+ existing = json.loads(manifest_path.read_text(encoding="utf-8"))
274
+ except (OSError, UnicodeError, json.JSONDecodeError) as error:
275
+ raise RenderError(f"corrupt existing generation {destination}: {error}") from error
276
+ if existing != manifest:
277
+ raise RenderError(f"generation collision or corruption at {destination}")
278
+ expected_files = set(manifest["outputs"]) | {"manifest.json"}
279
+ actual_files = {
280
+ path.relative_to(destination).as_posix()
281
+ for path in destination.rglob("*")
282
+ if path.is_file()
283
+ }
284
+ if actual_files != expected_files:
285
+ raise RenderError(f"generation contains unexpected or missing files: {destination}")
286
+ for name, expected_hash in manifest["outputs"].items():
287
+ try:
288
+ actual_hash = _hash_bytes((destination / name).read_bytes())
289
+ except OSError as error:
290
+ raise RenderError(f"failed to verify {destination / name}: {error}") from error
291
+ if actual_hash != expected_hash:
292
+ raise RenderError(f"generation output hash mismatch: {destination / name}")
293
+
294
+
295
+ def _publish(destination: Path, outputs: dict[str, bytes], manifest: dict[str, Any]) -> None:
296
+ destination.parent.mkdir(parents=True, exist_ok=True)
297
+ if destination.exists():
298
+ _verify_existing(destination, manifest)
299
+ return
300
+ staging = Path(tempfile.mkdtemp(prefix=".staging-", dir=destination.parent))
301
+ try:
302
+ for name, content in outputs.items():
303
+ path = staging / name
304
+ path.parent.mkdir(parents=True, exist_ok=True)
305
+ path.write_bytes(content)
306
+ (staging / "manifest.json").write_bytes(
307
+ json.dumps(manifest, ensure_ascii=False, indent=2, sort_keys=True).encode("utf-8")
308
+ + b"\n"
309
+ )
310
+ try:
311
+ os.rename(staging, destination)
312
+ except OSError:
313
+ if destination.exists():
314
+ _verify_existing(destination, manifest)
315
+ else:
316
+ raise
317
+ finally:
318
+ if staging.exists():
319
+ shutil.rmtree(staging, ignore_errors=True)
320
+
321
+
322
+ def render(project_root: Path, skill_dir: Path) -> Path:
323
+ project_root = project_root.resolve(strict=True)
324
+ skill_dir = skill_dir.resolve(strict=True)
325
+ if not (project_root / "_bmad").is_dir():
326
+ raise RenderError(f"project root does not contain _bmad/: {project_root}")
327
+
328
+ sources = _load_sources(skill_dir)
329
+ central = load_central_config(project_root)
330
+ has_customization = any(
331
+ _CUSTOM_TOKEN.search(content) for content in sources.values()
332
+ )
333
+ defaults = (
334
+ load_toml(skill_dir / "customize.toml", required=True)
335
+ if has_customization
336
+ else None
337
+ )
338
+ customization = (
339
+ load_customization(project_root, skill_dir) if has_customization else {}
340
+ )
341
+ replacements, input_values = _resolve_replacements(
342
+ sources, central, customization, defaults, project_root
343
+ )
344
+ source_hashes = {
345
+ name: _hash_bytes(content.encode("utf-8")) for name, content in sources.items()
346
+ }
347
+ root_hash = _hash_bytes(str(project_root).encode("utf-8"))[:12]
348
+ slug = re.sub(r"[^a-z0-9]+", "-", project_root.name.lower()).strip("-") or "project"
349
+ slug = slug[:80].rstrip("-") or "project"
350
+ renderer_hash = _hash_bytes(Path(__file__).read_bytes())
351
+ identity = {
352
+ "project_root": str(project_root),
353
+ "renderer_sha256": renderer_hash,
354
+ "resolved_values": input_values,
355
+ "source_sha256": source_hashes,
356
+ }
357
+ generation_hash = _hash_bytes(_canonical_json(identity))[:20]
358
+ destination = (
359
+ project_root
360
+ / "_bmad"
361
+ / "render"
362
+ / skill_dir.name
363
+ / f"{slug}-{root_hash}"
364
+ / generation_hash
365
+ )
366
+ rendered = _render_sources(sources, replacements, destination)
367
+ outputs = {name: content.encode("utf-8") for name, content in rendered.items()}
368
+ output_hashes = {name: _hash_bytes(content) for name, content in outputs.items()}
369
+ manifest = {
370
+ "schema_version": 1,
371
+ "skill": skill_dir.name,
372
+ "project_root": str(project_root),
373
+ "project_slug": slug,
374
+ "root_hash": root_hash,
375
+ "generation_hash": generation_hash,
376
+ "inputs": identity,
377
+ "outputs": output_hashes,
378
+ }
379
+ _publish(destination, outputs, manifest)
380
+ return destination / "workflow.md"
381
+
382
+
383
+ def main() -> int:
384
+ parser = argparse.ArgumentParser(description=__doc__)
385
+ parser.add_argument("--project-root", required=True)
386
+ parser.add_argument("--skill", required=True)
387
+ args = parser.parse_args()
388
+ reconfigure = getattr(sys.stdout, "reconfigure", None)
389
+ if reconfigure is not None:
390
+ reconfigure(encoding="utf-8")
391
+ try:
392
+ entry = render(Path(args.project_root), Path(args.skill))
393
+ except (ConfigError, RenderError, OSError, UnicodeError, ValueError) as error:
394
+ sys.stdout.write(f"HALT: {error}\n")
395
+ return 1
396
+ sys.stdout.write(f"read and follow {entry}\n")
397
+ return 0
398
+
399
+
400
+ if __name__ == "__main__":
401
+ raise SystemExit(main())
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env python3
2
+ # /// script
3
+ # requires-python = ">=3.11"
4
+ # ///
5
+ """Resolve BMad's four central TOML layers to JSON."""
6
+
7
+ import argparse
8
+ import json
9
+ import sys
10
+ from pathlib import Path
11
+
12
+ # Installed scripts are consumer files, not a location for interpreter caches.
13
+ sys.dont_write_bytecode = True
14
+
15
+ try:
16
+ from config_utils import ConfigError, load_central_config
17
+ except ModuleNotFoundError as error:
18
+ if error.name != "tomllib":
19
+ raise
20
+ sys.stderr.write("error: Python 3.11+ is required (stdlib `tomllib` not found).\n")
21
+ raise SystemExit(3) from None
22
+
23
+
24
+ _MISSING = object()
25
+
26
+
27
+ def extract_key(data, dotted_key: str):
28
+ current = data
29
+ for part in dotted_key.split("."):
30
+ if isinstance(current, dict) and part in current:
31
+ current = current[part]
32
+ else:
33
+ return _MISSING
34
+ return current
35
+
36
+
37
+ def main() -> int:
38
+ parser = argparse.ArgumentParser(
39
+ description="Resolve BMad central config using four-layer TOML merge."
40
+ )
41
+ parser.add_argument(
42
+ "--project-root",
43
+ "-p",
44
+ required=True,
45
+ help="Absolute project root containing _bmad/",
46
+ )
47
+ parser.add_argument(
48
+ "--key",
49
+ "-k",
50
+ action="append",
51
+ default=[],
52
+ help="Dotted field path to resolve (repeatable). Omit for full dump.",
53
+ )
54
+ args = parser.parse_args()
55
+
56
+ try:
57
+ merged = load_central_config(Path(args.project_root).resolve())
58
+ except ConfigError as error:
59
+ sys.stderr.write(f"error: {error}\n")
60
+ return 1
61
+
62
+ output = merged
63
+ if args.key:
64
+ output = {}
65
+ for key in args.key:
66
+ value = extract_key(merged, key)
67
+ if value is not _MISSING:
68
+ output[key] = value
69
+ sys.stdout.write(json.dumps(output, indent=2, ensure_ascii=False) + "\n")
70
+ return 0
71
+
72
+
73
+ if __name__ == "__main__":
74
+ raise SystemExit(main())
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env python3
2
+ # /// script
3
+ # requires-python = ">=3.11"
4
+ # ///
5
+ """Resolve a skill's default, team, and user TOML customization layers."""
6
+
7
+ import argparse
8
+ import json
9
+ import sys
10
+ from pathlib import Path
11
+
12
+ # Installed scripts are consumer files, not a location for interpreter caches.
13
+ sys.dont_write_bytecode = True
14
+
15
+ try:
16
+ from config_utils import ConfigError, load_customization
17
+ except ModuleNotFoundError as error:
18
+ if error.name != "tomllib":
19
+ raise
20
+ sys.stderr.write("error: Python 3.11+ is required (stdlib `tomllib` not found).\n")
21
+ raise SystemExit(3) from None
22
+
23
+
24
+ _MISSING = object()
25
+
26
+
27
+ def find_project_root(start: Path) -> Path | None:
28
+ current = start.resolve()
29
+ while True:
30
+ if (current / "_bmad").exists() or (current / ".git").exists():
31
+ return current
32
+ if current.parent == current:
33
+ return None
34
+ current = current.parent
35
+
36
+
37
+ def extract_key(data, dotted_key: str):
38
+ current = data
39
+ for part in dotted_key.split("."):
40
+ if isinstance(current, dict) and part in current:
41
+ current = current[part]
42
+ else:
43
+ return _MISSING
44
+ return current
45
+
46
+
47
+ def write_json_stdout(output) -> None:
48
+ reconfigure = getattr(sys.stdout, "reconfigure", None)
49
+ if reconfigure is not None:
50
+ reconfigure(encoding="utf-8")
51
+ sys.stdout.write(json.dumps(output, indent=2, ensure_ascii=False) + "\n")
52
+
53
+
54
+ def main() -> int:
55
+ parser = argparse.ArgumentParser(
56
+ description="Resolve skill customization using three-layer TOML merge."
57
+ )
58
+ parser.add_argument(
59
+ "--skill", "-s", required=True, help="Absolute path to the skill directory"
60
+ )
61
+ parser.add_argument(
62
+ "--project-root",
63
+ "-p",
64
+ help="Explicit project root containing _bmad/ (recommended)",
65
+ )
66
+ parser.add_argument(
67
+ "--key",
68
+ "-k",
69
+ action="append",
70
+ default=[],
71
+ help="Dotted field path to resolve (repeatable). Omit for full dump.",
72
+ )
73
+ args = parser.parse_args()
74
+
75
+ skill_dir = Path(args.skill).resolve()
76
+ project_root = (
77
+ Path(args.project_root).resolve()
78
+ if args.project_root
79
+ else find_project_root(skill_dir) or find_project_root(Path.cwd())
80
+ )
81
+ try:
82
+ merged = load_customization(project_root, skill_dir)
83
+ except ConfigError as error:
84
+ sys.stderr.write(f"error: {error}\n")
85
+ return 1
86
+
87
+ output = merged
88
+ if args.key:
89
+ output = {}
90
+ for key in args.key:
91
+ value = extract_key(merged, key)
92
+ if value is not _MISSING:
93
+ output[key] = value
94
+ write_json_stdout(output)
95
+ return 0
96
+
97
+
98
+ if __name__ == "__main__":
99
+ raise SystemExit(main())
@@ -0,0 +1,85 @@
1
+ import sys
2
+ import tempfile
3
+ import unittest
4
+ from pathlib import Path
5
+
6
+
7
+ sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
8
+
9
+ from config_utils import ( # noqa: E402
10
+ ConfigError,
11
+ load_central_config,
12
+ load_customization,
13
+ load_toml,
14
+ structural_merge,
15
+ )
16
+
17
+
18
+ class ConfigUtilsTests(unittest.TestCase):
19
+ def test_structural_merge_recurses_appends_and_replaces_keyed_tables(self):
20
+ base = {
21
+ "nested": {"keep": True, "replace": "old"},
22
+ "plain": ["base"],
23
+ "items": [{"id": "one", "value": "old"}],
24
+ }
25
+ override = {
26
+ "nested": {"replace": "new"},
27
+ "plain": ["override"],
28
+ "items": [
29
+ {"id": "one", "value": "new"},
30
+ {"id": "two", "value": "added"},
31
+ ],
32
+ }
33
+
34
+ merged = structural_merge(base, override)
35
+
36
+ self.assertEqual(merged["nested"], {"keep": True, "replace": "new"})
37
+ self.assertEqual(merged["plain"], ["base", "override"])
38
+ self.assertEqual(
39
+ merged["items"],
40
+ [
41
+ {"id": "one", "value": "new"},
42
+ {"id": "two", "value": "added"},
43
+ ],
44
+ )
45
+
46
+ def test_non_string_keyed_identifier_is_rejected(self):
47
+ with self.assertRaisesRegex(ConfigError, "identifier `id` must be a string"):
48
+ structural_merge([{"id": "valid"}], [{"id": 42}])
49
+
50
+ def test_present_malformed_optional_layer_is_rejected(self):
51
+ with tempfile.TemporaryDirectory() as temp_dir:
52
+ path = Path(temp_dir) / "optional.toml"
53
+ path.write_text("[broken\n", encoding="utf-8")
54
+
55
+ with self.assertRaisesRegex(ConfigError, "failed to parse"):
56
+ load_toml(path)
57
+
58
+ def test_missing_optional_layer_is_empty(self):
59
+ with tempfile.TemporaryDirectory() as temp_dir:
60
+ path = Path(temp_dir) / "optional.toml"
61
+
62
+ self.assertEqual(load_toml(path), {})
63
+
64
+ def test_filesystem_layer_precedence(self):
65
+ with tempfile.TemporaryDirectory() as temp_dir:
66
+ root = Path(temp_dir)
67
+ bmad = root / "_bmad"
68
+ custom = bmad / "custom"
69
+ skill = bmad / "bmm" / "sample-skill"
70
+ custom.mkdir(parents=True)
71
+ skill.mkdir(parents=True)
72
+ (bmad / "config.toml").write_text('[value]\norder = "base-team"\n', encoding="utf-8")
73
+ (bmad / "config.user.toml").write_text('[value]\norder = "base-user"\n', encoding="utf-8")
74
+ (custom / "config.toml").write_text('[value]\norder = "custom-team"\n', encoding="utf-8")
75
+ (custom / "config.user.toml").write_text('[value]\norder = "custom-user"\n', encoding="utf-8")
76
+ (skill / "customize.toml").write_text('[value]\norder = "default"\n', encoding="utf-8")
77
+ (custom / "sample-skill.toml").write_text('[value]\norder = "team"\n', encoding="utf-8")
78
+ (custom / "sample-skill.user.toml").write_text('[value]\norder = "user"\n', encoding="utf-8")
79
+
80
+ self.assertEqual(load_central_config(root)["value"]["order"], "custom-user")
81
+ self.assertEqual(load_customization(root, skill)["value"]["order"], "user")
82
+
83
+
84
+ if __name__ == "__main__":
85
+ unittest.main()