Skip to content

Resolve the dev primitive skill dynamically (bmad-build-auto / bmad-dev-auto) - #444

Open
ron332 wants to merge 1 commit into
bmad-code-org:mainfrom
ron332:fix/resolve-primitive-skill-rename
Open

Resolve the dev primitive skill dynamically (bmad-build-auto / bmad-dev-auto)#444
ron332 wants to merge 1 commit into
bmad-code-org:mainfrom
ron332:fix/resolve-primitive-skill-rename

Conversation

@ron332

@ron332 ron332 commented Aug 3, 2026

Copy link
Copy Markdown

Summary

  • The bmm module renamed the inner dev primitive skill bmad-dev-autobmad-build-auto, leaving bmad-dev-auto installed as a permanent one-file forwarding shim (no step-04-review.md/customize.toml of its own).
  • bmad-loop still hardcoded bmad-dev-auto as the primitive everywhere: the validate marker check, review-layer resolution, _bmad/custom/ override paths, the stories-dispatch probe, and worktree provisioning's copy list. This made bmad-loop validate report a correctly-installed post-rename project as skills.base-incomplete.
  • Worse than the validate cosmetic: review-layer resolution was reading the forwarding shim's file instead of the real primitive's step-04-review.md/customize.toml — a functional bug that would affect live dev/review sessions on any post-rename bmm install, not just validate output.
  • Adds resolve_primitive_skill(project, tree) as the single place that decides which skill is a project's real dev primitive: prefers bmad-build-auto whenever it's installed there at all (existence, not completeness — a half-upgraded install still resolves to bmad-build-auto so the marker gap is reported against the right name), falling back to bmad-dev-auto only when bmad-build-auto is entirely absent (pre-rename bmm installs).
  • Every consumer (missing_base_skills, resolve_review_layers, _merged_review_layers, missing_stories_support, the _bmad/custom/ override filenames, and provision_worktree's copy list via the new base_skills_for_tree) now resolves through that one function instead of a hardcoded literal, so they can never disagree about which skill is authoritative for a given project/tree.
  • DEV_BASE_SKILLS, BASE_SKILLS, STORIES_PROBE_SKILL are kept exported under their legacy values for backward compatibility with existing call sites.

Test plan

  • pytest tests/test_install.py — 234 passed (2 pre-existing/unrelated failures on main, macOS/APFS undecodable-filename tests, verified via git stash)
  • Full suite pytest tests/ — 3816 passed, 48 skipped, 1 pre-existing/unrelated failure (same macOS/APFS issue), reproduced identically before and after this change
  • ruff check / ruff format --check clean on all touched files
  • pyright clean (0 errors) on install.py, worktree_flow.py, cli.py
  • Added TestResolvePrimitiveSkill (5 cases) plus coverage for missing_base_skills, resolve_review_layers, customize-override precedence, base_skills_for_tree, and provision_worktree against a bmad-build-auto project
  • Verified end-to-end against a real post-rename bmm project: bmad-loop validate goes from a false FAIL: skills.base-incomplete to a clean pass

Summary by CodeRabbit

  • New Features

    • Added support for resolving the preferred development skill while retaining compatibility with legacy installations.
    • Isolated worktrees now receive the appropriate project-specific development and review skills.
    • Installation diagnostics identify the selected development skill.
    • Installation now excludes runtime files and policy configuration from version control.
  • Documentation

    • Updated validation and dispatch messages to reference the resolved development skill instead of a fixed name.
  • Tests

    • Added coverage for preference, fallback, customization, worktree provisioning, and legacy compatibility.

…ev-auto)

