Choosing versions together
A release report describes what changed. The skill assesses the change’s semantic impact on users, then produces a plan of the versions and workspace edits required to release it. This separation keeps a text diff from pretending to decide whether a consumer promise was broken.
Semantic impact and version bumps
The skill records each package’s semantic impact:
| Impact | Use |
|---|---|
breaking | A change breaks a consumer promise. |
nonbreaking | A compatible addition extends the consumer’s promises. |
patch | A correction or other change needs no stronger semantic impact. |
Consumer promises include documented behavior, CLI arguments, data formats, feature availability and build requirements, not just Rust function signatures.
The resulting numeric version bump is patch, minor or major.
The decisions document calls the semantic field impact; the proposal calls
the numeric field bump. An exact version can be supplied instead of a bump.
| Anchor version | Semantic impact | Example resulting version | Version bump |
|---|---|---|---|
1.4.0 | nonbreaking | 1.5.0 | minor |
1.4.0 | breaking | 2.0.0 | major |
0.4.2 | nonbreaking | 0.4.3 | patch |
0.4.2 | breaking | 0.5.0 | minor |
These examples start without an adequate pending increment or other group effects. Proposal generation retains an already sufficient version rather than incrementing it again.
The translation follows Cargo compatibility: the leftmost nonzero version
component defines the incompatible boundary. A 0.0.z package has no compatible
next version. Explicit target versions are also supported when numeric increments
do not express the intended release.
External API evidence is a floor
cargo-semver-checks detects
supported Rust API incompatibilities. Establish that the checker can run before
using its result: a rustdoc-format failure is not a compatibility pass.
Compare public library contracts with all features, but review feature changes manually too. Moving an existing item behind a new feature can break callers using a subset even when both all-features APIs contain the item. Removing a feature, changing observable behavior or breaking a CLI can also require a stronger decision than the checker reports.
Raise the decision when the contract requires it. Do not lower it below a valid compatibility finding.
The checker has a cross-crate limitation: re-exported dependency definitions are not fully available in its input. A public facade is the package consumers use while another package supplies its implementation. Review the definitions and behavior it exposes, including public methods on re-exported types, even when the facade’s own source is unchanged. A passing facade comparison does not replace this assessment.
Procedural macros transform consumer input into Rust code during compilation.
Their Cargo targets are unsupported for direct comparison, regardless of whether
they offer a public API. Review accepted invocations, attribute arguments, generated
APIs and trait implementations, consumer requirements, and promised behavior.
Explicit unavailable evidence supplies no minimum impact, not a default patch.
Consumer tests and source review support the decision; missing evidence is not
proof of compatibility.
Version groups
An exact local dependency such as version = "=1.4.0" joins its Git-tracked
workspace endpoints into a version group. Connected exact relationships,
in either dependency direction, make one group. There is no separate group
configuration.
Groups include normal, build and development dependencies, optional and target-specific declarations, and inherited workspace dependencies. A registry dependency, an outside-workspace path, a versionless path or an unused workspace entry does not create a group.
Use a single exact =major.minor.patch requirement. Partial exact versions,
prerelease/build suffixes and compound exact requirements do not declare valid
workspace groups.
Every member shares the resolved version. A non-publishable package
has publish = false. Its version can move for
group alignment, but its source changes receive
no semantic impact and it is never uploaded. The highest declared member
version participates in choosing the result, so alignment does not lower a member.
The running example is:
widget public library 1.4.0
exact dependency on widget_impl
widget_impl private implementation 1.4.0
widget-fixtures publish = false helper 1.4.0
exact dependency on widget_impl
widget-cli separate binary package 2.0.0
compatible dependency on widget
executable name: widget
The library, implementation and helper form one group. widget-cli does not:
compatible dependency requirements do not make exact-version groups.
Private APIs and public dependencies
A published implementation package can declare:
[package.metadata.release-plan]
private-api = true
That means its library surface serves another package rather than consumers
directly. It is not the same as publish = false. It still has released content,
version-group obligations and dependency effects.
When widget_impl changes, compatibility selection reaches the public widget
contract through their group. Re-exported items are compared where consumers use
them only to the extent supported by the checker; the author assesses their full
contract there rather than demanding compatibility for every private implementation item.
Unsupported procedural-macro members are excluded from direct comparison after group
selection, so a macro implementation change can still select the public facade.
All publishable packages remain in dependency-first semantic assessment.
A public origin is a workspace package defining types exposed by another package’s public API, directly or through another exposed owner’s API. Its incompatible version movement requires an incompatible release of the exposing package too. This is based on the version relationship, not a guess that the particular exposed types were unaffected.
Exposure comes from each package’s externally verified
allowed_external_types declarations. Defining crate names can differ from the
direct dependency through which a type is re-exported. The declarations are
followed transitively through the defining packages, reachable through normal
workspace dependencies. Sharing a third package’s origin does not make a
dependency’s own types part of the consumer’s API.
Private implementation packages do not interrupt breaking-change propagation. A re-exported type can expose another dependency through its methods without the outer library’s allow-list enumerating every such type. Following the defining implementation package’s own allow-list preserves that consumer contract. Using its whole allow-list can propagate changes from unrelated items, and exact-version groups can move an unaffected public member.
Inference or method-call ambiguity introduced by coexisting dependency versions’ trait implementations is intentionally considered nonbreaking. Implementation contributors are not tracked solely to prevent that ambiguity. Explicitly exposed trait and type identities, bounds and associated types still participate in propagation; other behavioral and API changes still require semantic assessment.
A compatible dependency-version movement imposes no type-identity break. It does not
cap the facade’s own impact at patch: a compatible new method on a re-exported type
is a nonbreaking facade capability. Conversely, changing an unexported implementation
item is not a public API change merely because it is pub inside that crate.
Assess its observable effects and let group alignment follow the semantic decisions.
This model requires a real
external-type validation gate.
Ordinary check does not verify those declarations against compiled APIs.
Requirement rewrites also matter
Workspace requirements must name the version their target declares, not merely
admit it within a broad range. An ordinary requirement such as 1.4.0 and an
exact requirement such as =1.4.0 have different grouping effects, but both need
updating when their target moves.
A dependent manifest rewrite is released content. Preview accounts for that rewrite and any binary lockfile effects, retaining sufficient pending increments. The complete release set is therefore larger than the packages whose source the author initially edited.