Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Why cargo-release-plan?

The cargo-release-plan toolkit automates release preparation and publication for Rust workspaces. It includes the cargo-release-plan command-line application, the cargo-release-plan-action reusable GitHub workflows, and the increment-versions agent skill. They are designed to be used together and are documented together in this book.

The goals are:

  • Every merged pull request immediately publishes its changed packages to crates.io.
  • Fully automated version-number increments that obey Semantic Versioning.
  • Reusable GitHub workflows for easy integration.
  • No stored secrets required for publishing.
  • Workspaces containing any number of library or binary packages.
  • cargo-binstall support for fast installation of binary packages.
  • Advanced workspace structures, including published packages with private APIs.

The skill prepares version changes as part of the pull request. Review approves the code and versions together; merging starts publication without another version-selection step. The normal workflow uses squash merges so the PR’s final content and versions enter release history together.

For version changes without publishing integration, use standalone planning. That scenario ends with the version-increment PR being reviewed and merged; publishing is not part of it.

Who this fits

The supported publication path is a Cargo workspace releasing to crates.io and GitHub. Libraries receive package tags; packages with an installable binary also receive GitHub releases and native ZIP archives usable by cargo-binstall.

Binary publication supports one executable per package, built with default features on supported native targets. Alternative registries or forges, cross-compilation, changelog generation and arbitrary release-policy hooks are outside this process.

Responsibilities

ParticipantResponsibility
Author or authoring agentUnderstand consumer promises and choose breaking, nonbreaking or patch semantic impacts.
cargo-release-planCollect release evidence, enforce version relationships, resolve and apply plans, and reconcile publication.
API compatibility checker (cargo-semver-checks)Detect supported Rust API changes; its result is evidence, not a complete behavioral assessment.
increment-versions skillGuide an agent through the tool’s planning operations and explain its decisions.
Reviewer and merge policyApprove the source and version changes together and require appropriate checks.
CargoResolve dependencies when requested, construct and verify package archives, and order registry uploads.
GitHub Action and workflowsInstall pinned tools, supply permissions, run jobs and transport evidence and outcomes.

Completing a plan or running the skill grants neither merge authority nor permission to publish. Publication starts from the reviewed, merged source under the repository’s release policy.

From change to delivery

flowchart TD
    A["Develop changes"] --> B["Execute increment-versions skill"]
    B --> C["Review the pull request, including its versions"]
    C --> D["Pull request is squash-merged"]
    D --> E["Reusable workflows publish the release"]
    E --> F["Packages appear on crates.io"]
    F --> G["Binary packages gain cargo-binstall archives"]

The chapters first explain the model, then walk through repository setup and local/GitHub integration. The operations chapters cover ordinary releases, verification and recovery. The reference distinguishes literal configuration and decision formats from conceptual examples and opaque tool-generated evidence.

This book is published at folo-rs.github.io/folo/cargo-release-plan/. For an older pinned installation, use the matching documentation and skill revision, not an assumption that the current website describes every older executable.

Which release are we changing?

Before choosing a new version, the skill needs to know which package version and content this pull request follows. The answer can differ between packages, and between an ordinary PR and a PR stacked on an unmerged parent.

Package anchors retain unfinished release work

The release branch is the branch whose merged versions are published. Release history is that branch up to the commit selected for an assessment.

A package’s anchor supplies the preceding version and source for its comparison. In actual release history, it is the latest first-parent commit that changed the package’s version. The anchor version means exactly the version declared by that package in the anchor commit.

For example, widget reached 1.4.0 in commit B:

release history

A ---- B ---- C ---- D
       |           |
       |           +-- unrelated documentation change
       +-- widget 1.4.0

The documentation change does not create a new widget release. Its anchor is still B. If commit C instead changed widget without increasing its version, that work must still be included in the next release. Comparing only with D would hide it.

This is why the tool retains anchors rather than simply comparing every package with the release branch’s latest tree: it can catch up after migration or manual changes that omitted version planning.

A stacked PR follows its parent’s final result

The merge target is the commit a PR intends to merge into. In a stack, that can be an unmerged parent PR.

Suppose the parent changes widget to 1.5.0, then receives another fix before review is complete. All of the parent’s final content belongs to its 1.5.0 release. The child must compare with that final snapshot, not the parent’s intermediate version-edit commit.

release history: widget 1.4.0
    |
    +-- parent PR: widget 1.5.0, including all final fixes
            |
            +-- child PR: further changes need a version above 1.5.0

For a new or incremented parent package, the parent’s final commit acts as the child’s anticipated anchor. Packages still at their release-history version keep their historical anchors and any catch-up work.

The managed workflow uses squash merges so each PR’s final version and content enter release history in one commit. The anticipated parent then becomes the actual preceding release. The child does not depend on a merge queue combining the PRs, and it cannot reuse the parent’s version merely because the parent has not merged yet.

What the package status means

The report uses the package’s anchor version and content to distinguish:

StatusMeaning
needs-incrementReleased content changed, but the version did not advance above the anchor version.
pending-releaseThis PR already has a higher version, or introduces the package.
unchangedThe package still matches its anchor’s content and version.

A pending increment is kept when it covers the full change. If later edits make the impact stronger, the skill raises it. Merely rerunning the skill is not a reason for another increment.

Packages with publish = false are not publication requests, but their versions can still participate in version groups.

History movement changes the question

If another PR merges a version that this PR planned to use, that version now belongs to different content. Likewise, a changed parent can alter what a child must be compared with. The skill records one history/target pair for the assessment and refreshes it before applying the plan.

When either input moves, the skill reassesses instead of attaching old results to the new history. Recovery describes that process.

Recorded versions do not prove delivery

After widget 1.5.0 merges, its source can be unchanged against its new anchor even if its registry upload failed. Package status describes version readiness, not whether crates.io, a GitHub tag or a binary archive exists.

Publication checks those destinations separately and completes missing work. That distinction makes a failed upload recoverable without inventing another version solely to retry it.

Released content

Released content is the content relevant to consumers of a Cargo package. The assessment follows the files Cargo would package and the dependency resolution an installable binary would deliver. It is not a filter over “production-looking” directory names.

Packaged files

Git-tracked files participate according to each package’s include and exclude rules. A path selected at either the anchor or the work tree counts, so deleted files and files removed from an allow-list remain visible. A package is identified by its name even when its directory moves.

Uncommitted changes to tracked files are assessed. Untracked files are reported as advisory evidence; add intended release inputs to Git before relying on their assessment. A nested package boundary keeps its files out of the enclosing package’s content.

The executable bit is released content as well as file bytes. Git’s file-mode handling allows the comparison to remain meaningful on filesystems without Unix executable permissions.

Cargo.toml is packaged content. Even a formatting-only change to a package manifest requires assessment; the tool does not maintain a second policy that decides which lines of that file are important.

Choose archive contents deliberately

An include allow-list makes the consumer build explicit and keeps new development-only files from entering the archive automatically. For example:

[package]
include = ["src/**/*"]

This is a manifest fragment, not a complete package. Add any build script and other files required by the consumer build. If source embeds a file outside src at compile time, the archive must contain it, including when that code is behind a published feature.

An inherited include value replaces rather than extends the workspace value. A package with additional compile-time inputs needs a complete list of its own. Tests, benchmarks and books can remain repository-only when consumers do not need them. Cargo’s actual rules, not this organizational recommendation, determine what the tool assesses.

Changing an allow-list changes the package manifest and can change the archive; it is itself a released-content change.

Inputs Cargo adds

Cargo packages its manifest and a generated lockfile. It also includes declared README and license files outside ordinary path rules, including inherited files or resources outside the package directory. Without an explicit README setting, Cargo detects a default README; readme = false opts out.

These inputs can affect a package even when its include list names only src. A package-root target directory is not packaged.

To audit the model against Cargo, use the explicit --verify-packaging probe. It is not part of the normal offline check.

Inherited workspace values

A package’s published manifest contains the values it inherits from [workspace.package] and [workspace.dependencies]. For example, changing an inherited Rust version or dependency requirement affects every package that inherits that value, without needing an edit under each package directory.

Attribution is per package, not a blanket release of the whole workspace. Workspace lint configuration does not participate in this released-content model. Path-only development dependencies are omitted by Cargo when packaging; adding a version requirement changes that boundary.

Binary locked dependencies

Library consumers resolve dependencies in their own graph. A library-only package therefore does not gain a release reason merely because Cargo puts a lockfile in its archive.

An installable binary is different: cargo install --locked uses its packaged resolution. A package containing a binary, including a mixed library/binary package, releases its locked dependency closure: the dependencies reachable for installation, identified by name, version and source.

The tool compares a package-rooted closure from the workspace lockfile, not raw workspace lockfile bytes. It is conservative: registry dependencies retain their workspace-unified locked edges, so packaging can prune optional edges that this assessment includes. It does not perform separate package/feature resolution during read-only classification. Normal and build dependencies across target platforms participate; development-only workspace edges do not. Examples, benchmarks, tests and build scripts do not turn a library into an installable binary.

Consequently:

  • An unrelated lockfile change need not release every binary.
  • A transitive binary dependency change can require a release with no source diff in that binary package.
  • Libraries still need consistent lockfiles for locked workspace commands even when lockfile changes are not release reasons.

Source identity matters: equal dependency names and versions do not make different registries or Git sources interchangeable. Unresolvable identity is an assessment error, not evidence of unchanged content.

During publication, Cargo can normalize the archive lockfile and prune inactive dependency branches. It must not select a dependency identity outside the installation closure that was assessed before merge.

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:

ImpactUse
breakingA change breaks a consumer promise.
nonbreakingA compatible addition extends the consumer’s promises.
patchA 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 versionSemantic impactExample resulting versionVersion bump
1.4.0nonbreaking1.5.0minor
1.4.0breaking2.0.0major
0.4.2nonbreaking0.4.3patch
0.4.2breaking0.5.0minor

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.

From changes to a version plan

The skill must answer two different questions: what changed, and what versions those changes require. A diff can answer the first question, but it cannot determine whether a behavioral change breaks a promise to users.

Facts first, then semantic impact

Keep command inputs stable

Keep assessed source, configuration and history unchanged while a command runs, apart from its explicit editing or dependency-resolution steps. Tools invoked for read-only work must follow the same requirement. Build output outside assessed inputs is permitted, as are Git object additions and index stat refreshes that leave semantic source and staged identities unchanged.

Each command acquires current inputs. It can reuse those observations during read-only work, but acquires new ones after its own writes or resolution and for new or relocated workspaces. Run source-mutating commands against the same workspace separately. This requirement does not freeze remote registry or publication state.

Assess the acquired facts

A release report lists each package’s changed released content, declared version and workspace dependencies. File patches explain source changes; structured entries explain inherited manifest values and binary dependency changes. Together with API comparison results, these are the facts used to choose versions.

A semantic impact describes what a package’s change means to its users: breaking, nonbreaking or patch. The skill chooses it from the report, API comparisons and the package’s behavioral contracts. Interpreting those facts and choosing the impacts is the release assessment.

For example, removing a public method is a breaking impact. Fixing a computation without withdrawing a promise is usually a patch impact. A Rust API checker can detect the removed method, but deciding whether the computation is a correction still requires understanding the promised behavior.

Preview makes the complete release visible

The skill turns the selected impacts into a version proposal. A proposal starts with the changed packages, but a workspace release can affect more than those packages:

  • Members of a version group must move together.
  • Dependencies must name the new versions.
  • A binary can need a release because its locked dependencies changed.

Preview computes these effects and produces a resolved plan: the complete package versions and exact manifest/lockfile edits to apply. Group expansion is part of that operation, not a separate user step.

Consider a compatible API addition in widget:

PackageStarting versionResultWhy
widget1.4.01.5.0A compatible API addition.
widget_impl1.4.01.5.0It shares widget’s version group.
widget-cli2.0.02.0.1Its dependency and locked binary inputs change.

The proposal begins with the API decision. Preview makes the complete set visible so the skill can assess the dependent changes too. A required dependent release is a minimum obligation, not a claim that every such change is harmless.

Apply the result that was reviewed

Suppose preview records widget at 1.5.0 and a particular dependency resolution. Application must install those exact edits, not run resolution again and silently choose a newer dependency. The resolved plan therefore also records the original inputs that those edits may replace.

If a manifest or source file changes after preview, the skill prepares a fresh plan. Editing the old generated plan by hand would hide which inputs were assessed. Applying a valid plan twice is a no-op the second time; a partially edited tree is not automatically treated as that completed result.

The same source binding matters for API checks. Comparisons before application use the preview’s retained workspace, where the proposed versions and lockfile are already present. Comparing an unrelated checkout would say nothing about the release described by the plan.

Planning files are not publishing instructions

The skill preserves its reports, comparisons and plan while preparing the PR. After merge, the publisher reads the approved versions from the merged source; it does not need the agent’s local working files.

Publication has a separate request that survives retries. That is the purpose of the publication manifest.

Deliver the release that was requested

A merged version change is only the start of delivery. The crate upload might succeed while a tag or one platform’s binary archive fails. Publication must be able to finish that release later, even if another PR has already merged.

That is why the publisher separates what was requested from what completed.

One request survives every retry

A publication manifest records the merged source commit, destination and exact package versions to deliver. It does not choose versions or contain mutable completed flags. It is the stable request shared by the publishing phases.

For example, a run requests widget-cli 2.0.1. Its crate reaches crates.io, but a binary build fails. While the prerequisite is repaired, 2.0.2 merges. Retrying the original manifest still completes 2.0.1; reading the current branch instead would silently change the request.

flowchart LR
    A["Merged source: widget-cli 2.0.1"] --> B["Publication manifest"]
    B --> C["Upload crate"]
    C --> D["Create package tag and release"]
    D --> E["Deliver binary archives"]
    E --> F["Complete release"]
    E -. "Retry missing work with the same manifest" .-> B

Each phase observes the actual destination and skips work already complete. Existing registry versions and tags are not rewritten to make a retry pass. Changing the requested source or versions requires a new manifest.

Outcomes explain partial completion

A publication outcome records what happened during one phase attempt. It is kept separately from the manifest so a later attempt does not erase the original request or earlier diagnostics.

The final report combines these outcomes with the current workflow job results. An old successful artifact cannot hide a newly failed job, and an unreadable destination is not treated as empty. This gives the operator a specific missing step to repair rather than a misleading overall success.

Tags fix the source of binary builds

The publication source is the merged commit that requested the release. A package tag identifies the source used to build that package’s binary archives. It may name a later release-branch commit only when the package’s version and released content are unchanged there.

Tags use {package}-v{version} and are never moved. Binaries build from the commit the actual tag resolves to, not whichever checkout happens to be current.

This matters when repairing an old release: rebuilding from today’s branch could put different code inside an archive still labelled 2.0.1.

Platform batches organize missing binary work

A platform batch lists the incomplete binary releases for one native target, with their executable names and tag commits. It lets a runner reuse compatible Cargo artifacts while building each package separately with its own default feature selection.

For example, a release may already have its Windows archive but still lack Linux assets. Reconciliation creates only the required remaining work. A target is complete when both its ZIP and checksum are present; an incomplete pair is replaced together so a new checksum cannot accidentally describe an old archive.

Package and executable names can differ:

package:     widget-cli 2.0.1
executable:  widget
tag:         widget-cli-v2.0.1
ZIP member:  widget.exe on Windows

The artifact reference describes the transported manifest, batch and outcome fields. Consumers use tool-produced artifacts rather than constructing them manually.

A failed phase is not a rollback

Successful uploads remain valid when a later step fails. Cargo verifies package archives and orders missing uploads; per-upload OIDC credentials avoid relying on a stored publishing secret.

If another version reaches the release branch before an older request can create its tag, that request may need an operator to create the exact missing tag at its recorded source. Independent releases can continue, but the incomplete workflow remains failed. Recovery explains how to finish the original request without relabelling newer content.

Install and inspect

Adoption starts with a known tool interface and a repository whose release history is understood. You do not need to clone the tool’s source repository.

Select a published version

Choose a tested published action revision whose tool supports the schemas documented in this book and copied skill. Check the selected action release and the crate’s published versions. Confirm that the exact package and its promised native archives are published before adoption. Source-mode tests are not evidence of published availability, and a missing release is not a reason to substitute an older incompatible tool.

The command examples target the tested PowerShell 7.6 support baseline, independently of the application’s CLI portability. This is a support policy, not a claim that every example uses a newly introduced language feature. Multi-step procedures explicitly enable terminating native-command errors; the PowerShell version alone does not enable that behavior. Join-Path keeps filesystem arguments native on Windows, Linux and macOS. Use one installation method:

$CrpVersion = "<published-version>"
cargo binstall "cargo-release-plan@$CrpVersion" --locked

Or install the published source with its lockfile:

$CrpVersion = "<published-version>"
cargo install cargo-release-plan --version "=$CrpVersion" --locked

Replace <published-version> with the exact tool version selected by that action. Binstall uses a prebuilt executable where available and can fall back to source. A successful fallback is useful installation behavior, but is not proof that a promised release archive exists.

Verify the executable you will actually invoke:

$ErrorActionPreference = "Stop"
$PSNativeCommandUseErrorActionPreference = $true
cargo release-plan version
cargo release-plan --help

version prints the application’s version and supported schema revisions as JSON, without a Cargo workspace or Git repository. The short --version identity remains available for installation probes.

The matching skill uses report/plan/prepared schema 8, semantic-decision schema 2, compatibility schema 3, and release-context schema 2. Exact skill/tool package version synchronization is unnecessary; check those schemas. If unexpected CLI errors suggest a mismatch, consider upgrading both tool and skill.

Separate the toolchains

Installing the application from source requires a compiler supported by that application release. Your consumer repository may use a different compiler. Select the installation toolchain explicitly when a local rust-toolchain.toml would otherwise select an older one.

Source installation also needs a native C/C++ build toolchain and CMake for the application’s dependencies. Installing a matching prebuilt binary avoids that compilation requirement.

Registry upload operations require Cargo 1.95 or later for the workspace publication behavior used by the tool. This runtime requirement is distinct from the compiler needed to build the application. Source verification and tagged binary builds must also satisfy the source’s own toolchain and native build requirements.

The published action pins the application and API compatibility checker versions and prepares their installation separately from consumer builds. Binary sources continue to supply their own toolchain, Cargo configuration and lockfile.

Native binary publication uses Git, Cargo, rustup and the GitHub CLI. ZIP creation and SHA-256 hashing are included in the application; no separate archiver needs installation on the runner or a direct CLI user’s machine.

Inspect your workspace before editing it

Run from the Cargo workspace root with Git and Cargo available:

$ErrorActionPreference = "Stop"
$PSNativeCommandUseErrorActionPreference = $true
git status --short
cargo metadata --no-deps --format-version 1
cargo release-plan --version

Establish:

  • Which branch actually releases, and whether full first-parent history exists.
  • Which tracked members are publishable, private implementation packages or non-publishable packages.
  • Which exact dependencies intentionally form version groups.
  • Which packages contain an installable binary and what each executable is named.
  • Whether the committed lockfile and toolchain describe a reproducible source build.
  • Whether current registry versions and existing tags correspond to known publication source.

Confirming registry versions and tags correspond to known publication source needs an existing-repository adoption audit, not just matching version strings.

Use an ignored, workspace-local directory for evidence, such as .release-plan-work. Keep each assessment’s inputs and outputs separate. Do not commit generated plans as an alternative source of version truth.

Continue with repository configuration.

Configure your repository

This walkthrough uses example/widgets, a placeholder GitHub repository. Replace it with your repository and select the branch that actually releases. The TOML fragments below belong in existing manifests; they are not complete standalone Cargo projects.

Workspace inputs

Track the workspace and package manifests, publication configuration, lockfile and build-toolchain selection. Prepare a consistent lockfile before collecting release evidence. Publication verifies the committed resolution rather than repairing it.

Choose package inclusion rules using the released-content model. For example:

# Workspace Cargo.toml
[workspace.package]
include = ["src/**/*"]
# packages/widget/Cargo.toml, within [package]
include.workspace = true

Packages needing additional compile-time inputs declare a complete allow-list of their own.

Declare groups and consumer contracts

The library uses its exact-version implementation:

# packages/widget/Cargo.toml
[dependencies]
widget_impl = { path = "../widget_impl", version = "=1.4.0" }

[package.metadata.cargo_check_external_types]
allowed_external_types = ["widget_impl::*"]

The implementation is published so Cargo can resolve it, but does not promise an independently supported API:

# packages/widget_impl/Cargo.toml
[package.metadata.release-plan]
private-api = true

A helper can share alignment without being published:

# packages/widget-fixtures/Cargo.toml
[package]
name = "widget-fixtures"
version = "1.4.0"
publish = false

[dependencies]
widget_impl = { path = "../widget_impl", version = "=1.4.0" }

The exact edges join all these members. Do not add a separate workspace group table. An ordinary version = "1.4.0" requirement names the same declared version but does not create an exact-version group.

Verify external-type exposure

Every library’s allow-list describes types outside that crate which its public API may expose. Use defining crate paths, including implementation paths reached through re-exports. A crate-level glob is useful for your own implementation partition; list other external types narrowly so accidental exposure remains visible.

An omitted allow-list declares no external exposure; it does not mean exposure is unknown. cargo-release-plan check reads declarations but does not compile and verify them. Adopt cargo-check-external-types as a required API-validation gate.

Pin the checker and its matching rustdoc nightly together. The revision below provides the workspace and multi-target checking required by this gate. Select a published release only when it supports those operations; take the matching nightly from its rust-toolchain.toml, independently of your application’s compiler.

$ErrorActionPreference = "Stop"
$PSNativeCommandUseErrorActionPreference = $true
$ExternalTypesRevision = "705a0941997ebeb3bfb1a7f14070ba342f79d879"
$ExternalTypesNightly = "nightly-2026-03-20"
rustup toolchain install $ExternalTypesNightly --profile minimal
cargo install cargo-check-external-types --locked --force `
    --git https://github.com/awslabs/cargo-check-external-types `
    --rev $ExternalTypesRevision
