Skip to content

fix: complete setup skill with legacy cleanup, result templates, and path fixes#36

Merged
bmadcode merged 4 commits intobmad-code-org:mainfrom
Sallvainian:main
Mar 25, 2026
Merged

fix: complete setup skill with legacy cleanup, result templates, and path fixes#36
bmadcode merged 4 commits intobmad-code-org:mainfrom
Sallvainian:main

Conversation

@Sallvainian
Copy link
Copy Markdown
Contributor

@Sallvainian Sallvainian commented Mar 23, 2026

What

Fixes the fresh-install-misidentified-as-legacy bug and completes
missing functionality in the bmad-builder-setup skill: result template
application, output directory creation, and legacy directory cleanup.

Fixes #35

Why

Fresh installs via the BMAD installer create per-module config files
that the setup skill incorrectly classifies as "legacy." Additionally,
the skill stored raw user answers without applying module.yaml's
result template, never created configured output directories, and
left hundreds of redundant skill files in _bmad/ subdirectories.

How

  • SKILL.md: Differentiate fresh install from legacy migration based
    on whether config.yaml already has a module section; add Create
    Output Directories and Cleanup Legacy Directories sections; bundle
    language questions; remove inconsistent "structured question tool"
    hint; fix bare skill-internal paths to use ./ prefix
  • merge-config.py: Add apply_result_templates() so skills
    becomes {project-root}/skills per module.yaml's result field
  • cleanup-legacy.py (new): Verifies skills exist at .claude/skills/
    then removes _bmad/{module-code}/, _bmad/core/, _bmad/_config/
  • docs/explanation/module-configuration.md: Added Legacy Directory
    Cleanup section

Testing

Tested on a fresh BMAD install (npx github:bmad-code-org/BMAD-METHOD).
Ran /bmad-builder-setup and confirmed: config.yaml has templated
values, config.user.yaml has user-only keys, output directories created,
legacy directories removed. 81 unit tests passing (26 new for
cleanup-legacy, 55 updated for merge-config).

Summary by CodeRabbit

  • New Features

    • Automatic, idempotent cleanup of legacy installer directories after setup (with safety checks to preserve installed skills).
    • Module result templating: output paths now consistently include the project-root prefix when configured.
  • Documentation

    • Clarified legacy migration and per-module configuration flows; added post-merge cleanup step and richer confirmation/reporting.
    • Combined language prompts into a single configuration question and clarified default-priority for module assets.

Sallvainian and others added 3 commits March 23, 2026 05:53
- New cleanup-legacy.py script removes redundant installer directories
  from _bmad/ after config migration, with safety verification that
  skills exist at .claude/skills/ before removal
- Fix merge-config.py to apply result templates from module.yaml so
  values like "skills" are stored as "{project-root}/skills"
- Update SKILL.md with fresh-vs-legacy detection, directory creation
  step, and cleanup step
- Add comprehensive tests for both changes (81 total, all passing)
- Update module-configuration.md docs with cleanup section

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove "Use a structured question tool if available" from Collect
  Configuration — causes inconsistent behavior across LLM sessions
- Bundle communication_language and document_output_language into a
  single question since the answer is always the same

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
All references to assets/ and scripts/ paths now use ./ prefix to
distinguish them from {project-root} paths per BMad path conventions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 23, 2026 19:58
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 23, 2026

Walkthrough

Adds migration and cleanup for legacy per-module _bmad/ directories, a safety-checked script to remove those directories, a template application step during config merge, and tests covering the new behaviors and idempotent cleanup.

Changes

