usm/upgrade [built]
usm upgrade — detect stale USM projects and guide users through adopting new optional capabilities (like the feedback policy) via a self-describing capability registry. Compares the installed USM version against system.usm.version, reports missing/recommended capabilities, offers interactive or default setup, and bumps the project version on completion.
Status: built
Intent
There is no upgrade path today. Existing users who npm update USM get new code, but their system.usm is untouched — new optional blocks (feedback, and future ones) are silently absent, generate omits the corresponding output, and the user gets no signal. The only version check is a passive $version warning in validate. This feature adds a capability registry where each optional block declares how to detect and set itself up, plus an usm upgrade command that orchestrates detection, guided setup, and version bumping — so adoption of new features is discovered and guided, not silent.
Decisions
capability-registry-pattern [accepted]
Decision: Capabilities self-describe (detect + setup + introducedIn); upgrade orchestrates without hardcoding.
Rationale: Adding a future capability means one registry entry, zero changes to upgrade. Same extensibility principle as the generators target list.
usm-version-field-for-alignment [accepted]
Decision: Use system.usm.usm_version for USM-tool alignment, NOT the project's own version field.
Rationale: The version field tracks the consuming project's own release (e.g. 5.2.0). Repurposing it would miscompare against the USM tool version. A dedicated usm_version field keeps project version and tool alignment separate and unambiguous.
Alternatives considered:
- Reuse the existing version field for alignment — rejected: Overwrites the consuming project's own release version; a project at 5.2.0 would be misread as 'ahead of' USM 0.1.0 and never offered upgrades.
- Use $version (schema format) for alignment — rejected: Conflates schema-format changes with feature additions; additive schema changes (like feedback) intentionally do not bump $version.
schema-version-independence [accepted]
Decision: Schema $version moves independently of the package version: additive schema changes do NOT bump $version (still v1); breaking changes bump $version + CURRENT_SCHEMA_VERSION together and ship a migration in usm upgrade.
Rationale: Decouples the file format from the tool release cadence. A 0.2.0 release may still be schema v1. Additive changes (new optional fields) are backward compatible and must not force every consumer to migrate.
setup-owned-by-capability [accepted]
Decision: Each registry entry owns its own setup function (interactive + default); upgrade just calls it.
Rationale: Avoids subprocess fragility (no need for the binary on PATH) and keeps each capability's setup logic co-located with its detection.
interactive-with-flag-fallback [accepted]
Decision: TTY prompts per capability; --apply uses defaults for CI/scripts; --check is report-only.
Rationale: Composes with existing per-feature setup commands (e.g. usm feedback) while supporting non-interactive and CI use cases.
non-destructive [accepted]
Decision: Upgrade only adds missing blocks and bumps usm_version; existing config is never overwritten.
Rationale: detect() returns true for configured capabilities → they are skipped. Safe to re-run idempotently.
Flows
Detect project version and missing capabilities (upgrade-detect)
Read the installed USM version and the project's system.usm.version, walk the capability registry, and produce a status report.
- read → installed USM version from package.json (resolved relative to the compiled module)
- read → project version from system.usm.version (absent = 0.0.0)
- compare → semver compare — stale if project < installed
- walk → capability registry — call detect(system) for each entry
- report → list missing (with recommended flag + setup hint), up-to-date, and version status
Set up missing capabilities (upgrade-setup)
For each missing capability, either prompt interactively (TTY) or apply defaults (--apply), calling the capability's own setup function.
- branch → interactive (TTY) vs --apply (defaults) vs --check (report only)
- prompt → for each missing recommended capability, ask whether to set it up
- setup → call capability.setup(systemPath, { interactive }) — e.g. feedback writes its policy block
- skip → capabilities where detect() returns true are never re-set-up
Bump project version after applying (upgrade-bump)
After capabilities are applied, set system.usm.version to the installed version, validate, and suggest regenerating outputs.
- write → system.usm.version = installed version
- validate → system.usm still passes schema validation
- suggest → run 'usm generate' to refresh rules files and docs with the new capabilities
Contracts
version-compared
Upgrade reads the installed package.json version and system.usm.version and reports stale or up-to-date.
Acceptance criteria:
- [ ] Installed version resolved from the USM package.json (not the project's)
- [ ] Absent system.usm.version treated as 0.0.0 (stale)
- [ ] Stale vs up-to-date clearly reported
capabilities-detected
Every registry capability is checked; missing and recommended ones are reported with a setup hint.
Acceptance criteria:
- [ ] All registry entries run through detect()
- [ ] Missing recommended capabilities surfaced prominently
- [ ] Each missing capability shows its setup hint
setup-non-destructive
Setup never overwrites an existing block.
Acceptance criteria:
- [ ] detect() returns true → capability skipped entirely
- [ ] No existing config field is overwritten
version-bumped-on-completion
After a successful apply, system.usm.version is set to the installed version.
Acceptance criteria:
- [ ] version field written only after setup succeeds
- [ ] Resulting system.usm validates against the schema
non-interactive-safe
Non-interactive modes work without prompts and are CI-safe.
Acceptance criteria:
- [ ] --apply runs all recommended missing capabilities with defaults, no prompts
- [ ] --check reports only and exits non-zero if stale
- [ ] Non-TTY defaults to report-only (no hanging on stdin)
Tests
stale-project-detected
Given:
- system_version_absent: true
Then:
- assertion: upgrade reports project as stale
- assertion: exit code non-zero under --check
up-to-date-noop
Given:
- system_version_equals_installed: true
- all_capabilities_configured: true
Then:
- assertion: upgrade reports nothing to do
- assertion: no files modified
missing-capability-reported
Given:
- system_has_no_feedback_block: true
Then:
- assertion: feedback listed as missing
- assertion: setup hint shown
existing-capability-skipped
Given:
- system_has_feedback_block: true
Then:
- assertion: feedback not offered for setup
apply-uses-defaults
Given:
- system_has_no_feedback_block: true
- flag_apply: true
Then:
- assertion: feedback block written with default human-gate policy
- assertion: no prompt blocks on stdin
version-bumped-after-apply
Given:
- apply_succeeds: true
Then:
- assertion: system.usm.version equals installed version
- assertion: system.usm validates
Implementation
- Primary: src/scan/upgrade.ts; src/scan/capabilities.ts; src/cli/index.ts
- Test code status: none
See Also
- usm/agent-feedback
- usm/cli-init
- usm/cli-check
- usm/cli-validate