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.
- package/.claude-plugin/marketplace.json +53 -0
- package/AGENTS.md +12 -0
- package/LICENSE +30 -0
- package/README.md +94 -0
- package/README_CN.md +108 -0
- package/README_VN.md +109 -0
- package/bmad-modules.yaml +133 -0
- package/package.json +123 -0
- package/removals.txt +87 -0
- package/src/bmm-skills/agents/bmad-agent-analyst/SKILL.md +76 -0
- package/src/bmm-skills/agents/bmad-agent-analyst/customize.toml +105 -0
- package/src/bmm-skills/agents/bmad-agent-architect/SKILL.md +76 -0
- package/src/bmm-skills/agents/bmad-agent-architect/customize.toml +65 -0
- package/src/bmm-skills/agents/bmad-agent-dev/SKILL.md +76 -0
- package/src/bmm-skills/agents/bmad-agent-dev/customize.toml +83 -0
- package/src/bmm-skills/agents/bmad-agent-pm/SKILL.md +76 -0
- package/src/bmm-skills/agents/bmad-agent-pm/customize.toml +75 -0
- package/src/bmm-skills/agents/bmad-agent-ux-designer/SKILL.md +76 -0
- package/src/bmm-skills/agents/bmad-agent-ux-designer/customize.toml +60 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/.memlog.md +12 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/.memlog.md:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/SKILL.md +86 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/SKILL.md:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/customize.toml +37 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/customize.toml:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/references/enrich-stories.md +19 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/references/enrich-stories.md:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/references/prompt-quality-canon.md +79 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/references/prompt-quality-canon.md:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/skills/bqa-setup/SKILL.md +80 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/skills/bqa-setup/SKILL.md:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/skills/bqa-setup/assets/module-help.csv +9 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/skills/bqa-setup/assets/module-help.csv:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/skills/bqa-setup/assets/module.yaml +16 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/skills/bqa-setup/assets/module.yaml:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/skills/bqa-setup/scripts/cleanup-legacy.py +287 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/skills/bqa-setup/scripts/cleanup-legacy.py:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/skills/bqa-setup/scripts/merge-config.py +441 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/skills/bqa-setup/scripts/merge-config.py:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/skills/bqa-setup/scripts/merge-help-csv.py +246 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/skills/bqa-setup/scripts/merge-help-csv.py:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/skills/quarkus-error-handling-i18n/SKILL.md +181 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/skills/quarkus-error-handling-i18n/SKILL.md:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/skills/quarkus-grpc-services/SKILL.md +141 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/skills/quarkus-grpc-services/SKILL.md:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/skills/quarkus-hexagonal-core/SKILL.md +392 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/skills/quarkus-hexagonal-core/SKILL.md:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/skills/quarkus-kafka-messaging/SKILL.md +148 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/skills/quarkus-kafka-messaging/SKILL.md:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/skills/quarkus-observability-otel/SKILL.md +180 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/skills/quarkus-observability-otel/SKILL.md:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/skills/quarkus-openapi-tmforum/SKILL.md +128 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/skills/quarkus-openapi-tmforum/SKILL.md:Zone.Identifier +0 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/skills/quarkus-sql-jdbc-agroal/SKILL.md +245 -0
- package/src/bmm-skills/agents/bmad-quarkus-architect/skills/quarkus-sql-jdbc-agroal/SKILL.md:Zone.Identifier +0 -0
- package/src/bmm-skills/module-help.csv +19 -0
- package/src/bmm-skills/module.yaml +92 -0
- package/src/bmm-skills/plan/bmad-architecture/SKILL.md +85 -0
- package/src/bmm-skills/plan/bmad-architecture/assets/spine-template.md +79 -0
- package/src/bmm-skills/plan/bmad-architecture/customize.toml +102 -0
- package/src/bmm-skills/plan/bmad-architecture/references/headless.md +26 -0
- package/src/bmm-skills/plan/bmad-architecture/references/reviewer-gate.md +13 -0
- package/src/bmm-skills/plan/bmad-architecture/scripts/lint_spine.py +257 -0
- package/src/bmm-skills/plan/bmad-architecture/scripts/tests/test_lint_spine.py +270 -0
- package/src/bmm-skills/plan/bmad-create-epics-and-stories/SKILL.md +93 -0
- package/src/bmm-skills/plan/bmad-create-epics-and-stories/customize.toml +41 -0
- package/src/bmm-skills/plan/bmad-create-epics-and-stories/steps/step-01-validate-prerequisites.md +263 -0
- package/src/bmm-skills/plan/bmad-create-epics-and-stories/steps/step-02-design-epics.md +242 -0
- package/src/bmm-skills/plan/bmad-create-epics-and-stories/steps/step-03-create-stories.md +255 -0
- package/src/bmm-skills/plan/bmad-create-epics-and-stories/steps/step-04-final-validation.md +143 -0
- package/src/bmm-skills/plan/bmad-create-epics-and-stories/templates/epics-template.md +61 -0
- package/src/bmm-skills/plan/bmad-generate-project-context/SKILL.md +10 -0
- package/src/bmm-skills/plan/bmad-prd/SKILL.md +94 -0
- package/src/bmm-skills/plan/bmad-prd/assets/headless-schemas.md +76 -0
- package/src/bmm-skills/plan/bmad-prd/assets/prd-template.md +165 -0
- package/src/bmm-skills/plan/bmad-prd/assets/prd-validation-checklist.md +135 -0
- package/src/bmm-skills/plan/bmad-prd/assets/validation-report-template.html +325 -0
- package/src/bmm-skills/plan/bmad-prd/customize.toml +149 -0
- package/src/bmm-skills/plan/bmad-prd/references/headless.md +39 -0
- package/src/bmm-skills/plan/bmad-prd/references/validate.md +97 -0
- package/src/bmm-skills/plan/bmad-prfaq/SKILL.md +135 -0
- package/src/bmm-skills/plan/bmad-prfaq/agents/artifact-analyzer.md +60 -0
- package/src/bmm-skills/plan/bmad-prfaq/agents/web-researcher.md +49 -0
- package/src/bmm-skills/plan/bmad-prfaq/assets/prfaq-template.md +62 -0
- package/src/bmm-skills/plan/bmad-prfaq/bmad-manifest.json +16 -0
- package/src/bmm-skills/plan/bmad-prfaq/customize.toml +41 -0
- package/src/bmm-skills/plan/bmad-prfaq/references/customer-faq.md +55 -0
- package/src/bmm-skills/plan/bmad-prfaq/references/internal-faq.md +51 -0
- package/src/bmm-skills/plan/bmad-prfaq/references/press-release.md +60 -0
- package/src/bmm-skills/plan/bmad-prfaq/references/verdict.md +83 -0
- package/src/bmm-skills/plan/bmad-product-brief/SKILL.md +91 -0
- package/src/bmm-skills/plan/bmad-product-brief/assets/brief-template.md +41 -0
- package/src/bmm-skills/plan/bmad-product-brief/customize.toml +101 -0
- 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/bmm-skills/plan/bmad-spec/SKILL.md +160 -0
- package/src/bmm-skills/plan/bmad-spec/assets/headless-schemas.md +33 -0
- package/src/bmm-skills/plan/bmad-spec/assets/spec-template.md +49 -0
- package/src/bmm-skills/plan/bmad-spec/assets/stories-schema.md +44 -0
- package/src/bmm-skills/plan/bmad-spec/customize.toml +52 -0
- package/src/bmm-skills/plan/bmad-sprint-planning/SKILL.md +62 -0
- package/src/bmm-skills/plan/bmad-sprint-planning/customize.toml +41 -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/plan/bmad-sprint-planning/sprint-status-template.yaml +71 -0
- package/src/bmm-skills/plan/bmad-ux/SKILL.md +90 -0
- package/src/bmm-skills/plan/bmad-ux/assets/color-themes.md +9 -0
- package/src/bmm-skills/plan/bmad-ux/assets/design-directions.md +9 -0
- package/src/bmm-skills/plan/bmad-ux/assets/design-example-editorial.md +158 -0
- package/src/bmm-skills/plan/bmad-ux/assets/design-example-mobile.md +93 -0
- package/src/bmm-skills/plan/bmad-ux/assets/design-example-shadcn.md +109 -0
- package/src/bmm-skills/plan/bmad-ux/assets/excalidraw-wireframe.md +19 -0
- package/src/bmm-skills/plan/bmad-ux/assets/experience-example-mobile.md +112 -0
- package/src/bmm-skills/plan/bmad-ux/assets/experience-example-shadcn.md +133 -0
- package/src/bmm-skills/plan/bmad-ux/assets/headless-schemas.md +84 -0
- package/src/bmm-skills/plan/bmad-ux/assets/key-screens.md +29 -0
- package/src/bmm-skills/plan/bmad-ux/assets/validation-report-template.html +319 -0
- package/src/bmm-skills/plan/bmad-ux/customize.toml +102 -0
- package/src/bmm-skills/plan/bmad-ux/references/creative-tools.md +19 -0
- package/src/bmm-skills/plan/bmad-ux/references/design-md-spec.md +50 -0
- package/src/bmm-skills/plan/bmad-ux/references/headless.md +37 -0
- package/src/bmm-skills/plan/bmad-ux/references/validate.md +115 -0
- package/src/bmm-skills/ship/bmad-build/SKILL.md +13 -0
- package/src/bmm-skills/ship/bmad-build/compile-epic-context.md +62 -0
- 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/ship/bmad-build/spec-template.md +89 -0
- package/src/bmm-skills/ship/bmad-build/step-01-clarify-and-route.md +105 -0
- package/src/bmm-skills/ship/bmad-build/step-02-plan.md +47 -0
- package/src/bmm-skills/ship/bmad-build/step-03-implement.md +49 -0
- package/src/bmm-skills/ship/bmad-build/step-04-review.md +55 -0
- package/src/bmm-skills/ship/bmad-build/step-05-present.md +75 -0
- 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/ship/bmad-build-auto/compile-epic-context.md +62 -0
- package/src/bmm-skills/ship/bmad-build-auto/customize.toml +121 -0
- package/src/bmm-skills/ship/bmad-build-auto/references/deletion-check.md +14 -0
- package/src/bmm-skills/ship/bmad-build-auto/review-prompts/edge-case-hunter.md +88 -0
- package/src/bmm-skills/ship/bmad-build-auto/review-prompts/verification-gap.md +113 -0
- package/src/bmm-skills/ship/bmad-build-auto/spec-template.md +99 -0
- 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/ship/bmad-build-auto/step-03-implement.md +44 -0
- package/src/bmm-skills/ship/bmad-build-auto/step-04-review.md +96 -0
- package/src/bmm-skills/ship/bmad-build-auto/workflow.md +104 -0
- package/src/bmm-skills/ship/bmad-checkpoint-preview/SKILL.md +68 -0
- package/src/bmm-skills/ship/bmad-checkpoint-preview/customize.toml +41 -0
- package/src/bmm-skills/ship/bmad-checkpoint-preview/generate-trail.md +38 -0
- package/src/bmm-skills/ship/bmad-checkpoint-preview/step-01-orientation.md +105 -0
- package/src/bmm-skills/ship/bmad-checkpoint-preview/step-02-walkthrough.md +89 -0
- package/src/bmm-skills/ship/bmad-checkpoint-preview/step-03-detail-pass.md +106 -0
- package/src/bmm-skills/ship/bmad-checkpoint-preview/step-04-testing.md +74 -0
- package/src/bmm-skills/ship/bmad-checkpoint-preview/step-05-wrapup.md +30 -0
- package/src/bmm-skills/ship/bmad-code-review/SKILL.md +90 -0
- package/src/bmm-skills/ship/bmad-code-review/customize.toml +113 -0
- 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/ship/bmad-code-review/steps/step-01-gather-context.md +87 -0
- package/src/bmm-skills/ship/bmad-code-review/steps/step-02-review.md +32 -0
- package/src/bmm-skills/ship/bmad-code-review/steps/step-03-triage.md +50 -0
- package/src/bmm-skills/ship/bmad-code-review/steps/step-04-present.md +132 -0
- package/src/bmm-skills/ship/bmad-correct-course/SKILL.md +300 -0
- package/src/bmm-skills/ship/bmad-correct-course/checklist.md +288 -0
- package/src/bmm-skills/ship/bmad-correct-course/customize.toml +41 -0
- package/src/bmm-skills/ship/bmad-qa-generate-e2e-tests/SKILL.md +176 -0
- package/src/bmm-skills/ship/bmad-qa-generate-e2e-tests/checklist.md +33 -0
- package/src/bmm-skills/ship/bmad-qa-generate-e2e-tests/customize.toml +41 -0
- package/src/bmm-skills/ship/bmad-retrospective/SKILL.md +94 -0
- package/src/bmm-skills/ship/bmad-retrospective/customize.toml +41 -0
- 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/v6-shims/bmad-create-architecture/SKILL.md +30 -0
- package/src/bmm-skills/v6-shims/bmad-create-architecture/customize.toml +41 -0
- package/src/bmm-skills/v6-shims/bmad-create-prd/SKILL.md +30 -0
- package/src/bmm-skills/v6-shims/bmad-create-prd/customize.toml +41 -0
- package/src/bmm-skills/v6-shims/bmad-create-story/SKILL.md +434 -0
- package/src/bmm-skills/v6-shims/bmad-create-story/checklist.md +357 -0
- package/src/bmm-skills/v6-shims/bmad-create-story/customize.toml +41 -0
- package/src/bmm-skills/v6-shims/bmad-create-story/discover-inputs.md +88 -0
- package/src/bmm-skills/v6-shims/bmad-create-story/template.md +49 -0
- package/src/bmm-skills/v6-shims/bmad-dev-auto/SKILL.md +19 -0
- package/src/bmm-skills/v6-shims/bmad-dev-story/SKILL.md +502 -0
- package/src/bmm-skills/v6-shims/bmad-dev-story/checklist.md +80 -0
- package/src/bmm-skills/v6-shims/bmad-dev-story/customize.toml +44 -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/v6-shims/bmad-edit-prd/SKILL.md +30 -0
- package/src/bmm-skills/v6-shims/bmad-edit-prd/customize.toml +42 -0
- 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-sprint-status/customize.toml +41 -0
- package/src/bmm-skills/v6-shims/bmad-technical-research/SKILL.md +14 -0
- package/src/bmm-skills/v6-shims/bmad-validate-prd/SKILL.md +30 -0
- package/src/bmm-skills/v6-shims/bmad-validate-prd/customize.toml +42 -0
- package/src/core-skills/bmad-advanced-elicitation/SKILL.md +65 -0
- package/src/core-skills/bmad-advanced-elicitation/assets/methods.csv +72 -0
- 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 +80 -0
- package/src/core-skills/bmad-brainstorming/assets/brain-icons.json +166 -0
- package/src/core-skills/bmad-brainstorming/assets/brain-methods.csv +109 -0
- package/src/core-skills/bmad-brainstorming/assets/brain-selector.html +328 -0
- package/src/core-skills/bmad-brainstorming/customize.toml +84 -0
- package/src/core-skills/bmad-brainstorming/references/converge.md +24 -0
- package/src/core-skills/bmad-brainstorming/references/finalize.md +26 -0
- package/src/core-skills/bmad-brainstorming/references/headless.md +54 -0
- package/src/core-skills/bmad-brainstorming/references/in-chat-techniques.md +18 -0
- package/src/core-skills/bmad-brainstorming/references/mode-autonomous.md +10 -0
- package/src/core-skills/bmad-brainstorming/references/mode-facilitator.md +11 -0
- package/src/core-skills/bmad-brainstorming/references/mode-partner.md +16 -0
- package/src/core-skills/bmad-brainstorming/references/resume.md +5 -0
- package/src/core-skills/bmad-brainstorming/scripts/brain.py +770 -0
- package/src/core-skills/bmad-brainstorming/scripts/tests/test_brain.py +239 -0
- package/src/core-skills/bmad-customize/SKILL.md +111 -0
- package/src/core-skills/bmad-customize/scripts/list_customizable_skills.py +231 -0
- package/src/core-skills/bmad-customize/scripts/tests/test_list_customizable_skills.py +249 -0
- 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 +107 -0
- package/src/core-skills/bmad-forge-idea/customize.toml +42 -0
- package/src/core-skills/bmad-forge-idea/scripts/resolve_personas.py +275 -0
- package/src/core-skills/bmad-forge-idea/scripts/tests/test_resolve_personas.py +138 -0
- package/src/core-skills/bmad-help/SKILL.md +75 -0
- package/src/core-skills/bmad-party-mode/SKILL.md +60 -0
- package/src/core-skills/bmad-party-mode/customize.toml +211 -0
- package/src/core-skills/bmad-party-mode/references/create-party.md +70 -0
- package/src/core-skills/bmad-party-mode/references/mode-agent-team.md +13 -0
- package/src/core-skills/bmad-party-mode/references/mode-auto.md +13 -0
- package/src/core-skills/bmad-party-mode/references/mode-subagent.md +31 -0
- package/src/core-skills/bmad-party-mode/references/party-memory.md +51 -0
- package/src/core-skills/bmad-party-mode/scripts/resolve_party.py +282 -0
- package/src/core-skills/bmad-party-mode/scripts/tests/test_resolve_party.py +146 -0
- 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 +10 -0
- package/src/core-skills/module.yaml +38 -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/memlog.py +224 -0
- package/src/scripts/render_skill.py +401 -0
- package/src/scripts/resolve_config.py +74 -0
- package/src/scripts/resolve_customization.py +99 -0
- 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_memlog.py +306 -0
- package/src/scripts/tests/test_resolve_config.py +89 -0
- package/src/scripts/tests/test_resolve_customization.py +77 -0
- package/tools/bundle-web-bundles.js +117 -0
- package/tools/format-workflow-md.js +263 -0
- package/tools/installer/README.md +60 -0
- package/tools/installer/bmad-cli.js +105 -0
- package/tools/installer/cli-utils.js +57 -0
- package/tools/installer/commands/install.js +149 -0
- package/tools/installer/commands/status.js +65 -0
- package/tools/installer/commands/uninstall.js +167 -0
- package/tools/installer/core/config.js +73 -0
- package/tools/installer/core/existing-install.js +121 -0
- package/tools/installer/core/install-paths.js +132 -0
- package/tools/installer/core/installer.js +1767 -0
- package/tools/installer/core/legacy-warnings.js +151 -0
- package/tools/installer/core/manifest-generator.js +859 -0
- package/tools/installer/core/manifest.js +433 -0
- package/tools/installer/core/uv-check.js +195 -0
- package/tools/installer/core/wsl-node-check.js +109 -0
- package/tools/installer/file-ops.js +204 -0
- package/tools/installer/fs-native.js +116 -0
- package/tools/installer/ide/_config-driven.js +972 -0
- package/tools/installer/ide/manager.js +324 -0
- package/tools/installer/ide/platform-codes.js +80 -0
- package/tools/installer/ide/platform-codes.yaml +343 -0
- package/tools/installer/ide/shared/installed-skills.js +50 -0
- package/tools/installer/ide/shared/path-utils.js +219 -0
- package/tools/installer/ide/shared/skill-manifest.js +57 -0
- package/tools/installer/install-messages.yaml +37 -0
- package/tools/installer/list-options.js +210 -0
- package/tools/installer/message-loader.js +83 -0
- package/tools/installer/modules/channel-plan.js +203 -0
- package/tools/installer/modules/channel-resolver.js +241 -0
- package/tools/installer/modules/custom-module-manager.js +924 -0
- package/tools/installer/modules/external-manager.js +671 -0
- package/tools/installer/modules/git-env.js +47 -0
- package/tools/installer/modules/module-help-schema.js +13 -0
- package/tools/installer/modules/official-modules.js +2257 -0
- package/tools/installer/modules/plugin-resolver.js +398 -0
- package/tools/installer/modules/version-resolver.js +336 -0
- package/tools/installer/project-root.js +224 -0
- package/tools/installer/prompts.js +791 -0
- package/tools/installer/set-overrides.js +330 -0
- package/tools/installer/ui.js +2167 -0
- package/tools/installer/yaml-format.js +245 -0
- package/tools/javascript-conventions.md +5 -0
- package/tools/migrate-custom-module-paths.js +124 -0
- package/tools/skill-validator.md +378 -0
- package/tools/validate-published-implementation-model.mjs +68 -0
- package/tools/validate-sidebar-order.js +388 -0
- package/tools/validate-skills.js +735 -0
- package/tools/validate-svg-changes.sh +356 -0
- package/web-bundles/README.md +46 -0
- package/web-bundles/brainstorming-coach/INSTRUCTIONS.md +86 -0
- package/web-bundles/brainstorming-coach/SKILL.md +83 -0
- package/web-bundles/brainstorming-coach/brain-methods.csv +62 -0
- package/web-bundles/bundles.json +139 -0
- package/web-bundles/market-and-industry-research/INSTRUCTIONS.md +88 -0
- package/web-bundles/market-and-industry-research/SKILL.md +59 -0
- package/web-bundles/prd-coach/INSTRUCTIONS.md +86 -0
- package/web-bundles/prd-coach/SKILL.md +101 -0
- package/web-bundles/prd-coach/prd-template.md +165 -0
- package/web-bundles/prd-coach/prd-validation-checklist.md +135 -0
- package/web-bundles/prfaq-coach/INSTRUCTIONS.md +86 -0
- package/web-bundles/prfaq-coach/SKILL.md +139 -0
- package/web-bundles/product-brief-coach/INSTRUCTIONS.md +86 -0
- package/web-bundles/product-brief-coach/SKILL.md +113 -0
- package/web-bundles/ux-coach/INSTRUCTIONS.md +92 -0
- package/web-bundles/ux-coach/SKILL.md +187 -0
- 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())
|
|
Binary file
|
|
@@ -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()
|