Cohort / File(s) Summary
Documentation
docs/explanation/module-configuration.md, src/skills/bmad-builder-setup/SKILL.md
Documented per-module vs centralized config detection, unified language question, relative asset paths, post-merge cleanup step (./scripts/cleanup-legacy.py), and reporting of removed legacy directories.
Cleanup script
src/skills/bmad-builder-setup/scripts/cleanup-legacy.py
New CLI tool to remove legacy _bmad/{module} directory trees with optional --skills-dir safety verification; outputs JSON results/errors and handles missing targets, permission errors, and idempotency.
Config merge logic
src/skills/bmad-builder-setup/scripts/merge-config.py
Added apply_result_templates() to transform module answers per result templates (injecting {project-root}), with a guard to avoid double-prefixing; integrated into merge flow and verbose logging.
Tests
src/skills/bmad-builder-setup/scripts/tests/test-cleanup-legacy.py, src/skills/bmad-builder-setup/scripts/tests/test-merge-config.py
Comprehensive tests for cleanup discovery, verification, file counting, deletion/idempotency, and for apply_result_templates() including edge cases and updated merge expectations.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

Poem

🐰 I hopped through folders old and new,
Whiskers sharp, I checked each clue;
I nudged configs into a single bed,
Tidied up where old trees spread —
Now builds can dance, all fresh and true. 🥕✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 44.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main changes: fixing legacy cleanup detection, adding result templates, and correcting paths in the setup skill.
Linked Issues check ✅ Passed The PR fully addresses issue #35 by differentiating fresh installs from legacy migrations through checking for module sections in config.yaml, implementing the cleanup logic, and applying result templates.
Out of Scope Changes check ✅ Passed All changes directly support the linked issue objectives: SKILL.md documents the new flow, cleanup-legacy.py implements the cleanup, merge-config.py adds result templating, and documentation is updated accordingly.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 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 and usage tips.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Completes the bmad-builder-setup skill’s end-to-end setup/migration flow by correctly distinguishing fresh installs from legacy layouts, applying module.yaml result templates to stored answers, and adding a post-merge cleanup step to remove redundant installer directories under _bmad/.

Changes:

  • Update SKILL.md to refine install vs. legacy detection guidance, add output-directory creation + legacy directory cleanup steps, and fix relative path references.
  • Add apply_result_templates() to merge-config.py so stored module config values respect each variable’s result template.
  • Introduce cleanup-legacy.py (with unit tests) to safely remove redundant legacy _bmad/ directory trees after migration, plus docs describing the cleanup behavior.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/skills/bmad-builder-setup/SKILL.md Updates setup skill instructions for fresh vs legacy detection, templating, output dir creation, and directory cleanup.
src/skills/bmad-builder-setup/scripts/merge-config.py Applies module.yaml result templates when writing module values into _bmad/config.yaml.
src/skills/bmad-builder-setup/scripts/cleanup-legacy.py New script to verify installed skills and remove redundant legacy directories under _bmad/.
src/skills/bmad-builder-setup/scripts/tests/test-merge-config.py Adds coverage for result templating and updates expectations for templated module paths.
src/skills/bmad-builder-setup/scripts/tests/test-cleanup-legacy.py New unit test suite covering discovery, verification, and deletion logic for legacy cleanup.
docs/explanation/module-configuration.md Documents the new legacy directory cleanup step and its safety guarantees.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +194 to +213
def apply_result_templates(
module_yaml: dict, module_answers: dict, verbose: bool = False
) -> dict:
"""Apply result templates from module.yaml to transform raw answer values.

For each answer, if the corresponding variable definition in module.yaml has
a 'result' field, replaces {value} in that template with the answer. Skips
the template if the answer already contains '{project-root}' to prevent
double-prefixing.
"""
transformed = {}
for key, value in module_answers.items():
var_def = module_yaml.get(key)
if (
isinstance(var_def, dict)
and "result" in var_def
and "{project-root}" not in str(value)
):
template = var_def["result"]
transformed[key] = template.replace("{value}", str(value))
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

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

apply_result_templates() will blindly apply the result template to any value that doesn’t already contain "{project-root}". If a user provides an absolute path (e.g. "/tmp/out") or a home-relative path ("~/out"), this will be transformed into something like "{project-root}//tmp/out", which is almost certainly unintended. Consider detecting absolute/home paths (and possibly Windows drive/UNC paths) and skipping templating in those cases, or otherwise validating/normalizing before applying the template.