cargo "+$ExternalTypesNightly" check-external-types --all-features --skip-unsupported `
    --manifest-path Cargo.toml

The explicit reinstall establishes the selected revision even when another build has the same package version; it costs a rebuild rather than relying on version-only reuse. An automated installer can avoid that cost by verifying Cargo’s recorded source revision before deciding whether to reinstall.

The workspace invocation checks each eligible library using its own allow-list, including implementation packages. A dynamically discovered package subset is an optional CI optimization, not a manually maintained library list. Cover the relevant feature and platform surfaces; an all-features run on Linux cannot prove a Windows-only signature. Review unused entries in context because some are needed only on another target. Avoid overlapping patterns.

Feed these checks into your required status alongside tests, builds and release checks. A successful version check cannot substitute for this prerequisite.

Configure publication

Commit .cargo/release_plan.toml at the selected workspace root:

schema-version = 1
repository = "example/widgets"
release-branch = "main"
targets = [
    "x86_64-unknown-linux-gnu",
    "x86_64-pc-windows-msvc",
]

This example selects native Linux and Windows x64 binaries. A library-only workspace can use targets = []. The full supported target set and precedence are in the configuration reference.

--config is workspace-relative, even when invoking from elsewhere. For a nested workspace:

$WorkspaceManifest = Join-Path "components" "widgets" "Cargo.toml"
$Configuration = Join-Path ".cargo" "release_plan.toml"
cargo release-plan check --manifest-path $WorkspaceManifest --config $Configuration

This reads components\widgets\.cargo\release_plan.toml. Artifact paths inside publication files are instead repository-relative so different jobs can transport them without sharing absolute checkout paths.

Describe the binary archive

widget-cli is the package; widget is its executable:

# packages/widget-cli/Cargo.toml
[package]
name = "widget-cli"
version = "2.0.0"
repository = "https://github.com/example/widgets"

[[bin]]
name = "widget"
path = "src/main.rs"

[dependencies]
widget = { path = "../widget", version = "1.4.0" }

[package.metadata.binstall]
pkg-url = "{ repo }/releases/download/{ name }-v{ version }/{ name }-v{ version }-{ target }.zip"
bin-dir = "{ bin }{ binary-ext }"
pkg-fmt = "zip"

Binary packages must declare one executable that builds with Cargo’s default features. Check that selection in your own build validation; the offline release check does not resolve features or compile. Publication uses the explicit native Cargo build to check feature requirements and buildability. Repository and binstall layout must match publication configuration. Package release-targets can narrow the workspace selection:

[package.metadata.release-plan]
release-targets = ["x86_64-pc-windows-msvc"]

Use this only for a genuinely restricted package. Binary packages must retain at least one selected target.

Validate the inputs without querying a registry:

cargo release-plan check --config (Join-Path ".cargo" "release_plan.toml")

Configuration changes are often released manifest changes too. Continue with local planning rather than fixing a failing version check by hand-incrementing isolated packages.

Plan versions with an agent

The supported version-planning interface is the increment-versions skill. The CLI supplies the operations that the skill executes; manually assembling a different command sequence is not a supported planning workflow.

The skill prepares a PR containing the source changes, version changes and an explanation of the complete release. Review approves that contribution together. There is no separate prompt to approve each version choice.

Install the complete skill

Copy the complete skill directory from a known toolkit revision into .github/skills/increment-versions in your repository. Include its command reference, decision guide and license.

Record the source revision in your repository’s adoption notes. The skill checks the schemas reported by the installed tool before editing source; it does not require matching package version numbers. On a mismatch, update the skill from the canonical repository. When developing that canonical skill, report the mismatch rather than overwriting it.

The agent needs Git, the selected Cargo/Rust toolchain, cargo-release-plan and cargo-semver-checks. Tool installation and upgrades follow your authorization policy. A private configured repository also needs authenticated history access.

Tell the agent what to assess

For configured publication, record the release branch and required checks in the repository’s agent instructions and provide .cargo/release_plan.toml. Then ask, for example:

Run increment-versions for this PR, assess the complete release impact, apply the resulting plan and update the PR’s Version/release plan.

For a stacked PR, identify the actual parent branch or PR. The skill must preserve the distinction between actual release history and the parent’s anticipated final release, rather than silently using the release branch as the child’s target.

Tracked files may have staged or unstaged edits. The agent does not need a clean checkout or a prior commit to assess them. Newly created intended release inputs must be tracked before the assessment.

For one-off planning without publishing configuration, explicitly request standalone mode.

What the skill produces

The skill reads the complete release report and API comparisons, chooses each package’s semantic impact, previews group and dependency effects, and applies the resolved plan. It retains reports and supporting files outside published source so it can verify that the applied result is the one it assessed.

The PR explains:

  • Which package versions change and why.
  • Group alignment and dependent-package changes, not only directly edited code.
  • Existing pending increments retained because they are sufficient.
  • Any unavailable API comparison or other unresolved prerequisite.

The API checker establishes only a minimum impact from the contracts it can see. The agent also considers behavior, CLI arguments, data formats and feature availability. Human review remains necessary.

Keep the result tied to the reviewed source

If source, release history or the parent changes, have the skill reassess. Do not edit generated plans or reuse a report from a different checkout as permission to apply changes. Repeating the skill retains an adequate pending increment; it does not increase versions solely because it ran again.

Configured mode includes publication-readiness checks and the first-publication handoff when required. Standalone mode stops at its version-increment PR. Neither invocation grants merge or publication authority by itself.

Add GitHub checks

The standard reusable check workflow is folo-rs/cargo-release-plan-action/.github/workflows/check.yml. Select a tested published release from the action repository and replace ACTION_REVISION below with its verified immutable commit. Use that same commit for every action/workflow example in this book; ACTION_REVISION is a placeholder, not an existing release or a GitHub Actions variable.

Add a caller such as .github/workflows/release-checks.yml:

name: Release checks

on:
  pull_request:
  merge_group:

permissions:
  contents: read
  actions: read

jobs:
  release-check:
    uses: folo-rs/cargo-release-plan-action/.github/workflows/check.yml@ACTION_REVISION
    with:
      working-directory: .
      config: .cargo/release_plan.toml
      install-method: binstall
      source-path: .

These inputs show their defaults. working-directory selects the consumer workspace; config is relative to it. source-path selects tool source only with install-method: path, not the consumer workspace for released installation. The reusable workflow and its internal composite use the same immutable action revision.

For a custom graph, the root composite provides individual operations. Its installation and configuration inputs are described under custom jobs. A version-readiness operation is deliberately narrower than the complete PR gate.

Required check coverage

A complete release gate combines:

  • Workspace-wide version readiness, including packages not directly edited.
  • Publication-input validation using committed configuration.
  • External compatibility evidence for the selected public library contracts.
  • Consumer-owned external-type exposure checks and ordinary build/test checks.

Do not scope offline version readiness to a changed-package list. It must find unversioned released content anywhere in the workspace, including inherited inputs and effects carried from an earlier contribution.

The core offline invocation uses the pair returned by release-context:

Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
$PSNativeCommandUseErrorActionPreference = $true
$AssessmentArguments = @("--release-history", $Context.release_history)
if ($Context.merge_target) {
    $AssessmentArguments += @("--merge-target", $Context.merge_target)
}
cargo release-plan check @AssessmentArguments `
    --config (Join-Path ".cargo" "release_plan.toml") --format github

This command alone is not a replacement for external compatibility checking. --format github adds annotations; it does not change the release rules.

The equivalent compatibility gate uses fresh bound evidence and rejects insufficient increments:

cargo release-plan check-compatibility @AssessmentArguments `
    --output (Join-Path ".release-plan-work" "ci-compatibility") --deny-findings

Use a new output directory. check-compatibility acquires a read-only report from source checked at command entry. Callers and tools invoked for read-only work must keep assessed source, configuration and history unchanged during comparison. It does not accept a detached report as permission to check another checkout.

The compatibility stage must distinguish a valid empty selection from a broken checker or unavailable comparison. Run the checker on the assessed source with the supported feature selection, and retain diagnostics. All-features checking does not remove the author’s feature-subset and behavioral review obligations.

Fetch and select the correct history

Use a full-history checkout. Resolve actual release history separately from any anticipated target:

EventHistory and target
PRFetch actual configured release history; pass pull_request.base.sha as the proposed merge target.
Merge queueKeep actual release history separate; the supplied integration base is a proposed target, not independently established history.
Pinned release-source checkExplicitly select that known release-branch commit as history; no anticipated target.
Other push, scheduled or manual checkLet context acquire configured release history rather than assuming the tested HEAD is already released.

Fetch the required base-repository history for fork PRs too. A similarly named branch in the fork is not a substitute.

For a custom graph, release-context --config <path> fetches the configured release branch and returns release_history plus nullable merge_target. Supply --merge-target <target-commit> for a PR or integration target. Context normalizes a target already in release history to null. Pass the returned pair unchanged to check and compatibility. Use --release-history <commit> only when the caller already knows the actual release-history boundary.

A repository can keep a deliberately narrow queue gate using the lower version-readiness operation and the candidate’s history/target pair. That does not remove the full compatibility and publication-input checks from ordinary PR validation.

Protect the branch

Configure squash merging and branch protection for the chosen release branch. Each PR’s target-aware checks establish its own version decisions; merge queue grouping does not replace those checks. Require a stable status that represents all merge-blocking work.

A final aggregation job is useful for dynamic matrices: it succeeds only when required prerequisites succeeded or were deliberately not applicable. Failure, cancellation, missing results and an unexpectedly skipped unconditional gate do not count as success.

Require the status on both PR and queue events. A queue check whose workflow never runs cannot establish readiness. Choose your own stable check name; no particular Folo job name is required.

Keep checks read-only

PR checking needs source/history access, not publication credentials, write permission or OIDC authority. Follow the repository’s normal approval policy for fork workflow runs. An unapproved or policy-disallowed check is not an executed pass.

Do not use privileged pull_request_target execution of contributor source as an adoption shortcut. Mutation privileges belong only in the separately gated publication workflow.

Before making the check required, exercise a passing change, an intentionally missing increment, a group/dependency violation and the queue event. Confirm the external-type gate covers your supported public API surfaces and that API compatibility checker execution failures remain failures in the final status.

Connect publication

Publication starts from clean merged source under your repository’s release policy. Its preparation step is automatic execution, not another human version approval gate.

The standard reusable flow is folo-rs/cargo-release-plan-action/.github/workflows/release.yml. Select a tested published action release and replace ACTION_REVISION with the same verified immutable commit used for release checks. Keep registry, GitHub reconciliation, platform batches and reporting in the same workflow run. Tag and release events created using the workflow’s GITHUB_TOKEN do not trigger downstream workflows.

Add the release caller

For the running example, use .github/workflows/release.yml:

name: Release

on:
  push:
    branches: [main]
  workflow_dispatch:
    inputs:
      source:
        description: Optional full original publication-source commit for recovery
        type: string
        default: ''
      verify-publishing-identity:
        description: Verify OIDC exchange and revocation without publication
        type: boolean
        default: false

permissions: {}

jobs:
  release:
    if: github.repository == 'example/widgets' && !inputs.verify-publishing-identity
    permissions:
      contents: write
      actions: read
      id-token: write
      issues: write
    uses: folo-rs/cargo-release-plan-action/.github/workflows/release.yml@ACTION_REVISION
    with:
      source: ${{ inputs.source }}

  identity:
    if: github.repository == 'example/widgets' && github.event_name == 'workflow_dispatch' && inputs.verify-publishing-identity
    permissions:
      contents: read
      actions: read
      id-token: write
    uses: folo-rs/cargo-release-plan-action/.github/workflows/identity-probe.yml@ACTION_REVISION

Replace example/widgets and the example main branch with the configuration for your repository. The shared flow also validates the destination and release branch before writes. The caller grants the scopes the flow needs; individual jobs narrow them.

Omitted inputs use the shared workflow defaults. For a protected environment, add the same setting to both jobs and register that environment with crates.io:

with:
  publishing-environment: release

For path installation, add install-method: path and select the tool checkout with source-path. Released installation does not use source-path.

An empty source uses the invocation’s commit. For explicit recovery, supply the full original source SHA; the tool still verifies its release-branch membership. This is a new invocation with new evidence, never an automatic fallback when an old artifact is missing. The source override selects the publication source only. The controller is the cargo-release-plan executable run by the workflow. In source-install mode, source-path selects its source from the invocation checkout, so recovering old publication source does not rebuild an old controller.

Establish publishing identity

Every package must already exist on crates.io before automatic publication. Complete first publication before a new package’s first merge.

Register each package’s crates.io Trusted Publisher with:

  • Your GitHub repository owner and repository name.
  • Your calling entry workflow filename, including when it calls a reusable workflow from the action repository.
  • The publishing environment, if you use a protected GitHub environment.

The called action repository is not the publisher identity. The job exchanging OIDC credentials must use the environment registered for that caller. See GitHub’s reusable-workflow OIDC documentation.

Use phase-specific permissions. Registry publication needs source read access and id-token: write; GitHub tag, release and asset operations need repository content writes; failure-issue reporting needs issue writes. Grant only the scopes required by each job, including any read access the selected workflow needs for artifact transport and action-revision identification.

Do not provision a registry PAT or stored-token fallback. The application acquires short-lived credentials through OIDC for Cargo’s initial credential preflight and independently for each upload after verification. Preflight credentials are not reused for uploads. Cleanup attempts to revoke every issued credential and reports failures. Package verification shares the publication job’s trust boundary, including its job-level OIDC identity. Run only reviewed repository code and trusted build dependencies in that job; credential handling is not a sandbox against code running under the same account.

Verify identity without publishing

The permanent caller above includes a mutually exclusive identity-only mode. Dispatch it through the same registered entry workflow filename:

gh workflow run release.yml --repo example/widgets --ref main `
    --field verify-publishing-identity=true

