---
title: Stability & compatibility
description: What gwm's 1.0 SemVer promise covers, what it deliberately leaves free to change, the MSRV policy, and how deprecations are run.
navigation:
title: Stability
---
# Stability & compatibility
gwm follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html)
(`MAJOR.MINOR.PATCH`). This page is the explicit, published compatibility
contract that backs the `1.0` line: it states which surfaces are covered by
that promise (a breaking change there forces a **major** bump) and which are
deliberately left free to change in a **minor** or **patch**.
The rule of thumb: anything a *machine* parses is covered; anything a *human*
reads on screen is not.
## Covered by SemVer (breaking change → major)
These surfaces are part of the public contract. A backward-incompatible change
— renaming or removing something, or changing its type or documented meaning —
is a conscious **major** version decision.
- **CLI surface** — the subcommands, their flags, and their documented
argument shapes. Adding a subcommand or an optional flag is additive
(minor); renaming or removing one is breaking.
- **Exit codes** — the deterministic `0` / `1` / `2` contract documented per
command (e.g. `gwm doctor`'s severity-derived code). Scripts and CI jobs key
off these, so a code's meaning is frozen under this promise.
- **`--format=json` output schemas** — the JSON payloads of `gwm list`,
`gwm doctor`, `gwm path`, and `gwm status --json`, documented under
[`docs/schema/`](https://github.com/kbrdn1/gwm-cli/tree/main/docs/schema)
and pinned by `tests/contract_tests.rs`.
- **Daemon JSON-RPC 2.0 protocol** — the `list` / `doctor` / `path` /
`subscribe` methods, the `worktrees.changed` notification, and the standard
JSON-RPC error codes. A daemon `list` result is byte-identical to
`gwm list --format=json`, so the two share one `SCHEMA_VERSION`.
- **`.gwm.toml` schema** — the top-level section set
(`worktree`, `bootstrap`, `hooks`, `doctor`, `tui`, `theme`, `git_tui`,
`review`, `labels`, `milestones`, `branch_types`, `aliases`, `gitmoji`,
`issue_template`, `pr_template`, `exec`, `clean`). A renamed or removed
stable section is breaking.
### Frozen by test vs. covered by promise
Three of these surfaces are *mechanically* frozen — a rename fails CI before
it can ship: the **JSON schemas**, the **daemon method/notification names**,
and the **`.gwm.toml` section set**, all pinned by `tests/contract_tests.rs`
against the single source of truth in
[`src/contract.rs`](https://github.com/kbrdn1/gwm-cli/blob/main/src/contract.rs).
The **CLI subcommands/flags** and the **exit-code meanings** are *not* freeze-
tested end-to-end (only `doctor`'s `exit_code` field rides the JSON schema);
they are covered by this written SemVer promise and reviewed per PR. Treat
them as just as binding — the absence of a guard test is not a licence to
break them silently.
### The machine-contract detail lives elsewhere
The per-field tiers (which exact fields are **stable** vs **experimental**),
the drift-detection mechanism (`SCHEMA_VERSION` on the daemon notification,
`gwm --version` for one-shot CLI consumers), and the `additionalProperties`
rules are documented in full in
[`docs/schema/README.md`](https://github.com/kbrdn1/gwm-cli/blob/main/docs/schema/README.md).
Notably, a few fields are **experimental** and may change without a major bump
— e.g. the workspace-only `repo` field on a `list` row and the top-level
`repo` on `status --json`. When in doubt about a specific field, that tiers
table is authoritative.
## NOT covered by SemVer (may change in minor/patch)
These are free to change without a major bump. Do not build automation on top
of them.
- **TUI layout & colours** — pane arrangement, widget placement, the theme /
colour scheme, and any visual detail of the ratatui interface. Scripting
against the rendered TUI is unsupported.
- **Human-readable strings** — log lines, status-bar messages, help blurbs,
the human (non-`--format=json`) output of any command. Parse the JSON
surface instead; the prose is allowed to be reworded at any time.
- **Internal Rust API** — the `gwm-cli` crate publishes a `[lib]` target
(named `gwm`) alongside the binary, but **only as a byproduct**: the binary
and the `tests/` integration suite share one module tree, and Rust
integration tests can reach it only through a `pub` lib. That surface (~460
`pub` items across ~33 modules) is an internal test seam, **not** a public
API — it is `#![doc(hidden)]` (nothing is advertised on docs.rs) and carries
**no SemVer guarantee**. Do not `cargo add gwm-cli` to depend on `gwm::*`;
those items may change in any release. (Decision recorded for [#342]: the
library API is *disclaimed*, not gated with `cargo-semver-checks` — owning
~460 items as a frozen contract would trip a major bump on every routine
internal refactor, which is the wrong trade-off for a seam that exists to be
tested, not consumed.)
[#342]: https://github.com/kbrdn1/gwm-cli/issues/342
## MSRV policy
The Minimum Supported Rust Version is declared as `rust-version` in
[`Cargo.toml`](https://github.com/kbrdn1/gwm-cli/blob/main/Cargo.toml)
(currently **1.86**) — the floor the crate is expected to compile against.
CI builds and tests on the *stable* toolchain, so it exercises the code but
does not compile it *at* 1.86. What it does catch: the clippy job runs with
`-D warnings`, and `clippy::incompatible_msrv` is warn-by-default, so an
accidental use of a **std API** newer than the declared floor fails CI. What it
does **not** catch: a newer **language / edition** feature, or a **dependency**
raising its own floor — neither surfaces on stable. Those are verified locally
before an MSRV bump with `cargo msrv verify` (or, without rustup,
`cargo +1.86.0 build --all-targets --locked`). A dedicated `toolchain@1.86`
build/test job would close that gap for the exhaustive guarantee; it is not
wired up yet (its first run has to be confirmed green before it can gate
merges).
In practice an MSRV bump rides a **minor** release, not a major one — it has
historically been driven by a dependency raising its own floor (the `1.86`
bump came in with `tui-term` / `portable-pty` when the PTY overlay landed),
and is treated as a routine toolchain update rather than a breaking change to
the public contract. Bumps are called out in the changelog so packagers are
not surprised.
## Deprecation process
When a covered surface has to change in a backward-incompatible way:
1. **Announce** — document the deprecation in the changelog under the release
that introduces it, and (where the surface supports it) emit a runtime
warning pointing at the replacement.
2. **Keep the old path working** through the rest of the current major line —
a deprecation is a heads-up, not an immediate removal.
3. **Remove only on a major bump**, with the removal listed in that release's
notes alongside the migration path.
Additive changes (a new subcommand, an optional flag, a new optional JSON
field, a new daemon method) are **not** deprecations — they ship in a minor
release and require no warning, because existing consumers keep working
unchanged (consumers MUST ignore unknown JSON fields).
## See also
- [`docs/schema/README.md`](https://github.com/kbrdn1/gwm-cli/blob/main/docs/schema/README.md)
— the per-field stable/experimental tiers and the drift-detection contract.
- [`src/contract.rs`](https://github.com/kbrdn1/gwm-cli/blob/main/src/contract.rs)
— the single source of truth for `SCHEMA_VERSION` and the frozen
method/section sets.
- [Contributing → Releases](/development/contributing) and
[`CONTRIBUTING.md`](https://github.com/kbrdn1/gwm-cli/blob/main/CONTRIBUTING.md)
— the SemVer release process and tagging workflow.