Merge 4e4179985a into 1da6bf80df
This commit is contained in:
commit
feb647221a
|
|
@ -46,7 +46,7 @@ Treat every entry in `{agent.persistent_facts}` as foundational context you carr
|
||||||
|
|
||||||
### Step 5: Load Config
|
### 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 `{user_name}` for greeting
|
||||||
- Use `{communication_language}` for all communications
|
- Use `{communication_language}` for all communications
|
||||||
- Use `{document_output_language}` for output documents
|
- Use `{document_output_language}` for output documents
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ Treat every entry in `{agent.persistent_facts}` as foundational context you carr
|
||||||
|
|
||||||
### Step 5: Load Config
|
### 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 `{user_name}` for greeting
|
||||||
- Use `{communication_language}` for all communications
|
- Use `{communication_language}` for all communications
|
||||||
- Use `{document_output_language}` for output documents
|
- Use `{document_output_language}` for output documents
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c
|
||||||
|
|
||||||
### Step 4: Load Config
|
### 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 `{user_name}` for greeting
|
||||||
- Use `{communication_language}` for all communications
|
- Use `{communication_language}` for all communications
|
||||||
- Use `{document_output_language}` for output documents
|
- Use `{document_output_language}` for output documents
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
|
|
||||||
### Configuration Loading
|
### 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`
|
- `project_knowledge`
|
||||||
- `user_name`
|
- `user_name`
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
### Configuration Loading
|
### 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`
|
- `project_knowledge`
|
||||||
- `user_name`
|
- `user_name`
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c
|
||||||
|
|
||||||
### Step 4: Load Config
|
### 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 `{user_name}` for greeting
|
||||||
- Use `{communication_language}` for all communications
|
- Use `{communication_language}` for all communications
|
||||||
- Use `{document_output_language}` for output documents
|
- Use `{document_output_language}` for output documents
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ At the opening greeting, let the user know they can invoke `bmad-party-mode` for
|
||||||
2. Execute each entry in `{workflow.activation_steps_prepend}` in order.
|
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.
|
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. `{workflow.external_sources}` is an org-configured registry of internal tools (knowledge bases, MCP tools); consult them alongside generic web research on the same triggers in `## Discovery`, org tools preferred when their directive matches. If a named tool is unavailable at runtime, fall back to standard behavior and note the gap when relevant.
|
4. `{workflow.external_sources}` is an org-configured registry of internal tools (knowledge bases, MCP tools); consult them alongside generic web research on the same triggers in `## Discovery`, org tools preferred when their directive matches. If a named tool is unavailable at runtime, fall back to standard behavior and note the gap when relevant.
|
||||||
5. 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}`.
|
5. 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}`.
|
||||||
6. Greet `{user_name}` in `{communication_language}` — and stay in `{communication_language}` for every turn for the entire run, not just the greeting. Detect intent (create / update / validate). If interactive and intent is unclear, ask; for headless behavior see `## Headless Mode`.
|
6. Greet `{user_name}` in `{communication_language}` — and stay in `{communication_language}` for every turn for the entire run, not just the greeting. Detect intent (create / update / validate). If interactive and intent is unclear, ask; for headless behavior see `## Headless Mode`.
|
||||||
7. Execute each entry in `{workflow.activation_steps_append}` in order.
|
7. 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.
|
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
|
## 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. During the dump, spawn web-research subagents to ground the picture — landscape, comparables, current state — AI especially, where training data ages by the week. Subagent searches; parent gets a digest. Deep work (full market sizing, exhaustive teardowns) → suggest `bmad-market-research` or `bmad-domain-research`.
|
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. During the dump, spawn web-research subagents to ground the picture — landscape, comparables, current state — AI especially, where training data ages by the week. Subagent searches; parent gets a digest. Deep work (full market sizing, exhaustive teardowns) → suggest `bmad-market-research` or `bmad-domain-research`.
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c
|
||||||
|
|
||||||
### Step 4: Load Config
|
### 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 `{user_name}` for greeting
|
||||||
- Use `{communication_language}` for all communications
|
- Use `{communication_language}` for all communications
|
||||||
- Use `{document_output_language}` for output documents
|
- Use `{document_output_language}` for output documents
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c
|
||||||
|
|
||||||
### Step 4: Load Config
|
### 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 `{user_name}` for greeting
|
||||||
- Use `{communication_language}` for all communications
|
- Use `{communication_language}` for all communications
|
||||||
- Use `{document_output_language}` for output documents
|
- Use `{document_output_language}` for output documents
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c
|
||||||
|
|
||||||
### Step 4: Load Config
|
### 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 `{user_name}` for greeting
|
||||||
- Use `{communication_language}` for all communications
|
- Use `{communication_language}` for all communications
|
||||||
- Use `{document_output_language}` for output documents
|
- Use `{document_output_language}` for output documents
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ Treat every entry in `{agent.persistent_facts}` as foundational context you carr
|
||||||
|
|
||||||
### Step 5: Load Config
|
### 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 `{user_name}` for greeting
|
||||||
- Use `{communication_language}` for all communications
|
- Use `{communication_language}` for all communications
|
||||||
- Use `{document_output_language}` for output documents
|
- Use `{document_output_language}` for output documents
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ Treat every entry in `{agent.persistent_facts}` as foundational context you carr
|
||||||
|
|
||||||
### Step 5: Load Config
|
### 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 `{user_name}` for greeting
|
||||||
- Use `{communication_language}` for all communications
|
- Use `{communication_language}` for all communications
|
||||||
- Use `{document_output_language}` for output documents
|
- Use `{document_output_language}` for output documents
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ This skill was consolidated into `bmad-prd`. It is retained as a thin compatibil
|
||||||
|
|
||||||
1. Resolve customization: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`. This picks up any `{project-root}/_bmad/custom/bmad-create-prd.toml` and `bmad-create-prd.user.toml` overrides for the legacy fields (`activation_steps_prepend`, `activation_steps_append`, `persistent_facts`, `on_complete`).
|
1. Resolve customization: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`. This picks up any `{project-root}/_bmad/custom/bmad-create-prd.toml` and `bmad-create-prd.user.toml` overrides for the legacy fields (`activation_steps_prepend`, `activation_steps_append`, `persistent_facts`, `on_complete`).
|
||||||
|
|
||||||
2. Load `{project-root}/_bmad/bmm/config.yaml` (and `config.user.yaml` if present) to resolve `{user_name}` and `{communication_language}`.
|
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 `{user_name}` and `{communication_language}`.
|
||||||
|
|
||||||
3. Emit a deprecation notice to the user in `{communication_language}`:
|
3. Emit a deprecation notice to the user in `{communication_language}`:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c
|
||||||
|
|
||||||
### Step 4: Load Config
|
### 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 `{user_name}` for greeting
|
||||||
- Use `{communication_language}` for all communications
|
- Use `{communication_language}` for all communications
|
||||||
- Use `{document_output_language}` for output documents
|
- Use `{document_output_language}` for output documents
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ This skill was consolidated into `bmad-prd`. It is retained as a thin compatibil
|
||||||
|
|
||||||
1. Resolve customization: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`. This picks up any `{project-root}/_bmad/custom/bmad-edit-prd.toml` and `bmad-edit-prd.user.toml` overrides for the legacy fields (`activation_steps_prepend`, `activation_steps_append`, `persistent_facts`, `on_complete`).
|
1. Resolve customization: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`. This picks up any `{project-root}/_bmad/custom/bmad-edit-prd.toml` and `bmad-edit-prd.user.toml` overrides for the legacy fields (`activation_steps_prepend`, `activation_steps_append`, `persistent_facts`, `on_complete`).
|
||||||
|
|
||||||
2. Load `{project-root}/_bmad/bmm/config.yaml` (and `config.user.yaml` if present) to resolve `{user_name}` and `{communication_language}`.
|
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 `{user_name}` and `{communication_language}`.
|
||||||
|
|
||||||
3. Emit a deprecation notice to the user in `{communication_language}`:
|
3. Emit a deprecation notice to the user in `{communication_language}`:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ You are a master facilitator and coach helping the user create, edit, or validat
|
||||||
|
|
||||||
1. Resolve customization: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`. On failure, read `{skill-root}/customize.toml` directly and use defaults.
|
1. Resolve customization: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`. On failure, read `{skill-root}/customize.toml` directly and use defaults.
|
||||||
2. Run `{workflow.activation_steps_prepend}`. Treat `{workflow.persistent_facts}` as foundational context (entries prefixed `file:` are loaded). `{workflow.external_sources}` is an org-configured registry of internal tools (knowledge bases, MCP tools); consult them alongside generic web research on the same triggers, org tools preferred when their directive matches. Research itself fires during Discovery — see **Research subagents**.
|
2. Run `{workflow.activation_steps_prepend}`. Treat `{workflow.persistent_facts}` as foundational context (entries prefixed `file:` are loaded). `{workflow.external_sources}` is an org-configured registry of internal tools (knowledge bases, MCP tools); consult them alongside generic web research on the same triggers, org tools preferred when their directive matches. Research itself fires during Discovery — see **Research subagents**.
|
||||||
3. Load `{project-root}/_bmad/bmm/config.yaml` (+ `config.user.yaml` if present). Resolve `{user_name}`, `{communication_language}`, `{document_output_language}`, `{planning_artifacts}`, `{project_name}`, `{date}`. Missing keys → neutral defaults; never block.
|
3. 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}`. Missing keys → neutral defaults; never block.
|
||||||
4. If headless, follow `references/headless.md` for the whole run. Otherwise greet the user **by name** using `{user_name}` and **in their language** using `{communication_language}` — and stay in `{communication_language}` for every turn for the entire run, not just the greeting. In the greeting, let the user know that at any point they can invoke `bmad-party-mode` for multi-agent perspectives or `bmad-advanced-elicitation` for deeper exploration on a specific section. Then scan for misroute on the first message: if the signal points elsewhere (game → BMad GDS; express build → `bmad-quick-dev`; one-pager → `bmad-product-brief`; vet product idea → `bmad-prfaq`; agent skill or custom agent → `bmad-workflow-builder`), suggest they might want the other options before continuing.
|
4. If headless, follow `references/headless.md` for the whole run. Otherwise greet the user **by name** using `{user_name}` and **in their language** using `{communication_language}` — and stay in `{communication_language}` for every turn for the entire run, not just the greeting. In the greeting, let the user know that at any point they can invoke `bmad-party-mode` for multi-agent perspectives or `bmad-advanced-elicitation` for deeper exploration on a specific section. Then scan for misroute on the first message: if the signal points elsewhere (game → BMad GDS; express build → `bmad-quick-dev`; one-pager → `bmad-product-brief`; vet product idea → `bmad-prfaq`; agent skill or custom agent → `bmad-workflow-builder`), suggest they might want the other options before continuing.
|
||||||
5. Detect intent: **Create** (no PRD), **Update** (existing PRD), **Validate** (critique only). If ambiguous, ask. For Create intent, before binding a fresh workspace, scan `{workflow.prd_output_path}` for prior in-progress runs (folders matching `{workflow.run_folder_pattern}` whose `prd.md` frontmatter `status` is not `final`); if any exist, offer to resume rather than starting over.
|
5. Detect intent: **Create** (no PRD), **Update** (existing PRD), **Validate** (critique only). If ambiguous, ask. For Create intent, before binding a fresh workspace, scan `{workflow.prd_output_path}` for prior in-progress runs (folders matching `{workflow.run_folder_pattern}` whose `prd.md` frontmatter `status` is not `final`); if any exist, offer to resume rather than starting over.
|
||||||
6. Run `{workflow.activation_steps_append}`.
|
6. Run `{workflow.activation_steps_append}`.
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ This skill was consolidated into `bmad-prd`. It is retained as a thin compatibil
|
||||||
|
|
||||||
1. Resolve customization: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`. This picks up any `{project-root}/_bmad/custom/bmad-validate-prd.toml` and `bmad-validate-prd.user.toml` overrides for the legacy fields (`activation_steps_prepend`, `activation_steps_append`, `persistent_facts`, `on_complete`).
|
1. Resolve customization: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`. This picks up any `{project-root}/_bmad/custom/bmad-validate-prd.toml` and `bmad-validate-prd.user.toml` overrides for the legacy fields (`activation_steps_prepend`, `activation_steps_append`, `persistent_facts`, `on_complete`).
|
||||||
|
|
||||||
2. Load `{project-root}/_bmad/bmm/config.yaml` (and `config.user.yaml` if present) to resolve `{user_name}` and `{communication_language}`.
|
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 `{user_name}` and `{communication_language}`.
|
||||||
|
|
||||||
3. Emit a deprecation notice to the user in `{communication_language}`:
|
3. Emit a deprecation notice to the user in `{communication_language}`:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ Treat every entry in `{agent.persistent_facts}` as foundational context you carr
|
||||||
|
|
||||||
### Step 5: Load Config
|
### 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 `{user_name}` for greeting
|
||||||
- Use `{communication_language}` for all communications
|
- Use `{communication_language}` for all communications
|
||||||
- Use `{document_output_language}` for output documents
|
- Use `{document_output_language}` for output documents
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c
|
||||||
|
|
||||||
### Step 4: Load Config
|
### 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 `{user_name}` for greeting
|
||||||
- Use `{communication_language}` for all communications
|
- Use `{communication_language}` for all communications
|
||||||
- Use `{document_output_language}` for output documents
|
- Use `{document_output_language}` for output documents
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c
|
||||||
|
|
||||||
### Step 4: Load Config
|
### 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 `{user_name}` for greeting
|
||||||
- Use `{communication_language}` for all communications
|
- Use `{communication_language}` for all communications
|
||||||
- Use `{document_output_language}` for output documents
|
- Use `{document_output_language}` for output documents
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c
|
||||||
|
|
||||||
### Step 4: Load Config
|
### 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 `{user_name}` for greeting
|
||||||
- Use `{communication_language}` for all communications
|
- Use `{communication_language}` for all communications
|
||||||
- Use `{document_output_language}` for output documents
|
- Use `{document_output_language}` for output documents
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c
|
||||||
|
|
||||||
### Step 4: Load Config
|
### 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 `{user_name}` for greeting
|
||||||
- Use `{communication_language}` for all communications
|
- Use `{communication_language}` for all communications
|
||||||
- Use `{document_output_language}` for output documents
|
- Use `{document_output_language}` for output documents
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ Treat every entry in `{agent.persistent_facts}` as foundational context you carr
|
||||||
|
|
||||||
### Step 5: Load Config
|
### 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 `{user_name}` for greeting
|
||||||
- Use `{communication_language}` for all communications
|
- Use `{communication_language}` for all communications
|
||||||
- Use `{document_output_language}` for output documents
|
- Use `{document_output_language}` for output documents
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c
|
||||||
|
|
||||||
### Step 4: Load Config
|
### 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`
|
- `implementation_artifacts`
|
||||||
- `planning_artifacts`
|
- `planning_artifacts`
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c
|
||||||
|
|
||||||
### Step 4: Load Config
|
### 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`
|
- `project_name`, `planning_artifacts`, `implementation_artifacts`, `user_name`
|
||||||
- `communication_language`, `document_output_language`, `user_skill_level`
|
- `communication_language`, `document_output_language`, `user_skill_level`
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c
|
||||||
|
|
||||||
### Step 4: Load Config
|
### 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`
|
- `project_name`, `user_name`
|
||||||
- `communication_language`, `document_output_language`
|
- `communication_language`, `document_output_language`
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c
|
||||||
|
|
||||||
### Step 4: Load Config
|
### 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`
|
- `project_name`, `user_name`
|
||||||
- `communication_language`, `document_output_language`
|
- `communication_language`, `document_output_language`
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c
|
||||||
|
|
||||||
### Step 4: Load Config
|
### 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`
|
- `project_name`, `user_name`
|
||||||
- `communication_language`, `document_output_language`
|
- `communication_language`, `document_output_language`
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c
|
||||||
|
|
||||||
### Step 4: Load Config
|
### 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`
|
- `project_name`, `user_name`
|
||||||
- `communication_language`, `document_output_language`
|
- `communication_language`, `document_output_language`
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c
|
||||||
|
|
||||||
### Step 4: Load Config
|
### 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`
|
- `project_name`, `planning_artifacts`, `implementation_artifacts`, `user_name`
|
||||||
- `communication_language`, `document_output_language`, `user_skill_level`
|
- `communication_language`, `document_output_language`, `user_skill_level`
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c
|
||||||
|
|
||||||
### Step 4: Load Config
|
### 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`
|
- `project_name`, `user_name`
|
||||||
- `communication_language`, `document_output_language`
|
- `communication_language`, `document_output_language`
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c
|
||||||
|
|
||||||
### Step 4: Load Config
|
### 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`
|
- `project_name`, `user_name`
|
||||||
- `communication_language`, `document_output_language`
|
- `communication_language`, `document_output_language`
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ Treat every entry in `{workflow.persistent_facts}` as foundational context you c
|
||||||
|
|
||||||
### Step 4: Load Config
|
### 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`
|
- `project_name`, `user_name`
|
||||||
- `communication_language`, `document_output_language`
|
- `communication_language`, `document_output_language`
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ This uses **micro-file architecture** for disciplined execution:
|
||||||
|
|
||||||
### Configuration Loading
|
### 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`
|
- `project_name`, `output_folder`, `user_name`
|
||||||
- `communication_language`, `document_output_language`, `user_skill_level`
|
- `communication_language`, `document_output_language`, `user_skill_level`
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ When this skill completes, the user should:
|
||||||
## Data Sources
|
## Data Sources
|
||||||
|
|
||||||
- **Catalog**: `{project-root}/_bmad/_config/bmad-help.csv` — assembled manifest of all installed module skills
|
- **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
|
- **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.
|
- **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.
|
- **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.
|
||||||
|
|
|
||||||
|
|
@ -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.
|
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 `{user_name}` for greeting
|
||||||
- Use `{communication_language}` for all communications
|
- Use `{communication_language}` for all communications
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
#!/usr/bin/env python3
|
#!/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)
|
1. {project-root}/_bmad/config.toml (installer-owned team)
|
||||||
2. {project-root}/_bmad/config.user.toml (installer-owned user)
|
2. {project-root}/_bmad/config.user.toml (installer-owned user)
|
||||||
3. {project-root}/_bmad/custom/config.toml (human-authored team, committed)
|
3. ~/.bmad/config/config.user.toml (global user preferences)
|
||||||
4. {project-root}/_bmad/custom/config.user.toml (human-authored user, gitignored)
|
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.
|
Outputs merged JSON to stdout. Errors go to stderr.
|
||||||
|
|
||||||
|
|
@ -40,6 +41,7 @@ except ImportError:
|
||||||
|
|
||||||
_MISSING = object()
|
_MISSING = object()
|
||||||
_KEYED_MERGE_FIELDS = ("code", "id")
|
_KEYED_MERGE_FIELDS = ("code", "id")
|
||||||
|
GLOBAL_DIR = Path.home() / ".bmad" / "config"
|
||||||
|
|
||||||
|
|
||||||
def load_toml(file_path: Path, required: bool = False) -> dict:
|
def load_toml(file_path: Path, required: bool = False) -> dict:
|
||||||
|
|
@ -136,7 +138,7 @@ def extract_key(data, dotted_key: str):
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser(
|
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(
|
parser.add_argument(
|
||||||
"--project-root", "-p", required=True,
|
"--project-root", "-p", required=True,
|
||||||
|
|
@ -153,10 +155,12 @@ def main():
|
||||||
|
|
||||||
base_team = load_toml(bmad_dir / "config.toml", required=True)
|
base_team = load_toml(bmad_dir / "config.toml", required=True)
|
||||||
base_user = load_toml(bmad_dir / "config.user.toml")
|
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_team = load_toml(bmad_dir / "custom" / "config.toml")
|
||||||
custom_user = load_toml(bmad_dir / "custom" / "config.user.toml")
|
custom_user = load_toml(bmad_dir / "custom" / "config.user.toml")
|
||||||
|
|
||||||
merged = deep_merge(base_team, base_user)
|
merged = deep_merge(base_team, base_user)
|
||||||
|
merged = deep_merge(merged, global_user)
|
||||||
merged = deep_merge(merged, custom_team)
|
merged = deep_merge(merged, custom_team)
|
||||||
merged = deep_merge(merged, custom_user)
|
merged = deep_merge(merged, custom_user)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
#!/usr/bin/env python3
|
#!/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)
|
1. {project-root}/_bmad/custom/{name}.user.toml (personal, gitignored)
|
||||||
2. {project-root}/_bmad/custom/{name}.toml (team/org, committed)
|
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.
|
Skill name is derived from the basename of the skill directory.
|
||||||
|
|
||||||
|
|
@ -51,6 +52,7 @@ except ImportError:
|
||||||
|
|
||||||
_MISSING = object()
|
_MISSING = object()
|
||||||
_KEYED_MERGE_FIELDS = ("code", "id")
|
_KEYED_MERGE_FIELDS = ("code", "id")
|
||||||
|
GLOBAL_DIR = Path.home() / ".bmad" / "config"
|
||||||
|
|
||||||
|
|
||||||
def find_project_root(start: Path):
|
def find_project_root(start: Path):
|
||||||
|
|
@ -179,7 +181,7 @@ def extract_key(data, dotted_key: str):
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser(
|
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,
|
add_help=True,
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
|
|
@ -211,7 +213,10 @@ def main():
|
||||||
team = load_toml(custom_dir / f"{skill_name}.toml")
|
team = load_toml(custom_dir / f"{skill_name}.toml")
|
||||||
user = load_toml(custom_dir / f"{skill_name}.user.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)
|
merged = deep_merge(merged, user)
|
||||||
|
|
||||||
if args.key:
|
if args.key:
|
||||||
|
|
|
||||||
|
|
@ -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: '[0m',
|
||||||
|
green: '[32m',
|
||||||
|
red: '[31m',
|
||||||
|
dim: '[2m',
|
||||||
|
};
|
||||||
|
|
||||||
|
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();
|
||||||
Loading…
Reference in New Issue