Skip to content

Commit ac2cb5d

Browse files
authored
feat(cli): implement specify self upgrade (#2475)
* feat(cli): implement specify self upgrade * fix(cli): normalize self-upgrade prerelease tags * fix(cli): tighten self-upgrade diagnostics * fix(cli): harden self-upgrade verification parsing * fix(cli): sanitize self-check fallback tags * fix(cli): harden self-check release display * fix(cli): validate resolved upgrade tags * fix(cli): tolerate invalid install metadata * test(cli): align upgrade network mocks * fix(cli): respect relative installer paths * fix(cli): tighten upgrade failure handling * fix(cli): align installer path diagnostics * fix(cli): validate release and version output * fix(cli): clarify source checkout guidance * fix(cli): harden upgrade detection helpers * fix(cli): avoid echoing invalid release tags * fix(cli): tolerate argv path resolve failures * chore: remove self-upgrade formatting-only diffs * fix: address self-upgrade review feedback * fix: address self-upgrade review followups * fix: address self-upgrade review edge cases * fix: address self-upgrade review docs * fix: refine self-upgrade review followups * fix: address self-upgrade review cleanup * fix: handle self-upgrade review edge cases * fix: address self-upgrade review nits * fix: address follow-up self-upgrade review * fix: resolve self-upgrade review and Windows CI failures - README: promote "Optional Commands" to ### so it is a sibling of "Core Commands" under "Available Slash Commands" (consistent heading levels; avoids the h2->h4 jump a revert would create). - _version: allow --tag prerelease/dev and build-metadata suffixes to compose (e.g. v1.0.0-rc1+build.42), matching PEP 440 / semver; the Version() check still enforces canonical validity. - tests: compare resolved argv0 as Path objects instead of POSIX strings so the assertion holds on Windows; skip the relative-installer-path executable-bit tests on Windows via a new requires_posix marker (they rely on chmod/X_OK semantics and chdir-into-tmp teardown that do not hold there). Add a combined prerelease+build-metadata tag test. * fix: address second self-upgrade review round - self_check: clarify that the "up to date" branch is reached only for parseable latest tags (the unparseable case returns earlier), so the InvalidVersion fallback assumption is not reintroduced. - self_upgrade: compare target/current as Version instances directly instead of re-parsing the canonical strings through _is_newer; the empty-current case stays explicit via the not-None guard. - tests: document the intentional broad GH_/GITHUB_ env scrub with a test asserting non-credential context vars (GH_HOST, GITHUB_REPOSITORY, …) are stripped from the installer subprocess env — a deliberate fail-safe that also catches credential-adjacent names without a recognized suffix. * fix: address third self-upgrade review round - self_upgrade: unify the no-op short-circuits on packaging Version equality instead of canonical-string equality. Version("1.0") equals Version("1.0.0") but their str() forms differ, so the old check could misreport an equal install as "already on latest release or newer". Both the unpinned and pinned branches now use Version comparison. - self_upgrade: compare the verified version as a parsed Version against the target so a non-version verifier result is a mismatch (exit 2) rather than a coincidental canonical-string match. - resolver: map HTTP 429 (Too Many Requests / secondary rate limit) to the rate-limited category so users get the same actionable token hint as 403. - _is_github_credential_env_key: document the precise (intentionally broad) scrub matching contract in the docstring. - tests: add a trailing-zero Version-equality regression test and a parametrized HTTP-status categorization test (429 -> rate limited; 404/502 -> verbatim). * fix: address fourth self-upgrade review round - self_upgrade: label a pinned target older than the installed version as "Downgrading" rather than "Upgrading" so `--tag <older>` is not mistaken for a forward upgrade. - resolver: drop the unused `typing.Optional` import and annotate the `--tag` option as `str | None`, consistent with the rest of the module (verified Typer resolves it on the supported Python versions). - _is_github_credential_env_key: add `_PASSWORD` and `_CREDENTIALS` to the recognized credential suffixes and document that only these shapes are scrubbed (not blanket coverage). - tests: assert the precise exit code (1) for the re-raised transient OSError path; skip the InvalidMetadataError test on Pythons where the real exception is absent instead of fabricating it; update the pinned downgrade test to expect the "Downgrading" label. * fix: accept uppercase V prefix in --tag Fold a leading uppercase `V` (a common paste) to the canonical lowercase `v` before validating `--tag`. The remainder of the tag stays case-sensitive on purpose: the validated value is used verbatim as a git ref, which is case-sensitive on GitHub, so rewriting label/build-metadata casing could point at a tag that does not exist. Adds a normalization test.
1 parent 1732b9b commit ac2cb5d

12 files changed

Lines changed: 3830 additions & 121 deletions

README.md

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,24 @@ specify init my-project --integration copilot
5959
cd my-project
6060
```
6161

62+
To check for updates or upgrade the installed CLI, use the self-management commands. See the [Upgrade Guide](./docs/upgrade.md) for detailed scenarios and customization options.
63+
64+
```bash
65+
# Check whether a newer release is available (read-only — does not modify anything)
66+
specify self check
67+
68+
# Preview what would run, without actually upgrading
69+
specify self upgrade --dry-run
70+
71+
# Upgrade in place to the latest stable release (auto-detects uv tool vs pipx install)
72+
specify self upgrade
73+
74+
# Or pin a specific release tag (replace vX.Y.Z[suffix] with your desired release tag)
75+
specify self upgrade --tag vX.Y.Z[suffix]
76+
```
77+
78+
Bare `specify self upgrade` executes immediately, matching the no-prompt behavior of commands like `pip install -U` and `npm update`. For `uv tool` installs, it runs `uv tool install specify-cli --force --from <git ref>` under the hood so pinned release tags work, including dev, alpha/beta/rc, or build metadata suffixes. `uvx` (ephemeral) runs and source checkouts are detected and produce path-specific guidance instead of running an installer. Set `SPECIFY_UPGRADE_TIMEOUT_SECS` to cap how long the installer subprocess may run (default: no timeout — interrupt with `Ctrl+C` if needed).
79+
6280
### 3. Establish project principles
6381

6482
Launch your coding agent in the project directory. Most agents expose spec-kit as `/speckit.*` slash commands; Codex CLI in skills mode uses `$speckit-*` instead.
@@ -133,7 +151,7 @@ Run `specify integration list` to see all available integrations in your install
133151

134152
After running `specify init`, your AI coding agent will have access to these slash commands for structured development. For integrations that support skills mode, passing `--integration <agent> --integration-options="--skills"` installs agent skills instead of slash-command prompt files.
135153

136-
#### Core Commands
154+
### Core Commands
137155

138156
Essential commands for the Spec-Driven Development workflow:
139157

@@ -146,7 +164,7 @@ Essential commands for the Spec-Driven Development workflow:
146164
| `/speckit.taskstoissues` | `speckit-taskstoissues`| Convert generated task lists into GitHub issues for tracking and execution |
147165
| `/speckit.implement` | `speckit-implement` | Execute all tasks to build the feature according to the plan |
148166

149-
#### Optional Commands
167+
### Optional Commands
150168

151169
Additional commands for enhanced quality and validation:
152170

docs/installation.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ specify version
8888

8989
This helps verify you are running the official Spec Kit build from GitHub, not an unrelated package with the same name.
9090

91+
**Stay current:** Run `specify self check` periodically to learn whether a newer release is available — it is read-only and never modifies your installation. When you are ready to upgrade, follow the [Upgrade Guide](./upgrade.md).
92+
9193
After initialization, you should see the following commands available in your coding agent:
9294

9395
- `/speckit.specify` - Create specifications

docs/upgrade.md

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@
88

99
| What to Upgrade | Command | When to Use |
1010
|----------------|---------|-------------|
11-
| **CLI Tool Only** | `uv tool install specify-cli --force --from git+https://github.com/github/spec-kit.git@vX.Y.Z` | Get latest CLI features without touching project files |
12-
| **CLI Tool Only (pipx)** | `pipx install --force git+https://github.com/github/spec-kit.git@vX.Y.Z` | Reinstall/upgrade a pipx-installed CLI to a specific release |
11+
| **CLI Tool (recommended)** | `specify self upgrade` | Latest stable release, in place. Auto-detects whether you installed via `uv tool` or `pipx`. |
12+
| **CLI Tool — pin a version** | `specify self upgrade --tag vX.Y.Z[suffix]` | Upgrade to a specific release tag instead of the latest stable. Suffixes are limited to dev, alpha/beta/rc, and/or build metadata forms. |
13+
| **CLI Tool — manual fallback** | `uv tool install specify-cli --force --from git+https://github.com/github/spec-kit.git@vX.Y.Z` | When `specify self upgrade` isn't available (older installs) or when you want explicit control. |
14+
| **CLI Tool — manual fallback (pipx)** | `pipx install --force git+https://github.com/github/spec-kit.git@vX.Y.Z` | Same as above, for pipx installs. |
1315
| **Project Files** | `specify init --here --force --integration <your-agent>` | Update slash commands, templates, and scripts in your project |
1416
| **Both** | Run CLI upgrade, then project update | Recommended for major version updates |
1517

@@ -19,12 +21,32 @@
1921

2022
The CLI tool (`specify`) is separate from your project files. Upgrade it to get the latest features and bug fixes.
2123

22-
Before upgrading, you can check whether a newer released version is available:
24+
### Recommended: `specify self upgrade`
25+
26+
The CLI ships with two self-management commands that handle the common case automatically:
2327

2428
```bash
29+
# Check whether a newer release is available (read-only — does not modify anything)
2530
specify self check
31+
32+
# Preview what would run, without actually upgrading
33+
specify self upgrade --dry-run
34+
35+
# Upgrade in place to the latest stable release (auto-detects uv tool vs pipx install)
36+
specify self upgrade
37+
38+
# Or pin a specific release tag (replace vX.Y.Z[suffix] with the tag you want)
39+
specify self upgrade --tag vX.Y.Z[suffix]
2640
```
2741

42+
Bare `specify self upgrade` executes immediately, matching the no-prompt behavior of commands like `pip install -U` and `npm update`. The CLI classifies your runtime into one of: `uv tool`, `pipx`, `uvx (ephemeral)`, source checkout, or unsupported. Only `uv tool` and `pipx` are upgraded automatically; for `uv tool` installs, it runs `uv tool install specify-cli --force --from <git ref>` under the hood so pinned release tags work. The other paths print path-specific guidance and exit 0 without touching anything.
43+
44+
Pinned tags must start with `vMAJOR.MINOR.PATCH`. Optional suffixes are limited to dev, alpha/beta/rc, and/or build metadata forms such as `v1.0.0-rc1`, `v0.8.0.dev0`, `v0.8.0+build.42`, or the combination `v1.0.0-rc1+build.42`; branch names, hash refs, `latest`, and bare versions without `v` are rejected.
45+
46+
Set `SPECIFY_UPGRADE_TIMEOUT_SECS` to cap how long the installer subprocess may run (default: no timeout — interrupt with `Ctrl+C` if needed). If that internal timeout fires, `specify self upgrade` exits 124 and reports that it timed out while waiting for the installer subprocess, including the configured timeout and manual retry command. A real installer exit code 124 is propagated with `Upgrade failed. Installer exit code: 124.`, so scripts should treat exit 124 as ambiguous and inspect the message when they need to distinguish the two cases.
47+
48+
If your installed CLI is older than the release that introduced `specify self upgrade`, use the manual equivalents below. These commands are also useful when you want explicit control over the installer command.
49+
2850
### If you installed with `uv tool install`
2951

3052
Upgrade to a specific release (check [Releases](https://github.com/github/spec-kit/releases) for the latest tag):
@@ -54,10 +76,14 @@ pipx install --force git+https://github.com/github/spec-kit.git@vX.Y.Z
5476
### Verify the upgrade
5577

5678
```bash
79+
# Confirms the CLI is working and shows installed tools
5780
specify check
81+
82+
# Confirms the installed version against the latest GitHub release
83+
specify self check
5884
```
5985

60-
This shows installed tools and confirms the CLI is working. Use `specify version` to confirm which persistent CLI version is currently on your `PATH`.
86+
`specify check` shows the surrounding tool environment; `specify self check` is read-only and tells you whether you're now on the latest release (`Up to date: X.Y.Z`) or if a newer one became available between releases.
6187

6288
---
6389

@@ -186,8 +212,8 @@ Restart your IDE to refresh the command list.
186212
### Scenario 1: "I just want new slash commands"
187213

188214
```bash
189-
# Upgrade CLI (if using persistent install)
190-
uv tool install specify-cli --force --from git+https://github.com/github/spec-kit.git
215+
# Upgrade CLI (auto-detects uv tool vs pipx install)
216+
specify self upgrade
191217

192218
# Update project files to get new commands
193219
specify init --here --force --integration copilot
@@ -204,7 +230,7 @@ cp .specify/memory/constitution.md /tmp/constitution-backup.md
204230
cp -r .specify/templates /tmp/templates-backup
205231

206232
# 2. Upgrade CLI
207-
uv tool install specify-cli --force --from git+https://github.com/github/spec-kit.git
233+
specify self upgrade
208234

209235
# 3. Update project
210236
specify init --here --force --integration copilot
@@ -388,15 +414,19 @@ Only Spec Kit infrastructure files:
388414

389415
### "CLI upgrade doesn't seem to work"
390416

391-
If a command behaves like an older Spec Kit version, first check for local CLI drift:
417+
If a command behaves like an older Spec Kit version, first ask the CLI itself:
392418

393419
```bash
420+
# Read-only — prints "Up to date: X.Y.Z" or "Update available: X.Y.Z → vY.Z.W"
394421
specify self check
422+
423+
# Preview the install method, current version, and target tag the upgrade would use
424+
specify self upgrade --dry-run
395425
```
396426

397427
`specify check` is an offline environment scan; `specify self check` is the CLI version lookup.
398428

399-
Verify the installation:
429+
If `self check` shows the wrong version, verify the installation:
400430

401431
```bash
402432
# Check installed tools

0 commit comments

Comments
 (0)