- Fix config restoration for zero-byte files by using `is not None`
instead of truthiness checks on `config_backup` (empty bytes `b""` is
falsy, causing silent loss of empty config.yaml files)
- Move config restore into the try/except block so mkdir/write_bytes
errors are caught and reported as structured JSON instead of tracebacks
- Add logging.error() call on failure for observability
- Replace rglob("SKILL.md") with targeted glob() calls to avoid
unnecessary deep traversal — only the two canonical installable
layouts are checked
- Add docstring note explaining why find_skill_dirs() is intentionally
stricter than the installer's recursive collectSkills()
- Add path traversal validation rejecting "..", "/", "\\" in dir names
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The cleanup-legacy.py script used an overly broad rglob("SKILL.md") that
matched template and asset files nested deep in the directory tree (e.g.
bmad-module-builder/assets/setup-skill-template/SKILL.md). This caused
cleanup to abort when it couldn't verify non-installable templates at the
skills directory.
Scopes find_skill_dirs() to only match SKILL.md at recognized installable
positions: direct children ({name}/SKILL.md) and skills subfolder
(skills/{name}/SKILL.md). Also adds config.yaml backup/restore around
shutil.rmtree() so per-module configs needed by bmad-init are preserved.
Fixes#2175
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>