bmad-method 6.10.1-next.8 → 6.11.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.
- package/.claude-plugin/marketplace.json +30 -57
- package/README.md +49 -82
- package/README_CN.md +10 -10
- package/README_VN.md +11 -11
- package/bmad-modules.yaml +43 -39
- package/package.json +7 -3
- package/removals.txt +17 -0
- package/src/bmm-skills/{1-analysis → agents}/bmad-agent-analyst/SKILL.md +1 -1
- package/src/bmm-skills/{1-analysis → agents}/bmad-agent-analyst/customize.toml +22 -7
- package/src/bmm-skills/{3-solutioning → agents}/bmad-agent-architect/SKILL.md +1 -1
- package/src/bmm-skills/{3-solutioning → agents}/bmad-agent-architect/customize.toml +2 -2
- package/src/bmm-skills/{4-implementation → agents}/bmad-agent-dev/SKILL.md +1 -1
- package/src/bmm-skills/{4-implementation → agents}/bmad-agent-dev/customize.toml +7 -14
- package/src/bmm-skills/{2-plan-workflows → agents}/bmad-agent-pm/SKILL.md +1 -1
- package/src/bmm-skills/{2-plan-workflows → agents}/bmad-agent-pm/customize.toml +2 -2
- package/src/bmm-skills/{2-plan-workflows → agents}/bmad-agent-ux-designer/SKILL.md +1 -1
- package/src/bmm-skills/module-help.csv +14 -26
- package/src/bmm-skills/module.yaml +5 -15
- package/src/bmm-skills/{3-solutioning → plan}/bmad-architecture/SKILL.md +3 -3
- package/src/bmm-skills/{3-solutioning → plan}/bmad-architecture/customize.toml +4 -2
- package/src/bmm-skills/{3-solutioning → plan}/bmad-create-epics-and-stories/SKILL.md +1 -1
- package/src/bmm-skills/{3-solutioning → plan}/bmad-create-epics-and-stories/steps/step-04-final-validation.md +1 -1
- package/src/bmm-skills/plan/bmad-generate-project-context/SKILL.md +10 -0
- package/src/bmm-skills/{2-plan-workflows → plan}/bmad-prd/SKILL.md +3 -1
- package/src/bmm-skills/{2-plan-workflows → plan}/bmad-prd/assets/prd-template.md +1 -1
- package/src/bmm-skills/{2-plan-workflows → plan}/bmad-prd/customize.toml +5 -3
- package/src/bmm-skills/{2-plan-workflows → plan}/bmad-prd/references/validate.md +3 -3
- package/src/bmm-skills/{1-analysis → plan}/bmad-prfaq/SKILL.md +1 -1
- package/src/bmm-skills/{1-analysis → plan}/bmad-prfaq/bmad-manifest.json +1 -1
- package/src/bmm-skills/{1-analysis → plan}/bmad-prfaq/references/verdict.md +1 -1
- package/src/bmm-skills/{1-analysis → plan}/bmad-product-brief/SKILL.md +1 -1
- package/src/bmm-skills/{1-analysis → plan}/bmad-product-brief/customize.toml +5 -3
- package/src/bmm-skills/plan/bmad-project-context/SKILL.md +110 -0
- package/src/bmm-skills/plan/bmad-project-context/customize.toml +24 -0
- package/src/bmm-skills/plan/bmad-project-context/references/best-practices.md +65 -0
- package/src/bmm-skills/plan/bmad-project-context/references/template.md +55 -0
- package/src/{core-skills → bmm-skills/plan}/bmad-spec/SKILL.md +18 -3
- package/src/{core-skills → bmm-skills/plan}/bmad-spec/assets/spec-template.md +1 -1
- package/src/bmm-skills/plan/bmad-spec/assets/stories-schema.md +44 -0
- package/src/{core-skills → bmm-skills/plan}/bmad-spec/customize.toml +3 -4
- package/src/bmm-skills/plan/bmad-sprint-planning/SKILL.md +62 -0
- package/src/bmm-skills/plan/bmad-sprint-planning/references/fix-sprint-status.md +30 -0
- package/src/bmm-skills/plan/bmad-sprint-planning/references/generate-tracking.md +25 -0
- package/src/bmm-skills/plan/bmad-sprint-planning/references/readiness-gate.md +20 -0
- package/src/bmm-skills/plan/bmad-sprint-planning/references/status-view.md +14 -0
- package/src/bmm-skills/plan/bmad-sprint-planning/references/validate.md +10 -0
- package/src/bmm-skills/plan/bmad-sprint-planning/scripts/__pycache__/sprint_plan.cpython-311.pyc +0 -0
- package/src/bmm-skills/plan/bmad-sprint-planning/scripts/sprint_plan.py +697 -0
- package/src/bmm-skills/plan/bmad-sprint-planning/scripts/tests/__pycache__/test_sprint_plan.cpython-311-pytest-9.1.1.pyc +0 -0
- package/src/bmm-skills/plan/bmad-sprint-planning/scripts/tests/test_sprint_plan.py +524 -0
- package/src/bmm-skills/{4-implementation → plan}/bmad-sprint-planning/sprint-status-template.yaml +9 -7
- package/src/bmm-skills/{2-plan-workflows → plan}/bmad-ux/SKILL.md +1 -1
- package/src/bmm-skills/{2-plan-workflows → plan}/bmad-ux/assets/color-themes.md +1 -1
- package/src/bmm-skills/{2-plan-workflows → plan}/bmad-ux/customize.toml +4 -2
- package/src/bmm-skills/{2-plan-workflows → plan}/bmad-ux/references/creative-tools.md +1 -1
- package/src/bmm-skills/{2-plan-workflows → plan}/bmad-ux/references/validate.md +1 -1
- package/src/bmm-skills/ship/bmad-build/SKILL.md +13 -0
- package/src/bmm-skills/{4-implementation/bmad-quick-dev → ship/bmad-build}/compile-epic-context.md +1 -1
- package/src/bmm-skills/ship/bmad-build/customize.toml +164 -0
- package/src/bmm-skills/ship/bmad-build/references/deletion-check.md +14 -0
- package/src/bmm-skills/ship/bmad-build/review-prompts/edge-case-hunter.md +88 -0
- package/src/bmm-skills/ship/bmad-build/review-prompts/verification-gap.md +113 -0
- package/src/bmm-skills/{4-implementation/bmad-quick-dev → ship/bmad-build}/step-01-clarify-and-route.md +20 -19
- package/src/bmm-skills/{4-implementation/bmad-quick-dev → ship/bmad-build}/step-02-plan.md +5 -9
- package/src/bmm-skills/{4-implementation/bmad-quick-dev → ship/bmad-build}/step-03-implement.md +10 -6
- package/src/bmm-skills/{4-implementation/bmad-quick-dev → ship/bmad-build}/step-04-review.md +9 -15
- package/src/bmm-skills/{4-implementation/bmad-quick-dev → ship/bmad-build}/step-05-present.md +10 -13
- package/src/bmm-skills/ship/bmad-build/step-oneshot.md +77 -0
- package/src/bmm-skills/ship/bmad-build/sync-sprint-status.md +19 -0
- package/src/bmm-skills/ship/bmad-build/workflow.md +84 -0
- package/src/bmm-skills/ship/bmad-build-auto/SKILL.md +13 -0
- package/src/bmm-skills/{4-implementation/bmad-dev-auto → ship/bmad-build-auto}/compile-epic-context.md +1 -1
- package/src/bmm-skills/ship/bmad-build-auto/customize.toml +121 -0
- package/src/{core-skills/bmad-review-edge-case-hunter/SKILL.md → bmm-skills/ship/bmad-build-auto/review-prompts/edge-case-hunter.md} +21 -6
- package/src/{core-skills/bmad-review-verification-gap/SKILL.md → bmm-skills/ship/bmad-build-auto/review-prompts/verification-gap.md} +15 -8
- package/src/bmm-skills/{4-implementation/bmad-dev-auto → ship/bmad-build-auto}/spec-template.md +2 -1
- package/src/bmm-skills/ship/bmad-build-auto/step-01-clarify-and-route.md +83 -0
- package/src/bmm-skills/ship/bmad-build-auto/step-02-plan.md +27 -0
- package/src/bmm-skills/{4-implementation/bmad-dev-auto → ship/bmad-build-auto}/step-03-implement.md +6 -4
- package/src/bmm-skills/{4-implementation/bmad-dev-auto → ship/bmad-build-auto}/step-04-review.md +26 -25
- package/src/bmm-skills/ship/bmad-build-auto/workflow.md +104 -0
- package/src/bmm-skills/{4-implementation → ship}/bmad-checkpoint-preview/SKILL.md +1 -1
- package/src/bmm-skills/{4-implementation → ship}/bmad-checkpoint-preview/step-05-wrapup.md +1 -1
- package/src/bmm-skills/{4-implementation → ship}/bmad-code-review/SKILL.md +1 -1
- package/src/bmm-skills/{4-implementation → ship}/bmad-code-review/customize.toml +37 -17
- package/src/bmm-skills/ship/bmad-code-review/references/deletion-check.md +14 -0
- package/src/bmm-skills/ship/bmad-code-review/review-prompts/edge-case-hunter.md +88 -0
- package/src/bmm-skills/ship/bmad-code-review/review-prompts/verification-gap.md +113 -0
- package/src/bmm-skills/{4-implementation → ship}/bmad-code-review/steps/step-01-gather-context.md +7 -4
- package/src/bmm-skills/{4-implementation → ship}/bmad-code-review/steps/step-02-review.md +1 -1
- package/src/bmm-skills/{4-implementation → ship}/bmad-code-review/steps/step-04-present.md +1 -1
- package/src/bmm-skills/{4-implementation → ship}/bmad-correct-course/SKILL.md +7 -8
- package/src/bmm-skills/{4-implementation → ship}/bmad-qa-generate-e2e-tests/SKILL.md +2 -2
- package/src/bmm-skills/ship/bmad-retrospective/SKILL.md +94 -0
- package/src/bmm-skills/{4-implementation → ship}/bmad-retrospective/customize.toml +2 -2
- package/src/bmm-skills/ship/bmad-retrospective/references/acceptance-verdict.md +55 -0
- package/src/bmm-skills/ship/bmad-retrospective/references/aggregate-views.md +17 -0
- package/src/bmm-skills/ship/bmad-retrospective/references/evidence-gathering.md +30 -0
- package/src/bmm-skills/ship/bmad-retrospective/references/retro-document.md +84 -0
- package/src/bmm-skills/ship/bmad-retrospective/references/team-discussion.md +22 -0
- package/src/bmm-skills/ship/bmad-retrospective/scripts/__pycache__/sprint_status.cpython-311.pyc +0 -0
- package/src/bmm-skills/ship/bmad-retrospective/scripts/git_evidence.py +304 -0
- package/src/bmm-skills/ship/bmad-retrospective/scripts/sprint_status.py +746 -0
- package/src/bmm-skills/ship/bmad-retrospective/scripts/tests/__pycache__/test_git_evidence.cpython-311-pytest-9.1.1.pyc +0 -0
- package/src/bmm-skills/ship/bmad-retrospective/scripts/tests/__pycache__/test_sprint_status.cpython-311-pytest-9.1.1.pyc +0 -0
- package/src/bmm-skills/ship/bmad-retrospective/scripts/tests/fixtures/sprint-status-template.yaml +71 -0
- package/src/bmm-skills/ship/bmad-retrospective/scripts/tests/test_git_evidence.py +750 -0
- package/src/bmm-skills/ship/bmad-retrospective/scripts/tests/test_sprint_status.py +1579 -0
- package/src/bmm-skills/v6-shims/README.md +28 -0
- package/src/bmm-skills/{3-solutioning → v6-shims}/bmad-create-architecture/SKILL.md +2 -2
- package/src/bmm-skills/{2-plan-workflows → v6-shims}/bmad-create-prd/SKILL.md +4 -4
- package/src/bmm-skills/{4-implementation → v6-shims}/bmad-create-story/SKILL.md +5 -3
- package/src/bmm-skills/v6-shims/bmad-dev-auto/SKILL.md +19 -0
- package/src/bmm-skills/{4-implementation → v6-shims}/bmad-dev-story/SKILL.md +5 -3
- package/src/bmm-skills/{4-implementation → v6-shims}/bmad-dev-story/customize.toml +3 -0
- package/src/bmm-skills/v6-shims/bmad-document-project/SKILL.md +14 -0
- package/src/bmm-skills/v6-shims/bmad-domain-research/SKILL.md +14 -0
- package/src/bmm-skills/{2-plan-workflows → v6-shims}/bmad-edit-prd/SKILL.md +4 -4
- package/src/bmm-skills/v6-shims/bmad-market-research/SKILL.md +14 -0
- package/src/bmm-skills/v6-shims/bmad-quick-dev/SKILL.md +19 -0
- package/src/bmm-skills/v6-shims/bmad-sprint-status/SKILL.md +26 -0
- package/src/bmm-skills/v6-shims/bmad-technical-research/SKILL.md +14 -0
- package/src/bmm-skills/{2-plan-workflows → v6-shims}/bmad-validate-prd/SKILL.md +4 -4
- package/src/core-skills/bmad-advanced-elicitation/SKILL.md +26 -103
- package/src/core-skills/bmad-advanced-elicitation/customize.toml +54 -0
- package/src/core-skills/bmad-advanced-elicitation/scripts/pick_methods.py +233 -0
- package/src/core-skills/bmad-advanced-elicitation/scripts/tests/test_pick_methods.py +228 -0
- package/src/core-skills/bmad-brainstorming/SKILL.md +3 -3
- package/src/core-skills/bmad-brainstorming/assets/brain-selector.html +2 -0
- package/src/core-skills/bmad-brainstorming/references/mode-autonomous.md +1 -1
- package/src/core-skills/bmad-brainstorming/scripts/brain.py +36 -6
- package/src/core-skills/bmad-brainstorming/scripts/tests/test_brain.py +22 -0
- package/src/core-skills/bmad-customize/SKILL.md +2 -2
- package/src/core-skills/bmad-deep-recon/SKILL.md +82 -0
- package/src/core-skills/bmad-deep-recon/assets/research.template.md +18 -0
- package/src/core-skills/bmad-deep-recon/customize.toml +212 -0
- package/src/core-skills/bmad-deep-recon/references/draft.md +8 -0
- package/src/core-skills/bmad-deep-recon/references/finalize.md +11 -0
- package/src/core-skills/bmad-deep-recon/references/html-briefing.md +16 -0
- package/src/core-skills/bmad-deep-recon/references/lifecycle.md +11 -0
- package/src/core-skills/bmad-deep-recon/references/process.md +10 -0
- package/src/core-skills/bmad-deep-recon/references/run.md +73 -0
- package/src/core-skills/bmad-deep-recon/references/selection.md +13 -0
- package/src/core-skills/bmad-deep-recon/references/synthesis.md +16 -0
- package/src/core-skills/bmad-deep-recon/references/verification.md +29 -0
- package/src/core-skills/bmad-deep-recon/scripts/recon_kit.py +322 -0
- package/src/core-skills/bmad-deep-recon/scripts/tests/test_recon_kit.py +144 -0
- package/src/core-skills/bmad-deep-recon/types/academic-lit.md +19 -0
- package/src/core-skills/bmad-deep-recon/types/competitive.md +19 -0
- package/src/core-skills/bmad-deep-recon/types/domain.md +19 -0
- package/src/core-skills/bmad-deep-recon/types/market.md +19 -0
- package/src/core-skills/bmad-deep-recon/types/technical.md +19 -0
- package/src/core-skills/bmad-deep-recon/types/user-voice.md +19 -0
- package/src/core-skills/bmad-forge-idea/SKILL.md +2 -2
- package/src/core-skills/bmad-forge-idea/scripts/resolve_personas.py +7 -2
- package/src/core-skills/bmad-help/SKILL.md +3 -3
- package/src/core-skills/bmad-party-mode/SKILL.md +2 -2
- package/src/core-skills/bmad-party-mode/customize.toml +1 -1
- package/src/core-skills/bmad-party-mode/scripts/resolve_party.py +14 -4
- package/src/core-skills/bmad-review/SKILL.md +49 -0
- package/src/core-skills/bmad-review/customize.toml +141 -0
- package/src/core-skills/bmad-review/references/editorial-common.md +56 -0
- package/src/core-skills/bmad-review/references/lens-adversarial.md +19 -0
- package/src/core-skills/bmad-review/references/lens-edge-case-hunter.md +54 -0
- package/src/core-skills/bmad-review/references/lens-prose.md +7 -0
- package/src/core-skills/bmad-review/references/lens-structure.md +9 -0
- package/src/core-skills/bmad-review/references/lens-verification-gap.md +92 -0
- package/src/core-skills/bmad-review/references/structure-models.md +44 -0
- package/src/core-skills/bmad-review/scripts/tests/test_word_metrics.py +62 -0
- package/src/core-skills/bmad-review/scripts/word_metrics.py +102 -0
- package/src/core-skills/module-help.csv +4 -8
- package/src/core-skills/module.yaml +5 -0
- package/src/core-skills/v6-shims/README.md +25 -0
- package/src/core-skills/v6-shims/bmad-editorial-review/SKILL.md +6 -0
- package/src/core-skills/v6-shims/bmad-editorial-review/customize.toml +31 -0
- package/src/core-skills/v6-shims/bmad-editorial-review-prose/SKILL.md +6 -0
- package/src/core-skills/v6-shims/bmad-editorial-review-structure/SKILL.md +6 -0
- package/src/core-skills/v6-shims/bmad-review-adversarial-general/SKILL.md +6 -0
- package/src/core-skills/v6-shims/bmad-review-edge-case-hunter/SKILL.md +6 -0
- package/src/core-skills/v6-shims/bmad-review-verification-gap/SKILL.md +6 -0
- package/src/scripts/__pycache__/config_utils.cpython-311.pyc +0 -0
- package/src/scripts/config_utils.py +119 -0
- package/src/scripts/render_skill.py +401 -0
- package/src/scripts/resolve_config.py +32 -136
- package/src/scripts/resolve_customization.py +43 -184
- package/src/scripts/tests/__pycache__/test_config_utils.cpython-311.pyc +0 -0
- package/src/scripts/tests/__pycache__/test_resolve_config.cpython-311.pyc +0 -0
- package/src/scripts/tests/__pycache__/test_resolve_customization.cpython-311.pyc +0 -0
- package/src/scripts/tests/test_config_utils.py +85 -0
- package/src/scripts/tests/test_resolve_config.py +89 -0
- package/src/scripts/tests/test_resolve_customization.py +27 -0
- package/tools/installer/cli-utils.js +6 -2
- package/tools/installer/core/installer.js +31 -9
- package/tools/installer/core/manifest-generator.js +1 -1
- package/tools/installer/core/uv-check.js +122 -24
- package/tools/installer/ide/_config-driven.js +1 -1
- package/tools/installer/ide/platform-codes.yaml +7 -0
- package/tools/installer/ide/shared/path-utils.js +2 -2
- package/tools/installer/install-messages.yaml +3 -2
- package/tools/installer/modules/custom-module-manager.js +12 -6
- package/tools/installer/modules/external-manager.js +12 -8
- package/tools/installer/modules/git-env.js +47 -0
- package/tools/installer/modules/official-modules.js +1 -1
- package/tools/installer/prompts.js +41 -102
- package/tools/installer/ui.js +99 -22
- package/tools/skill-validator.md +11 -1
- package/tools/validate-published-implementation-model.mjs +68 -0
- package/tools/validate-skills.js +33 -0
- package/web-bundles/prd-coach/prd-template.md +1 -1
- package/src/bmm-skills/1-analysis/bmad-agent-tech-writer/SKILL.md +0 -76
- package/src/bmm-skills/1-analysis/bmad-agent-tech-writer/customize.toml +0 -81
- package/src/bmm-skills/1-analysis/bmad-agent-tech-writer/explain-concept.md +0 -20
- package/src/bmm-skills/1-analysis/bmad-agent-tech-writer/mermaid-gen.md +0 -20
- package/src/bmm-skills/1-analysis/bmad-agent-tech-writer/validate-doc.md +0 -19
- package/src/bmm-skills/1-analysis/bmad-agent-tech-writer/write-document.md +0 -20
- package/src/bmm-skills/1-analysis/bmad-document-project/SKILL.md +0 -62
- package/src/bmm-skills/1-analysis/bmad-document-project/checklist.md +0 -245
- package/src/bmm-skills/1-analysis/bmad-document-project/customize.toml +0 -41
- package/src/bmm-skills/1-analysis/bmad-document-project/documentation-requirements.csv +0 -12
- package/src/bmm-skills/1-analysis/bmad-document-project/instructions.md +0 -128
- package/src/bmm-skills/1-analysis/bmad-document-project/templates/deep-dive-template.md +0 -345
- package/src/bmm-skills/1-analysis/bmad-document-project/templates/index-template.md +0 -169
- package/src/bmm-skills/1-analysis/bmad-document-project/templates/project-overview-template.md +0 -103
- package/src/bmm-skills/1-analysis/bmad-document-project/templates/project-scan-report-schema.json +0 -160
- package/src/bmm-skills/1-analysis/bmad-document-project/templates/source-tree-template.md +0 -135
- package/src/bmm-skills/1-analysis/bmad-document-project/workflows/deep-dive-instructions.md +0 -300
- package/src/bmm-skills/1-analysis/bmad-document-project/workflows/deep-dive-workflow.md +0 -34
- package/src/bmm-skills/1-analysis/bmad-document-project/workflows/full-scan-instructions.md +0 -1108
- package/src/bmm-skills/1-analysis/bmad-document-project/workflows/full-scan-workflow.md +0 -34
- package/src/bmm-skills/1-analysis/research/bmad-domain-research/SKILL.md +0 -96
- package/src/bmm-skills/1-analysis/research/bmad-domain-research/customize.toml +0 -41
- package/src/bmm-skills/1-analysis/research/bmad-domain-research/domain-steps/step-01-init.md +0 -137
- package/src/bmm-skills/1-analysis/research/bmad-domain-research/domain-steps/step-02-domain-analysis.md +0 -229
- package/src/bmm-skills/1-analysis/research/bmad-domain-research/domain-steps/step-03-competitive-landscape.md +0 -238
- package/src/bmm-skills/1-analysis/research/bmad-domain-research/domain-steps/step-04-regulatory-focus.md +0 -206
- package/src/bmm-skills/1-analysis/research/bmad-domain-research/domain-steps/step-05-technical-trends.md +0 -234
- package/src/bmm-skills/1-analysis/research/bmad-domain-research/domain-steps/step-06-research-synthesis.md +0 -450
- package/src/bmm-skills/1-analysis/research/bmad-domain-research/research.template.md +0 -29
- package/src/bmm-skills/1-analysis/research/bmad-market-research/SKILL.md +0 -96
- package/src/bmm-skills/1-analysis/research/bmad-market-research/customize.toml +0 -41
- package/src/bmm-skills/1-analysis/research/bmad-market-research/research.template.md +0 -29
- package/src/bmm-skills/1-analysis/research/bmad-market-research/steps/step-01-init.md +0 -184
- package/src/bmm-skills/1-analysis/research/bmad-market-research/steps/step-02-customer-behavior.md +0 -239
- package/src/bmm-skills/1-analysis/research/bmad-market-research/steps/step-03-customer-pain-points.md +0 -251
- package/src/bmm-skills/1-analysis/research/bmad-market-research/steps/step-04-customer-decisions.md +0 -261
- package/src/bmm-skills/1-analysis/research/bmad-market-research/steps/step-05-competitive-analysis.md +0 -173
- package/src/bmm-skills/1-analysis/research/bmad-market-research/steps/step-06-research-completion.md +0 -484
- package/src/bmm-skills/1-analysis/research/bmad-technical-research/SKILL.md +0 -96
- package/src/bmm-skills/1-analysis/research/bmad-technical-research/customize.toml +0 -41
- package/src/bmm-skills/1-analysis/research/bmad-technical-research/research.template.md +0 -29
- package/src/bmm-skills/1-analysis/research/bmad-technical-research/technical-steps/step-01-init.md +0 -137
- package/src/bmm-skills/1-analysis/research/bmad-technical-research/technical-steps/step-02-technical-overview.md +0 -239
- package/src/bmm-skills/1-analysis/research/bmad-technical-research/technical-steps/step-03-integration-patterns.md +0 -248
- package/src/bmm-skills/1-analysis/research/bmad-technical-research/technical-steps/step-04-architectural-patterns.md +0 -202
- package/src/bmm-skills/1-analysis/research/bmad-technical-research/technical-steps/step-05-implementation-research.md +0 -233
- package/src/bmm-skills/1-analysis/research/bmad-technical-research/technical-steps/step-06-research-synthesis.md +0 -493
- package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/SKILL.md +0 -91
- package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/customize.toml +0 -41
- package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/steps/step-01-document-discovery.md +0 -179
- package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/steps/step-02-prd-analysis.md +0 -168
- package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/steps/step-03-epic-coverage-validation.md +0 -169
- package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/steps/step-04-ux-alignment.md +0 -129
- package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/steps/step-05-epic-quality-review.md +0 -241
- package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/steps/step-06-final-assessment.md +0 -132
- package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/templates/readiness-report-template.md +0 -4
- package/src/bmm-skills/3-solutioning/bmad-generate-project-context/SKILL.md +0 -81
- package/src/bmm-skills/3-solutioning/bmad-generate-project-context/customize.toml +0 -41
- package/src/bmm-skills/3-solutioning/bmad-generate-project-context/project-context-template.md +0 -21
- package/src/bmm-skills/3-solutioning/bmad-generate-project-context/steps/step-01-discover.md +0 -186
- package/src/bmm-skills/3-solutioning/bmad-generate-project-context/steps/step-02-generate.md +0 -321
- package/src/bmm-skills/3-solutioning/bmad-generate-project-context/steps/step-03-complete.md +0 -284
- package/src/bmm-skills/4-implementation/bmad-dev-auto/SKILL.md +0 -103
- package/src/bmm-skills/4-implementation/bmad-dev-auto/customize.toml +0 -108
- package/src/bmm-skills/4-implementation/bmad-dev-auto/step-01-clarify-and-route.md +0 -66
- package/src/bmm-skills/4-implementation/bmad-dev-auto/step-02-plan.md +0 -31
- package/src/bmm-skills/4-implementation/bmad-quick-dev/SKILL.md +0 -115
- package/src/bmm-skills/4-implementation/bmad-quick-dev/customize.toml +0 -83
- package/src/bmm-skills/4-implementation/bmad-quick-dev/step-oneshot.md +0 -82
- package/src/bmm-skills/4-implementation/bmad-quick-dev/sync-sprint-status.md +0 -19
- package/src/bmm-skills/4-implementation/bmad-retrospective/SKILL.md +0 -1527
- package/src/bmm-skills/4-implementation/bmad-sprint-planning/SKILL.md +0 -319
- package/src/bmm-skills/4-implementation/bmad-sprint-planning/checklist.md +0 -34
- package/src/bmm-skills/4-implementation/bmad-sprint-status/SKILL.md +0 -311
- package/src/core-skills/bmad-brainstorming/analysis/catalog-analysis.md +0 -239
- package/src/core-skills/bmad-brainstorming/analysis/method-matrix.csv +0 -109
- package/src/core-skills/bmad-editorial-review-prose/SKILL.md +0 -86
- package/src/core-skills/bmad-editorial-review-structure/SKILL.md +0 -179
- package/src/core-skills/bmad-index-docs/SKILL.md +0 -66
- package/src/core-skills/bmad-review-adversarial-general/SKILL.md +0 -37
- package/src/core-skills/bmad-shard-doc/SKILL.md +0 -105
- /package/src/bmm-skills/{2-plan-workflows → agents}/bmad-agent-ux-designer/customize.toml +0 -0
- /package/src/bmm-skills/{3-solutioning → plan}/bmad-architecture/assets/spine-template.md +0 -0
- /package/src/bmm-skills/{3-solutioning → plan}/bmad-architecture/references/headless.md +0 -0
- /package/src/bmm-skills/{3-solutioning → plan}/bmad-architecture/references/reviewer-gate.md +0 -0
- /package/src/bmm-skills/{3-solutioning → plan}/bmad-architecture/scripts/lint_spine.py +0 -0
- /package/src/bmm-skills/{3-solutioning → plan}/bmad-architecture/scripts/tests/test_lint_spine.py +0 -0
- /package/src/bmm-skills/{3-solutioning → plan}/bmad-create-epics-and-stories/customize.toml +0 -0
- /package/src/bmm-skills/{3-solutioning → plan}/bmad-create-epics-and-stories/steps/step-01-validate-prerequisites.md +0 -0
- /package/src/bmm-skills/{3-solutioning → plan}/bmad-create-epics-and-stories/steps/step-02-design-epics.md +0 -0
- /package/src/bmm-skills/{3-solutioning → plan}/bmad-create-epics-and-stories/steps/step-03-create-stories.md +0 -0
- /package/src/bmm-skills/{3-solutioning → plan}/bmad-create-epics-and-stories/templates/epics-template.md +0 -0
- /package/src/bmm-skills/{2-plan-workflows → plan}/bmad-prd/assets/headless-schemas.md +0 -0
- /package/src/bmm-skills/{2-plan-workflows → plan}/bmad-prd/assets/prd-validation-checklist.md +0 -0
- /package/src/bmm-skills/{2-plan-workflows → plan}/bmad-prd/assets/validation-report-template.html +0 -0
- /package/src/bmm-skills/{2-plan-workflows → plan}/bmad-prd/references/headless.md +0 -0
- /package/src/bmm-skills/{1-analysis → plan}/bmad-prfaq/agents/artifact-analyzer.md +0 -0
- /package/src/bmm-skills/{1-analysis → plan}/bmad-prfaq/agents/web-researcher.md +0 -0
- /package/src/bmm-skills/{1-analysis → plan}/bmad-prfaq/assets/prfaq-template.md +0 -0
- /package/src/bmm-skills/{1-analysis → plan}/bmad-prfaq/customize.toml +0 -0
- /package/src/bmm-skills/{1-analysis → plan}/bmad-prfaq/references/customer-faq.md +0 -0
- /package/src/bmm-skills/{1-analysis → plan}/bmad-prfaq/references/internal-faq.md +0 -0
- /package/src/bmm-skills/{1-analysis → plan}/bmad-prfaq/references/press-release.md +0 -0
- /package/src/bmm-skills/{1-analysis → plan}/bmad-product-brief/assets/brief-template.md +0 -0
- /package/src/{core-skills → bmm-skills/plan}/bmad-spec/assets/headless-schemas.md +0 -0
- /package/src/bmm-skills/{4-implementation → plan}/bmad-sprint-planning/customize.toml +0 -0
- /package/src/bmm-skills/{2-plan-workflows → plan}/bmad-ux/assets/design-directions.md +0 -0
- /package/src/bmm-skills/{2-plan-workflows → plan}/bmad-ux/assets/design-example-editorial.md +0 -0
- /package/src/bmm-skills/{2-plan-workflows → plan}/bmad-ux/assets/design-example-mobile.md +0 -0
- /package/src/bmm-skills/{2-plan-workflows → plan}/bmad-ux/assets/design-example-shadcn.md +0 -0
- /package/src/bmm-skills/{2-plan-workflows → plan}/bmad-ux/assets/excalidraw-wireframe.md +0 -0
- /package/src/bmm-skills/{2-plan-workflows → plan}/bmad-ux/assets/experience-example-mobile.md +0 -0
- /package/src/bmm-skills/{2-plan-workflows → plan}/bmad-ux/assets/experience-example-shadcn.md +0 -0
- /package/src/bmm-skills/{2-plan-workflows → plan}/bmad-ux/assets/headless-schemas.md +0 -0
- /package/src/bmm-skills/{2-plan-workflows → plan}/bmad-ux/assets/key-screens.md +0 -0
- /package/src/bmm-skills/{2-plan-workflows → plan}/bmad-ux/assets/validation-report-template.html +0 -0
- /package/src/bmm-skills/{2-plan-workflows → plan}/bmad-ux/references/design-md-spec.md +0 -0
- /package/src/bmm-skills/{2-plan-workflows → plan}/bmad-ux/references/headless.md +0 -0
- /package/src/bmm-skills/{4-implementation/bmad-quick-dev → ship/bmad-build}/spec-template.md +0 -0
- /package/src/{core-skills/bmad-review-edge-case-hunter → bmm-skills/ship/bmad-build-auto}/references/deletion-check.md +0 -0
- /package/src/bmm-skills/{4-implementation → ship}/bmad-checkpoint-preview/customize.toml +0 -0
- /package/src/bmm-skills/{4-implementation → ship}/bmad-checkpoint-preview/generate-trail.md +0 -0
- /package/src/bmm-skills/{4-implementation → ship}/bmad-checkpoint-preview/step-01-orientation.md +0 -0
- /package/src/bmm-skills/{4-implementation → ship}/bmad-checkpoint-preview/step-02-walkthrough.md +0 -0
- /package/src/bmm-skills/{4-implementation → ship}/bmad-checkpoint-preview/step-03-detail-pass.md +0 -0
- /package/src/bmm-skills/{4-implementation → ship}/bmad-checkpoint-preview/step-04-testing.md +0 -0
- /package/src/bmm-skills/{4-implementation → ship}/bmad-code-review/steps/step-03-triage.md +0 -0
- /package/src/bmm-skills/{4-implementation → ship}/bmad-correct-course/checklist.md +0 -0
- /package/src/bmm-skills/{4-implementation → ship}/bmad-correct-course/customize.toml +0 -0
- /package/src/bmm-skills/{4-implementation → ship}/bmad-qa-generate-e2e-tests/checklist.md +0 -0
- /package/src/bmm-skills/{4-implementation → ship}/bmad-qa-generate-e2e-tests/customize.toml +0 -0
- /package/src/bmm-skills/{3-solutioning → v6-shims}/bmad-create-architecture/customize.toml +0 -0
- /package/src/bmm-skills/{2-plan-workflows → v6-shims}/bmad-create-prd/customize.toml +0 -0
- /package/src/bmm-skills/{4-implementation → v6-shims}/bmad-create-story/checklist.md +0 -0
- /package/src/bmm-skills/{4-implementation → v6-shims}/bmad-create-story/customize.toml +0 -0
- /package/src/bmm-skills/{4-implementation → v6-shims}/bmad-create-story/discover-inputs.md +0 -0
- /package/src/bmm-skills/{4-implementation → v6-shims}/bmad-create-story/template.md +0 -0
- /package/src/bmm-skills/{4-implementation → v6-shims}/bmad-dev-story/checklist.md +0 -0
- /package/src/bmm-skills/{2-plan-workflows → v6-shims}/bmad-edit-prd/customize.toml +0 -0
- /package/src/bmm-skills/{4-implementation → v6-shims}/bmad-sprint-status/customize.toml +0 -0
- /package/src/bmm-skills/{2-plan-workflows → v6-shims}/bmad-validate-prd/customize.toml +0 -0
- /package/src/core-skills/bmad-advanced-elicitation/{methods.csv → assets/methods.csv} +0 -0
- /package/src/core-skills/bmad-party-mode/scripts/tests/{test-resolve_party.py → test_resolve_party.py} +0 -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())
|
|
@@ -1,134 +1,32 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
1. {project-root}/_bmad/config.toml (installer-owned team)
|
|
7
|
-
2. {project-root}/_bmad/config.user.toml (installer-owned user)
|
|
8
|
-
3. {project-root}/_bmad/custom/config.toml (human-authored team, committed)
|
|
9
|
-
4. {project-root}/_bmad/custom/config.user.toml (human-authored user, gitignored)
|
|
10
|
-
|
|
11
|
-
Outputs merged JSON to stdout. Errors go to stderr.
|
|
12
|
-
|
|
13
|
-
Uses only the Python stdlib (`tomllib`) — no third-party dependencies.
|
|
14
|
-
BMad is standardizing on `uv run` to invoke scripts (uv provisions a suitable
|
|
15
|
-
interpreter for you); a plain `python3` on PATH still works during the
|
|
16
|
-
transition. Either runner needs Python 3.11+ for `tomllib`.
|
|
17
|
-
|
|
18
|
-
uv run resolve_config.py --project-root /abs/path/to/project
|
|
19
|
-
uv run resolve_config.py --project-root ... --key core
|
|
20
|
-
uv run resolve_config.py --project-root ... --key agents
|
|
21
|
-
|
|
22
|
-
Merge rules (same as resolve_customization.py):
|
|
23
|
-
- Scalars: override wins
|
|
24
|
-
- Tables: deep merge
|
|
25
|
-
- Arrays of tables where every item shares `code` or `id`: merge by that key
|
|
26
|
-
- All other arrays: append
|
|
27
|
-
"""
|
|
2
|
+
# /// script
|
|
3
|
+
# requires-python = ">=3.11"
|
|
4
|
+
# ///
|
|
5
|
+
"""Resolve BMad's four central TOML layers to JSON."""
|
|
28
6
|
|
|
29
7
|
import argparse
|
|
30
8
|
import json
|
|
31
9
|
import sys
|
|
32
10
|
from pathlib import Path
|
|
33
11
|
|
|
12
|
+
# Installed scripts are consumer files, not a location for interpreter caches.
|
|
13
|
+
sys.dont_write_bytecode = True
|
|
14
|
+
|
|
34
15
|
try:
|
|
35
|
-
import
|
|
36
|
-
except
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
)
|
|
40
|
-
|
|
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
|
|
41
22
|
|
|
42
23
|
|
|
43
24
|
_MISSING = object()
|
|
44
|
-
_KEYED_MERGE_FIELDS = ("code", "id")
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def load_toml(file_path: Path, required: bool = False) -> dict:
|
|
48
|
-
if not file_path.exists():
|
|
49
|
-
if required:
|
|
50
|
-
sys.stderr.write(f"error: required config file not found: {file_path}\n")
|
|
51
|
-
sys.exit(1)
|
|
52
|
-
return {}
|
|
53
|
-
try:
|
|
54
|
-
with file_path.open("rb") as f:
|
|
55
|
-
parsed = tomllib.load(f)
|
|
56
|
-
if not isinstance(parsed, dict):
|
|
57
|
-
return {}
|
|
58
|
-
return parsed
|
|
59
|
-
except tomllib.TOMLDecodeError as error:
|
|
60
|
-
level = "error" if required else "warning"
|
|
61
|
-
sys.stderr.write(f"{level}: failed to parse {file_path}: {error}\n")
|
|
62
|
-
if required:
|
|
63
|
-
sys.exit(1)
|
|
64
|
-
return {}
|
|
65
|
-
except OSError as error:
|
|
66
|
-
level = "error" if required else "warning"
|
|
67
|
-
sys.stderr.write(f"{level}: failed to read {file_path}: {error}\n")
|
|
68
|
-
if required:
|
|
69
|
-
sys.exit(1)
|
|
70
|
-
return {}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
def _detect_keyed_merge_field(items):
|
|
74
|
-
if not items or not all(isinstance(item, dict) for item in items):
|
|
75
|
-
return None
|
|
76
|
-
for candidate in _KEYED_MERGE_FIELDS:
|
|
77
|
-
if all(item.get(candidate) is not None for item in items):
|
|
78
|
-
return candidate
|
|
79
|
-
return None
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
def _merge_by_key(base, override, key_name):
|
|
83
|
-
result = []
|
|
84
|
-
index_by_key = {}
|
|
85
|
-
for item in base:
|
|
86
|
-
if not isinstance(item, dict):
|
|
87
|
-
continue
|
|
88
|
-
if item.get(key_name) is not None:
|
|
89
|
-
index_by_key[item[key_name]] = len(result)
|
|
90
|
-
result.append(dict(item))
|
|
91
|
-
for item in override:
|
|
92
|
-
if not isinstance(item, dict):
|
|
93
|
-
result.append(item)
|
|
94
|
-
continue
|
|
95
|
-
key = item.get(key_name)
|
|
96
|
-
if key is not None and key in index_by_key:
|
|
97
|
-
result[index_by_key[key]] = dict(item)
|
|
98
|
-
else:
|
|
99
|
-
if key is not None:
|
|
100
|
-
index_by_key[key] = len(result)
|
|
101
|
-
result.append(dict(item))
|
|
102
|
-
return result
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
def _merge_arrays(base, override):
|
|
106
|
-
base_arr = base if isinstance(base, list) else []
|
|
107
|
-
override_arr = override if isinstance(override, list) else []
|
|
108
|
-
keyed_field = _detect_keyed_merge_field(base_arr + override_arr)
|
|
109
|
-
if keyed_field:
|
|
110
|
-
return _merge_by_key(base_arr, override_arr, keyed_field)
|
|
111
|
-
return base_arr + override_arr
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
def deep_merge(base, override):
|
|
115
|
-
if isinstance(base, dict) and isinstance(override, dict):
|
|
116
|
-
result = dict(base)
|
|
117
|
-
for key, over_val in override.items():
|
|
118
|
-
if key in result:
|
|
119
|
-
result[key] = deep_merge(result[key], over_val)
|
|
120
|
-
else:
|
|
121
|
-
result[key] = over_val
|
|
122
|
-
return result
|
|
123
|
-
if isinstance(base, list) and isinstance(override, list):
|
|
124
|
-
return _merge_arrays(base, override)
|
|
125
|
-
return override
|
|
126
25
|
|
|
127
26
|
|
|
128
27
|
def extract_key(data, dotted_key: str):
|
|
129
|
-
parts = dotted_key.split(".")
|
|
130
28
|
current = data
|
|
131
|
-
for part in
|
|
29
|
+
for part in dotted_key.split("."):
|
|
132
30
|
if isinstance(current, dict) and part in current:
|
|
133
31
|
current = current[part]
|
|
134
32
|
else:
|
|
@@ -136,43 +34,41 @@ def extract_key(data, dotted_key: str):
|
|
|
136
34
|
return current
|
|
137
35
|
|
|
138
36
|
|
|
139
|
-
def main():
|
|
37
|
+
def main() -> int:
|
|
140
38
|
parser = argparse.ArgumentParser(
|
|
141
|
-
description="Resolve BMad central config using four-layer TOML merge."
|
|
39
|
+
description="Resolve BMad central config using four-layer TOML merge."
|
|
142
40
|
)
|
|
143
41
|
parser.add_argument(
|
|
144
|
-
"--project-root",
|
|
145
|
-
|
|
42
|
+
"--project-root",
|
|
43
|
+
"-p",
|
|
44
|
+
required=True,
|
|
45
|
+
help="Absolute project root containing _bmad/",
|
|
146
46
|
)
|
|
147
47
|
parser.add_argument(
|
|
148
|
-
"--key",
|
|
48
|
+
"--key",
|
|
49
|
+
"-k",
|
|
50
|
+
action="append",
|
|
51
|
+
default=[],
|
|
149
52
|
help="Dotted field path to resolve (repeatable). Omit for full dump.",
|
|
150
53
|
)
|
|
151
54
|
args = parser.parse_args()
|
|
152
55
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
custom_team = load_toml(bmad_dir / "custom" / "config.toml")
|
|
159
|
-
custom_user = load_toml(bmad_dir / "custom" / "config.user.toml")
|
|
160
|
-
|
|
161
|
-
merged = deep_merge(base_team, base_user)
|
|
162
|
-
merged = deep_merge(merged, custom_team)
|
|
163
|
-
merged = deep_merge(merged, custom_user)
|
|
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
|
|
164
61
|
|
|
62
|
+
output = merged
|
|
165
63
|
if args.key:
|
|
166
64
|
output = {}
|
|
167
65
|
for key in args.key:
|
|
168
66
|
value = extract_key(merged, key)
|
|
169
67
|
if value is not _MISSING:
|
|
170
68
|
output[key] = value
|
|
171
|
-
else:
|
|
172
|
-
output = merged
|
|
173
|
-
|
|
174
69
|
sys.stdout.write(json.dumps(output, indent=2, ensure_ascii=False) + "\n")
|
|
70
|
+
return 0
|
|
175
71
|
|
|
176
72
|
|
|
177
73
|
if __name__ == "__main__":
|
|
178
|
-
main()
|
|
74
|
+
raise SystemExit(main())
|