Suggested change
def apply_result_templates(
module_yaml: dict, module_answers: dict, verbose: bool = False
) -> dict:
"""Apply result templates from module.yaml to transform raw answer values.
For each answer, if the corresponding variable definition in module.yaml has
a 'result' field, replaces {value} in that template with the answer. Skips
the template if the answer already contains '{project-root}' to prevent
double-prefixing.
"""
transformed = {}
for key, value in module_answers.items():
var_def = module_yaml.get(key)
if (
isinstance(var_def, dict)
and "result" in var_def
and "{project-root}" not in str(value)
):
template = var_def["result"]
transformed[key] = template.replace("{value}", str(value))
def _looks_like_rooted_path(value: str) -> bool:
"""Heuristically determine whether a value looks like an absolute or home/Windows path.
This is intentionally conservative: if it looks like a rooted path, we avoid
prefixing it with {project-root} in result templates.
"""
s = str(value)
if not s:
return False
# POSIX absolute path
if s.startswith("/"):
return True
# Home-relative path
if s.startswith("~"):
return True
# Windows UNC path
if s.startswith("\\\\"):
return True
# Windows drive letter path, e.g. C:\ or D:/...
if len(s) >= 3 and s[0].isalpha() and s[1] == ":" and s[2] in ("/", "\\"):
return True
return False
def apply_result_templates(
module_yaml: dict, module_answers: dict, verbose: bool = False
) -> dict:
"""Apply result templates from module.yaml to transform raw answer values.
For each answer, if the corresponding variable definition in module.yaml has
a 'result' field, replaces {value} in that template with the answer.
When the template contains '{project-root}', the transformation is skipped if
the answer already contains '{project-root}' or if the answer looks like an
absolute/home/Windows path, to avoid unintended double-prefixing such as
'{project-root}//tmp/out'.
"""
transformed = {}
for key, value in module_answers.items():
var_def = module_yaml.get(key)
if isinstance(var_def, dict) and "result" in var_def:
template = var_def["result"]
value_str = str(value)
if "{project-root}" in template:
# Skip templating if the value already contains {project-root} or
# looks like an absolute/home/Windows path.
if "{project-root}" in value_str or _looks_like_rooted_path(value_str):
transformed[key] = value
continue
transformed[key] = template.replace("{value}", value_str)

Copilot uses AI. Check for mistakes.
Comment on lines +203 to +207
bmad_dir = args.bmad_dir
module_code = args.module_code

# Build the list of directories to remove
dirs_to_remove = [module_code, "core"] + args.also_remove
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

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

cleanup-legacy.py uses user-controlled directory names (module_code and --also-remove entries) directly in Path(bmad_dir) / dirname. Without validating that these are simple directory basenames (no path separators, no '.'/'..'), a malicious or accidental value like ".." could cause the script to delete outside of _bmad/. Please validate/sanitize these inputs before building dirs_to_remove and refuse unsafe names.

Copilot uses AI. Check for mistakes.
Comment thread src/skills/bmad-builder-setup/SKILL.md Outdated

## Create Output Directories

After writing config, create any output directories that were configured. Resolve `{project-root}` to the actual project root and create each path-type value from `config.yaml` that does not yet exist — this includes `output_folder` and any module variable whose value starts with `{project-root}/`. Use `mkdir -p` or equivalent to create the full path.
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

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

This section says to "Resolve {project-root} to the actual project root" when creating directories, but earlier the skill notes {project-root} is a literal token that should not be substituted in config values. Consider clarifying here that substitution is only for filesystem operations (mkdir), while the config files must continue to store the literal token.

Suggested change
After writing config, create any output directories that were configured. Resolve `{project-root}` to the actual project root and create each path-type value from `config.yaml` that does not yet exist — this includes `output_folder` and any module variable whose value starts with `{project-root}/`. Use `mkdir -p` or equivalent to create the full path.
After writing config, create any output directories that were configured. For filesystem operations only (such as creating directories), resolve the `{project-root}` token to the actual project root and create each path-type value from `config.yaml` that does not yet exist — this includes `output_folder` and any module variable whose value starts with `{project-root}/`. The paths stored in the config files must continue to use the literal `{project-root}` token; only the directories on disk should use the resolved paths. Use `mkdir -p` or equivalent to create the full path.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
src/skills/bmad-builder-setup/scripts/tests/test-cleanup-legacy.py (1)