The BMad Method (bmm) module renamed the inner dev primitive skill from
bmad-dev-auto to bmad-build-auto, leaving bmad-dev-auto as a permanent
one-file forwarding shim. bmad-loop still hardcoded bmad-dev-auto as the
primitive everywhere (validate's marker check, review-layer resolution,
customize-override paths, the stories-dispatch probe, and worktree
provisioning's copy list), so `bmad-loop validate` reported a correctly
installed project as `skills.base-incomplete`, and review-layer resolution
was reading the forwarding shim's SKILL.md instead of the real
step-04-review.md/customize.toml — a functional bug in live dev/review
sessions, not just a validate false positive.

Add resolve_primitive_skill(), the single place that decides which skill
is a project's real primitive per tree: prefers bmad-build-auto whenever
it's installed there at all, falling back to bmad-dev-auto only when
build-auto is absent (pre-rename bmm installs). Every consumer now
resolves through this function instead of a hardcoded name, so they can
never disagree about which skill is authoritative for a given project.
@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The installer now resolves bmad-build-auto or legacy bmad-dev-auto per project tree. Validation, review layers, customization overrides, diagnostics, and worktree provisioning use the resolved primitive. Tests cover preference, fallback, validation, and copying behavior.

Changes

Primitive resolution and provisioning

Layer / File(s) Summary
Primitive selection and review inputs
src/bmad_loop/install.py
The installer resolves the active primitive per tree and uses it for base skills, review layers, customization overrides, and review-step paths.
Validation and dispatch checks
src/bmad_loop/install.py, src/bmad_loop/cli.py
Base-skill and stories-dispatch checks use the resolved primitive and report its name in diagnostics.
Worktree provisioning and coverage
src/bmad_loop/worktree_flow.py, tests/test_install.py
Worktree provisioning copies the resolved primitive. Tests cover renamed and legacy installations, incomplete markers, overrides, review layers, and copying.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CLI
  participant Installer
  participant WorktreeFlow
  participant ProjectTree
  CLI->>Installer: validate installed skills
  Installer->>ProjectTree: resolve primitive for tree
  ProjectTree-->>Installer: bmad-build-auto or bmad-dev-auto
  Installer-->>CLI: primitive-specific validation result
  WorktreeFlow->>Installer: request base skills for tree
  Installer-->>WorktreeFlow: resolved primitive and review skills
  WorktreeFlow->>ProjectTree: copy required skills into worktree
Loading

Possibly related PRs

Suggested reviewers: pbean, dracic, polloinfilzato

Poem

I hop through trees where build skills grow,
bmad-build-auto leads the flow.
Old names wait when new ones hide,
Review layers travel side by side.
Worktrees bloom with skills in place. 🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: dynamic resolution between the new and legacy dev primitive skill names.
Docstring Coverage ✅ Passed Docstring coverage is 92.86% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch fix/resolve-primitive-skill-rename
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/bmad_loop/install.py (1)

154-191: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Derive base_skills_for_tree from BASE_SKILLS instead of duplicating the composition.

base_skills_for_tree hand-lists the same four fallback skills (bmad-review-adversarial-general, bmad-review-edge-case-hunter, bmad-review-verification-gap, MERGED_REVIEW_SKILL) that BASE_SKILLS already expresses via DEV_BASE_SKILLS. A future change to that composition (add/remove a fallback skill) now has two places to update. Miss one, and the static catalog and the dynamic per-tree resolution silently diverge.

Build the dynamic dict from BASE_SKILLS directly, swapping in the resolved primitive key, to keep one source of truth for the skill list.

♻️ Proposed refactor to derive from a single source of truth
 def base_skills_for_tree(project: Path, tree: str) -> dict[str, tuple[str, ...]]:
     """:data:`BASE_SKILLS`, but with the dev primitive resolved for THIS project/tree.
 
     Same composition — the primitive plus the fallback review hunters, the merged
     reviewer, and the pre-consolidation verification-gap forwarder — except the
     primitive entry names whichever skill :func:`resolve_primitive_skill` actually
     finds installed, instead of always the legacy name. Worktree provisioning uses
     this (not the static ``BASE_SKILLS``) so an isolated run copies the SAME
     primitive the main repo's preflight validated — a project on `bmad-build-auto`
     must not have its worktree seeded with a `bmad-dev-auto` that was never there.
     """
     primitive = resolve_primitive_skill(project, tree)
-    return {
-        primitive: PRIMITIVE_MARKERS,
-        "bmad-review-adversarial-general": (),
-        "bmad-review-edge-case-hunter": (),
-        "bmad-review-verification-gap": (),
-        MERGED_REVIEW_SKILL: (),
-    }
+    skills = dict(BASE_SKILLS)
+    if primitive != LEGACY_PRIMITIVE_SKILL:
+        del skills[LEGACY_PRIMITIVE_SKILL]
+    skills[primitive] = PRIMITIVE_MARKERS
+    return skills
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/bmad_loop/install.py` around lines 154 - 191, Update base_skills_for_tree
to derive its returned mapping from BASE_SKILLS, replacing the primitive entry
with the key resolved by resolve_primitive_skill while preserving
PRIMITIVE_MARKERS for that entry. Do not hand-list the fallback skills; retain
their existing marker values and ensure the dynamic result reflects any future
changes to BASE_SKILLS.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/bmad_loop/install.py`:
- Around line 154-191: Update base_skills_for_tree to derive its returned
mapping from BASE_SKILLS, replacing the primitive entry with the key resolved by
resolve_primitive_skill while preserving PRIMITIVE_MARKERS for that entry. Do
not hand-list the fallback skills; retain their existing marker values and
ensure the dynamic result reflects any future changes to BASE_SKILLS.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e4a749e3-82e4-4345-9f5d-23f8ac81bb9a

📥 Commits

Reviewing files that changed from the base of the PR and between 29c147c and d0ce8ef.

📒 Files selected for processing (4)
  • src/bmad_loop/cli.py
  • src/bmad_loop/install.py
  • src/bmad_loop/worktree_flow.py
  • tests/test_install.py

@greptile-apps

greptile-apps Bot commented Aug 3, 2026

Copy link
Copy Markdown

Greptile Summary

This PR fixes a functional regression caused by the bmad-dev-autobmad-build-auto rename in the upstream bmm module: every path in install.py that hardcoded the legacy skill name now resolves through a new resolve_primitive_skill(project, tree) function, eliminating false FAIL: skills.base-incomplete errors and ensuring review-layer resolution reads the real primitive's files instead of the forwarding shim's.

  • Adds resolve_primitive_skill as the single authoritative resolver (prefers bmad-build-auto on presence of its SKILL.md, falls back to bmad-dev-auto for pre-rename installs), and threads it through missing_base_skills, resolve_review_layers, missing_stories_support, _customize_overrides, and worktree provisioning via the new base_skills_for_tree.
  • Replaces the static BASE_SKILLS reference in provision_worktree with base_skills_for_tree(repo_root, tree) so isolated worktrees are seeded with whichever primitive the main repo actually has installed.
  • Keeps DEV_BASE_SKILLS, BASE_SKILLS, and STORIES_PROBE_SKILL exported at their legacy values for backward compatibility; drops DEV_PRIMITIVE_SKILL without a compat alias.

Confidence Score: 4/5

Safe to merge; the core fix is correct and well-tested, with no regressions in the full 3816-test suite.

The implementation logic is sound — resolve_primitive_skill correctly uses SKILL.md existence as the signal, every consumer is updated consistently, and backward-compatible exports are preserved. The minor concerns are: DEV_PRIMITIVE_SKILL is a previously-public constant dropped without a compat alias; ReviewResolution's docstring still names bmad-dev-auto after the rest of the module was updated; and missing_stories_support for the bmad-build-auto topology has no explicit test despite being a changed code path. None of these affect correctness of the fix itself.

Files Needing Attention: src/bmad_loop/install.py — the dropped DEV_PRIMITIVE_SKILL export and stale ReviewResolution docstring. tests/test_install.py — missing missing_stories_support coverage for the new topology.

Important Files Changed

Filename Overview
src/bmad_loop/install.py Core of the fix — adds resolve_primitive_skill, base_skills_for_tree, and _customize_overrides(primitive); threads the resolved primitive through all consumers. DEV_PRIMITIVE_SKILL is dropped without a compat alias; ReviewResolution docstring retains a stale bmad-dev-auto reference.
src/bmad_loop/worktree_flow.py Swaps BASE_SKILLS for base_skills_for_tree(repo_root, tree) in the worktree copy-list floor; resolve_primitive_skill is called twice for the same tree, a minor redundancy the PR's passthrough mechanism could avoid.
src/bmad_loop/cli.py Documentation-only changes: user-facing messages and docstrings updated from hardcoded bmad-dev-auto to generic 'dev primitive' phrasing; no logic changes.
tests/test_install.py Adds _install_build_auto, TestResolvePrimitiveSkill (5 cases), and explicit bmad-build-auto tests for missing_base_skills, resolve_review_layers, customize-override precedence, base_skills_for_tree, and provision_worktree; missing_stories_support against the new topology is not covered.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["project / tree"] --> B["resolve_primitive_skill(project, tree)"]
    B -->|"bmad-build-auto/SKILL.md exists"| C["PRIMITIVE_SKILL (bmad-build-auto)"]
    B -->|"absent — pre-rename or no install"| D["LEGACY_PRIMITIVE_SKILL (bmad-dev-auto)"]
    C & D --> E["primitive"]
    E --> F["missing_base_skills"]
    E --> G["resolve_review_layers"]
    E --> H["missing_stories_support"]
    E --> I["_customize_overrides"]
    E --> J["base_skills_for_tree"]
    J --> K["provision_worktree"]
Loading

Comments Outside Diff (2)

  1. src/bmad_loop/install.py, line 231-232 (link)

    P2 Stale bmad-dev-auto reference in ReviewResolution docstring

    The class-level docstring on ReviewResolution still says "Which skills the installed bmad-dev-auto's review step actually invokes." — every other comparable string in the module was updated to say "dev primitive" or "resolved primitive". A contributor reading the NamedTuple in isolation gets a misleading impression that it is always tied to the legacy name, which contradicts the whole point of this PR.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: src/bmad_loop/install.py
    Line: 231-232
    
    Comment:
    **Stale `bmad-dev-auto` reference in `ReviewResolution` docstring**
    
    The class-level docstring on `ReviewResolution` still says "Which skills the installed `bmad-dev-auto`'s review step actually invokes." — every other comparable string in the module was updated to say "dev primitive" or "resolved primitive". A contributor reading the NamedTuple in isolation gets a misleading impression that it is always tied to the legacy name, which contradicts the whole point of this PR.
    
    ---
    
    For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

  2. tests/test_install.py, line 535-543 (link)

    P2 No missing_stories_support test for the bmad-build-auto topology

    missing_stories_support was updated in this PR to resolve the primitive dynamically via resolve_primitive_skill, but the new tests only cover missing_base_skills, resolve_review_layers, base_skills_for_tree, and provision_worktree against a bmad-build-auto project. The stories probe path is only exercised via STORIES_PROBE_SKILL (which still resolves to "bmad-dev-auto", the pre-rename path). Adding a counterpart test that installs bmad-build-auto and calls missing_stories_support would mirror the coverage pattern used for all other functions in this change.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: tests/test_install.py
    Line: 535-543
    
    Comment:
    **No `missing_stories_support` test for the `bmad-build-auto` topology**
    
    `missing_stories_support` was updated in this PR to resolve the primitive dynamically via `resolve_primitive_skill`, but the new tests only cover `missing_base_skills`, `resolve_review_layers`, `base_skills_for_tree`, and `provision_worktree` against a `bmad-build-auto` project. The stories probe path is only exercised via `STORIES_PROBE_SKILL` (which still resolves to `"bmad-dev-auto"`, the pre-rename path). Adding a counterpart test that installs `bmad-build-auto` and calls `missing_stories_support` would mirror the coverage pattern used for all other functions in this change.
    
    ---
    
    For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.
Prompt To Fix All With AI
### Issue 1
src/bmad_loop/install.py:231-232
**Stale `bmad-dev-auto` reference in `ReviewResolution` docstring**

The class-level docstring on `ReviewResolution` still says "Which skills the installed `bmad-dev-auto`'s review step actually invokes." — every other comparable string in the module was updated to say "dev primitive" or "resolved primitive". A contributor reading the NamedTuple in isolation gets a misleading impression that it is always tied to the legacy name, which contradicts the whole point of this PR.

### Issue 2
src/bmad_loop/install.py:167
**`DEV_PRIMITIVE_SKILL` removed without a backward-compat alias**

The PR explicitly preserves `DEV_BASE_SKILLS`, `BASE_SKILLS`, and `STORIES_PROBE_SKILL` for backward compat with existing call sites, but the equally-public `DEV_PRIMITIVE_SKILL = "bmad-dev-auto"` constant is silently dropped. Any downstream code that does `from bmad_loop.install import DEV_PRIMITIVE_SKILL` (outside the test files updated in this PR) will get an `ImportError`. If no external callers are known, adding a one-line alias `DEV_PRIMITIVE_SKILL = LEGACY_PRIMITIVE_SKILL` alongside the other preserved exports would close the risk at zero cost.

### Issue 3
src/bmad_loop/worktree_flow.py:234-236
**`resolve_primitive_skill` called twice for the same project/tree**

`base_skills_for_tree(repo_root, tree)` and `resolve_review_layers(repo_root, tree)` each independently call `resolve_primitive_skill(repo_root, tree)`, resulting in two identical filesystem probes for the same `SKILL.md`. The optional `primitive` parameter added to `resolve_review_layers` in this very PR exists for exactly this use-case — `missing_base_skills` already takes advantage of it to pass the resolved primitive through to `_review_findings`. Passing the resolved primitive here too would make the pattern consistent across all call sites.

### Issue 4
tests/test_install.py:535-543
**No `missing_stories_support` test for the `bmad-build-auto` topology**

`missing_stories_support` was updated in this PR to resolve the primitive dynamically via `resolve_primitive_skill`, but the new tests only cover `missing_base_skills`, `resolve_review_layers`, `base_skills_for_tree`, and `provision_worktree` against a `bmad-build-auto` project. The stories probe path is only exercised via `STORIES_PROBE_SKILL` (which still resolves to `"bmad-dev-auto"`, the pre-rename path). Adding a counterpart test that installs `bmad-build-auto` and calls `missing_stories_support` would mirror the coverage pattern used for all other functions in this change.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "Resolve the dev primitive skill dynamica..." | Re-trigger Greptile

Comment thread src/bmad_loop/install.py
# primitive name for back-compat with existing call sites and tests that treat it
# as a fixed catalog; `base_skills_for_tree` is the per-project/tree equivalent
# that resolves the primitive dynamically, and is what worktree provisioning uses.
BASE_SKILLS = {**DEV_BASE_SKILLS, "bmad-review-verification-gap": (), MERGED_REVIEW_SKILL: ()}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 DEV_PRIMITIVE_SKILL removed without a backward-compat alias

The PR explicitly preserves DEV_BASE_SKILLS, BASE_SKILLS, and STORIES_PROBE_SKILL for backward compat with existing call sites, but the equally-public DEV_PRIMITIVE_SKILL = "bmad-dev-auto" constant is silently dropped. Any downstream code that does from bmad_loop.install import DEV_PRIMITIVE_SKILL (outside the test files updated in this PR) will get an ImportError. If no external callers are known, adding a one-line alias DEV_PRIMITIVE_SKILL = LEGACY_PRIMITIVE_SKILL alongside the other preserved exports would close the risk at zero cost.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/bmad_loop/install.py
Line: 167

Comment:
**`DEV_PRIMITIVE_SKILL` removed without a backward-compat alias**

The PR explicitly preserves `DEV_BASE_SKILLS`, `BASE_SKILLS`, and `STORIES_PROBE_SKILL` for backward compat with existing call sites, but the equally-public `DEV_PRIMITIVE_SKILL = "bmad-dev-auto"` constant is silently dropped. Any downstream code that does `from bmad_loop.install import DEV_PRIMITIVE_SKILL` (outside the test files updated in this PR) will get an `ImportError`. If no external callers are known, adding a one-line alias `DEV_PRIMITIVE_SKILL = LEGACY_PRIMITIVE_SKILL` alongside the other preserved exports would close the risk at zero cost.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment on lines 234 to +236
resolved = resolve_review_layers(repo_root, tree)
required = dict.fromkeys((*BASE_SKILLS, *(resolved.skills() if resolved else ())))
required = dict.fromkeys(
(*base_skills_for_tree(repo_root, tree), *(resolved.skills() if resolved else ()))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 resolve_primitive_skill called twice for the same project/tree

base_skills_for_tree(repo_root, tree) and resolve_review_layers(repo_root, tree) each independently call resolve_primitive_skill(repo_root, tree), resulting in two identical filesystem probes for the same SKILL.md. The optional primitive parameter added to resolve_review_layers in this very PR exists for exactly this use-case — missing_base_skills already takes advantage of it to pass the resolved primitive through to _review_findings. Passing the resolved primitive here too would make the pattern consistent across all call sites.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/bmad_loop/worktree_flow.py
Line: 234-236

Comment:
**`resolve_primitive_skill` called twice for the same project/tree**

`base_skills_for_tree(repo_root, tree)` and `resolve_review_layers(repo_root, tree)` each independently call `resolve_primitive_skill(repo_root, tree)`, resulting in two identical filesystem probes for the same `SKILL.md`. The optional `primitive` parameter added to `resolve_review_layers` in this very PR exists for exactly this use-case — `missing_base_skills` already takes advantage of it to pass the resolved primitive through to `_review_findings`. Passing the resolved primitive here too would make the pattern consistent across all call sites.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant