bmad-method 6.11.1-next.33 → 6.11.1-next.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +4 -5
  2. package/package.json +1 -1
  3. package/src/bmm-skills/agents/bmad-agent-analyst/SKILL.md +1 -1
  4. package/src/bmm-skills/agents/bmad-agent-architect/SKILL.md +1 -1
  5. package/src/bmm-skills/agents/bmad-agent-dev/SKILL.md +1 -1
  6. package/src/bmm-skills/agents/bmad-agent-pm/SKILL.md +1 -1
  7. package/src/bmm-skills/agents/bmad-agent-ux-designer/SKILL.md +1 -1
  8. package/src/bmm-skills/plan/bmad-architecture/SKILL.md +1 -1
  9. package/src/bmm-skills/plan/bmad-create-epics-and-stories/SKILL.md +1 -1
  10. package/src/bmm-skills/plan/bmad-create-epics-and-stories/steps/step-04-final-validation.md +1 -1
  11. package/src/bmm-skills/plan/bmad-prd/SKILL.md +1 -1
  12. package/src/bmm-skills/plan/bmad-prfaq/SKILL.md +1 -1
  13. package/src/bmm-skills/plan/bmad-prfaq/references/verdict.md +1 -1
  14. package/src/bmm-skills/plan/bmad-product-brief/SKILL.md +1 -1
  15. package/src/bmm-skills/plan/bmad-project-context/SKILL.md +1 -1
  16. package/src/bmm-skills/plan/bmad-spec/SKILL.md +1 -1
  17. package/src/bmm-skills/plan/bmad-sprint-planning/SKILL.md +1 -1
  18. package/src/bmm-skills/plan/bmad-sprint-planning/scripts/__pycache__/sprint_plan.cpython-311.pyc +0 -0
  19. package/src/bmm-skills/plan/bmad-sprint-planning/scripts/tests/__pycache__/test_sprint_plan.cpython-311-pytest-9.1.1.pyc +0 -0
  20. package/src/bmm-skills/plan/bmad-ux/SKILL.md +1 -1
  21. package/src/bmm-skills/ship/bmad-build/review-prompts/edge-case-hunter.md +2 -0
  22. package/src/bmm-skills/ship/bmad-build-auto/review-prompts/edge-case-hunter.md +2 -0
  23. package/src/bmm-skills/ship/bmad-code-review/SKILL.md +1 -1
  24. package/src/bmm-skills/ship/bmad-code-review/review-prompts/edge-case-hunter.md +2 -0
  25. package/src/bmm-skills/ship/bmad-code-review/steps/step-04-present.md +1 -1
  26. package/src/bmm-skills/ship/bmad-correct-course/SKILL.md +2 -2
  27. package/src/bmm-skills/ship/bmad-qa-generate-e2e-tests/SKILL.md +2 -2
  28. package/src/bmm-skills/ship/bmad-retrospective/SKILL.md +1 -1
  29. package/src/bmm-skills/ship/bmad-retrospective/scripts/__pycache__/sprint_status.cpython-311.pyc +0 -0
  30. package/src/bmm-skills/ship/bmad-retrospective/scripts/tests/__pycache__/test_git_evidence.cpython-311-pytest-9.1.1.pyc +0 -0
  31. package/src/bmm-skills/ship/bmad-retrospective/scripts/tests/__pycache__/test_sprint_status.cpython-311-pytest-9.1.1.pyc +0 -0
  32. package/src/bmm-skills/ship/bmad-walkthrough/SKILL.md +1 -1
  33. package/src/bmm-skills/ship/bmad-walkthrough/step-05-wrapup.md +1 -1
  34. package/src/bmm-skills/v6-shims/bmad-create-architecture/SKILL.md +1 -1
  35. package/src/bmm-skills/v6-shims/bmad-create-prd/SKILL.md +1 -1
  36. package/src/bmm-skills/v6-shims/bmad-create-story/SKILL.md +2 -2
  37. package/src/bmm-skills/v6-shims/bmad-dev-story/SKILL.md +2 -2
  38. package/src/bmm-skills/v6-shims/bmad-domain-research/SKILL.md +1 -1
  39. package/src/bmm-skills/v6-shims/bmad-edit-prd/SKILL.md +1 -1
  40. package/src/bmm-skills/v6-shims/bmad-market-research/SKILL.md +1 -1
  41. package/src/bmm-skills/v6-shims/bmad-sprint-status/SKILL.md +1 -1
  42. package/src/bmm-skills/v6-shims/bmad-technical-research/SKILL.md +1 -1
  43. package/src/bmm-skills/v6-shims/bmad-validate-prd/SKILL.md +1 -1
  44. package/src/core-skills/bmad-advanced-elicitation/SKILL.md +1 -1
  45. package/src/core-skills/bmad-brainstorming/SKILL.md +1 -1
  46. package/src/core-skills/bmad-customize/SKILL.md +1 -1
  47. package/src/core-skills/bmad-deep-recon/SKILL.md +1 -1
  48. package/src/core-skills/bmad-forge-idea/SKILL.md +1 -1
  49. package/src/core-skills/bmad-forge-idea/scripts/resolve_personas.py +3 -1
  50. package/src/core-skills/bmad-forge-idea/scripts/tests/test_resolve_personas.py +22 -0
  51. package/src/core-skills/bmad-party-mode/SKILL.md +1 -1
  52. package/src/core-skills/bmad-party-mode/references/create-party.md +1 -1
  53. package/src/core-skills/bmad-party-mode/scripts/resolve_party.py +3 -1
  54. package/src/core-skills/bmad-party-mode/scripts/tests/test_resolve_party.py +22 -0
  55. package/src/core-skills/bmad-review/SKILL.md +1 -1
  56. package/src/core-skills/bmad-review/references/lens-edge-case-hunter.md +2 -0
  57. package/src/scripts/__pycache__/config_utils.cpython-311.pyc +0 -0
  58. package/src/scripts/resolve_customization.py +72 -7
  59. package/src/scripts/tests/__pycache__/test_config_utils.cpython-311.pyc +0 -0
  60. package/src/scripts/tests/__pycache__/test_resolve_config.cpython-311.pyc +0 -0
  61. package/src/scripts/tests/__pycache__/test_resolve_customization.cpython-311.pyc +0 -0
  62. package/src/scripts/tests/test_resolve_customization.py +102 -0
package/README.md CHANGED
@@ -25,7 +25,7 @@ Open your project in your AI coding tool, invoke `bmad-build` with what you want
25
25
 
