The update check used a `beta` dist-tag that doesn't exist on this
package — it only publishes `latest` and `next`. Substring matching
on `Beta/beta/alpha/rc` also missed the actual prerelease format
(`-next.N`).
Switch to `semver.prerelease()` for detection, query `@next` for
prerelease users, and check both `@next` and `@latest` so a user
stuck on a stale `@next` is still steered to the higher version.
Refs #2317
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(installer): restructure installer with clean separation of concerns
Move tools/cli/ to tools/installer/ with major structural cleanup:
- InstallPaths async factory for path resolution and directory creation
- Config value object (frozen) replaces mutable config bag
- ExistingInstall value object replaces stateful Detector class
- OfficialModules + CustomModules + ExternalModuleManager replace monolithic ModuleManager
- install() is prompt-free; all user interaction in ui.js
- Update state returned explicitly instead of mutating customConfig
- Delete dead code: dependency-resolver, _base-ide, IdeConfigManager,
platform-codes helpers, npx wrapper, xml-utils
- Flatten directory structure: custom/handler → custom-handler,
tools/cli/ → tools/installer/, lib/ directories removed
- Update all path references in package.json, tests, CI, and docs
* fix(installer): guard ExistingInstall.version and surface module.yaml errors
Guard ExistingInstall.version access with .installed check in
uninstall.js, ui.js, and installer.js to prevent throwing on
empty/partial _bmad dirs. Surface invalid module.yaml parse errors
as warnings instead of silently returning empty results.