Run this before enabling routine publication and whenever the registered caller or environment changes. The reusable probe checks OIDC exchange and revocation without a Cargo workspace or publication; it has no working-directory or config input. It uses the same action commit and environment as publication. Do not move the probe to a differently named caller or replace the production file to run it.

Its CLI operation is:

cargo release-plan check-publishing-identity

Run this in the authorized GitHub Actions OIDC context. It exchanges and immediately revokes a temporary credential, uploading nothing. Success exercises the caller identity path, not every package’s Trusted Publisher grant; verify package administration separately.

Capture the merged source

These commands demonstrate the CLI boundaries independently of workflow filenames. Run preparation from a clean checkout of the selected merged commit:

$ErrorActionPreference = "Stop"
$PSNativeCommandUseErrorActionPreference = $true
$Source = (git rev-parse HEAD).Trim()
$Work = ".release-plan-work"
$Publication = Join-Path $Work "publication.json"
$Outcomes = Join-Path $Work "outcomes"
cargo release-plan prepare-publish --source $Source `
    --config (Join-Path ".cargo" "release_plan.toml") --output $Publication

$Source must be the full commit ID. Preparation verifies HEAD, clean source, tracked Cargo inputs and first-parent membership in the configured release branch. It fetches that branch to establish eligibility; it does not replace the selected source with the fetched tip. Preparation preserves the recorded lockfile without resolving dependencies. Cargo’s publication and native build commands use --locked.

Keep output in an ignored artifact directory or outside tracked source. Preparation writes no registry package, tag or release. Repeating identical intent may reuse the manifest; different intent needs a different destination.

In CI, prepare this manifest once and retain it for every subsequent phase and retry. A failed-job rerun downloads original intent rather than regenerating it from the current release-branch tip.

Inspect registry work without uploading

cargo release-plan publish registry --publication $Publication `
    --output (Join-Path $Work "registry-dry-run-1" "outcome.json") --dry-run

The dry run queries exact-version availability without exchanging publication credentials or uploading. Its outcome is never marked complete, even if every requested version already exists.

The following invocation performs live registry work and belongs only in the authorized publishing job, not tests or the local version-planning skill:

cargo release-plan publish registry --publication $Publication `
    --output (Join-Path $Outcomes "registry-attempt-1" "outcome.json")

Use a new outcome path on every attempt. Naming each live phase file outcome.json inside its own artifact subdirectory lets the final reporter discover it. Keep local dry-run and nonpublishing verification output separate from those publication outcomes. When the workspace is nested or the source checkout moved between jobs, --manifest-path must select the original publication-source workspace. Do not substitute a prospective planning workspace or later source commit.

The Cargo runtime requirement supports locked package verification and dependency-ordered workspace uploads. Cargo owns normalization and hashing of the package bytes it uploads. Neither a failed upload command nor an old successful outcome replaces a fresh exact-version observation.

Continue through GitHub and native jobs

After successful registry reconciliation, inspect GitHub work without writes:

cargo release-plan publish github --publication $Publication `
    --output (Join-Path $Work "github-dry-run-1" "outcome.json") `
    --batches (Join-Path $Work "batches-dry-run-1") --dry-run

A dry run is not completed delivery and cannot establish missing tags for native builds. The authorized GitHub job performs live reconciliation:

$Batches = Join-Path $Work "batches-attempt-1"
$GithubOutcome = Join-Path $Outcomes "github-attempt-1" "outcome.json"
cargo release-plan publish github --publication $Publication `
    --output $GithubOutcome --batches $Batches

The outcome supplies relative paths and batch_id values for frozen platform batches. Both the outcome path and batch directory must be new. For a nested workspace, pass --manifest-path for the original publication source, just as in registry publication.

Do not derive binary work from “packages uploaded in this attempt.” A registry no-op can still require a missing tag, release or checksum.

A failure to tag one release does not suppress valid work for another. Valid manifest-linked batches can continue after registry prerequisites succeed, while the reconciliation failure remains a workflow failure. Missing batch artifacts, invalid identities or cancellation do not authorize downstream work.

On the matching native runner, select the emitted batch for its target. This example assumes the outcome includes Windows x64 work:

Choose an absolute shared Cargo build-cache location outside the clean source, or beneath an ignored directory. This preserves source cleanliness in repositories that do not ignore Cargo’s default target directory:

$env:CARGO_TARGET_DIR = [IO.Path]::GetFullPath((Join-Path $Work "build-cache"))
$Routing = Get-Content $GithubOutcome -Raw | ConvertFrom-Json
$Selected = $Routing.batches | Where-Object target -eq "x86_64-pc-windows-msvc"
if ($null -eq $Selected) {
    throw "This outcome contains no Windows x64 batch."
}
$Batch = Join-Path $Batches $Selected.path
cargo release-plan publish binaries --publication $Publication --batch $Batch `
    --output (Join-Path $Outcomes "windows-attempt-1" "outcome.json") `
    --artifacts (Join-Path $Work "windows-artifacts-attempt-1")

The tool rechecks tag identities and assets, then builds each executable separately from its actual peeled tag commit. To stage the frozen batch without tag/release/asset queries or uploads, use --no-upload with separate destinations:

cargo release-plan publish binaries --publication $Publication --batch $Batch `
    --output (Join-Path $Work "windows-verification-1" "outcome.json") `
    --artifacts (Join-Path $Work "windows-verification-artifacts-1") --no-upload

This mode still verifies source and archives. It may fetch an unavailable source commit from the configured repository, including using repository-read authentication. It does not discover a batch, select substitute source or produce a completed publication outcome.

Report every attempt

The final reporting job downloads each phase’s artifact subdirectory, retaining its outcome.json, and supplies current job results as jobs.json. The fixed keys are prepare, registry, github and binaries; values come from GitHub’s job results, including the binary matrix’s aggregate result:

{
  "prepare": "success",
  "registry": "success",
  "github": "failure",
  "binaries": "skipped"
}

This is a format example, not values to hardcode into a real report. Run the reporter even after failed phases:

