diff --git a/src/bmm-skills/1-analysis/bmad-agent-analyst/SKILL.md b/src/bmm-skills/1-analysis/bmad-agent-analyst/SKILL.md index 4653171df..daa69616f 100644 --- a/src/bmm-skills/1-analysis/bmad-agent-analyst/SKILL.md +++ b/src/bmm-skills/1-analysis/bmad-agent-analyst/SKILL.md @@ -46,7 +46,7 @@ Treat every entry in `{agent.persistent_facts}` as foundational context you carr ### Step 5: Load Config -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - Use `{user_name}` for greeting - Use `{communication_language}` for all communications - Use `{document_output_language}` for output documents diff --git a/src/bmm-skills/1-analysis/bmad-agent-tech-writer/SKILL.md b/src/bmm-skills/1-analysis/bmad-agent-tech-writer/SKILL.md index ff6430d93..8b5749163 100644 --- a/src/bmm-skills/1-analysis/bmad-agent-tech-writer/SKILL.md +++ b/src/bmm-skills/1-analysis/bmad-agent-tech-writer/SKILL.md @@ -46,7 +46,7 @@ Treat every entry in `{agent.persistent_facts}` as foundational context you carr ### Step 5: Load Config -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - Use `{user_name}` for greeting - Use `{communication_language}` for all communications - Use `{document_output_language}` for output documents diff --git a/src/bmm-skills/1-analysis/bmad-document-project/SKILL.md b/src/bmm-skills/1-analysis/bmad-document-project/SKILL.md index 112732031..0a628e291 100644 --- a/src/bmm-skills/1-analysis/bmad-document-project/SKILL.md +++ b/src/bmm-skills/1-analysis/bmad-document-project/SKILL.md @@ -40,7 +40,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c ### Step 4: Load Config -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - Use `{user_name}` for greeting - Use `{communication_language}` for all communications - Use `{document_output_language}` for output documents diff --git a/src/bmm-skills/1-analysis/bmad-document-project/workflows/deep-dive-workflow.md b/src/bmm-skills/1-analysis/bmad-document-project/workflows/deep-dive-workflow.md index c55f036a7..1796ea8d8 100644 --- a/src/bmm-skills/1-analysis/bmad-document-project/workflows/deep-dive-workflow.md +++ b/src/bmm-skills/1-analysis/bmad-document-project/workflows/deep-dive-workflow.md @@ -11,7 +11,7 @@ ### Configuration Loading -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - `project_knowledge` - `user_name` diff --git a/src/bmm-skills/1-analysis/bmad-document-project/workflows/full-scan-workflow.md b/src/bmm-skills/1-analysis/bmad-document-project/workflows/full-scan-workflow.md index 5aaf4a580..367179ec3 100644 --- a/src/bmm-skills/1-analysis/bmad-document-project/workflows/full-scan-workflow.md +++ b/src/bmm-skills/1-analysis/bmad-document-project/workflows/full-scan-workflow.md @@ -10,7 +10,7 @@ ### Configuration Loading -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - `project_knowledge` - `user_name` diff --git a/src/bmm-skills/1-analysis/bmad-prfaq/SKILL.md b/src/bmm-skills/1-analysis/bmad-prfaq/SKILL.md index 6ce2d33ed..55338d6f9 100644 --- a/src/bmm-skills/1-analysis/bmad-prfaq/SKILL.md +++ b/src/bmm-skills/1-analysis/bmad-prfaq/SKILL.md @@ -50,7 +50,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c ### Step 4: Load Config -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - Use `{user_name}` for greeting - Use `{communication_language}` for all communications - Use `{document_output_language}` for output documents diff --git a/src/bmm-skills/1-analysis/bmad-product-brief/SKILL.md b/src/bmm-skills/1-analysis/bmad-product-brief/SKILL.md index 0d26145af..1b5614e6f 100644 --- a/src/bmm-skills/1-analysis/bmad-product-brief/SKILL.md +++ b/src/bmm-skills/1-analysis/bmad-product-brief/SKILL.md @@ -21,7 +21,7 @@ Briefs produced here are honest, right-sized to purpose, and built for what come 1. Resolve customization: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`. On failure, surface the diagnostic and halt. 2. Execute each entry in `{workflow.activation_steps_prepend}` in order. 3. Treat every entry in `{workflow.persistent_facts}` as foundational context for the rest of the run. Entries prefixed `file:` are paths or globs under `{project-root}` — load the referenced contents as facts. All other entries are facts verbatim. -4. Load `{project-root}/_bmad/bmm/config.yaml` (and `config.user.yaml` if present). Resolve `{user_name}`, `{communication_language}`, `{document_output_language}`, `{planning_artifacts}`, `{project_name}`, `{date}`. +4. Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve `{user_name}`, `{communication_language}`, `{document_output_language}`, `{planning_artifacts}`, `{project_name}`, `{date}`. 5. Greet `{user_name}` in `{communication_language}`. Detect intent (create / update / validate). If interactive and intent is unclear, ask; for headless behavior see `## Headless Mode`. 6. Execute each entry in `{workflow.activation_steps_append}` in order. @@ -59,6 +59,13 @@ When invoked headless, do not ask. Complete the intent using what is provided, w Omit keys for artifacts that were not produced. +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: +- Use `{user_name}` for greeting +- Use `{communication_language}` for all communications +- Use `{document_output_language}` for output documents +- Use `{planning_artifacts}` for output location and artifact scanning +- Use `{project_knowledge}` for additional context scanning + ## Discovery Conversationally surface what the user brings, why this brief exists, and the domain — echo back how each shapes your approach. Open with space for the full picture: invite a brain dump and ask up front for any source material they already have (memo, deck, transcript, prior brief, slack thread). Read what exists first; ask only what is missing. After the dump, a simple "anything else?" often surfaces what they almost forgot. Drill into specifics only after the broad shape is on the table; premature granular questions interrupt the dump and miss the room. Get a read on stakes early (passion project, internal pitch, investor input, public launch), and let that calibrate how hard you push. Suggest research (web, competitive, market) only when the stakes warrant it. diff --git a/src/bmm-skills/1-analysis/research/bmad-domain-research/SKILL.md b/src/bmm-skills/1-analysis/research/bmad-domain-research/SKILL.md index be364aa2f..d4fa24e05 100644 --- a/src/bmm-skills/1-analysis/research/bmad-domain-research/SKILL.md +++ b/src/bmm-skills/1-analysis/research/bmad-domain-research/SKILL.md @@ -44,7 +44,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c ### Step 4: Load Config -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - Use `{user_name}` for greeting - Use `{communication_language}` for all communications - Use `{document_output_language}` for output documents diff --git a/src/bmm-skills/1-analysis/research/bmad-market-research/SKILL.md b/src/bmm-skills/1-analysis/research/bmad-market-research/SKILL.md index 964049085..eb96b59f8 100644 --- a/src/bmm-skills/1-analysis/research/bmad-market-research/SKILL.md +++ b/src/bmm-skills/1-analysis/research/bmad-market-research/SKILL.md @@ -44,7 +44,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c ### Step 4: Load Config -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - Use `{user_name}` for greeting - Use `{communication_language}` for all communications - Use `{document_output_language}` for output documents diff --git a/src/bmm-skills/1-analysis/research/bmad-technical-research/SKILL.md b/src/bmm-skills/1-analysis/research/bmad-technical-research/SKILL.md index 582a05c60..e46a82239 100644 --- a/src/bmm-skills/1-analysis/research/bmad-technical-research/SKILL.md +++ b/src/bmm-skills/1-analysis/research/bmad-technical-research/SKILL.md @@ -44,7 +44,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c ### Step 4: Load Config -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - Use `{user_name}` for greeting - Use `{communication_language}` for all communications - Use `{document_output_language}` for output documents diff --git a/src/bmm-skills/2-plan-workflows/bmad-agent-pm/SKILL.md b/src/bmm-skills/2-plan-workflows/bmad-agent-pm/SKILL.md index 693072603..9ab429a27 100644 --- a/src/bmm-skills/2-plan-workflows/bmad-agent-pm/SKILL.md +++ b/src/bmm-skills/2-plan-workflows/bmad-agent-pm/SKILL.md @@ -46,7 +46,7 @@ Treat every entry in `{agent.persistent_facts}` as foundational context you carr ### Step 5: Load Config -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - Use `{user_name}` for greeting - Use `{communication_language}` for all communications - Use `{document_output_language}` for output documents diff --git a/src/bmm-skills/2-plan-workflows/bmad-agent-ux-designer/SKILL.md b/src/bmm-skills/2-plan-workflows/bmad-agent-ux-designer/SKILL.md index cb261c3fb..2e666a9b8 100644 --- a/src/bmm-skills/2-plan-workflows/bmad-agent-ux-designer/SKILL.md +++ b/src/bmm-skills/2-plan-workflows/bmad-agent-ux-designer/SKILL.md @@ -46,7 +46,7 @@ Treat every entry in `{agent.persistent_facts}` as foundational context you carr ### Step 5: Load Config -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - Use `{user_name}` for greeting - Use `{communication_language}` for all communications - Use `{document_output_language}` for output documents diff --git a/src/bmm-skills/2-plan-workflows/bmad-create-prd/SKILL.md b/src/bmm-skills/2-plan-workflows/bmad-create-prd/SKILL.md index 1ad02d01d..c32eff2fe 100644 --- a/src/bmm-skills/2-plan-workflows/bmad-create-prd/SKILL.md +++ b/src/bmm-skills/2-plan-workflows/bmad-create-prd/SKILL.md @@ -73,7 +73,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c ### Step 4: Load Config -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - Use `{user_name}` for greeting - Use `{communication_language}` for all communications - Use `{document_output_language}` for output documents diff --git a/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/SKILL.md b/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/SKILL.md index 496473b1e..76f865681 100644 --- a/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/SKILL.md +++ b/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/SKILL.md @@ -47,7 +47,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c ### Step 4: Load Config -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - Use `{user_name}` for greeting - Use `{communication_language}` for all communications - Use `{document_output_language}` for output documents diff --git a/src/bmm-skills/2-plan-workflows/bmad-edit-prd/SKILL.md b/src/bmm-skills/2-plan-workflows/bmad-edit-prd/SKILL.md index e209df340..3b5c7526c 100644 --- a/src/bmm-skills/2-plan-workflows/bmad-edit-prd/SKILL.md +++ b/src/bmm-skills/2-plan-workflows/bmad-edit-prd/SKILL.md @@ -73,7 +73,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c ### Step 4: Load Config -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - Use `{user_name}` for greeting - Use `{communication_language}` for all communications - Use `{document_output_language}` for output documents diff --git a/src/bmm-skills/2-plan-workflows/bmad-validate-prd/SKILL.md b/src/bmm-skills/2-plan-workflows/bmad-validate-prd/SKILL.md index 90ec68f17..28c2797ea 100644 --- a/src/bmm-skills/2-plan-workflows/bmad-validate-prd/SKILL.md +++ b/src/bmm-skills/2-plan-workflows/bmad-validate-prd/SKILL.md @@ -73,7 +73,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c ### Step 4: Load Config -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - Use `{user_name}` for greeting - Use `{communication_language}` for all communications - Use `{document_output_language}` for output documents diff --git a/src/bmm-skills/3-solutioning/bmad-agent-architect/SKILL.md b/src/bmm-skills/3-solutioning/bmad-agent-architect/SKILL.md index 1650aee09..52e9829b4 100644 --- a/src/bmm-skills/3-solutioning/bmad-agent-architect/SKILL.md +++ b/src/bmm-skills/3-solutioning/bmad-agent-architect/SKILL.md @@ -46,7 +46,7 @@ Treat every entry in `{agent.persistent_facts}` as foundational context you carr ### Step 5: Load Config -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - Use `{user_name}` for greeting - Use `{communication_language}` for all communications - Use `{document_output_language}` for output documents diff --git a/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/SKILL.md b/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/SKILL.md index 1d5133f90..725ce3e31 100644 --- a/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/SKILL.md +++ b/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/SKILL.md @@ -69,7 +69,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c ### Step 4: Load Config -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - Use `{user_name}` for greeting - Use `{communication_language}` for all communications - Use `{document_output_language}` for output documents diff --git a/src/bmm-skills/3-solutioning/bmad-create-architecture/SKILL.md b/src/bmm-skills/3-solutioning/bmad-create-architecture/SKILL.md index ca89a71cf..ebe478f73 100644 --- a/src/bmm-skills/3-solutioning/bmad-create-architecture/SKILL.md +++ b/src/bmm-skills/3-solutioning/bmad-create-architecture/SKILL.md @@ -50,7 +50,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c ### Step 4: Load Config -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - Use `{user_name}` for greeting - Use `{communication_language}` for all communications - Use `{document_output_language}` for output documents diff --git a/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/SKILL.md b/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/SKILL.md index a3f0f61c8..c535de75e 100644 --- a/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/SKILL.md +++ b/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/SKILL.md @@ -71,7 +71,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c ### Step 4: Load Config -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - Use `{user_name}` for greeting - Use `{communication_language}` for all communications - Use `{document_output_language}` for output documents diff --git a/src/bmm-skills/3-solutioning/bmad-generate-project-context/SKILL.md b/src/bmm-skills/3-solutioning/bmad-generate-project-context/SKILL.md index 42fd2e8fc..13e712fcd 100644 --- a/src/bmm-skills/3-solutioning/bmad-generate-project-context/SKILL.md +++ b/src/bmm-skills/3-solutioning/bmad-generate-project-context/SKILL.md @@ -50,7 +50,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c ### Step 4: Load Config -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - Use `{user_name}` for greeting - Use `{communication_language}` for all communications - Use `{document_output_language}` for output documents diff --git a/src/bmm-skills/4-implementation/bmad-agent-dev/SKILL.md b/src/bmm-skills/4-implementation/bmad-agent-dev/SKILL.md index 95a3b9594..4d2ec3711 100644 --- a/src/bmm-skills/4-implementation/bmad-agent-dev/SKILL.md +++ b/src/bmm-skills/4-implementation/bmad-agent-dev/SKILL.md @@ -46,7 +46,7 @@ Treat every entry in `{agent.persistent_facts}` as foundational context you carr ### Step 5: Load Config -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - Use `{user_name}` for greeting - Use `{communication_language}` for all communications - Use `{document_output_language}` for output documents diff --git a/src/bmm-skills/4-implementation/bmad-checkpoint-preview/SKILL.md b/src/bmm-skills/4-implementation/bmad-checkpoint-preview/SKILL.md index 101dcf2bc..db1d7fba2 100644 --- a/src/bmm-skills/4-implementation/bmad-checkpoint-preview/SKILL.md +++ b/src/bmm-skills/4-implementation/bmad-checkpoint-preview/SKILL.md @@ -40,7 +40,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c ### Step 4: Load Config -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - `implementation_artifacts` - `planning_artifacts` diff --git a/src/bmm-skills/4-implementation/bmad-code-review/SKILL.md b/src/bmm-skills/4-implementation/bmad-code-review/SKILL.md index 44223f11a..909a86477 100644 --- a/src/bmm-skills/4-implementation/bmad-code-review/SKILL.md +++ b/src/bmm-skills/4-implementation/bmad-code-review/SKILL.md @@ -40,7 +40,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c ### Step 4: Load Config -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - `project_name`, `planning_artifacts`, `implementation_artifacts`, `user_name` - `communication_language`, `document_output_language`, `user_skill_level` diff --git a/src/bmm-skills/4-implementation/bmad-correct-course/SKILL.md b/src/bmm-skills/4-implementation/bmad-correct-course/SKILL.md index adea0bda0..0456d63dd 100644 --- a/src/bmm-skills/4-implementation/bmad-correct-course/SKILL.md +++ b/src/bmm-skills/4-implementation/bmad-correct-course/SKILL.md @@ -40,7 +40,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c ### Step 4: Load Config -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - `project_name`, `user_name` - `communication_language`, `document_output_language` diff --git a/src/bmm-skills/4-implementation/bmad-create-story/SKILL.md b/src/bmm-skills/4-implementation/bmad-create-story/SKILL.md index cf14039c1..516095eee 100644 --- a/src/bmm-skills/4-implementation/bmad-create-story/SKILL.md +++ b/src/bmm-skills/4-implementation/bmad-create-story/SKILL.md @@ -47,7 +47,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c ### Step 4: Load Config -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - `project_name`, `user_name` - `communication_language`, `document_output_language` diff --git a/src/bmm-skills/4-implementation/bmad-dev-story/SKILL.md b/src/bmm-skills/4-implementation/bmad-dev-story/SKILL.md index 218b234ab..3db2e9954 100644 --- a/src/bmm-skills/4-implementation/bmad-dev-story/SKILL.md +++ b/src/bmm-skills/4-implementation/bmad-dev-story/SKILL.md @@ -47,7 +47,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c ### Step 4: Load Config -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - `project_name`, `user_name` - `communication_language`, `document_output_language` diff --git a/src/bmm-skills/4-implementation/bmad-qa-generate-e2e-tests/SKILL.md b/src/bmm-skills/4-implementation/bmad-qa-generate-e2e-tests/SKILL.md index ef9d7e87a..655130c71 100644 --- a/src/bmm-skills/4-implementation/bmad-qa-generate-e2e-tests/SKILL.md +++ b/src/bmm-skills/4-implementation/bmad-qa-generate-e2e-tests/SKILL.md @@ -40,7 +40,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c ### Step 4: Load Config -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - `project_name`, `user_name` - `communication_language`, `document_output_language` diff --git a/src/bmm-skills/4-implementation/bmad-quick-dev/SKILL.md b/src/bmm-skills/4-implementation/bmad-quick-dev/SKILL.md index f5326fc3f..c0ba1b8a1 100644 --- a/src/bmm-skills/4-implementation/bmad-quick-dev/SKILL.md +++ b/src/bmm-skills/4-implementation/bmad-quick-dev/SKILL.md @@ -59,7 +59,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c ### Step 4: Load Config -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - `project_name`, `planning_artifacts`, `implementation_artifacts`, `user_name` - `communication_language`, `document_output_language`, `user_skill_level` diff --git a/src/bmm-skills/4-implementation/bmad-retrospective/SKILL.md b/src/bmm-skills/4-implementation/bmad-retrospective/SKILL.md index b6d0c96c6..e1cdb2764 100644 --- a/src/bmm-skills/4-implementation/bmad-retrospective/SKILL.md +++ b/src/bmm-skills/4-implementation/bmad-retrospective/SKILL.md @@ -56,7 +56,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c ### Step 4: Load Config -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - `project_name`, `user_name` - `communication_language`, `document_output_language` diff --git a/src/bmm-skills/4-implementation/bmad-sprint-planning/SKILL.md b/src/bmm-skills/4-implementation/bmad-sprint-planning/SKILL.md index 25266d716..887bd45bf 100644 --- a/src/bmm-skills/4-implementation/bmad-sprint-planning/SKILL.md +++ b/src/bmm-skills/4-implementation/bmad-sprint-planning/SKILL.md @@ -40,7 +40,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c ### Step 4: Load Config -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - `project_name`, `user_name` - `communication_language`, `document_output_language` diff --git a/src/bmm-skills/4-implementation/bmad-sprint-status/SKILL.md b/src/bmm-skills/4-implementation/bmad-sprint-status/SKILL.md index c52a84947..57273ba05 100644 --- a/src/bmm-skills/4-implementation/bmad-sprint-status/SKILL.md +++ b/src/bmm-skills/4-implementation/bmad-sprint-status/SKILL.md @@ -40,7 +40,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c ### Step 4: Load Config -Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - `project_name`, `user_name` - `communication_language`, `document_output_language` diff --git a/src/core-skills/bmad-brainstorming/workflow.md b/src/core-skills/bmad-brainstorming/workflow.md index 168dab93e..7eccef9b7 100644 --- a/src/core-skills/bmad-brainstorming/workflow.md +++ b/src/core-skills/bmad-brainstorming/workflow.md @@ -32,7 +32,7 @@ This uses **micro-file architecture** for disciplined execution: ### Configuration Loading -Load config from `{project-root}/_bmad/core/config.yaml` and resolve: +Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - `project_name`, `output_folder`, `user_name` - `communication_language`, `document_output_language`, `user_skill_level` diff --git a/src/core-skills/bmad-help/SKILL.md b/src/core-skills/bmad-help/SKILL.md index 62b06a747..85e3e6e16 100644 --- a/src/core-skills/bmad-help/SKILL.md +++ b/src/core-skills/bmad-help/SKILL.md @@ -23,7 +23,7 @@ When this skill completes, the user should: ## Data Sources - **Catalog**: `{project-root}/_bmad/_config/bmad-help.csv` — assembled manifest of all installed module skills -- **Config**: `config.yaml` and `user-config.yaml` files in `{project-root}/_bmad/` and its subfolders — resolve `output-location` variables, provide `communication_language` and `project_knowledge` +- **Config**: Run `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` to get merged config. If that fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself — resolve `output-location` variables, provide `communication_language` and `project_knowledge` - **Artifacts**: Files matching `outputs` patterns at resolved `output-location` paths reveal which steps are possibly completed; their content may also provide grounding context for recommendations - **Project knowledge**: If `project_knowledge` resolves to an existing path, read it for grounding context. Never fabricate project-specific details. - **Module docs**: Rows with `_meta` in the `skill` column carry a URL or path in `output-location` pointing to the module's documentation (e.g., llms.txt). Fetch and use these to answer general questions about that module. diff --git a/src/core-skills/bmad-party-mode/SKILL.md b/src/core-skills/bmad-party-mode/SKILL.md index 6f4ee3e63..6f3906da8 100644 --- a/src/core-skills/bmad-party-mode/SKILL.md +++ b/src/core-skills/bmad-party-mode/SKILL.md @@ -22,7 +22,7 @@ Party mode accepts optional arguments when invoked: 1. **Parse arguments** — check for `--model` and `--solo` flags from the user's invocation. -2. Load config from `{project-root}/_bmad/core/config.yaml` and resolve: +2. Load config by running `python3 {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (requires Python 3.11+). If the command fails, read the merge logic in `{project-root}/_bmad/scripts/resolve_config.py` and apply it yourself to resolve the config variables. Resolve: - Use `{user_name}` for greeting - Use `{communication_language}` for all communications diff --git a/src/scripts/resolve_config.py b/src/scripts/resolve_config.py index eb9e20288..5f6c8d7bd 100644 --- a/src/scripts/resolve_config.py +++ b/src/scripts/resolve_config.py @@ -1,12 +1,13 @@ #!/usr/bin/env python3 """ -Resolve BMad's central config using four-layer TOML merge. +Resolve BMad's central config using five-layer TOML merge. -Reads from four layers (highest priority last): +Reads from five layers (highest priority last): 1. {project-root}/_bmad/config.toml (installer-owned team) 2. {project-root}/_bmad/config.user.toml (installer-owned user) - 3. {project-root}/_bmad/custom/config.toml (human-authored team, committed) - 4. {project-root}/_bmad/custom/config.user.toml (human-authored user, gitignored) + 3. ~/.bmad/config/config.user.toml (global user preferences) + 4. {project-root}/_bmad/custom/config.toml (human-authored team, committed) + 5. {project-root}/_bmad/custom/config.user.toml (human-authored user, gitignored) Outputs merged JSON to stdout. Errors go to stderr. @@ -40,6 +41,7 @@ except ImportError: _MISSING = object() _KEYED_MERGE_FIELDS = ("code", "id") +GLOBAL_DIR = Path.home() / ".bmad" / "config" def load_toml(file_path: Path, required: bool = False) -> dict: @@ -136,7 +138,7 @@ def extract_key(data, dotted_key: str): def main(): parser = argparse.ArgumentParser( - description="Resolve BMad central config using four-layer TOML merge.", + description="Resolve BMad central config using five-layer TOML merge.", ) parser.add_argument( "--project-root", "-p", required=True, @@ -153,10 +155,12 @@ def main(): base_team = load_toml(bmad_dir / "config.toml", required=True) base_user = load_toml(bmad_dir / "config.user.toml") + global_user = load_toml(GLOBAL_DIR / "config.user.toml") custom_team = load_toml(bmad_dir / "custom" / "config.toml") custom_user = load_toml(bmad_dir / "custom" / "config.user.toml") merged = deep_merge(base_team, base_user) + merged = deep_merge(merged, global_user) merged = deep_merge(merged, custom_team) merged = deep_merge(merged, custom_user) diff --git a/src/scripts/resolve_customization.py b/src/scripts/resolve_customization.py index 28901ed0f..d8af2d26f 100755 --- a/src/scripts/resolve_customization.py +++ b/src/scripts/resolve_customization.py @@ -1,11 +1,12 @@ #!/usr/bin/env python3 """ -Resolve customization for a BMad skill using three-layer TOML merge. +Resolve customization for a BMad skill using four-layer TOML merge. -Reads customization from three layers (highest priority first): +Reads customization from four layers (highest priority first): 1. {project-root}/_bmad/custom/{name}.user.toml (personal, gitignored) 2. {project-root}/_bmad/custom/{name}.toml (team/org, committed) - 3. {skill-root}/customize.toml (skill defaults) + 3. ~/.bmad/config/{name}.user.toml (global user preferences) + 4. {skill-root}/customize.toml (skill defaults) Skill name is derived from the basename of the skill directory. @@ -51,6 +52,7 @@ except ImportError: _MISSING = object() _KEYED_MERGE_FIELDS = ("code", "id") +GLOBAL_DIR = Path.home() / ".bmad" / "config" def find_project_root(start: Path): @@ -179,7 +181,7 @@ def extract_key(data, dotted_key: str): def main(): parser = argparse.ArgumentParser( - description="Resolve customization for a BMad skill using three-layer TOML merge.", + description="Resolve customization for a BMad skill using four-layer TOML merge.", add_help=True, ) parser.add_argument( @@ -211,7 +213,10 @@ def main(): team = load_toml(custom_dir / f"{skill_name}.toml") user = load_toml(custom_dir / f"{skill_name}.user.toml") - merged = deep_merge(defaults, team) + global_user = load_toml(GLOBAL_DIR / f"{skill_name}.user.toml") + + merged = deep_merge(defaults, global_user) + merged = deep_merge(merged, team) merged = deep_merge(merged, user) if args.key: diff --git a/test/test-config-resolution.js b/test/test-config-resolution.js new file mode 100644 index 000000000..e19baf103 --- /dev/null +++ b/test/test-config-resolution.js @@ -0,0 +1,341 @@ +/** + * Config Resolution Tests + * + * Tests the Python config resolution scripts by invoking them as subprocesses + * with temporary TOML fixtures. Validates: + * - Global user layer is loaded and merged with correct priority + * - Project layers override global layers + * - Missing global dir doesn't break anything (backward compat) + * - resolve_customization.py global skill layer + * + * Usage: node test/test-config-resolution.js + */ + +const path = require('node:path'); +const os = require('node:os'); +const fs = require('node:fs/promises'); +const { execSync } = require('node:child_process'); + +const SCRIPTS_DIR = path.resolve(__dirname, '..', 'src', 'scripts'); + +const colors = { + reset: '', + green: '', + red: '', + dim: '', +}; + +let passed = 0; +let failed = 0; + +function assert(condition, testName, errorMessage = '') { + if (condition) { + console.log(`${colors.green}✓${colors.reset} ${testName}`); + passed++; + } else { + console.log(`${colors.red}✗${colors.reset} ${testName}`); + if (errorMessage) { + console.log(` ${colors.dim}${errorMessage}${colors.reset}`); + } + failed++; + } +} + +function writeToml(filePath, data) { + const lines = []; + for (const [key, val] of Object.entries(data)) { + if (typeof val === 'string') { + lines.push(`${key} = "${val}"`); + } else if (typeof val === 'number' || typeof val === 'boolean') { + lines.push(`${key} = ${val}`); + } + } + return fs.writeFile(filePath, lines.join('\n') + '\n'); +} + +async function withTempDir(fn) { + const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-config-test-')); + try { + return await fn(dir); + } finally { + await fs.rm(dir, { recursive: true, force: true }); + } +} + +async function testResolveConfig() { + console.log('\n--- resolve_config.py ---\n'); + + // Test 1: global user overrides installer defaults + await withTempDir(async (tmpDir) => { + const globalDir = path.join(tmpDir, '.bmad', 'config'); + await fs.mkdir(globalDir, { recursive: true }); + await writeToml(path.join(globalDir, 'config.user.toml'), { + user_name: 'GlobalAlice', + communication_language: 'en', + }); + + const projectDir = path.join(tmpDir, 'project'); + const bmadDir = path.join(projectDir, '_bmad'); + await fs.mkdir(path.join(bmadDir, 'custom'), { recursive: true }); + await writeToml(path.join(bmadDir, 'config.toml'), { + project_name: 'TestProject', + user_name: 'InstallerDefault', + }); + await writeToml(path.join(bmadDir, 'config.user.toml'), { + user_name: 'InstallerUserDefault', + }); + + const origHome = process.env.HOME; + process.env.HOME = tmpDir; + try { + const result = JSON.parse(execSync(`python3 ${SCRIPTS_DIR}/resolve_config.py --project-root ${projectDir}`, { encoding: 'utf-8' })); + + assert( + result.user_name === 'GlobalAlice', + 'global user overrides installer defaults', + `Expected "GlobalAlice", got "${result.user_name}"`, + ); + assert( + result.communication_language === 'en', + 'global communication_language preserved when installer has no override', + `Expected "en", got "${result.communication_language}"`, + ); + assert( + result.project_name === 'TestProject', + 'installer team config values preserved', + `Expected "TestProject", got "${result.project_name}"`, + ); + } finally { + process.env.HOME = origHome; + } + }); + + // Test 2: missing global dir — backward compat + await withTempDir(async (tmpDir) => { + const projectDir = path.join(tmpDir, 'project'); + const bmadDir = path.join(projectDir, '_bmad'); + await fs.mkdir(path.join(bmadDir, 'custom'), { recursive: true }); + await writeToml(path.join(bmadDir, 'config.toml'), { + project_name: 'NoGlobalProject', + }); + + const origHome = process.env.HOME; + process.env.HOME = tmpDir; + try { + const result = JSON.parse(execSync(`python3 ${SCRIPTS_DIR}/resolve_config.py --project-root ${projectDir}`, { encoding: 'utf-8' })); + + assert( + result.project_name === 'NoGlobalProject', + 'works fine without global config dir', + `Expected "NoGlobalProject", got "${result.project_name}"`, + ); + } finally { + process.env.HOME = origHome; + } + }); + + // Test 3: full priority chain — base_team < base_user < global < custom_team < custom_user + await withTempDir(async (tmpDir) => { + const globalDir = path.join(tmpDir, '.bmad', 'config'); + await fs.mkdir(globalDir, { recursive: true }); + await writeToml(path.join(globalDir, 'config.user.toml'), { + user_name: 'L2-Global', + }); + + const projectDir = path.join(tmpDir, 'project'); + const bmadDir = path.join(projectDir, '_bmad'); + await fs.mkdir(path.join(bmadDir, 'custom'), { recursive: true }); + await writeToml(path.join(bmadDir, 'config.toml'), { user_name: 'L0-BaseTeam' }); + await writeToml(path.join(bmadDir, 'config.user.toml'), { user_name: 'L1-BaseUser' }); + await writeToml(path.join(bmadDir, 'custom', 'config.toml'), { user_name: 'L3-CustomTeam' }); + await writeToml(path.join(bmadDir, 'custom', 'config.user.toml'), { user_name: 'L4-CustomUser' }); + + const origHome = process.env.HOME; + process.env.HOME = tmpDir; + try { + const result = JSON.parse(execSync(`python3 ${SCRIPTS_DIR}/resolve_config.py --project-root ${projectDir}`, { encoding: 'utf-8' })); + + assert( + result.user_name === 'L4-CustomUser', + 'highest priority layer (custom user) wins', + `Expected "L4-CustomUser", got "${result.user_name}"`, + ); + } finally { + process.env.HOME = origHome; + } + }); + + // Test 4: --key flag works with global layer + await withTempDir(async (tmpDir) => { + const globalDir = path.join(tmpDir, '.bmad', 'config'); + await fs.mkdir(globalDir, { recursive: true }); + await writeToml(path.join(globalDir, 'config.user.toml'), { + user_name: 'KeyTestGlobal', + communication_language: 'fr', + }); + + const projectDir = path.join(tmpDir, 'project'); + const bmadDir = path.join(projectDir, '_bmad'); + await fs.mkdir(path.join(bmadDir, 'custom'), { recursive: true }); + await writeToml(path.join(bmadDir, 'config.toml'), { + project_name: 'KeyTestProject', + }); + + const origHome = process.env.HOME; + process.env.HOME = tmpDir; + try { + const result = JSON.parse( + execSync(`python3 ${SCRIPTS_DIR}/resolve_config.py --project-root ${projectDir} --key user_name --key communication_language`, { + encoding: 'utf-8', + }), + ); + + assert( + Object.keys(result).length === 2, + '--key returns only requested keys', + `Expected 2 keys, got ${Object.keys(result).length}: ${JSON.stringify(Object.keys(result))}`, + ); + assert( + result.user_name === 'KeyTestGlobal', + '--key user_name returns global value', + `Expected "KeyTestGlobal", got "${result.user_name}"`, + ); + assert( + result.communication_language === 'fr', + '--key communication_language returns global value', + `Expected "fr", got "${result.communication_language}"`, + ); + } finally { + process.env.HOME = origHome; + } + }); +} + +async function testResolveCustomization() { + console.log('\n--- resolve_customization.py ---\n'); + + // Test 1: global skill user overrides skill defaults + await withTempDir(async (tmpDir) => { + const globalDir = path.join(tmpDir, '.bmad', 'config'); + await fs.mkdir(globalDir, { recursive: true }); + await writeToml(path.join(globalDir, 'test-skill.user.toml'), { + agent: 'global-agent-prompt', + }); + + const skillDir = path.join(tmpDir, 'skill', 'test-skill'); + await fs.mkdir(skillDir, { recursive: true }); + await writeToml(path.join(skillDir, 'customize.toml'), { + agent: 'default-agent-prompt', + version: '1.0.0', + }); + + const origHome = process.env.HOME; + process.env.HOME = tmpDir; + try { + const result = JSON.parse(execSync(`python3 ${SCRIPTS_DIR}/resolve_customization.py --skill ${skillDir}`, { encoding: 'utf-8' })); + + assert( + result.agent === 'global-agent-prompt', + 'global user overrides skill defaults', + `Expected "global-agent-prompt", got "${result.agent}"`, + ); + assert(result.version === '1.0.0', 'skill default values preserved', `Expected "1.0.0", got "${result.version}"`); + } finally { + process.env.HOME = origHome; + } + }); + + // Test 2: global skill layer provides value not in defaults + await withTempDir(async (tmpDir) => { + const globalDir = path.join(tmpDir, '.bmad', 'config'); + await fs.mkdir(globalDir, { recursive: true }); + await writeToml(path.join(globalDir, 'test-skill.user.toml'), { + extra_global_key: 'from-global', + }); + + const skillDir = path.join(tmpDir, 'skill', 'test-skill'); + await fs.mkdir(skillDir, { recursive: true }); + await writeToml(path.join(skillDir, 'customize.toml'), { + version: '2.0.0', + }); + + const origHome = process.env.HOME; + process.env.HOME = tmpDir; + try { + const result = JSON.parse(execSync(`python3 ${SCRIPTS_DIR}/resolve_customization.py --skill ${skillDir}`, { encoding: 'utf-8' })); + + assert( + result.extra_global_key === 'from-global', + 'global key not in defaults is preserved', + `Expected "from-global", got "${result.extra_global_key}"`, + ); + assert(result.version === '2.0.0', 'skill defaults still present', `Expected "2.0.0", got "${result.version}"`); + } finally { + process.env.HOME = origHome; + } + }); + + // Test 3: missing global dir — backward compat + await withTempDir(async (tmpDir) => { + const skillDir = path.join(tmpDir, 'skill', 'test-skill'); + await fs.mkdir(skillDir, { recursive: true }); + await writeToml(path.join(skillDir, 'customize.toml'), { + version: '3.0.0', + }); + + const origHome = process.env.HOME; + process.env.HOME = tmpDir; + try { + const result = JSON.parse(execSync(`python3 ${SCRIPTS_DIR}/resolve_customization.py --skill ${skillDir}`, { encoding: 'utf-8' })); + + assert(result.version === '3.0.0', 'works without global config dir', `Expected "3.0.0", got "${result.version}"`); + } finally { + process.env.HOME = origHome; + } + }); + + // Test 4: full priority chain — defaults < global < team < user + await withTempDir(async (tmpDir) => { + const globalDir = path.join(tmpDir, '.bmad', 'config'); + await fs.mkdir(globalDir, { recursive: true }); + await writeToml(path.join(globalDir, 'test-skill.user.toml'), { + agent: 'L1-Global', + }); + + const skillDir = path.join(tmpDir, 'project', '_bmad', 'skills', 'test-skill'); + await fs.mkdir(skillDir, { recursive: true }); + await writeToml(path.join(skillDir, 'customize.toml'), { agent: 'L0-Defaults' }); + + const customDir = path.join(tmpDir, 'project', '_bmad', 'custom'); + await fs.mkdir(customDir, { recursive: true }); + await writeToml(path.join(customDir, 'test-skill.toml'), { agent: 'L2-Team' }); + await writeToml(path.join(customDir, 'test-skill.user.toml'), { agent: 'L3-User' }); + + const origHome = process.env.HOME; + process.env.HOME = tmpDir; + try { + const result = JSON.parse(execSync(`python3 ${SCRIPTS_DIR}/resolve_customization.py --skill ${skillDir}`, { encoding: 'utf-8' })); + + assert(result.agent === 'L3-User', 'highest priority layer (project user) wins', `Expected "L3-User", got "${result.agent}"`); + } finally { + process.env.HOME = origHome; + } + }); +} + +async function main() { + console.log('Config Resolution Tests\n'); + + try { + await testResolveConfig(); + await testResolveCustomization(); + } catch (error) { + console.error(`${colors.red}Fatal error: ${error.message}${colors.reset}`); + process.exit(1); + } + + console.log(`\n${colors.green}${passed} passed${colors.reset}, ${colors.red}${failed} failed${colors.reset}`); + process.exit(failed > 0 ? 1 : 0); +} + +main();