206-218: Consider prefixing unused unpacked variables with underscores.

Static analysis flags several unused variables from tuple unpacking (e.g., count, not_found). While the tests work correctly, prefixing unused variables with _ follows Python convention and silences linter warnings.

Example fix for line 212
-            removed, not_found, count = cleanup_directories(
+            removed, _not_found, _count = cleanup_directories(
                 bmad_dir, ["bmb", "core", "_config"]
             )

Similar changes apply to lines 236, 265, 295, and 414.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/skills/bmad-builder-setup/scripts/tests/test-cleanup-legacy.py` around
lines 206 - 218, In the test functions (e.g.,
test_removes_module_core_and_config) rename unused tuple-unpacked variables to
start with an underscore to satisfy linters: change count and not_found to
_count and _not_found (and similarly prefix any other unused unpacked vars in
the other test functions referenced at lines 236, 265, 295, 414) so the tuple
unpacking still returns the same values but unused names follow Python
convention and silence static analysis warnings.
src/skills/bmad-builder-setup/scripts/cleanup-legacy.py (1)

207-215: Consider using unpacking syntax for list construction.

The current concatenation works correctly, but unpacking is more idiomatic in modern Python.

Suggested change
-    dirs_to_remove = [module_code, "core"] + args.also_remove
+    dirs_to_remove = [module_code, "core", *args.also_remove]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/skills/bmad-builder-setup/scripts/cleanup-legacy.py` around lines 207 -
215, Replace the manual concatenation and dedup loop for dirs_to_remove with a
more idiomatic unpacking + order-preserving dedupe: create dirs_to_remove by
combining module_code, the literal "core", and the elements of args.also_remove
using unpacking, then deduplicate while preserving order (e.g., via
dict.fromkeys or an OrderedDict) and assign back to dirs_to_remove; remove the
seen/unique_dirs loop and retain the same final semantics. Reference symbols:
dirs_to_remove, module_code, "core", args.also_remove, seen, unique_dirs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/skills/bmad-builder-setup/scripts/cleanup-legacy.py`:
- Around line 207-215: Replace the manual concatenation and dedup loop for
dirs_to_remove with a more idiomatic unpacking + order-preserving dedupe: create
dirs_to_remove by combining module_code, the literal "core", and the elements of
args.also_remove using unpacking, then deduplicate while preserving order (e.g.,
via dict.fromkeys or an OrderedDict) and assign back to dirs_to_remove; remove
the seen/unique_dirs loop and retain the same final semantics. Reference
symbols: dirs_to_remove, module_code, "core", args.also_remove, seen,
unique_dirs.

In `@src/skills/bmad-builder-setup/scripts/tests/test-cleanup-legacy.py`:
- Around line 206-218: In the test functions (e.g.,
test_removes_module_core_and_config) rename unused tuple-unpacked variables to
start with an underscore to satisfy linters: change count and not_found to
_count and _not_found (and similarly prefix any other unused unpacked vars in
the other test functions referenced at lines 236, 265, 295, 414) so the tuple
unpacking still returns the same values but unused names follow Python
convention and silence static analysis warnings.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 20819e93-59cc-4503-b5ca-3d08e795285f

📥 Commits

Reviewing files that changed from the base of the PR and between 50eb6ac and fc2e37a.

📒 Files selected for processing (6)
  • docs/explanation/module-configuration.md
  • src/skills/bmad-builder-setup/SKILL.md
  • src/skills/bmad-builder-setup/scripts/cleanup-legacy.py
  • src/skills/bmad-builder-setup/scripts/merge-config.py
  • src/skills/bmad-builder-setup/scripts/tests/test-cleanup-legacy.py
  • src/skills/bmad-builder-setup/scripts/tests/test-merge-config.py

Address review feedback: the Overview says {project-root} is a literal
token that should never be substituted, but Create Output Directories
said to "resolve" it. Clarify that substitution applies only to
filesystem operations (mkdir), not to stored config values.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/skills/bmad-builder-setup/SKILL.md`:
- Around line 33-37: Wrap long Markdown lines in the SKILL.md sections that
include the "Default priority" and "Core config" paragraphs so they satisfy the
repo's markdown-lint rules; break the long sentences/lists into shorter lines
(e.g., ~80 characters) while preserving content and punctuation, and ensure the
paragraphs that reference keys like user_name, communication_language,
document_output_language, output_folder and the rule about legacy configs are
wrapped consistently; also apply the same hard-wrapping to the other noted long
lines in the file (around the later paragraphs) so all MD paragraphs and list
items pass linting.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 40f9b0f4-e877-43ed-83cb-b6653db1cd04

📥 Commits

Reviewing files that changed from the base of the PR and between fc2e37a and 0833542.

📒 Files selected for processing (1)
  • src/skills/bmad-builder-setup/SKILL.md

Comment on lines +33 to +37
Ask the user for values. Show defaults in brackets. Present all values together so the user can respond once with only the values they want to change (e.g. "change language to Swahili, rest are fine"). Never tell the user to "press enter" or "leave blank" — in a chat interface they must type something to respond.

**Default priority** (highest wins): existing new config values > legacy config values > `assets/module.yaml` defaults. When legacy configs exist, read them and use matching values as defaults instead of `module.yaml` defaults. Only keys that match the current schema are carried forward — changed or removed keys are ignored.
**Default priority** (highest wins): existing new config values > legacy config values > `./assets/module.yaml` defaults. When legacy configs exist, read them and use matching values as defaults instead of `module.yaml` defaults. Only keys that match the current schema are carried forward — changed or removed keys are ignored.

**Core config** (only if no core keys exist yet): `user_name` (default: BMad), `communication_language` (default: English), `document_output_language` (default: English), `output_folder` (default: `{project-root}/_bmad-output`). Of these, `user_name` and `communication_language` are written exclusively to `config.user.yaml`. The rest go to `config.yaml` at root and are shared across all modules.
**Core config** (only if no core keys exist yet): `user_name` (default: BMad), `communication_language` and `document_output_language` (default: English — ask as a single language question, both keys get the same answer), `output_folder` (default: `{project-root}/_bmad-output`). Of these, `user_name` and `communication_language` are written exclusively to `config.user.yaml`. The rest go to `config.yaml` at root and are shared across all modules.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Please wrap long Markdown lines to avoid lint failures.

Several updated lines are very long and may fail markdown lint in CI. Please hard-wrap these paragraphs/lists into shorter lines.

Suggested wrap style
-Ask the user for values. Show defaults in brackets. Present all values together so the user can respond once with only the values they want to change (e.g. "change language to Swahili, rest are fine"). Never tell the user to "press enter" or "leave blank" — in a chat interface they must type something to respond.
+Ask the user for values. Show defaults in brackets.
+Present all values together so the user can respond once with only the values
+they want to change (e.g. "change language to Swahili, rest are fine").
+Never tell the user to "press enter" or "leave blank" — in a chat interface
+they must type something to respond.

As per coding guidelines: "**/*.md: Markdown files must pass markdown linting as part of the test suite".

Also applies to: 56-57, 72-72

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/skills/bmad-builder-setup/SKILL.md` around lines 33 - 37, Wrap long
Markdown lines in the SKILL.md sections that include the "Default priority" and
"Core config" paragraphs so they satisfy the repo's markdown-lint rules; break
the long sentences/lists into shorter lines (e.g., ~80 characters) while
preserving content and punctuation, and ensure the paragraphs that reference
keys like user_name, communication_language, document_output_language,
output_folder and the rule about legacy configs are wrapped consistently; also
apply the same hard-wrapping to the other noted long lines in the file (around
the later paragraphs) so all MD paragraphs and list items pass linting.

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.

bmad-builder-setup skill flags fresh bmb install config as legacy due to format mismatch with installer

3 participants