cargo release-plan publish report --repository example/widgets `
    --publication $Publication --outcomes $Outcomes `
    --jobs (Join-Path $Work "jobs.json") `
    --output (Join-Path $Work "publication-report-attempt-1.md")

The command requires the GitHub run context, writes the Markdown report even for incomplete delivery, and exits nonzero when work remains. It creates or updates the run-qualified failure issue with exact operator recovery instructions. --no-issue disables issue writes but does not remove the run-context requirement or turn an incomplete report into success.

If the publication manifest is unavailable, omit --publication. The reporter still writes a report and failure issue; missing intent is always incomplete, never a successful empty release.

Outcomes carry optional github: {run_id, run_attempt} metadata when produced in GitHub Actions. Preserve it so the reporter can enforce outcome identity and freshness.

Prepare native build environments

Configure the subset your packages support from the supported native targets. The action revision owns the corresponding runner mapping.

Provide native libraries or other source-build prerequisites through the fixed optional .github/actions/release-plan-setup/action.yml. It prepares the environment in Cargo verification and binary-build jobs, not GitHub-only reconciliation jobs, and must not edit captured source.

The action’s controller installation, the invocation checkout and each tagged binary source are distinct. A historical tag need not contain the current controller or current publication configuration.

Queue and retain, without hiding failures

Serialize publication runs for the same repository, release branch and workspace with non-cancelling queued concurrency (queue: max). Disable matrix fail-fast so independent native targets can finish. Queue capacity and platform cancellation still need operational attention.

Choose artifact retention long enough for operator recovery. Preserve the manifest, frozen batches, every attempt outcome and diagnostics, including on failure. Use run/attempt-qualified artifact names from the selected action interface rather than overwriting intent.

Queuing publication does not prevent a newer version from merging. For superseded-version tag failures, follow the original-source manual-tag recovery.

An ordinary release

Once the packages, Trusted Publishers and shared workflows are configured, an ordinary release is part of the PR process. For a new package, complete first publication before its first merge.

Make the change and invoke the skill

Develop the change with its tests and public documentation, then ask the agent:

Run increment-versions for this PR and update its Version/release plan.

The installed skill handles compatibility assessment, version selection, dependent-package changes and application of the plan. You do not need to choose tool commands, edit generated planning files or assemble the release table yourself.

Review the proposed release

Review the source changes and the skill’s generated Version/release plan together. For a compatible operation added to widget, the table might contain:

Package or groupPrevious versionProposed versionReason
widget, widget_impl, widget-fixtures1.4.01.5.0Compatible public operation; implementation and fixture helper align with the group. The helper is not published.
widget-cli2.0.02.0.1Dependency update without a stronger CLI change; its existing pending patch increment is sufficient.

The review question is whether those reasons accurately describe the change. For example, removing or changing an existing operation’s promised behavior may require a breaking release instead. A dependent package’s movement should have a clear explanation rather than an invented user-facing feature.

If the table’s reasoning does not match the source, ask the agent to correct the assessment and rerun the skill. If source or release history changes during review, have it refresh the plan rather than editing version numbers or generated artifacts by hand.

Merge under normal repository policy

Review and the required checks approve the source and version changes together. Invoking the skill is not itself merge authorization.

After an authorized merge, the shared workflow publishes the requested packages, tags, releases and native archives automatically. Successful publication needs no separate manual delivery audit.

If something goes wrong, the release workflow fails and its reporter creates or updates a failure issue. Follow failure recovery for the safe retry or repair action instead of starting another version increment by default.

Recover a failed release

You do not need to inspect tags, archives or registry entries after every successful release. The shared workflow owns those checks. Use this chapter when GitHub reports a failure and you need to decide what to retry or repair.

Find out what failed

Before merge, a failed PR check appears on the pull request. Fix the reported problem before merging; these checks do not start publication.

After merge, an incomplete release marks its workflow and affected commit checks as failed. The final reporter creates or updates an issue titled Release failed: workflow run …, linking the run and describing incomplete packages and any required operator action. A failed retry updates that same issue rather than creating a new issue for each attempt.

Start with the issue’s instructions and the failed job’s logs. If there is no issue, open the failed or cancelled run in the repository’s Actions tab. Cancellation, startup failure or missing reporting permissions can prevent an issue from being created; its absence does not mean the release succeeded.

Choose the recovery action

Reported problemWhat to do
Temporary network, registry or runner failureWait for the service to recover, then rerun the original failed jobs.
Publishing permission or Trusted Publisher registration is wrongHave a maintainer correct the setting, then retry. If the fix changes workflow code, use a new recovery run as described below.
A native build prerequisite is unavailableRepair the build environment. Retry the original run if it can use that repair; use a new recovery run if updated automation is required.
A ZIP or checksum upload is incompleteRerun the original failed jobs. The publisher completes missing work and repairs an incomplete pair together.
The failure issue names a missing tag after another version mergedCreate that exact tag at the recorded source, then retry the original failed jobs.
Required artifacts expired, or the publisher needs corrected automationStart a new recovery run for the original source. Do not reconstruct or edit the old artifacts.
The released package’s source is defectiveCorrect it in a new PR, run the skill and release a new version. Retrying unchanged source will not fix it.
An existing tag or uploaded version refers to unexpected contentStop automatic retries and investigate. Do not move the tag or overwrite the version to make the run green.

Retry the original run when its request is still correct

In Actions, open the original failed release run and choose Re-run failed jobs after correcting the cause. Rerunning the whole original workflow is also safe when that is necessary: it still requests the original source and versions.

Already published versions, established tags and complete archive pairs are preserved. Retrying an upload failure therefore does not normally need a new package version. Independent packages may have completed even though the overall run failed.

A retry does not update the run to newer source or newer workflow code. If that is what the repair requires, choose the appropriate path below rather than repeatedly rerunning the same failing inputs.

When a retry succeeds, close its failure issue with a link to the successful attempt. Successful retries do not automatically close those issues.

Missing tag after a newer version merges

For example, the run for widget-cli 2.0.1 can finish its crate upload after 2.0.2 has already merged. The old run still needs the 2.0.1 tag, but cannot automatically tag the newer source as that version.

The failure issue identifies the exact tag and original source commit. An authorized maintainer must create that tag at the recorded commit, not at the current branch tip. Do not move an existing tag.

These commands are for that specific manual repair. Replace the example values with those from the failure issue:

Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
$PSNativeCommandUseErrorActionPreference = $true

$Repository = "example/widgets"
$Tag = "widget-cli-v2.0.1"
$Source = "<original-source-SHA-from-the-failure-issue>"
$RepositoryUrl = "https://github.com/$Repository.git"

# Inspect the recorded source and check whether the tag already exists.
git fetch $RepositoryUrl $Source
git show --no-patch --format=fuller $Source
git ls-remote $RepositoryUrl "refs/tags/$Tag" "refs/tags/$Tag^{}"

Only after confirming the intended source and that the tag is absent, create it:

Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
$PSNativeCommandUseErrorActionPreference = $true

git tag --annotate $Tag $Source --message "Release $Tag"
git push $RepositoryUrl "refs/tags/$Tag"

Then use Re-run failed jobs on the original run. It recognizes the created tag and completes the remaining work from that source. If the tag already exists but identifies different content, investigate instead of force-updating it.

Start a new recovery run when retrying is insufficient

If the original artifacts are unavailable or a fix requires newer publishing automation, open the release workflow in Actions and choose Run workflow. Select the release branch and set its source input to the full original publication-source commit from the failure issue or failed run.

This uses the release branch’s current automation to create a new publication request for that original source and its configuration. It observes existing uploads and completes what is still missing. It does not recreate the old outcomes or turn the earlier failed run into a successful attempt. If the original source cannot be restored, do not substitute different content under the same versions; prepare a new release instead.

The caller needs the recovery input shown in Connect publication. If your caller does not expose it, have a maintainer update the caller rather than assembling publication commands or editing artifacts manually.

If the original publication configuration itself was wrong, selecting the same source also selects that old configuration. Correct the configuration through a reviewed PR and let its normal release run issue a new request instead. Do not edit an existing publication manifest to change its targets or destination.

After a successful replacement run, close the original failure issue with a link and explain that the new run supersedes it. The original failed run remains an accurate record of the request it could not complete.

When a new version is necessary

Create a forward release when the repair changes released package content or when an occupied version cannot describe the intended code. Ask the agent to make the correction and run increment-versions; review its proposed versions normally.

A transient service failure, missing archive, permission repair or automation-only correction does not by itself require new package versions. The skill determines whether a proposed source/configuration correction actually changes released content. A newer successful release does not retroactively repair an older defective one; record that the old failure is superseded rather than retrying it indefinitely.

Release-branch movement

If a PR’s version check fails because release history or its parent advanced, ask the agent to refresh its branch and rerun increment-versions. It preserves your source changes, reassesses against the new history and updates the release table. Do not compensate by manually increasing stale proposed versions.

First publication and adoption

The normal automatic path assumes established crates.io packages and a release history that corresponds to their content. Introducing a new package and adopting an existing repository need explicit maintainer setup.

First publication happens before the first merge

crates.io Trusted Publishing cannot publish a package that does not yet exist, because its publisher registration is configured on that existing package. A maintainer performs the first publication from the feature branch before the package’s first merge.

  1. Review and validate the new package and its publication dependencies.
  2. Publish the related bootstrap packages manually, letting Cargo order their dependencies.
  3. Configure its crates.io Trusted Publisher for the consuming repository’s calling workflow and optional environment.
  4. Prepare a strictly higher version for the first merge, including exact-group alignment, dependent requirements and consistent lockfiles.
  5. Update the PR’s release explanation with both versions and the completed maintainer setup.
  6. Merge under the repository’s normal review policy. This performs the second publication: the first automated release.

For example, if introducing the running example as new packages:

PackageManual bootstrapFirst merged automated version
widget_impl, widget1.4.01.5.0
widget-cli2.0.02.0.1
widget-fixturesNot publishedAlign to 1.5.0

These numbers illustrate the ordering requirement; choose versions appropriate to the actual new packages. The helper needs no registry account or publisher.

The manual operation is ordinary Cargo publication, for example:

cargo publish --package widget_impl --package widget --package widget-cli --registry crates-io --locked

Use the supported Cargo publication runtime and the reviewed, current lockfile. Select the complete related package set; separate invocations are needed only when packages must be handled independently.

It belongs to an authorized maintainer, not tests, the local skill or an unprivileged workflow. Follow crates.io’s supported maintainer authentication procedure; do not add persistent publication credentials to the release automation.

Do not merge the bootstrap version unchanged. Do not rerun a release-branch workflow expecting it to publish source that exists only on a feature branch. For binary packages, the first automated merged version receives its GitHub release and native assets.

Registry preflight is not publisher verification

check-published without a plan discovers missing and unknown packages across the workspace as an advisory. Before applying a resolved plan, check-published --plan <resolved-plan.json> validates its targets and requires an established registry package for every publishable target. Missing or unknown registry results block that gate; non-publishable packages make no query.

These observations do not verify Trusted Publisher registration. Complete that setup explicitly before resuming. The separate check-publishing-identity OIDC exchange/revocation probe tests the calling workflow identity without uploading; it does not establish every package’s grant either. A package can lack a package anchor while its manually published bootstrap version already exists in crates.io.

Adopting an already-published workspace

Before enabling publication, establish correspondence between current versions, their source and existing remote releases:

  • Identify the source used to publish each current package version.
  • Compare relevant package content and normalized publication inputs where needed, including binary dependency resolution.
  • Verify repository ownership of package identities and existing package tags.
  • Check that full first-parent history supports the intended anchors.
  • Inspect existing binary releases against the configured naming and target contract.
  • Run publication-input checks and read-only reconciliation before enabling writes.

Matching version strings or newly imported Git history are not proof of matching released content. Resolve discrepancies through reviewed forward versions rather than rewriting existing registry versions or tags.

When replacing another publisher, keep only one mutating release path active. Preserve the caller workflow filename where existing Trusted Publisher registrations depend on it. Drain or explicitly account for old queued runs; changing a concurrency identity does not coordinate with those old runs.

Source-mode tests, dry runs and a successfully built book do not prove that the published action/tool combination can perform the live path. Complete an authorized acceptance release using the exact pinned combination before relying on it for routine delivery.

Emergency manual publication

When the automated registry path is unavailable, an authorized maintainer can publish the exact reviewed merged versions with Cargo in dependency order. Preserve their source identity and locked-resolution requirements.

Then resume reconciliation for that original source so tags, releases and binary assets complete. Already uploaded versions are skipped. This does not require another version increment solely because automation needs retrying.

Keep emergency access out of the normal automation. The automatic path retains OIDC and its phase-specific permissions.

Upgrade tools, actions and skills

The application, GitHub integration and copied skill have related interfaces but different distribution lifecycles. Upgrade them as a tested combination rather than assuming equal version numbers.

The walkthrough and copied skill use report/plan/prepared schema 8, semantic-decision schema 2, compatibility schema 3, and release-context schema 2. cargo release-plan version reports these revisions as JSON. ACTION_REVISION in workflow examples must be replaced with the verified immutable commit of a tested published action release selecting that interface. Use the same commit for the root composite, check, release and identity-probe workflows. The placeholder is not a claim that an action tag already exists.

Keep an adoption record

Record:

  • The immutable action revision.
  • Its exact cargo-release-plan and API compatibility checker (cargo-semver-checks) pins.
  • The immutable source revision of the copied skill directory.
  • The source revision of the book used for that combination.
  • Consumer-owned external-type checker/nightly pins, libraries, feature selections and platform targets covered by those checks.

The action’s release manifest is authoritative for its installed tools. A published action revision does not accept a silently substituted application version underneath it. Source mode is an explicit, different selection.

The website follows current documentation. To read an older combination, use GitHub’s tag or commit selector in the book source directory and read the Markdown at the recorded immutable revision. Use the same method for .github/skills/increment-versions. Do not copy the latest skill while retaining an older unsupported command interface.

Upgrade checklist

  1. Read the chosen release’s command, configuration and artifact compatibility notes.
  2. Confirm the exact application package and promised native archives exist.
  3. Verify the installed executable and schemas with cargo release-plan version.
  4. Exercise the selected action revision in read-only checks on a representative consumer workspace.
  5. If the skill’s supported schemas differ, update its complete directory from the canonical repository and record its revision. In the canonical repository, report a mismatch instead of overwriting the skill being developed.
  6. Verify OIDC exchange/revocation with the identity probe when the caller or publishing environment changes, and confirm package-specific grants separately. Use the permanent release caller’s identity-only mode.
  7. Regenerate stale local preparation/report/preview evidence rather than editing schema numbers.
  8. Inspect retained publication runs before changing the tool used for retries.
  9. Review and merge the coordinated configuration and pin changes.

A moving action major reference is convenient but can resolve to different source on a later invocation. An immutable commit or release tag gives a stable selection, including for full workflow reruns. The reusable workflow’s internal composite also uses that same immutable revision.

Artifact compatibility

Reports and version plans have a schema lifecycle separate from publication manifests and outcomes. Neither an action version nor a package version is an artifact schema number.

Unsupported local-planning formats require new prepare and preview evidence. Captured file content and input identity are not fields to repair by hand.

For an incomplete publication, prefer the original compatible tool and retained manifest. If that combination cannot be restored, explicitly prepare new evidence from the retained original source under the recovery procedure. Do not replace original intent with current branch content or pretend that a new manifest is an old outcome.

Verify distribution, not just source

An action release must be usable with its exact published pins. Source-mode tests do not establish that a crates.io package exists or that each supported archive installs without fallback.

Test source installation and promised native binary installation separately. Verify API compatibility checker installation independently; the release action does not manufacture that tool’s archives. Update the external-type checker (cargo-check-external-types) and its rustdoc nightly together.

Adding a target requires published tool support, native runner provisioning, archive verification and an assessed action/tool release. It is not a consumer configuration shortcut around the supported target set.

One-off version increments

Use the increment-versions skill in standalone mode when you want a version-increment PR without adopting publishing integration. The user supplies the planning inputs instead of release_plan.toml; no publishing configuration, GitHub remote or workflow installation is required.

The scenario ends with the reviewed version-increment PR being merged under the repository’s normal authorization policy. Publishing after that merge is outside standalone mode. The shared workflow remains the toolkit’s canonical supported publishing path.

Provide inputs, not a publishing configuration

Tell the agent which Cargo workspace to assess, the local ref or commit delimiting release history, and the parent target ref for a stacked PR.

For example:

Run increment-versions in standalone mode for this workspace. Use my local origin/main ref as release history and no unmerged parent. Prepare the version-increment PR; do not configure or run publication.

Those ref names are your choices, not defaults inferred from a publishing setup. A repository without remotes can use a local branch or commit. If you want a remote-tracking ref refreshed first, say so.

The agent prerequisites still apply. Tracked files may have staged or unstaged changes; newly created intended release inputs must be tracked before assessment.

The same version model, without publication setup

The skill uses the same reports, semantic impacts, version groups, dependency effects and captured-plan checks as configured planning. It supplies your selected history and target through the existing tool inputs; it does not create a dummy publishing configuration.

It does not require registry identities, Trusted Publisher bootstrap or binary archive metadata merely to edit versions. API comparison remains separate supporting information and can still read published crates. An unavailable comparison is not proof of compatibility.

Standalone mode is explicit, not a fallback for bypassing a failed configured check. Existing repository CI and merge-triggered automation remain in force; choosing this planning mode does not disable them.

Review and merge the version PR

Review the code and complete version explanation together, including dependent packages and retained pending versions. If the source or selected refs move, have the skill reassess rather than editing its generated files.

The skill does not grant merge permission. Once the version-increment PR is merged with normal authorization, the standalone workflow is complete.

Custom jobs and source identity

Prefer the reusable workflows for the standard process. Use the root composite when your repository needs a different job arrangement or a supported target on a different native runner. Neither option changes release policy.

Root composite

The root action lives at folo-rs/cargo-release-plan-action. It selects an operation with command; the operation determines its additional inputs. Consult the exact selected revision’s input contract.

The shared inputs are:

InputMeaning
commandSelect the supported operation, not arbitrary shell or Cargo arguments.
working-directorySelect the consumer Cargo workspace.
configSelect publication configuration relative to that workspace.
install-methodChoose binstall, install or explicit source mode path.
source-pathSelect the tool source checkout for path installation.

The root operations include version, version-readiness, check, prepare-publish and publish-registry. version-readiness is the narrow offline gate; do not treat it as full compatibility and publication-input validation.

This step illustrates executable identity checking. Replace ACTION_REVISION with the same verified immutable commit selected from a tested published action release for every caller:

- uses: folo-rs/cargo-release-plan-action@ACTION_REVISION
  with:
    command: version
    install-method: binstall

The action version is independent of the application version printed by this operation. A released revision pins exact tools; binstall permits source fallback, install builds the exact published source with its lockfile, and path builds the selected tool source. Path mode must not restore a released executable cache in place of that source.

Consumers of released tools do not need a Folo checkout. Only deliberate tool development/source-mode testing supplies one.

Shared workflow inputs

The public reusable workflows are .github/workflows/check.yml, .github/workflows/release.yml and .github/workflows/identity-probe.yml in the action repository:

InputDefaultWorkflows
working-directory.Check and release
config.cargo/release_plan.tomlCheck and release
install-methodbinstallAll
source-path.All
publishing-environmentEmptyRelease and identity probe

The identity probe needs neither a Cargo workspace nor configuration. It tests OIDC exchange/revocation in the calling workflow’s identity. Use the registered entry workflow filename and publishing environment, not another caller and an assumption that the identity is equivalent.

The check caller and publication callers show complete examples. Do not add arbitrary per-field release-policy inputs; committed configuration remains the policy source.

Preserve source identities

IdentityRole
Release-history commitSelect actual publishing-branch history for assessment.
Merge-target commitSupply an unmerged parent’s final state as an anticipated squash release.
Per-package anchorSupply that package’s comparison content and version.
Prepared source and prospective workspaceBind semantic evidence and the exact plan application.
Publication sourceFix the merged source and declared versions to deliver.
Peeled tag commitFix the source actually used to build one binary release.
Action revisionFix the reusable workflow implementation and its released tool pins.
Controller source in path installationSelect the application built from the invocation checkout.

Do not substitute one for another. In particular, a source checkout’s rust-toolchain.toml must not accidentally select an incompatible compiler for installing the controller. Conversely, installing a newer controller does not authorize building a historical binary with today’s source and lockfile.

Tagged builds run with the tag’s source as their working directory, not merely a different --manifest-path while inheriting another checkout’s Cargo configuration. Standard builds use release mode, locked dependencies, the selected native target and default features.

Own the orchestration, not another release model

A custom graph must preserve these boundaries:

  • Prepare immutable intent once; pass it unchanged to later phases.
  • Require registry completion and fresh prerequisite validation before GitHub writes.
  • Admit native work only from valid manifest-linked batches with established tag identities.
  • Continue independent valid releases after a per-release failure while keeping the aggregate run failed.
  • Keep credentials phase-local and out of native source-build subprocesses. Registry verification shares the trusted publication job’s identity.
  • Retain input artifacts and attempt-specific diagnostics on failure.
  • Treat missing artifacts as errors and retry original intent.

Do not hand-maintain package lists, derive upload order from semantic assessment batches or synthesize platform-batch JSON. The tool owns selection and artifact validation; Cargo owns registry ordering.

Use the fixed optional .github/actions/release-plan-setup/action.yml only to prepare native build requirements. It is not an arbitrary release-policy hook and cannot modify captured source.

For nested workspaces, keep working-directory and config consistent with their workspace-relative rules. Give independent release contexts distinct concurrency and artifact identities, while preserving one context across retries.

release-context supplies the configured repository, release_branch, release_history, nullable merge_target and stable concurrency_group. With an explicit --release-history, it uses that commit without fetching history. It does not replace publication preparation or require a clean checkout.

Use the exact phase commands for custom jobs. Download outcomes into distinct artifact subdirectories named per run and attempt, preserving outcome.json and batch linkage. The final reporter takes the fixed prepare, registry, github and binaries job results, regardless of the display names in your graph.

Troubleshoot at the boundary that failed

Use --verbose to see selection inputs and reasons without mixing diagnostics into machine-readable output. Identify whether a failure occurred in offline assessment, explicit resolution, API compatibility checker execution, source validation, registry observation or GitHub publication.

Do not fix an offline classification issue by adding registry lookups, a API compatibility checker execution failure by suppressing its result, or an artifact mismatch by overwriting the artifact. Resolve the relevant input or tool combination and regenerate evidence where required.

Configuration reference

Publication uses committed TOML and package metadata. Source commits, output locations and attempt identities are invocation inputs, not persistent policy.

Workspace publication configuration

The default file is .cargo/release_plan.toml relative to the selected Cargo workspace. Relative --config overrides resolve from that workspace, not the shell’s invocation directory. Absolute overrides are also accepted. Publication requires tracked configuration inside the source repository; it need not be inside a nested Cargo workspace.

This is a complete example of the configuration format:

schema-version = 1
repository = "example/widgets"
release-branch = "main"
targets = [
    "x86_64-unknown-linux-gnu",
    "aarch64-unknown-linux-gnu",
    "x86_64-pc-windows-msvc",
    "aarch64-pc-windows-msvc",
    "aarch64-apple-darwin",
]

Replace the example repository and release branch; no particular release-branch name is required.

KeyMeaning
schema-versionConfiguration schema, currently 1. This spelling uses a hyphen, unlike JSON artifact fields.
repositoryGitHub destination in owner/repository form.
release-branchBranch whose merged source is eligible for publication.
targetsSelected supported native targets for binary archives.

For a library-only workspace, an empty targets = [] is valid. For a binary package, the effective target set must be nonempty.

The supported native target set is:

TargetPlatform
x86_64-unknown-linux-gnuLinux x64
aarch64-unknown-linux-gnuLinux ARM64
x86_64-pc-windows-msvcWindows x64
aarch64-pc-windows-msvcWindows ARM64
aarch64-apple-darwinmacOS ARM64

The action revision selects matching native runners. Intel macOS is not in this support set.

check --config validates configuration and binary publication inputs offline. Ordinary check without that option retains its version-readiness scope. Publication reads configuration by default. None of these settings silently changes the history default for version assessment; automation supplies --release-history and any --merge-target explicitly.

Package metadata

These entries are optional:

[package.metadata.release-plan]
private-api = true
release-targets = ["x86_64-pc-windows-msvc", "aarch64-pc-windows-msvc"]

private-api declares that a library does not offer an independently supported consumer API. It defaults to public when absent; binary-only packages have no library contract to compare. The declaration does not disable publication or dependency propagation.

Checker capability is separate. Procedural-macro libraries can offer a supported consumer API without declaring it private. Their Cargo target metadata excludes them from direct checker invocation and records the unsupported comparison explicitly. They remain subject to semantic assessment, release planning and publication. A private implementation’s re-exported items are assessed through the public package; the private declaration does not make that exposed contract private.

A CLI package with an internal library target must also declare private-api = true when it offers no supported Rust library API. This does not waive compatibility requirements for its command line or documented artifact formats.

release-targets narrows the workspace’s binary target selection. Omit it for the whole workspace selection. Invalid targets or an empty effective binary selection are errors.

Cargo’s publish = false is different: it excludes the package from publication while retaining tracked version-group alignment.

Version groups come from exact local workspace dependencies, not metadata. The obsolete [workspace.metadata.release-plan.groups] table is not accepted.

External types

[package.metadata.cargo_check_external_types]
allowed_external_types = [
    "widget_impl::*",
    "other_library::PublicType",
]

Entries describe defining crate paths, including re-exported types. Verify them with cargo-check-external-types for supported features and platforms. An absent list allows no external exposure. See the adoption prerequisite.

Binary metadata and naming

Each publishable binary package declares one executable that builds with Cargo’s default features. Authors are responsible for checking that build selection. The offline metadata check validates binary selection and archive declarations, not feature eligibility or buildability; Cargo checks those during the native build. Its Cargo repository is exactly https://github.com/<owner>/<repository> using the configured destination, without a trailing slash or .git suffix, and its binstall metadata describes the standard ZIP layout:

[package]
repository = "https://github.com/example/widgets"

[package.metadata.binstall]
pkg-url = "{ repo }/releases/download/{ name }-v{ version }/{ name }-v{ version }-{ target }.zip"
bin-dir = "{ bin }{ binary-ext }"
pkg-fmt = "zip"

The brace expressions are literal cargo-binstall template syntax, not placeholders to replace manually.

ArtifactNaming contract
Package tag and binary release tag{package}-v{version}
Archive{package}-v{version}-{target}.zip
Sidecar{package}-v{version}-{target}.sha256
ZIP memberExecutable name at archive root, with .exe on Windows

The sidecar does not append .sha256 to the ZIP filename. Package and executable names can differ. Unix ZIP members retain executable permissions. Both assets must be uploaded for the pair to be complete.

Publication does not silently enable additional features to reach a gated binary, accept arbitrary Cargo build arguments or cross-compile an archive.

Command tasks and prerequisites

This reference describes the tool operations used by the skill and workflows. Use increment-versions for version planning rather than constructing a manual sequence from these commands.

Use cargo release-plan version to read JSON identifying the executable and its supported artifact schemas, and cargo release-plan --help for its command interface. The short --version output remains available for installation checks. The copied skill checks schema compatibility rather than requiring an exact package version.

Angle-bracket values below describe arguments to supply, not literal shell input. The integration walkthrough provides concrete PowerShell command sequences.

Input acquisition

Each command acquires current inputs and may reuse those observations after the required input checks during read-only work. Keep inputs stable during read-only command stages. Commands acquire new inputs after their own edits or resolution and for new or relocated workspaces. No workflow-managed cache identifier or observation-storage configuration is needed. Ordinary Cargo/compiler caches and checker build-directory configuration remain independent of release evidence.

Keep evidence outside Cargo’s target directory if it must survive cargo clean.

Release context and identity

cargo release-plan release-context [--manifest-path <path>] [--config <path>]
    [--release-history <rev>] [--merge-target <rev>] [--verbose]
cargo release-plan check-publishing-identity [--verbose]

release-context reads the selected working-tree configuration and prints the release-context artifact. It fetches the configured release branch unless --release-history supplies the known history commit explicitly. --merge-target identifies the intended PR target; a target already in release history produces a null target in the output. It accepts dirty source and does not require the configuration to match a committed file. In contrast, prepare-publish requires tracked committed inputs.

check-publishing-identity requires GitHub Actions OIDC context but no workspace. It exchanges and revokes a short-lived crates.io credential without uploading. Success verifies the caller identity path, not each package’s publisher grant. Do not pass workspace configuration to this command.

Assessment

cargo release-plan report --out-dir <dir> [--release-history <rev>] [--merge-target <rev>]
    [--manifest-path <path>] [--verbose]
cargo release-plan check [--release-history <rev>] [--merge-target <rev>]
    [--manifest-path <path>] [--config <path>]
    [--format text|github] [--verify-packaging] [--verbose]

report writes report.json and patches for released file changes. Enumerate the report’s statuses, not the patch directory, to find every affected package.

check fails on missing increments, inconsistent groups, malformed/stale workspace requirements and required public-dependency breaking propagation. With --config, it also validates publication inputs offline.

Ordinary assessment uses Git and cargo metadata --no-deps: it does not fetch, contact crates.io, resolve dependencies or compile. report, prepare, preview, apply and unconfigured check do not require release_plan.toml or workflow integration. The standalone workflow supplies history/target inputs explicitly and stops at the version-increment PR.

--verify-packaging is an explicit audit against cargo package --list. Divergences are warnings rather than changes to the check result. This probe performs Cargo package preparation and resolution, and allows dirty trees; investigate clean-tree divergence rather than treating it as proof that the ordinary check performed the same work.

Prepare, choose and preview

cargo release-plan prepare --output <dir> [--release-history <rev>] [--merge-target <rev>]
    [--manifest-path <path>] [--verbose]
cargo release-plan analysis-order --report <file-or-dir> [--verbose]
cargo release-plan semver-targets --report <file-or-dir> [--verbose]
cargo release-plan propose --report <file-or-dir> --decisions <decisions.json>
    --out <plan.json> [--verbose]
cargo release-plan preview --prepared <prepared.json> --plan <plan.json> --output <dir>
    [--manifest-path <path>] [--verbose]

prepare supplies a consistent lockfile and evidence for that source, using cargo update --offline --workspace; it can modify the lockfile. preview produces the complete version and dependency changes in a retained workspace before recording the exact result to apply.

analysis-order, semver-targets and propose operate only on supplied artifacts. A report argument can be a JSON file or a directory containing report.json. They invoke neither Git nor Cargo. analysis-order and semver-targets print JSON to stdout; propose writes its plan to --out and prints a readable summary.

Compatibility and publication preflight

cargo release-plan check-compatibility --output <new-directory> [--manifest-path <path>]
    [--prepared <prepared.json> | --plan <resolved-plan.json>]
    [--release-history <rev>] [--merge-target <rev>]
    [--deny-findings] [--verbose]
cargo release-plan check-published [--plan <resolved-plan.json>]
    [--manifest-path <path>] [--verbose]

check-compatibility collects external API evidence without choosing semantic decisions. Select exactly one source mode:

  • --prepared checks the captured original prepared inputs.
  • --plan checks a resolved preview’s retained workspace.
  • Without either, a fresh read-only report uses --release-history and optional --merge-target. These inputs cannot be combined with already-bound evidence.

It checks the selected inputs and regenerates a report from those same observations. Assessed source, configuration and history must remain unchanged during checking. An assessed workspace with selected contracts must have a lockfile accepted by Cargo’s default metadata resolution without changes. It never accepts detached --report evidence. The regenerated report uses report/plan schema 8.

For a resolved preview, input checks at command entry cover both the original workspace and the retained candidate. The original workspace must match the captured initial state or the completely applied plan; the retained candidate must match the captured final state.

The new output directory contains compatibility.json, semver-checks.log and the read-only report. Evidence records the checker identity, comparison versions and sources, completed comparisons and semantic floors. An identical-source canary verifies the checker. When no supported public targets are selected, neither checker execution nor registry access is required. Procedural-macro targets are excluded before either, independently of private-api, and receive explicit unsupported-proc-macro records. Their public version-group members remain eligible.

Operational failures do not become compatibility passes. Completed findings are planning evidence by default; --deny-findings makes an insufficient increment fail the command as well. Read completed, compared and required_impact, not merely the exit code. completed: true can include unavailable comparisons; read their not_compared_reason and retain semantic assessment of every affected package.

check-published --plan validates the resolved target set and fails on a never-published or indeterminate publishable target. Non-publishable packages are excluded. Without a plan, workspace-wide missing/unknown-package discovery is advisory. Neither mode uploads a first version or verifies Trusted Publisher administration.

Inspect and apply

cargo release-plan inspect-plan --plan <expanded.json> [--require-resolved]
    [--manifest-path <path>] [--verbose]
cargo release-plan verify-preview --plan <plan.json> --manifest-path <prospective-manifest>
    [--verbose]
cargo release-plan apply --plan <plan.json> [--dry-run] [--manifest-path <path>] [--verbose]

inspect-plan --require-resolved validates captured inputs and the retained compatibility workspace, then prints publication target names and its manifest path. Non-publishable packages are not publication targets.

verify-preview checks that external analysis left the prospective workspace unchanged. It does not resolve or compile.

apply accepts only a resolved preview and installs its exact manifest and lockfile edits. --dry-run validates those same inputs without writing.

Prepare and publish exact versions

cargo release-plan prepare-publish --source <full-commit-SHA> --output <manifest.json>
    [--manifest-path <path>] [--config <path>] [--verbose]
cargo release-plan publish registry --publication <manifest.json> --output <new-outcome.json>
    [--manifest-path <original-source-Cargo.toml>] [--dry-run] [--verbose]
cargo release-plan publish github --publication <manifest.json> --output <new-outcome.json>
    --batches <new-directory> [--manifest-path <original-source-Cargo.toml>]
    [--dry-run] [--verbose]
cargo release-plan publish binaries --publication <manifest.json> --batch <batch.json>
    --output <new-outcome.json> --artifacts <new-directory>
    [--manifest-path <original-source-Cargo.toml>] [--no-upload]

Preparation requires clean source with HEAD matching the full supplied SHA, tracked configuration and Cargo inputs, a consistent lockfile, and first-parent membership in the configured release branch. It fetches that branch but makes no remote publication writes.

Registry publication requires the original captured source. It queries exact availability and delegates verification and ordered uploads to Cargo. Automatic uploads require Cargo 1.95 or later and the calling workflow’s crates.io Trusted Publisher registration with GitHub OIDC. The Cargo floor supports stable multi-package workspace publication: one cargo publish invocation accepts repeated --package arguments and orders dependent uploads. It is separate from the application’s compiler requirement; revalidate that capability when changing the floor. See Cargo’s publish reference.

Registry --dry-run reads availability without exchanging credentials or uploading. GitHub --dry-run observes tags, releases and assets without writes. Neither dry run is a completed publication outcome.

publish github verifies registry availability for the complete manifest before reconciling package tags and binary releases. It writes an outcome and frozen platform batches with relative routing paths and stable batch_id values. Missing-tag failures retain original-source recovery instructions and do not suppress independent valid work.

publish binaries consumes one frozen batch and its parent manifest. It rechecks tag identity and current assets, builds each executable from the recorded tag commit, and completes missing ZIP/checksum pairs. --no-upload stages that frozen work without tag/release/asset queries or uploads, retaining source and archive verification. Missing source objects may still be fetched from the configured repository, requiring repository-read access. The binary-outcome reference defines its per-item states and completion semantics.

Every attempt needs new outcome and batch/artifact destinations. Name phase outcomes outcome.json inside separate artifact subdirectories when handing them to the final reporter. The publication walkthrough shows the complete sequence without caller-authored batch JSON.

Final publication reporting

cargo release-plan publish report --repository <owner/repo>
    --outcomes <download-directory> --jobs <jobs.json> --output <new-report.md>
    [--publication <manifest.json>] [--no-issue]

Run this in the original GitHub workflow context after downloading phase artifacts. It reads retained outcome.json files and the fixed current job results, then writes Markdown and creates or updates a run-qualified failure issue when publication is incomplete.

--repository identifies the GitHub repository used by the report and must match the repository captured in a supplied valid publication manifest.

The manifest can be omitted when unavailable; the report still runs but cannot claim complete delivery. Incomplete publication exits nonzero even when Markdown and issue creation succeed. --no-issue suppresses GitHub writes only.

Retain run/attempt attribution so the reporter can enforce outcome identity and freshness.

Paths and output

--manifest-path defaults to Cargo.toml in the current directory. For planning, --release-history defaults to the branch advertised by origin, then origin/main. Automation supplies a commit explicitly. release-context is the explicit configuration-aware acquisition operation; unlike ordinary assessment, it fetches the configured branch when no explicit --release-history is supplied.

Ordinary artifact arguments resolve from the invocation directory. Publication relative --config paths resolve from the selected workspace; absolute overrides are also accepted. Publication requires the selected configuration to be tracked inside the repository. Paths recorded in transported publication artifacts are repository-relative, not host-absolute paths.

Requested machine output stays separate from diagnostics. --verbose writes explanatory selection and validation information to stderr.

Runtime boundaries

WorkPrerequisites and effects
Identity/helpInstalled executable; no workspace needed.
Release contextConfiguration, Git and release-history access unless the caller supplies an explicit history commit; no clean-source requirement.
Publishing identity probeGitHub Actions OIDC identity and crates.io exchange/revocation access; no Cargo workspace or upload.
Artifact-only planningSupported report/decision artifacts; no checkout or network.
Ordinary assessmentGit history, work tree and Cargo metadata; offline and no resolution.
Preparation/previewCargo and available offline resolution inputs; can change the relevant lockfile.
External API checkingCompatible checker/compiler and comparison inputs; can compile and retrieve registry sources.
Publication preflightRegistry reads; plan-scoped checks fail closed, workspace discovery is advisory.
Publication preparationClean merged source, Git/Cargo, configuration and release-branch access; private repositories require GitHub authentication.
Live registry workcrates.io access, supported Cargo, registered caller OIDC identity and package verification prerequisites.
Native binariesNative runner and source toolchain/build dependencies; ZIP creation is built in, and upload authority is separate from compilation.
Final reportRetained outcomes, current job results and GitHub run context; issue writes unless --no-issue.

Do not treat the compiler used to install the controller, Cargo’s publication runtime floor and a tagged source’s toolchain as one interchangeable requirement.

Artifact reference

Local planning and post-merge publication serve different purposes and have separate schema lifecycles. Use tool-produced evidence where specified; do not fabricate captured inputs, identities or publication outcomes.

You choose output destinations and are responsible for their placement relative to source, configuration, existing evidence and other data. Commands write to the supplied locations without checking for input collisions. Each writer manages only its documented artifacts and owned workspace directories.

Preview validates captured inputs before invalidating its previous completion marker. Missing or malformed preparation and stale captured inputs leave that marker untouched. An existing completion marker does not prove that the captured inputs are still valid; consumers still check those inputs.

Captured source inputs

Preparation and preview capture declared inputs, Git-tracked files and source-directory contents. Source directories include src and dedicated custom target or build-script directories below each package root, including path dependencies. A target at the package root or outside it contributes its declared file without recursively capturing the package root or its ancestors.

Supporting modules and other build inputs outside those source directories must be Git-tracked. Use git add; a commit is not required. The tool does not interpret Rust modules or invoke a compiler to discover additional inputs.

Local decisions and plans

A decisions document is caller-authored literal JSON:

{
  "schema_version": 2,
  "changes": [
    { "name": "widget", "impact": "nonbreaking" },
    { "name": "widget_impl", "impact": "patch" }
  ]
}

changes[].impact is semantic: breaking, nonbreaking or patch. Non-publishable packages have no semantic impacts.

The proposed-plan format uses report/plan schema revision 8. This is a literal example of that format:

{
  "schema_version": 8,
  "increments": [
    { "name": "widget", "bump": "minor" },
    { "name": "widget-cli", "version": "2.0.1" }
  ]
}

increments[].bump is numeric: major, minor or patch. Supply exactly one of bump and version per entry. Names select tracked workspace members; group members expand together. Explicit targets cannot lower declared versions, and group targets use plain major.minor.patch versions.

Preview produces an expanded plan with explicit package versions and the resolved source/files needed for application, including resolved file contents, original inputs and evidence_manifest_path.

Treat prepared.json and the resolved object as opaque. Preserve them intact; regenerate unsupported or stale evidence with prepare and preview.

Reports

The revision-8 report’s top level contains:

FieldContent
schema_versionReport/plan format revision.
headSource commit associated with the assessment.
release_historySelected actual release-history commit.
merge_targetOptional final parent commit supplying anticipated releases.
packagesPublishable package assessments.
non_publishable_packagesTracked alignment targets without release assessments.
groupsComplete group membership across both package arrays.

Each publishable entry includes name, declared_version, status, changed, stat, dependencies, public_origins, dependents, consumer_contract and proc_macro. Optional evidence includes its group, anchor, patch path and advisory untracked files. An anchor can identify a release-history version change or the final snapshot of an anticipated parent version; the report’s history/target fields identify that context.

Changed entries distinguish:

sourceEvidence
packagePackaged file path and change kind.
inheritedChanged inherited workspace field.
lockfileChanged binary installation dependency identity.

Dependencies record name, req and exact_pin. public_origins is the sorted set of defining workspace package names exposed directly or through another exposed owner’s API. It is independent of the dependency paths supplying the types and is required even when empty. consumer_contract is true for a library offering a supported API for direct consumers, rather than declaring private-api = true. proc_macro independently records Cargo’s procedural-macro library target kind. Both booleans are required. A public macro can have both true while remaining unsupported for direct comparison. Neither flag removes semantic, dependency or publication obligations.

Non-publishable packages carry name, declared_version and an optional group, not a status or semantic impact. Group records include complete sorted members and their highest declared version.

Patch paths are relative to the report directory. Patches are zero-context unified file diffs, not the complete report. An inherited-only or lockfile-only change can have no patch.

A report’s HEAD alone does not prove that an arbitrary dirty checkout matches the evidence. check-compatibility regenerates a bound read-only report from prepared inputs, a resolved preview or a fresh source assessment. It does not accept a detached report.

Release context

release-context prints JSON with these fields:

FieldMeaning
schema_versionRelease-context format revision, currently 2.
repositoryConfigured GitHub owner/repository.
release_branchConfigured release branch.
release_historyCommit delimiting actual release-branch history.
merge_targetAnticipated unmerged parent commit, or null when no additional target is needed.
headAssessed source HEAD.
workspace_manifestRepository-relative workspace manifest location.
config_pathRepository-relative configuration location.
concurrency_groupStable workspace-scoped release concurrency identity.

This is acquired context, not publication intent. Save it for the local planning run and reuse its history/target pair. Refresh both before application; do not replace either beneath prepared evidence.

Compatibility evidence

Each check-compatibility invocation uses a new output directory and writes compatibility.json, semver-checks.log and a regenerated read-only report. The evidence retains checker identity and exact comparison versions. Its linked report identifies any anticipated-parent anchor supplying the comparison source.

Compatibility evidence uses schema 3:

FieldMeaning
schema_versionCompatibility-evidence format revision.
checkerIdentified checker version, or an explanation when no identity was established.
reportLocation of the source-bound report generated for this comparison.
completedWhether required input checks and supported comparisons completed, with exclusions recorded.
findingsWhether completed comparisons found an insufficient increment.
packagesComparison records containing the fields below.

Each comparison record contains name, compared, nullable baseline_version, and nullable required_impact. A compared package uses a published version or its anticipated parent’s version and final source. An unavailable comparison has compared: false and no comparison version or required impact. Unavailable records also contain not_compared_reason: unsupported-proc-macro for a target the checker cannot compare directly, or no-baseline for a supported target without a comparison version. Compared records omit this field. Affected macro records include private packages and macro members of affected version groups; unrelated unchanged packages need no record. An operational failure can leave only earlier completed records; inspect the overall completion flag and retained diagnostics rather than treating absent records as passes.

Require completed: true before using the result. A package’s compared: false means no comparison was available, not proof of compatibility. required_impact is a semantic breaking or nonbreaking floor; null establishes no minimum. The author still judges behavioral, CLI, format and feature-subset effects. This includes APIs and behavior supplied through re-exports, which are not fully covered by the checker, and macro invocation syntax and generated code. See semantic assessment.

Do not confuse a completed comparison with a passing merge gate: --deny-findings additionally rejects insufficient increments. Source inputs are checked at command entry. Callers and tools invoked for read-only work must keep assessed source, configuration and history unchanged during comparison; operational errors never become semantic passes.

Artifact-only command output

analysis-order emits a dependency-first array:

[
  { "order": 1, "packages": ["widget_impl"], "cyclic": false },
  { "order": 2, "packages": ["widget"], "cyclic": false },
  { "order": 3, "packages": ["widget-cli"], "cyclic": false }
]

This example assumes those are the publishable packages. Every publishable member appears once, including unchanged ones; non-publishable packages do not. Cycle batches represent actual dependencies, not group alignment.

semver-targets emits sorted directly checkable public package names, or [] when none are selected. Procedural macros are excluded regardless of consumer policy; their public version-group members remain eligible. inspect-plan emits:

{
  "publication_targets": ["widget", "widget-cli", "widget_impl"],
  "evidence_manifest_path": null
}

This example represents structural inspection without retained preview evidence. For a resolved artifact, the manifest path identifies the verified prospective workspace. Request --require-resolved before relying on that workspace for the complete local release flow.

Publication manifest

prepare-publish creates this envelope:

{
  id,
  publication: {
    schema_version: 1,
    tool_version,
    source,
    workspace_manifest,
    config_path,
    configuration,
    packages: [
      { name, version, manifest, binary: null | { name, targets } }
    ]
  }
}

This is a field-shape sketch, not JSON input. source is the full publication commit. Workspace, package and configuration paths are repository-relative. configuration captures the effective committed configuration. A binary’s name is the executable name, not necessarily its package name.

The package array includes every publishable exact version at that source, including unchanged packages, and excludes non-publishable packages. The artifact records producer identity and schema independently of local-plan schemas.

id links subsequent work to the captured payload. It is an integrity and linkage check, not a signature or independent authorization. A fetched branch tip is not part of the captured intent digest. Do not change the payload, recompute its identity by hand or add mutable publication flags.

Registry outcome

Registry outcomes use schema 1 and record:

FieldMeaning
schema_versionOutcome format revision.
publication_idOriginal publication manifest identity.
phaseregistry.
dry_runWhether the attempt only observed intended work.
completeWhether this live registry attempt established completion.
packagesEntries with exact name, version and state.
errors, notesFailure diagnostics and explanatory observations.
githubOptional {run_id, run_attempt} linkage captured in GitHub Actions.

The exact state spellings are:

StateInterpretation
already_presentThe exact version was observed without needing this attempt to publish it.
publishedPublication completed and the exact version was observed.
would_publishDry-run work for a missing version.
missingThe requested version remains absent.
unknownAvailability could not be established.

A dry run never sets complete to true. Use the outcome and command diagnostics together; unknown queries and partial failure are not reduced to successful skips. Each attempt writes a new outcome path.

GitHub outcome

GitHub reconciliation outcomes use schema 1:

FieldMeaning
schema_versionOutcome format revision.
publication_idOriginal publication manifest identity.
phasegithub.
dry_runWhether the attempt only observed intended work.
completeWhether live GitHub reconciliation completed; binary delivery can still remain.
packagesPackage/version/tag identities with the state and source evidence described below.
batchesRouting entries containing target, path and batch_id for separate platform-batch files.
planned_targetsNative targets selected for potential binary work, not batch payloads or delivery evidence.
errorsReconciliation diagnostics.
githubOptional {run_id, run_attempt} linkage captured in GitHub Actions.

Each package record contains name, version, tag and state, with these state spellings:

StateInterpretation
pendingThis record does not establish completed reconciliation.
completeThe required tag and, for a binary package, GitHub release were reconciled.
would_create_tagA dry run identified a missing tag.
would_create_releaseA dry run identified a missing GitHub release.
failedReconciliation for the package failed.

The nullable source field retains an observed commit identity. A failure can retain an unverified observation; this field alone does not authorize a build. The nullable recovery_source is the original publication source retained for missing-tag recovery, while observed_version is the candidate version when available. Failures need not have missing-tag recovery evidence.

A dry run never sets the outcome’s complete field to true. A live complete GitHub outcome does not imply that its binary archives are uploaded. Each batch path is relative to the separately emitted batch directory; retain those files and the parent publication manifest alongside the outcome.

Platform batch

publish github produces a separate schema 1 platform-batch file for each selected native target. Its shape is:

{
  schema_version: 1, publication_id, repository, target, batch_id,
  binaries: [{name, bin, version, tag, source_sha}]
}

This is a field-shape sketch, not caller-authored JSON. name identifies the Cargo package and bin its executable. source_sha is the frozen peeled package-tag commit. publication_id identifies the parent manifest; batch_id identifies the batch’s exact work, including those source commits. repository and target retain their configured identities.

Binary outcome

publish binaries produces a schema 1 outcome:

{
  schema_version: 1, publication_id, phase: "binaries",
  target, no_upload, complete, batch_id,
  github?: {run_id, run_attempt},
  items: [
    {
      binary: {name, bin, version, tag, source_sha},
      status, stage, diagnostic, cleanup_error
    }
  ]
}

github is omitted outside GitHub Actions. Item diagnostic and cleanup_error values are nullable strings. The supported status/stage combinations are:

StatusStageExecution mode
publisheduploadUpload
skipped-completerefreshUpload
staged-onlypackageNo upload
failedrefresh or uploadUpload
failedsource, build, package or cancelledEither
unattemptedcancelledEither

A failed item includes a diagnostic. Cleanup failure is recorded separately without replacing the item’s operation status. complete requires successful upload-mode results for every requested binary and no cleanup failures. no_upload staging never establishes delivery completion.

Retain the original manifest and platform batch with these outcomes. Consume the tool-produced artifacts rather than synthesizing them; their identities and attempt ordering are required evidence for reporting.

Derived batches and later outcomes

GitHub reconciliation emits new platform batches linked to the original manifest. A batch fixes the native target, package versions, executable names, tags and actual peeled tag commits. Its stable batch_id binds that content; each outcome routing entry supplies target, relative path and batch_id. Those observations do not get written back into the original manifest.

Native execution refreshes remote completeness before building. Later reconciliation can emit another batch for remaining work without mutating an older batch. Binary outcomes link both the publication and batch identities. Phase outcomes include optional github: {run_id, run_attempt} metadata, which the workflow preserves across artifact transport.

An older successful binary outcome cannot satisfy newer GitHub evidence of missing assets. This remains true when the new batch contains exactly the same requests and therefore has the same batch_id. Its outcome must be at least as new as the selected GitHub reconciliation.

Consume these artifacts through the matching tool/action interface rather than writing them manually. Missing or incompatible artifacts require explicit recovery; they are not a valid empty work set.

Reporter job results

publish report --jobs reads a JSON object with fixed keys. Each value is one of success, failure, cancelled or skipped, taken from GitHub’s workflow job-result context rather than the broader check-run conclusion vocabulary:

{
  "prepare": "success",
  "registry": "success",
  "github": "success",
  "binaries": "success"
}

This demonstrates the format, not a default successful state. Supply observed results from the current workflow; binaries is the binary matrix job result. Skipped required phases are incomplete. A skipped binary matrix is acceptable only when the selected GitHub outcome requires no platform batches. Do not omit failed/skipped jobs, rename keys after your caller’s display names, or synthesize success from existing assets.

The reporter recursively reads outcome.json files beneath --outcomes, retaining their artifact subdirectories and run/attempt linkage. It compares the latest applicable outcomes with current job results and the original manifest. Old successes cannot hide failed jobs or newer missing-asset evidence.

--output names the new Markdown report. The command can report and file a failure issue without the publication manifest, but always marks that case incomplete. --no-issue suppresses issue writes, not GitHub-context validation or incomplete-delivery failure.