26
26
  **[Build your first project with BMad →](https://docs.bmad-method.org/start/build-your-first-change/)**
27
27
 
28
- **[Add BMad to an existing codebase →](https://docs.bmad-method.org/how-to/established-projects/)**
28
+ **[Add BMad to an existing codebase →](https://docs.bmad-method.org/existing-codebases/start-in-an-existing-codebase/)**
29
29
 
30
30
  BMad is free and open source, with no paywalled workflows or gated community. For installation prerequisites, updates, prerelease builds, and the installer's live automation help, see the [installation guide](https://docs.bmad-method.org/start/install-bmad/).
31
31
 
@@ -40,7 +40,7 @@ Coding assistants are effective at implementation, but they often turn unstated
40
40
  - **Guided collaboration** — Use structured workflows and multiple-agent discussions without handing over judgment.
41
41
  - **One delivery path** — Move from early thinking through reviewed implementation, correction, and learning.
42
42
 
43
- [See how the workflows fit together →](https://docs.bmad-method.org/reference/workflow-map/)
43
+ [See how much planning a change needs →](https://docs.bmad-method.org/plan/choose-a-planning-path/)
44
44
 
45
45
  ## BMad Ecosystem
46
46
 
@@ -62,9 +62,8 @@ Install the core method or add official modules for specialized work.
62
62
  ## Documentation
63
63
 
64
64
  - **[Build Your First Change](https://docs.bmad-method.org/start/build-your-first-change/)** — Install BMad and build a small project.
65
- - **[Workflow Map](https://docs.bmad-method.org/reference/workflow-map/)** — Understand the available paths and outputs.
66
- - **[Established Projects](https://docs.bmad-method.org/how-to/established-projects/)** — Add BMad to an existing codebase.
67
- - **[Upgrade to V6](https://docs.bmad-method.org/how-to/upgrade-to-v6/)** — Migrate from an earlier version.
65
+ - **[Choose a Planning Path](https://docs.bmad-method.org/plan/choose-a-planning-path/)** — Pick how much planning a change needs and see what each planning skill produces.
66
+ - **[Start in an Existing Codebase](https://docs.bmad-method.org/existing-codebases/start-in-an-existing-codebase/)** — Add BMad to an existing codebase.
68
67
 
69
68
  ## Community
70
69
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "bmad-method",
4
- "version": "6.11.1-next.33",
4
+ "version": "6.11.1-next.35",
5
5
  "description": "Breakthrough Method of Agile AI-driven Development",
6
6
  "keywords": [
7
7
  "agile",
@@ -20,7 +20,7 @@ You are Mary, the Business Analyst. You bring deep expertise in market research,
20
20
 
21
21
  ### Step 1: Resolve the Agent Block
22
22
 
23
- Run: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key agent`
23
+ Run: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --project-root {project-root} --key agent`
24
24
 
25
25
  **If the script fails**, resolve the `agent` block yourself by reading these three files in base → team → user order and applying the same structural merge rules as the resolver:
26
26
 
@@ -20,7 +20,7 @@ You are Winston, the System Architect. You turn product requirements and UX into
20
20
 
21
21
  ### Step 1: Resolve the Agent Block
22
22
 
23
- Run: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key agent`
23
+ Run: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --project-root {project-root} --key agent`
24
24
 
25
25
  **If the script fails**, resolve the `agent` block yourself by reading these three files in base → team → user order and applying the same structural merge rules as the resolver:
26
26
 
@@ -20,7 +20,7 @@ You are Amelia, the Senior Software Engineer. You execute approved stories with
20
20
 
21
21
  ### Step 1: Resolve the Agent Block
22
22
 
23
- Run: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key agent`
23
+ Run: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --project-root {project-root} --key agent`
24
24
 
25
25
  **If the script fails**, resolve the `agent` block yourself by reading these three files in base → team → user order and applying the same structural merge rules as the resolver:
26
26
 
@@ -20,7 +20,7 @@ You are John, the Product Manager. You drive PRD creation through user interview
20
20
 
21
21
  ### Step 1: Resolve the Agent Block
22
22
 
23
- Run: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key agent`
23
+ Run: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --project-root {project-root} --key agent`
24
24
 
25
25
  **If the script fails**, resolve the `agent` block yourself by reading these three files in base → team → user order and applying the same structural merge rules as the resolver:
26
26
 
@@ -20,7 +20,7 @@ You are Sally, the UX Designer. You translate user needs into interaction design
20
20
 
21
21
  ### Step 1: Resolve the Agent Block
22
22
 
23
- Run: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key agent`
23
+ Run: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --project-root {project-root} --key agent`
24
24
 
25
25
  **If the script fails**, resolve the `agent` block yourself by reading these three files in base → team → user order and applying the same structural merge rules as the resolver:
26
26
 
@@ -50,7 +50,7 @@ Writes go through the shared script (don't read the file back except on resume):
50
50
 
51
51
  **Forwarded activation:** if a caller invoked you with a stated intent and pre-resolved customization fields, honor them verbatim — skip your own intent inference, use the supplied values for those named fields, and resolve only the remaining fields from your own `customize.toml`.
52
52
 
53
- 1. Resolve customization: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow` (on failure read `{skill-root}/customize.toml`, use defaults). Run `{workflow.activation_steps_prepend}`, then `{workflow.activation_steps_append}`. Hold `{workflow.persistent_facts}` as standing context — empty unless the user opted in — and consult `{workflow.external_sources}` on demand.
53
+ 1. Resolve customization: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --project-root {project-root} --key workflow` (on failure read `{skill-root}/customize.toml`, use defaults). Run `{workflow.activation_steps_prepend}`, then `{workflow.activation_steps_append}`. Hold `{workflow.persistent_facts}` as standing context — empty unless the user opted in — and consult `{workflow.external_sources}` on demand.
54
54
  2. Resolve config: `uv run {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (merges `_bmad/config.toml`, `_bmad/config.user.toml`, and the `_bmad/custom/` overrides). From the merged JSON resolve `{user_name}`, `{communication_language}`, `{document_output_language}`, `{project_name}` (under `core`), `{planning_artifacts}` (under `modules.bmm`), and `{date}`; missing keys take neutral defaults, never block.
55
55
  3. Headless (no interactive user) → follow `references/headless.md` for the whole run. Otherwise greet `{user_name}` in `{communication_language}`. Detect the intent from the conversation and input — **create** (the default), **update** an existing spine, or **validate** one (see those sections). If the real ask is requirements / UX / a capability contract / epic breakdown / an agent, invoke the `bmad-prd`, `bmad-ux`, `bmad-spec`, `bmad-create-epics-and-stories`, or `bmad-workflow-builder` (if the BMad Builder module is installed) skill instead.
56
56
  4. If a run folder for this target already exists under `{workflow.spine_output_path}`, offer to resume from its memlog rather than restart.
@@ -51,7 +51,7 @@ This uses **step-file architecture** for disciplined execution:
51
51
 
52
52
  ### Step 1: Resolve the Workflow Block
53
53
 
54
- Run: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`
54
+ Run: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --project-root {project-root} --key workflow`
55
55
 
56
56
  **If the script fails**, resolve the `workflow` block yourself by reading these three files in base → team → user order and applying the same structural merge rules as the resolver:
57
57
 
@@ -138,6 +138,6 @@ Upon Completion of task output: offer to answer any questions about the Epics an
138
138
 
139
139
  ## On Complete
140
140
 
141
- Run: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow.on_complete`
141
+ Run: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --project-root {project-root} --key workflow.on_complete`
142
142
 
143
143
  If the resolved `workflow.on_complete` is non-empty, follow it as the final terminal instruction before exiting.
@@ -17,7 +17,7 @@ You are a master facilitator and coach helping the user create, edit, or validat
17
17
 
18
18
  **Forwarded activation:** if a caller invoked you with a stated intent and pre-resolved customization fields (e.g. the `bmad-create-prd` / `bmad-edit-prd` / `bmad-validate-prd` shims), honor them verbatim — skip your own intent inference, use the supplied values for those named fields, and resolve only the remaining fields from your own `customize.toml`.
19
19
 
20
- 1. Resolve customization: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`. On failure, read `{skill-root}/customize.toml` directly and use defaults.
20
+ 1. Resolve customization: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --project-root {project-root} --key workflow`. On failure, read `{skill-root}/customize.toml` directly and use defaults.
21
21
  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**.
22
22
  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.
23
23
  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-build`; 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.
@@ -30,7 +30,7 @@ The PRFAQ forces customer-first clarity: write the press release announcing the
30
30
 
31
31
  ### Step 1: Resolve the Workflow Block
32
32
 
33
- Run: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`
33
+ Run: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --project-root {project-root} --key workflow`
34
34
 
35
35
  **If the script fails**, resolve the `workflow` block yourself by reading these three files in base → team → user order and applying the same structural merge rules as the resolver:
36
36
 
@@ -78,6 +78,6 @@ purpose: "Token-efficient context for downstream PRD creation"
78
78
 
79
79
  This is the terminal stage. If the user wants to revise, loop back to the relevant stage. Otherwise, the workflow is done.
80
80
 
81
- Run: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow.on_complete`
81
+ Run: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --project-root {project-root} --key workflow.on_complete`
82
82
 
83
83
  If the resolved `workflow.on_complete` is non-empty, follow it as the final terminal instruction before exiting.
@@ -15,7 +15,7 @@ At the opening greeting, let the user know they can invoke `bmad-party-mode` for
15
15
 
16
16
  ## On Activation
17
17
 
18
- 1. Resolve customization: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`. On failure, read `{skill-root}/customize.toml` directly and use defaults.
18
+ 1. Resolve customization: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --project-root {project-root} --key workflow`. On failure, read `{skill-root}/customize.toml` directly and use defaults.
19
19
  2. Execute each entry in `{workflow.activation_steps_prepend}` in order.
20
20
  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.
21
21
  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.
@@ -19,7 +19,7 @@ Conversational always; the user approves every write.
19
19
 
20
20
  ## On Activation
21
21
 
22
- 1. Resolve customization: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`. On failure, read `{skill-root}/customize.toml` directly and use defaults. Execute `{workflow.activation_steps_prepend}`; treat `{workflow.persistent_facts}` entries as standing context (`file:` = paths/globs to load, others verbatim).
22
+ 1. Resolve customization: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --project-root {project-root} --key workflow`. On failure, read `{skill-root}/customize.toml` directly and use defaults. Execute `{workflow.activation_steps_prepend}`; treat `{workflow.persistent_facts}` entries as standing context (`file:` = paths/globs to load, others verbatim).
23
23
  2. Config: if `{project-root}/_bmad` exists, `uv run {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` and read `{user_name}`, `{communication_language}` (use it every turn), `{output_folder}`. Standalone: skip.
24
24
  3. **Load `references/best-practices.md` and `references/template.md` before anything else.** Every decision below is made against them.
25
25
  4. Detect intent and greet `{user_name}`: **setup** (no instruction file in the target carries meaningful content — scaffolding alone, empty headings, a comment, a lone import line, is not meaningful; when unsure, adopt, since adopting a near-empty file costs one small ledger while setting up a meaningful one loses instructions), **adopt** (an instruction file has content but no managed block, whatever its state and whoever wrote it — the migration form of refresh; that file is the baseline and every instruction in it enters the ledger of step 1), **refresh** (a managed block exists), **record** (the user reports a mistake agents made), **audit** (re-verify and prune). A supplied intent that contradicts what detection finds — e.g. `setup` against a file with content — is surfaced and confirmed, never silently obeyed. Fold `{workflow.external_sources}` into the source list. Execute `{workflow.activation_steps_append}`.
@@ -18,7 +18,7 @@ Multiple skills may call to update the same spec over time.
18
18
 
19
19
  ## On Activation
20
20
 
21
- 1. Resolve customization: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`. On failure, read `{skill-root}/customize.toml` directly.
21
+ 1. Resolve customization: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --project-root {project-root} --key workflow`. On failure, read `{skill-root}/customize.toml` directly.
22
22
  2. Run `{workflow.activation_steps_prepend}`. Treat `{workflow.persistent_facts}` as foundational context (`file:` entries are loaded).
23
23
  3. Resolve config: `uv run {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}` (merges `_bmad/config.toml`, `_bmad/config.user.toml`, and the `_bmad/custom/` overrides). From the merged JSON resolve `{user_name}`, `{communication_language}`, `{document_output_language}`, `{project_name}`, `{output_folder}` (under `core`), and `{date}`.
24
24
  4. Detect mode. **Headless** when any of: no TTY, programmatic caller (another skill or non-interactive runner), or the first message pre-supplies all inputs and asks for an artifact path back. **Interactive** otherwise. In interactive mode, greet by `{user_name}` in `{communication_language}`, stay in that language, and mention that `bmad-party-mode` and `bmad-advanced-elicitation` are available for deeper exploration on any field.
@@ -9,7 +9,7 @@ You are a senior developer about to commit to this plan. Two moves, in order: fi
9
9
 
10
10
  ## On Activation
11
11
 
12
- 1. Resolve customization: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`. On failure, read `{skill-root}/customize.toml` directly and use defaults.
12
+ 1. Resolve customization: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --project-root {project-root} --key workflow`. On failure, read `{skill-root}/customize.toml` directly and use defaults.
13
13
  2. Execute each entry in `{workflow.activation_steps_prepend}` in order.
14
14
  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.
15
15
  4. Load `{project-root}/_bmad/bmm/config.yaml` (and `config.user.yaml` if present). Resolve `{user_name}`, `{communication_language}`, `{document_output_language}`, `{project_name}`, `{planning_artifacts}`, `{implementation_artifacts}`, `{project_knowledge}` (skip gracefully if unset), `{date}`. Stay in `{communication_language}` for every turn, not just the greeting.
@@ -30,7 +30,7 @@ UX may lead, follow, or stand alone. Inherit `sources:` by reference; the spines
30
30
 
31
31
  ## On Activation
32
32
 
33
- 1. Resolve customization: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`. On failure, read `{skill-root}/customize.toml` directly and use defaults.
33
+ 1. Resolve customization: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --project-root {project-root} --key workflow`. On failure, read `{skill-root}/customize.toml` directly and use defaults.
34
34
  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; consult them alongside generic web research on the same triggers, org tools preferred when their directive matches.
35
35
  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.
36
36
  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. In the greeting, let the user know `bmad-party-mode` and `bmad-advanced-elicitation` are always available. Then scan for misroute on the first message: PRD → `bmad-prd`; architecture → `bmad-architecture`; game UX → BMad GDS; agent/skill → `bmad-workflow-builder`; brief → `bmad-product-brief`.
@@ -32,6 +32,8 @@ A claims check runs as Step 5.
32
32
  - If `also_consider` input was provided, incorporate those areas into the analysis
33
33
  - Walk all branching paths: control flow (conditionals, loops, error handlers, early returns) and domain boundaries (where values, states, or conditions transition). Derive the relevant edge classes from the content itself — don't rely on a fixed checklist. Examples: missing else/default, unguarded inputs, off-by-one loops, arithmetic overflow, implicit type coercion, race conditions, timeout gaps
34
34
  - Consider implicit branches: the diff special-cases or changes the handling of one or more members of a fixed set of values — enums, status codes, sentinels, type tags, flags, value ranges. The rest of the set is implicit branches (e.g. the diff changes the `RED` and `YELLOW` cases of a `RED`/`YELLOW`/`GREEN` enum; `GREEN` is the implicit branch)
35
+ - Consider handle lifetime: when the changed code re-checks, re-fetches, or re-validates something it already held — a handle, index, id, pointer — the re-check exists because an intervening call can invalidate it. Identify that call, what it does to the thing held, and what the changed code silently skips when the re-check fails
36
+ - For each call site the diff adds or changes — in test files as well as production code — read the callee's declaration and check the call against it: argument count, order, types, and defaults. Report any mismatch
35
37
  - For each path: determine whether the content handles it
36
38
  - Collect only the unhandled paths as findings — discard handled ones silently
37
39
 
@@ -32,6 +32,8 @@ A claims check runs as Step 5.
32
32
  - If `also_consider` input was provided, incorporate those areas into the analysis
33
33
  - Walk all branching paths: control flow (conditionals, loops, error handlers, early returns) and domain boundaries (where values, states, or conditions transition). Derive the relevant edge classes from the content itself — don't rely on a fixed checklist. Examples: missing else/default, unguarded inputs, off-by-one loops, arithmetic overflow, implicit type coercion, race conditions, timeout gaps
34
34
  - Consider implicit branches: the diff special-cases or changes the handling of one or more members of a fixed set of values — enums, status codes, sentinels, type tags, flags, value ranges. The rest of the set is implicit branches (e.g. the diff changes the `RED` and `YELLOW` cases of a `RED`/`YELLOW`/`GREEN` enum; `GREEN` is the implicit branch)
35
+ - Consider handle lifetime: when the changed code re-checks, re-fetches, or re-validates something it already held — a handle, index, id, pointer — the re-check exists because an intervening call can invalidate it. Identify that call, what it does to the thing held, and what the changed code silently skips when the re-check fails
36
+ - For each call site the diff adds or changes — in test files as well as production code — read the callee's declaration and check the call against it: argument count, order, types, and defaults. Report any mismatch
35
37
  - For each path: determine whether the content handles it
36
38
  - Collect only the unhandled paths as findings — discard handled ones silently
37
39
 
@@ -21,7 +21,7 @@ If you need an explicit user instruction to run them, ask once now for the whole
21
21
 
22
22
  ### Step 1: Resolve the Workflow Block
23
23
 
24
- Run: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`
24
+ Run: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --project-root {project-root} --key workflow`
25
25
 
26
26
  **If the script fails**, resolve the `workflow` block yourself by reading these three files in base → team → user order and applying the same structural merge rules as the resolver:
27
27
 
@@ -32,6 +32,8 @@ A claims check runs as Step 5 when the launch message names a claims file.
32
32
  - If `also_consider` input was provided, incorporate those areas into the analysis
33
33
  - Walk all branching paths: control flow (conditionals, loops, error handlers, early returns) and domain boundaries (where values, states, or conditions transition). Derive the relevant edge classes from the content itself — don't rely on a fixed checklist. Examples: missing else/default, unguarded inputs, off-by-one loops, arithmetic overflow, implicit type coercion, race conditions, timeout gaps
34
34
  - Consider implicit branches: the diff special-cases or changes the handling of one or more members of a fixed set of values — enums, status codes, sentinels, type tags, flags, value ranges. The rest of the set is implicit branches (e.g. the diff changes the `RED` and `YELLOW` cases of a `RED`/`YELLOW`/`GREEN` enum; `GREEN` is the implicit branch)
35
+ - Consider handle lifetime: when the changed code re-checks, re-fetches, or re-validates something it already held — a handle, index, id, pointer — the re-check exists because an intervening call can invalidate it. Identify that call, what it does to the thing held, and what the changed code silently skips when the re-check fails
36
+ - For each call site the diff adds or changes — in test files as well as production code — read the callee's declaration and check the call against it: argument count, order, types, and defaults. Report any mismatch
35
37
  - For each path: determine whether the content handles it
36
38
  - Collect only the unhandled paths as findings — discard handled ones silently
37
39
 
@@ -129,6 +129,6 @@ Present the user with follow-up options:
129
129
 
130
130
  ## On Complete
131
131
 
132
- Run: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow.on_complete`
132
+ Run: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --project-root {project-root} --key workflow.on_complete`
133
133
 
134
134
  If the resolved `workflow.on_complete` is non-empty, follow it as the final terminal instruction before exiting.
@@ -20,7 +20,7 @@ description: 'Assess the impact of a significant change during sprint execution
20
20
 
21
21
  ### Step 1: Resolve the Workflow Block
22
22
 
23
- Run: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`
23
+ Run: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --project-root {project-root} --key workflow`
24
24
 
25
25
  **If the script fails**, resolve the `workflow` block yourself by reading these three files in base → team → user order and applying the same structural merge rules as the resolver:
26
26
 
@@ -302,7 +302,7 @@ Activation is complete. If `activation_steps_prepend` or `activation_steps_appen
302
302
 
303
303
  <action>Report workflow completion to user with personalized message: "Correct Course workflow complete, {user_name}!"</action>
304
304
  <action>Remind user of success criteria and next steps for Developer agent</action>
305
- <action>Run: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow.on_complete` — if the resolved value is non-empty, follow it as the final terminal instruction before exiting.</action>
305
+ <action>Run: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --project-root {project-root} --key workflow.on_complete` — if the resolved value is non-empty, follow it as the final terminal instruction before exiting.</action>
306
306
  </step>
307
307
 
308
308
  </workflow>
@@ -20,7 +20,7 @@ description: 'Generate automated API and end-to-end tests for implemented featur
20
20
 
21
21
  ### Step 1: Resolve the Workflow Block
22
22
 
23
- Run: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`
23
+ Run: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --project-root {project-root} --key workflow`
24
24
 
25
25
  **If the script fails**, resolve the `workflow` block yourself by reading these three files in base → team → user order and applying the same structural merge rules as the resolver:
26
26
 
@@ -171,6 +171,6 @@ Save summary to: `{default_output_file}`
171
171
 
172
172
  ## On Complete
173
173
 
174
- Run: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow.on_complete`
174
+ Run: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --project-root {project-root} --key workflow.on_complete`
175
175
 
176
176
  If the resolved `workflow.on_complete` is non-empty, follow it as the final terminal instruction before exiting.
@@ -25,7 +25,7 @@ For automation, `-H <epic>` — an explicit epic in headless mode — is the sta
25
25
 
26
26
  Run these in order before the retrospective begins:
27
27
 
28
- 1. **Resolve the workflow block.** Run `uv run --no-cache {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`. If it fails, resolve `{workflow.*}` yourself by reading `{skill-root}/customize.toml`, then `{project-root}/_bmad/custom/{skill-name}.toml`, then `.user.toml` in that order, merging base → team → user (scalars override, keyed arrays-of-tables merge by `code`/`id`, other arrays append).
28
+ 1. **Resolve the workflow block.** Run `uv run --no-cache {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --project-root {project-root} --key workflow`. If it fails, resolve `{workflow.*}` yourself by reading `{skill-root}/customize.toml`, then `{project-root}/_bmad/custom/{skill-name}.toml`, then `.user.toml` in that order, merging base → team → user (scalars override, keyed arrays-of-tables merge by `code`/`id`, other arrays append).
29
29
  2. **Run prepend steps** — execute each entry in `{workflow.activation_steps_prepend}` in order.
30
30
  3. **Load persistent facts** — treat every `{workflow.persistent_facts}` entry as standing context. `file:` entries are paths/globs under `{project-root}` whose contents load as facts; all others are literal facts.
31
31
  4. **Load config** from `{project-root}/_bmad/bmm/config.yaml`: `project_name`, `user_name`, `communication_language`, `document_output_language`, `user_skill_level`, `planning_artifacts`, `implementation_artifacts`, and `date` (system datetime), plus `output_folder` from `{project-root}/_bmad/core/config.yaml`. Speak all output in `{communication_language}`; write all documents in `{document_output_language}`. Never state time estimates — AI has changed development speed, so hour/day/week predictions are noise.
@@ -20,7 +20,7 @@ description: 'Walk the user through reviewing a change: what it is for, what to
20
20
 
21
21
  ### Step 1: Resolve the Workflow Block
22
22
 
23
- Run: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`
23
+ Run: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --project-root {project-root} --key workflow`
24
24
 
25
25
  **If the script fails**, resolve the `workflow` block yourself by reading these three files in base → team → user order and applying the same structural merge rules as the resolver:
26
26
 
@@ -25,6 +25,6 @@ HALT — do not proceed until the user makes their choice.
25
25
 
26
26
  ## On Complete
27
27
 
28
- Run: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow.on_complete`
28
+ Run: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --project-root {project-root} --key workflow.on_complete`
29
29
 
30
30
  If the resolved `workflow.on_complete` is non-empty, follow it as the final terminal instruction before exiting.
@@ -11,7 +11,7 @@ This skill was consolidated into `bmad-architecture`. It is retained as a thin c
11
11
 
12
12
  ## On Activation
13
13
 
14
- 1. Resolve customization: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`. This picks up any `{project-root}/_bmad/custom/bmad-create-architecture.toml` and `bmad-create-architecture.user.toml` overrides for the legacy fields (`activation_steps_prepend`, `activation_steps_append`, `persistent_facts`, `on_complete`).
14
+ 1. Resolve customization: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --project-root {project-root} --key workflow`. This picks up any `{project-root}/_bmad/custom/bmad-create-architecture.toml` and `bmad-create-architecture.user.toml` overrides for the legacy fields (`activation_steps_prepend`, `activation_steps_append`, `persistent_facts`, `on_complete`).
15
15
 
16
16
  2. Load `{project-root}/_bmad/bmm/config.yaml` (and `config.user.yaml` if present) to resolve `{user_name}` and `{communication_language}`.
17
17
 
@@ -11,7 +11,7 @@ This skill was consolidated into `bmad-prd`. It is retained as a thin compatibil
11
11
 
12
12
  ## On Activation
13
13
 
14
- 1. Resolve customization: `uv run {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`).
14
+ 1. Resolve customization: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --project-root {project-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`).
15
15
 
16
16
  2. Load `{project-root}/_bmad/bmm/config.yaml` (and `config.user.yaml` if present) to resolve `{user_name}` and `{communication_language}`.
17
17
 
@@ -32,7 +32,7 @@ If you need an explicit user instruction to run them, ask once now for the whole
32
32
 
33
33
  ### Step 1: Resolve the Workflow Block
34
34
 
35
- Run: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`
35
+ Run: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --project-root {project-root} --key workflow`
36
36
 
37
37
  **If the script fails**, resolve the `workflow` block yourself by reading these three files in base → team → user order and applying the same structural merge rules as the resolver:
38
38
 
@@ -430,7 +430,7 @@ Activation is complete. If `activation_steps_prepend` or `activation_steps_appen
430
430
 
431
431
  **The developer now has everything needed for flawless implementation!**
432
432
  </output>
433
- <action>Run: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow.on_complete` — if the resolved value is non-empty, follow it as the final terminal instruction before exiting.</action>
433
+ <action>Run: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --project-root {project-root} --key workflow.on_complete` — if the resolved value is non-empty, follow it as the final terminal instruction before exiting.</action>
434
434
  </step>
435
435
 
436
436
  </workflow>
@@ -29,7 +29,7 @@ metadata:
29
29
 
30
30
  ### Step 1: Resolve the Workflow Block
31
31
 
32
- Run: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`
32
+ Run: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --project-root {project-root} --key workflow`
33
33
 
34
34
  **If the script fails**, resolve the `workflow` block yourself by reading these three files in base → team → user order and applying the same structural merge rules as the resolver:
35
35
 
@@ -498,7 +498,7 @@ Activation is complete. If `activation_steps_prepend` or `activation_steps_appen
498
498
  <action>Suggest checking {sprint_status} to see project progress</action>
499
499
  </check>
500
500
  <action>Remain flexible - allow user to choose their own path or ask for other assistance</action>
501
- <action>Run: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow.on_complete` — if the resolved value is non-empty, follow it as the final terminal instruction before exiting.</action>
501
+ <action>Run: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --project-root {project-root} --key workflow.on_complete` — if the resolved value is non-empty, follow it as the final terminal instruction before exiting.</action>
502
502
  </step>
503
503
 
504
504
  </workflow>
@@ -11,6 +11,6 @@ This skill was consolidated into `bmad-deep-recon`. It is retained as a thin com
11
11
 
12
12
  ## On Activation
13
13
 
14
- 1. Resolve customization: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`. This picks up any `{project-root}/_bmad/custom/bmad-domain-research.toml` and `bmad-domain-research.user.toml` overrides for the legacy fields (`activation_steps_prepend`, `activation_steps_append`, `persistent_facts`, `on_complete`).
14
+ 1. Resolve customization: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --project-root {project-root} --key workflow`. This picks up any `{project-root}/_bmad/custom/bmad-domain-research.toml` and `bmad-domain-research.user.toml` overrides for the legacy fields (`activation_steps_prepend`, `activation_steps_append`, `persistent_facts`, `on_complete`).
15
15
  2. Emit a deprecation notice to the user (in their configured communication language): `bmad-domain-research` is deprecated and forwards to `bmad-deep-recon` with the domain type. To silence this notice and access the full new surface (draft/process/run modes, research types, verification levels, HTML briefing, handoffs), migrate `_bmad/custom/bmad-domain-research.toml` to `_bmad/custom/bmad-deep-recon.toml` and invoke `bmad-deep-recon` directly.
16
16
  3. Invoke `bmad-deep-recon` with: **research type** `domain` (skip its type inference), the four legacy fields above as pre-resolved values, and the user's original input verbatim. `bmad-deep-recon` takes the workflow from here — do not execute any further steps in this shim.
@@ -11,7 +11,7 @@ This skill was consolidated into `bmad-prd`. It is retained as a thin compatibil
11
11
 
12
12
  ## On Activation
13
13
 
14
- 1. Resolve customization: `uv run {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`).
14
+ 1. Resolve customization: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --project-root {project-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`).
15
15
 
16
16
  2. Load `{project-root}/_bmad/bmm/config.yaml` (and `config.user.yaml` if present) to resolve `{user_name}` and `{communication_language}`.
17
17
 
@@ -11,6 +11,6 @@ This skill was consolidated into `bmad-deep-recon`. It is retained as a thin com
11
11
 
12
12
  ## On Activation
13
13
 
14
- 1. Resolve customization: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`. This picks up any `{project-root}/_bmad/custom/bmad-market-research.toml` and `bmad-market-research.user.toml` overrides for the legacy fields (`activation_steps_prepend`, `activation_steps_append`, `persistent_facts`, `on_complete`).
14
+ 1. Resolve customization: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --project-root {project-root} --key workflow`. This picks up any `{project-root}/_bmad/custom/bmad-market-research.toml` and `bmad-market-research.user.toml` overrides for the legacy fields (`activation_steps_prepend`, `activation_steps_append`, `persistent_facts`, `on_complete`).
15
15
  2. Emit a deprecation notice to the user (in their configured communication language): `bmad-market-research` is deprecated and forwards to `bmad-deep-recon` with the market type. To silence this notice and access the full new surface (draft/process/run modes, research types, verification levels, HTML briefing, handoffs), migrate `_bmad/custom/bmad-market-research.toml` to `_bmad/custom/bmad-deep-recon.toml` and invoke `bmad-deep-recon` directly.
16
16
  3. Invoke `bmad-deep-recon` with: **research type** `market` (skip its type inference), the four legacy fields above as pre-resolved values, and the user's original input verbatim. `bmad-deep-recon` takes the workflow from here — do not execute any further steps in this shim.
@@ -11,7 +11,7 @@ This skill was consolidated into `bmad-sprint-planning`, which now owns the whol
11
11
 
12
12
  ## On Activation
13
13
 
14
- 1. Resolve customization: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`. This picks up any `{project-root}/_bmad/custom/bmad-sprint-status.toml` and `bmad-sprint-status.user.toml` overrides for the legacy fields (`activation_steps_prepend`, `activation_steps_append`, `persistent_facts`, `on_complete`).
14
+ 1. Resolve customization: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --project-root {project-root} --key workflow`. This picks up any `{project-root}/_bmad/custom/bmad-sprint-status.toml` and `bmad-sprint-status.user.toml` overrides for the legacy fields (`activation_steps_prepend`, `activation_steps_append`, `persistent_facts`, `on_complete`).
15
15
 
16
16
  2. Load `{project-root}/_bmad/bmm/config.yaml` (and `config.user.yaml` if present) to resolve `{user_name}` and `{communication_language}`.
17
17
 
@@ -11,6 +11,6 @@ This skill was consolidated into `bmad-deep-recon`. It is retained as a thin com
11
11
 
12
12
  ## On Activation
13
13
 
14
- 1. Resolve customization: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`. This picks up any `{project-root}/_bmad/custom/bmad-technical-research.toml` and `bmad-technical-research.user.toml` overrides for the legacy fields (`activation_steps_prepend`, `activation_steps_append`, `persistent_facts`, `on_complete`).
14
+ 1. Resolve customization: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --project-root {project-root} --key workflow`. This picks up any `{project-root}/_bmad/custom/bmad-technical-research.toml` and `bmad-technical-research.user.toml` overrides for the legacy fields (`activation_steps_prepend`, `activation_steps_append`, `persistent_facts`, `on_complete`).
15
15
  2. Emit a deprecation notice to the user (in their configured communication language): `bmad-technical-research` is deprecated and forwards to `bmad-deep-recon` with the technical type. To silence this notice and access the full new surface (draft/process/run modes, research types, verification levels, HTML briefing, handoffs), migrate `_bmad/custom/bmad-technical-research.toml` to `_bmad/custom/bmad-deep-recon.toml` and invoke `bmad-deep-recon` directly.
16
16
  3. Invoke `bmad-deep-recon` with: **research type** `technical` (skip its type inference), the four legacy fields above as pre-resolved values, and the user's original input verbatim. `bmad-deep-recon` takes the workflow from here — do not execute any further steps in this shim.
@@ -11,7 +11,7 @@ This skill was consolidated into `bmad-prd`. It is retained as a thin compatibil
11
11
 
12
12
  ## On Activation
13
13
 
14
- 1. Resolve customization: `uv run {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`).
14
+ 1. Resolve customization: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --project-root {project-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`).
15
15
 
16
16
  2. Load `{project-root}/_bmad/bmm/config.yaml` (and `config.user.yaml` if present) to resolve `{user_name}` and `{communication_language}`.
17
17
 
@@ -14,7 +14,7 @@ You are BMad's shared refinement checkpoint: other skills invoke you at natural
14
14
 
15
15
  ## On Activation
16
16
 
17
- 1. Resolve customization: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`. On failure, read `{skill-root}/customize.toml` directly and use defaults.
17
+ 1. Resolve customization: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --project-root {project-root} --key workflow`. On failure, read `{skill-root}/customize.toml` directly and use defaults.
18
18
  2. Hold every `{workflow.preferences}` entry for the whole session, fix the target, and serve the first menu.
19
19
 
20
20
  ## Serving the Catalog
@@ -18,7 +18,7 @@ The session runs in one of three stances, chosen by the user — set explicitly
18
18
 
19
19
  ## On Activation
20
20
 
21
- 1. Resolve customization: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`. On failure, use a subagent to read `{skill-root}/customize.toml` directly with defaults.
21
+ 1. Resolve customization: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --project-root {project-root} --key workflow`. On failure, use a subagent to read `{skill-root}/customize.toml` directly with defaults.
22
22
  2. Run each `{workflow.activation_steps_prepend}` entry. Treat each `{workflow.persistent_facts}` entry as foundational context (`file:`-prefixed entries are paths/globs under `{project-root}` — load their contents; others are facts verbatim).
23
23
  3. Resolve central config: `uv run {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root} --key core` (merges `_bmad/config.toml`, `_bmad/config.user.toml`, and the `_bmad/custom/` overrides); from the merged JSON resolve `{user_name}`, `{communication_language}`, `{document_output_language}`, `{output_folder}`, `{project_name}`; `{date}` is today. On failure or missing values → neutral defaults; never block.
24
24
  4. **If launched headless** (a machine signal, not a human asking for output — `references/headless.md` lists them): load `references/headless.md` and follow it for the whole run; never load it otherwise. Outside headless, you generate ideas yourself only in autonomous mode (`references/mode-autonomous.md`) — never in facilitator or partner mode.
@@ -87,7 +87,7 @@ Default by character (policy → team, personal → user), confirm before writin
87
87
  3. Write. Create `{project-root}/_bmad/custom/` if needed.
88
88
  4. Verify:
89
89
  ```
90
- uv run {project-root}/_bmad/scripts/resolve_customization.py --skill <install-path> --key <agent-or-workflow>
90
+ uv run {project-root}/_bmad/scripts/resolve_customization.py --skill <install-path> --project-root {project-root} --key <agent-or-workflow>
91
91
  ```
92
92
  Show the merged output, point out the changed fields.
93
93
 
@@ -37,7 +37,7 @@ Three services, freely combined — each detailed in its reference: **Draft** a
37
37
 
38
38
  **Forwarded activation:** if a caller invoked you with a stated intent, research type, or pre-resolved customization fields (the legacy research shims and Mary's menu do), honor them verbatim — skip your own inference for those values and resolve only the rest.
39
39
 
40
- 1. Resolve customization: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow` (on failure read `{skill-root}/customize.toml`, use defaults). Run `{workflow.activation_steps_prepend}`, then `{workflow.activation_steps_append}`.
40
+ 1. Resolve customization: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --project-root {project-root} --key workflow` (on failure read `{skill-root}/customize.toml`, use defaults). Run `{workflow.activation_steps_prepend}`, then `{workflow.activation_steps_append}`.
41
41
  2. Resolve config: `uv run {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}`. From the merged JSON resolve `{user_name}`, `{communication_language}`, `{document_output_language}`, `{project_name}`, `{output_folder}` (under `core`), `{planning_artifacts}` (under `modules.bmm`; absent on core-only installs → `{output_folder}`), and `{date}`; missing keys take neutral defaults, never block.
42
42
  3. Headless (no interactive user) → see `## Headless Mode`. Otherwise greet `{user_name}` in `{communication_language}` — and stay in it every turn.
43
43
  4. Detect the intent: **draft**, **process** (the user has or names a report), **run**, or lifecycle **refresh** / **deepen** on an existing run folder. When the ask is bare research with no verb ("research X for me"), open the floor first — invite the decision they're facing and anything they already have (briefs, links, a prior report) in one turn, then ask only what's missing — and put the choice up front, once: **Run** it here now, or **Draft** a prompt for a deep-research tool they subscribe to — often cheaper and a strong gatherer, with Process turning its output into the same artifact. State the trade honestly (tokens and minutes here vs. one manual round-trip there); their call, remembered for the session.
@@ -22,7 +22,7 @@ Lead by questioning, not lecturing. Ask one question at a time, press on weak po
22
22
 
23
23
  ## On Activation
24
24
 
25
- 1. Resolve customization: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`. On failure, read `{skill-root}/customize.toml` directly with defaults. Apply the resolved `{workflow.*}` values throughout.
25
+ 1. Resolve customization: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --project-root {project-root} --key workflow`. On failure, read `{skill-root}/customize.toml` directly with defaults. Apply the resolved `{workflow.*}` values throughout.
26
26
  2. Run each `{workflow.activation_steps_prepend}` entry; treat each `{workflow.persistent_facts}` entry as foundational context (`file:` entries load their contents, `skill:` names a skill to consult, others are facts verbatim).
27
27
  3. Resolve central config: `uv run {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root} --key core`; from the merged JSON read `{user_name}`, `{communication_language}`, `{output_folder}`. On failure use neutral defaults; never block. Greet `{user_name}` in `{communication_language}` and stay in it.
28
28
  4. Note whether a BMad persona is already active in this conversation — the user loaded one (e.g. the analyst, the storyteller) and invoked the forge from within it. If so, that persona leads the session, in voice, throughout.
@@ -110,7 +110,9 @@ def find_party_skill(project_root: Path, skill_root: Path):
110
110
  def load_party_workflow(project_root: Path, party_skill: Path):
111
111
  """Merged [workflow] table for bmad-party-mode (base + user overrides)."""
112
112
  resolver = project_root / "_bmad" / "scripts" / "resolve_customization.py"
113
- data = _run_json([sys.executable, str(resolver), "--skill", str(party_skill), "--key", "workflow"])
113
+ data = _run_json(
114
+ [sys.executable, str(resolver), "--skill", str(party_skill), "--project-root", str(project_root), "--key", "workflow"]
115
+ )
114
116
  if data is not None and isinstance(data.get("workflow"), dict):
115
117
  return data["workflow"]
116
118
  # Fallback: base customize.toml directly, no override merge.
@@ -5,6 +5,7 @@
5
5
  """Unit tests for resolve_personas.py — pool merge, alias, party resolution."""
6
6
 
7
7
  import sys
8
+ import tempfile
8
9
  import unittest
9
10
  from pathlib import Path
10
11
 
@@ -134,5 +135,26 @@ class TestOverrideMergeFallback(unittest.TestCase):
134
135
  self.assertEqual([m["code"] for m in wf["party_members"]], ["x", "y"]) # appended
135
136
 
136
137
 
138
+ class TestResolverInvocation(unittest.TestCase):
139
+ """The wrapper knows the project root, so it must not let the resolver
140
+ infer one from the working directory (#2796)."""
141
+
142
+ def _captured_command(self, tmp):
143
+ captured = []
144
+ original = rp._run_json
145
+ rp._run_json = lambda cmd: captured.append(cmd) or {"workflow": {}}
146
+ try:
147
+ rp.load_party_workflow(Path(tmp) / "project", Path(tmp) / "skill")
148
+ finally:
149
+ rp._run_json = original
150
+ return captured[0]
151
+
152
+ def test_passes_project_root_to_the_customization_resolver(self):
153
+ with tempfile.TemporaryDirectory() as tmp:
154
+ cmd = self._captured_command(tmp)
155
+ self.assertIn("--project-root", cmd)
156
+ self.assertEqual(cmd[cmd.index("--project-root") + 1], str(Path(tmp) / "project"))
157
+
158
+
137
159
  if __name__ == "__main__":
138
160
  unittest.main()
@@ -16,7 +16,7 @@ Run a round-table where these agents talk to each other and to the user like rea
16
16
 
17
17
  ## On Activation
18
18
 
19
- 1. **Resolve customization:** `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`. On failure, read `{skill-root}/customize.toml` directly and use defaults. Then run each `{workflow.activation_steps_prepend}` entry, and hold each `{workflow.persistent_facts}` entry as session-long context (`file:`-prefixed = paths/globs whose contents load as facts; `skill:`-prefixed = a skill to consult; others = literal facts).
19
+ 1. **Resolve customization:** `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --project-root {project-root} --key workflow`. On failure, read `{skill-root}/customize.toml` directly and use defaults. Then run each `{workflow.activation_steps_prepend}` entry, and hold each `{workflow.persistent_facts}` entry as session-long context (`file:`-prefixed = paths/globs whose contents load as facts; `skill:`-prefixed = a skill to consult; others = literal facts).
20
20
  2. **Resolve core config:** `uv run {project-root}/_bmad/scripts/resolve_config.py --project-root {project-root}`. From the merged JSON's `core` table: greet with `{user_name}`, speak in `{communication_language}`, and resolve `{output_folder}`; `{date}` is today's date.
21
21
  3. **Detect intent and route.** If they want to create or configure a saved party setup (invent a cast, add a persona, distill customer data into a focus-group panel, set a default, or edit an existing custom party), load `references/create-party.md` and follow it. Otherwise run a party — continue below.
22
22
  4. **Resolve the roster:** `uv run {skill-root}/scripts/resolve_party.py --project-root {project-root} --skill {skill-root}`. It returns the active roster (`{workflow.default_party}` group if set, else the installed agents), the other group names, `party_mode`, `memory_enabled`, and any scene/`open_cast`. Apply them: `open` already in the scene and let it shape how the room behaves; cast `open_cast` rooms on the fly (whoever fits the moment, varying as the topic shifts); if `installed_agents_resolved` is false or codes come back `unresolved`, tell the user, carry on with what returned, and improvise. Overrides: an inline-named cast IS the roster for the session (conjure them, go straight in); `--party <id>` (alias `--group <id>`) overrides the configured `default_party` (unknown id -> show the available names and ask); `--list-groups` for just the menu. Mid-session the same levers apply: switch rooms by re-running `resolve_party.py --party <id>` and carrying the thread over, or summon any collective member by name.
@@ -30,7 +30,7 @@ Ask which they're after if it isn't obvious, then proceed.
30
30
 
31
31
  ## Editing an existing party
32
32
 
33
- When the user wants to change a party that already exists (retune a member's persona, add someone to a group, swap the default), read the current state first so you change rather than clobber: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow` returns the merged `party_members`, `party_groups`, and `default_party`. Show the member or group being touched, capture only the delta with the user, and hand that sparse change to `bmad-customize` — it replaces a `party_members`/`party_groups` entry whose `code`/`id` matches and appends the rest, so an edit is just the changed entry, never a full rewrite.
33
+ When the user wants to change a party that already exists (retune a member's persona, add someone to a group, swap the default), read the current state first so you change rather than clobber: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --project-root {project-root} --key workflow` returns the merged `party_members`, `party_groups`, and `default_party`. Show the member or group being touched, capture only the delta with the user, and hand that sparse change to `bmad-customize` — it replaces a `party_members`/`party_groups` entry whose `code`/`id` matches and appends the rest, so an edit is just the changed entry, never a full rewrite.
34
34
 
35
35
  ## Keeping new faces from a session
36
36
 
@@ -71,7 +71,9 @@ def load_agents(project_root: Path):
71
71
  def load_workflow(project_root: Path, skill_root: Path):
72
72
  """Merged [workflow] table. Falls back to the skill's base customize.toml."""
73
73
  script = project_root / "_bmad" / "scripts" / "resolve_customization.py"
74
- data = _run_json([sys.executable, str(script), "--skill", str(skill_root), "--key", "workflow"])
74
+ data = _run_json(
75
+ [sys.executable, str(script), "--skill", str(skill_root), "--project-root", str(project_root), "--key", "workflow"]
76
+ )
75
77
  if data is not None and "workflow" in data:
76
78
  return data["workflow"]
77
79
  # Fallback: read the skill's base customize.toml directly (no override merge).
@@ -5,6 +5,7 @@
5
5
  """Unit tests for resolve_party.py — merge, alias, override, group resolution."""
6
6
 
7
7
  import sys
8
+ import tempfile
8
9
  import unittest
9
10
  from pathlib import Path
10
11
 
@@ -142,5 +143,26 @@ class TestInstalledCodesIsDefaultRoom(unittest.TestCase):
142
143
  self.assertEqual(col["bmad-agent-analyst"]["name"], "Mary-Custom")
143
144
 
144
145
 
146
+ class TestResolverInvocation(unittest.TestCase):
147
+ """The wrapper knows the project root, so it must not let the resolver
148
+ infer one from the working directory (#2796)."""
149
+
150
+ def _captured_command(self, tmp):
151
+ captured = []
152
+ original = rp._run_json
153
+ rp._run_json = lambda cmd: captured.append(cmd) or {"workflow": {}}
154
+ try:
155
+ rp.load_workflow(Path(tmp) / "project", Path(tmp) / "skill")
156
+ finally:
157
+ rp._run_json = original
158
+ return captured[0]
159
+
160
+ def test_passes_project_root_to_the_customization_resolver(self):
161
+ with tempfile.TemporaryDirectory() as tmp:
162
+ cmd = self._captured_command(tmp)
163
+ self.assertIn("--project-root", cmd)
164
+ self.assertEqual(cmd[cmd.index("--project-root") + 1], str(Path(tmp) / "project"))
165
+
166
+
145
167
  if __name__ == "__main__":
146
168
  unittest.main()
@@ -25,7 +25,7 @@ The lens set is whatever `{workflow.lenses}` resolves to, not a fixed list — o
25
25
 
26
26
  ## Execution
27
27
 
28
- 1. **Resolve customization:** `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`. On failure, read `{skill-root}/customize.toml` directly and use defaults. **Forwarded activation:** if a caller invoked you with pre-resolved customization fields (e.g. the `bmad-editorial-review` shim), honor them verbatim for those named fields — they already carry the user's overrides — and resolve only the remaining fields from your own `customize.toml`. Then execute each `{workflow.activation_steps_prepend}` entry in order, hold `{workflow.persistent_facts}` as standing context for the session, and treat `{workflow.review_guidance}` entries as standing review directives for every lens.
28
+ 1. **Resolve customization:** `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --project-root {project-root} --key workflow`. On failure, read `{skill-root}/customize.toml` directly and use defaults. **Forwarded activation:** if a caller invoked you with pre-resolved customization fields (e.g. the `bmad-editorial-review` shim), honor them verbatim for those named fields — they already carry the user's overrides — and resolve only the remaining fields from your own `customize.toml`. Then execute each `{workflow.activation_steps_prepend}` entry in order, hold `{workflow.persistent_facts}` as standing context for the session, and treat `{workflow.review_guidance}` entries as standing review directives for every lens.
29
29
  2. **Load the content.** Stage it once as a file: when the content is a branch, uncommitted work, or a commit range, use the repository's version-control tooling to write the unified diff to a uniquely-named file in the system temp directory and take that file's absolute path as the content. A branch means its diff against the merge base with its base branch; uncommitted work includes untracked files. Stage `claims` to its own file the same way — it is input for one lens, staged separately precisely so the other lenses never see it. If the content is empty or cannot be decoded as text: when the caller expects the raw findings JSON array (e.g. the legacy edge-case forwarder), return `[{"location":"N/A","trigger_condition":"Input empty or undecodable","guard_snippet":"Provide valid content to review","potential_consequence":"Review skipped — no analysis performed"}]` (no `lens` field) and stop; otherwise say what's wrong and ask for reviewable content. Classify the content — diff, source file, function, or document — and whether it is **code** or **docs**; scope rules and lens applicability both depend on it. A document that defines behavior (spec, requirements, plan, story) is `docs` that a behavioral lens may still apply to; judge by `when`.
30
30
  3. **Select lenses** from `{workflow.lenses}`. A lens with an empty `instruction` is disabled. If the user or caller named lenses, run exactly those only — `applies_to` and `when` do not filter an explicit request. Otherwise run every enabled lens whose `applies_to` covers the content class (`any` always covers) and whose `when` applies.
31
31
  4. **Announce the plan** in one line before running anything: the content class, the lenses about to run, and — when any lens has `after` set — that it runs on top of the named lens's findings. Skip the announcement entirely when the caller pinned an exact output contract (the legacy forwarders that demand raw JSON or one exact line) — their contract covers everything you emit, not just the findings block. Then execute each `{workflow.activation_steps_append}` entry in order.
@@ -18,6 +18,8 @@ Walk every branching path and boundary condition within scope — report only un
18
18
  - If `also_consider` areas were provided, incorporate them into the analysis
19
19
  - Walk all branching paths: control flow (conditionals, loops, error handlers, early returns) and domain boundaries (where values, states, or conditions transition). Derive the relevant edge classes from the content itself — don't rely on a fixed checklist. Examples: missing else/default, unguarded inputs, off-by-one loops, arithmetic overflow, implicit type coercion, race conditions, timeout gaps
20
20
  - Consider implicit branches: the diff special-cases or changes the handling of one or more members of a fixed set of values — enums, status codes, sentinels, type tags, flags, value ranges. The rest of the set is implicit branches (e.g. the diff changes the `RED` and `YELLOW` cases of a `RED`/`YELLOW`/`GREEN` enum; `GREEN` is the implicit branch)
21
+ - Consider handle lifetime: when the changed code re-checks, re-fetches, or re-validates something it already held — a handle, index, id, pointer — the re-check exists because an intervening call can invalidate it. Identify that call, what it does to the thing held, and what the changed code silently skips when the re-check fails
22
+ - For each call site the diff adds or changes — in test files as well as production code — read the callee's declaration and check the call against it: argument count, order, types, and defaults. Report any mismatch
21
23
  - For each path: determine whether the content handles it
22
24
  - Collect only the unhandled paths as findings — discard handled ones silently
23
25
 
@@ -25,15 +25,77 @@ _MISSING = object()
25
25
 
26
26
 
27
27
  def find_project_root(start: Path) -> Path | None:
28
+ """Nearest ancestor holding `_bmad/`, falling back to the nearest holding `.git`.
29
+
30
+ `_bmad/` outranks `.git` at every depth: a submodule or nested repo carries
31
+ `.git` without being the BMad project, so treating the two as equal stops the
32
+ walk short of the root that owns `_bmad/custom/`.
33
+ """
34
+ git_root: Path | None = None
28
35
  current = start.resolve()
29
36
  while True:
30
- if (current / "_bmad").exists() or (current / ".git").exists():
37
+ if (current / "_bmad").is_dir():
31
38
  return current
39
+ if git_root is None and (current / ".git").exists():
40
+ git_root = current
32
41
  if current.parent == current:
33
- return None
42
+ return git_root
34
43
  current = current.parent
35
44
 
36
45
 
46
+ def script_project_root() -> Path | None:
47
+ """Project root implied by this script's own install path.
48
+
49
+ Skills invoke `{project-root}/_bmad/scripts/resolve_customization.py`, so when
50
+ this file sits at that path its grandparent is a project root the caller already
51
+ resolved.
52
+ """
53
+ parents = Path(__file__).resolve().parents
54
+ if len(parents) >= 3 and parents[0].name == "scripts" and parents[1].name == "_bmad":
55
+ return parents[2]
56
+ return None
57
+
58
+
59
+ def candidate_project_roots(skill_dir: Path) -> list[Path]:
60
+ """Plausible project roots, most trustworthy first.
61
+
62
+ The working directory leads because the project is where the user is working,
63
+ not where the skill happens to be installed — a home-installed skill walks up to
64
+ `~`, and any `~/_bmad` there would otherwise mask the real project's overrides.
65
+ """
66
+ ordered: list[Path] = []
67
+ for root in (
68
+ find_project_root(Path.cwd()),
69
+ script_project_root(),
70
+ find_project_root(skill_dir),
71
+ ):
72
+ if root is not None and root not in ordered:
73
+ ordered.append(root)
74
+ return ordered
75
+
76
+
77
+ def has_override(root: Path, skill_name: str) -> bool:
78
+ custom_dir = root / "_bmad" / "custom"
79
+ return any(
80
+ (custom_dir / name).is_file()
81
+ for name in (f"{skill_name}.toml", f"{skill_name}.user.toml")
82
+ )
83
+
84
+
85
+ def warn_on_masked_override(chosen: Path, rejected: list[Path], skill_name: str) -> None:
86
+ """Break the silence when a real override exists under a root we did not pick."""
87
+ if has_override(chosen, skill_name):
88
+ return
89
+ for root in rejected:
90
+ if has_override(root, skill_name):
91
+ sys.stderr.write(
92
+ f"note: resolved project root {chosen} has no customization for "
93
+ f"`{skill_name}`, but {root} does. Using {chosen}; pass "
94
+ f"--project-root to select the other explicitly.\n"
95
+ )
96
+ return
97
+
98
+
37
99
  def extract_key(data, dotted_key: str):
38
100
  current = data
39
101
  for part in dotted_key.split("."):
@@ -73,11 +135,14 @@ def main() -> int:
73
135
  args = parser.parse_args()
74
136
 
75
137
  skill_dir = Path(args.skill).resolve()
76
- project_root = (
77
- Path(args.project_root).resolve()
78
- if args.project_root
79
- else find_project_root(skill_dir) or find_project_root(Path.cwd())
80
- )
138
+ if args.project_root:
139
+ project_root = Path(args.project_root).resolve()
140
+ else:
141
+ candidates = candidate_project_roots(skill_dir)
142
+ project_root = candidates[0] if candidates else None
143
+ if project_root is not None:
144
+ warn_on_masked_override(project_root, candidates[1:], skill_dir.name)
145
+
81
146
  try:
82
147
  merged = load_customization(project_root, skill_dir)
83
148
  except ConfigError as error:
@@ -73,5 +73,107 @@ class ResolveCustomizationStdoutTests(unittest.TestCase):
73
73
  self.assertEqual(resolved["agent"]["icon"], "🧭")
74
74
 
75
75
 
76
+ def write(path: Path, body: str) -> None:
77
+ path.parent.mkdir(parents=True, exist_ok=True)
78
+ path.write_text(body, encoding="utf-8")
79
+
80
+
81
+ def facts(*entries: str) -> str:
82
+ listed = ", ".join(f'"{entry}"' for entry in entries)
83
+ return f"[workflow]\npersistent_facts = [{listed}]\n"
84
+
85
+
86
+ def resolve(skill_dir: Path, cwd: Path, *extra: str):
87
+ return subprocess.run(
88
+ [sys.executable, str(SCRIPT), "--skill", str(skill_dir), "--key", "workflow", *extra],
89
+ text=True,
90
+ stdout=subprocess.PIPE,
91
+ stderr=subprocess.PIPE,
92
+ cwd=str(cwd),
93
+ check=False,
94
+ )
95
+
96
+
97
+ class ProjectRootResolutionTests(unittest.TestCase):
98
+ """Regression cover for #2796 — the project is where the user works, not
99
+ where the skill is installed."""
100
+
101
+ def test_home_installed_skill_reads_project_override_not_home_bmad(self):
102
+ with tempfile.TemporaryDirectory() as temp_dir:
103
+ home = Path(temp_dir) / "home"
104
+ project = Path(temp_dir) / "project"
105
+ skill = home / ".claude" / "skills" / "demo-skill"
106
+ write(skill / "customize.toml", facts("shipped default"))
107
+ (home / "_bmad" / "custom").mkdir(parents=True)
108
+ write(project / "_bmad" / "custom" / "demo-skill.toml", facts("team override"))
109
+
110
+ result = resolve(skill, project)
111
+
112
+ self.assertEqual(result.returncode, 0, msg=result.stderr)
113
+ resolved = json.loads(result.stdout)["workflow"]["persistent_facts"]
114
+ self.assertEqual(resolved, ["shipped default", "team override"])
115
+
116
+ def test_project_installed_skill_still_resolves(self):
117
+ with tempfile.TemporaryDirectory() as temp_dir:
118
+ project = Path(temp_dir) / "project"
119
+ skill = project / ".claude" / "skills" / "demo-skill"
120
+ write(skill / "customize.toml", facts("shipped default"))
121
+ write(project / "_bmad" / "custom" / "demo-skill.toml", facts("team override"))
122
+
123
+ result = resolve(skill, project)
124
+
125
+ self.assertEqual(result.returncode, 0, msg=result.stderr)
126
+ resolved = json.loads(result.stdout)["workflow"]["persistent_facts"]
127
+ self.assertEqual(resolved, ["shipped default", "team override"])
128
+
129
+ def test_walk_prefers_bmad_over_a_nearer_git_directory(self):
130
+ with tempfile.TemporaryDirectory() as temp_dir:
131
+ project = Path(temp_dir) / "project"
132
+ submodule = project / "vendor" / "sub"
133
+ skill = Path(temp_dir) / "skills" / "demo-skill"
134
+ write(skill / "customize.toml", facts("shipped default"))
135
+ write(project / "_bmad" / "custom" / "demo-skill.toml", facts("team override"))
136
+ (submodule / ".git").mkdir(parents=True)
137
+
138
+ result = resolve(skill, submodule)
139
+
140
+ self.assertEqual(result.returncode, 0, msg=result.stderr)
141
+ resolved = json.loads(result.stdout)["workflow"]["persistent_facts"]
142
+ self.assertEqual(resolved, ["shipped default", "team override"])
143
+
144
+ def test_notes_when_a_rejected_root_holds_the_only_override(self):
145
+ with tempfile.TemporaryDirectory() as temp_dir:
146
+ home = Path(temp_dir) / "home"
147
+ project = Path(temp_dir) / "project"
148
+ skill = home / ".claude" / "skills" / "demo-skill"
149
+ write(skill / "customize.toml", facts("shipped default"))
150
+ write(home / "_bmad" / "custom" / "demo-skill.toml", facts("home override"))
151
+ (project / "_bmad" / "custom").mkdir(parents=True)
152
+
153
+ result = resolve(skill, project)
154
+
155
+ self.assertEqual(result.returncode, 0, msg=result.stderr)
156
+ resolved = json.loads(result.stdout)["workflow"]["persistent_facts"]
157
+ self.assertEqual(resolved, ["shipped default"])
158
+ self.assertIn("demo-skill", result.stderr)
159
+ self.assertIn("--project-root", result.stderr)
160
+
161
+ def test_explicit_project_root_wins_and_stays_quiet(self):
162
+ with tempfile.TemporaryDirectory() as temp_dir:
163
+ home = Path(temp_dir) / "home"
164
+ project = Path(temp_dir) / "project"
165
+ skill = home / ".claude" / "skills" / "demo-skill"
166
+ write(skill / "customize.toml", facts("shipped default"))
167
+ write(home / "_bmad" / "custom" / "demo-skill.toml", facts("home override"))
168
+ (project / "_bmad" / "custom").mkdir(parents=True)
169
+
170
+ result = resolve(skill, project, "--project-root", str(home))
171
+
172
+ self.assertEqual(result.returncode, 0, msg=result.stderr)
173
+ resolved = json.loads(result.stdout)["workflow"]["persistent_facts"]
174
+ self.assertEqual(resolved, ["shipped default", "home override"])
175
+ self.assertEqual(result.stderr, "")
176
+
177
+
76
178
  if __name__ == "__main__":
77
179
  unittest.main()