Skip to main content

vership

crates.io PyPI CI License: MIT codecov

A release orchestrator that handles version bumping, changelog generation, and publishing — with zero configuration.

$ vership bump patch
✓ Working tree is clean
✓ On branch main
✓ Tag v0.4.1 does not exist
✓ Lock file in sync
✓ Lint passes
✓ Tests pass
→ Bumping 0.4.0 → 0.4.1
→ Updated rust
→ Updating version files
→ Generated changelog (3 entries)
→ Running artifact: cargo run --release -- schema generate
→ Committed: chore: bump version to v0.4.1
→ Tagged: v0.4.1
→ Pushed to origin

Why vership?

Most release tools require config files, plugins, or CI integration before they do anything. vership works out of the box: it detects your project type, runs pre-flight checks, generates a changelog from conventional commits, bumps the version, and pushes — in one command.

vership cargo-release semantic-release git-cliff
Zero config Yes No No No
Multi-ecosystem Rust, Node, Python, Go, Gradle, Ansible Rust only Node only Any (changelog only)
Changelog generation Built-in External tool Plugin Yes
Multi-file version sync Built-in No Plugin No
Artifact regeneration Built-in No Plugin No
Pre-flight checks Built-in Partial No No
Single binary Yes Yes No (Node runtime) Yes
Agent-friendly (--json, schema) Yes No No No

Install

# Homebrew
brew install rvben/tap/vership

# From crates.io
cargo install vership

# From PyPI
pip install vership

# From source
git clone https://github.com/rvben/vership && cd vership && cargo install --path .

Quick Start

No setup required. Just use conventional commits and run:

vership bump patch    # 0.1.0 → 0.1.1
vership bump minor    # 0.1.1 → 0.2.0
vership bump major    # 0.2.0 → 1.0.0

Initial release? Tag the version already in your manifest:

vership release       # tag the current Cargo.toml/package.json version as-is

Interrupted run? Continue where it stopped:

vership resume        # finishes commit/tag/push using the on-disk version

Preview before releasing:

vership bump patch --dry-run

Commands

vership bump <patch|minor|major>   Bump version, generate changelog, tag, push
  --dry-run                        Preview without making changes
  --skip-checks                    Skip lint and test checks
  --no-push                        Stop after tagging, do not push
  --prepare                        Commit changes, but do not tag or push
vership release                    Tag the on-disk version as-is (no bump)
  --dry-run / --skip-checks / --no-push / --prepare    same as bump
vership resume                     Finish an interrupted bump (trusts on-disk version)
  --dry-run / --skip-checks / --no-push / --prepare    same as bump
vership changelog [patch|minor|major]  Preview the exact release section
vership preflight [patch|minor|major]  Check the intended release target
vership status                     Show version, project type, unreleased commits
vership verify [<version>]         Verify a release is live on all publish targets
  --targets <list> / --skip <list>         Filter targets
vership update-local [<version>]   Update this machine's installs to a version
  --managers <list> / --skip <list>        Filter package managers
  --dry-run                        Print the install commands without running them
vership config init                Create vership.toml with defaults
vership schema                     JSON schema for agent integration
vership completions <shell>        Generate shell completions

bump auto-detects an interrupted prior run when the on-disk version already matches the expected post-bump value AND the working tree is dirty. The explicit resume subcommand is the escape hatch for cases where auto-detection doesn't fire.

What It Does

vership bump patch runs this flow:

  1. Detect project type (Rust, Rust+Maturin, Node, Go, Python, Gradle, Ansible Collection)
  2. Check clean working tree (including untracked files), correct branch, intended tag doesn't exist locally or on origin, lockfile in sync
  3. Check lint and tests pass (skippable with --skip-checks)
  4. Bump version in project files (Cargo.toml, package.json, pyproject.toml, gradle.properties, galaxy.yml) or tag directly (Go)
  5. Update version references in extra files (version_files)
  6. Generate changelog from conventional commits since last tag
  7. Regenerate artifacts from commands (artifacts)
  8. Commit, tag, and atomically push the branch and tag

A repo carrying several manifests is resolved by precedence: galaxy.yml, Cargo.toml, package.json, go.mod, pyproject.toml, Gradle. A package.json marked "private": true is skipped in that order and considered only once nothing else matches, so a vendored test harness or docs site does not outrank the manifest the repo actually releases. Set [project] type in vership.toml to override detection entirely.

Your existing CI release workflow (GitHub Actions, etc.) triggers on the tag push as usual. vership handles the local side only.

Release commits carry a Vership-Release trailer. If a pre-push hook or the network fails after the local tag is created but before origin receives it, rerunning the same command safely removes that unpublished tag and retries the same version. Vership never rewrites a tag that already exists on origin.

Post-Release Verification

A pushed tag does not mean the release published: a CI job can fail after tagging, skip a registry, or upload an empty release. vership verify checks that a version is actually live everywhere the repo publishes:

$ vership verify
verify 0.5.6
  ok   tag        v0.5.6
  ok   release    0.5.6
  ok   crates     0.5.6
  FAIL pypi       not found

Targets are autodetected: tag and release from the GitHub remote, crates from Cargo.toml (unless publish = false), pypi from pyproject.toml, npm from a non-private package.json, homebrew and ghcr from publish steps in .github/workflows/. The [verify] section in vership.toml can skip targets or set the tap, formula, and image coordinates:

[verify]
skip = ["npm"]
tap = "owner/homebrew-tap"     # default: <owner>/homebrew-tap
formula = "name"               # default: repo name
image = "owner/name"           # default: owner/repo lowercased

Exit codes: 0 when every target passes; 8 (unpublished, retryable) when anything is missing, version-mismatched, or errored. Publishing may still be in flight, so 8 is a state, not a failure. To block until a release is fully live, compose with tarry:

tarry cmd --timeout 20m -- vership verify

Local Install Update

Releasing a tool you use yourself leaves your own machine on the old version, and often on several old versions at once: the same executable can be installed by cargo, uv, npm and Homebrew, and whichever directory comes first on $PATH is the one you actually run. vership update-local closes that gap:

$ vership update-local
update-local 0.5.13
  ok   cargo      0.5.12 -> 0.5.13
  ok   uv         0.5.13
  ok   vership    /Users/you/.cargo/bin/vership (cargo 0.5.13)
                  shadowed /usr/local/bin/vership (unmanaged)

Only managers that already hold the package are touched; nothing is newly installed. The package name each manager is asked about comes from the same detection verify uses: the crate name from Cargo.toml, the project name from pyproject.toml, the package name from package.json, the formula from the detected tap.

Before anything is installed, each manager's registry is checked for the target version. This matters because an unpinned reinstall is a silent downgrade: cargo install <crate> --force fetches whatever the registry currently serves and exits 0, so running it a minute after vership bump would reinstall the version you just replaced. A registry that has not caught up reports unpublished (exit 8, retryable) and runs nothing, for every manager and not only the lagging one: registries publish at different speeds on the same release, and a half-updated machine is not a state "retry and you are done" can describe. Exit 8 therefore means the machine is exactly as it was. --dry-run reports the same wait and the same exit 8, because a preview that claimed the installs would run would be predicting something a real run at that moment would not do; the commands are still printed. It composes with tarry to close a release in one line:

vership bump patch && tarry cmd --timeout 20m -- vership update-local

After the installs, every copy of every executable on $PATH is reported in $PATH order. Ownership is decided by resolved file identity, not by directory, so a hand-copied binary sitting next to a uv shim is reported as unmanaged rather than credited to uv, and its version is left blank rather than guessed. A copy resolving into a Homebrew keg is the one exception, credited to brew at the version the keg path states, since Homebrew owns everything under Cellar/ by construction. That covers the copies no manager was asked about: brew is probed only when the project's tap is detected. A mise shim is followed to the executable it hands off to: mise keeps a shim for every name it has ever installed ahead of everything else on $PATH, and a shim for a tool mise no longer manages runs the next copy down, so the report shows shims/vership -> .cargo/bin/vership (cargo 0.5.22) rather than an unmanaged file. Exit 1 when an install fails, or when a stale or unmanaged copy shadows the one that was updated: an update nothing reaches is not an update.

Installs that would not reproduce the release are reported and left alone: a git or alternate-registry install, and a cargo install --path install of a different project. A --path install of the project you are in is rebuilt from it.

Caveat: the uv update is a pinned uv tool install <pkg>==<version> --reinstall --refresh, which is the only deterministic form (uv tool upgrade has no --refresh and reports nothing to do against a cached index). A pinned reinstall drops --with extras the tool was originally installed with; re-add them by hand if you use them.

Changelog Format

Generated from conventional commits in Keep a Changelog format:

## [0.2.1](https://github.com/you/repo/compare/v0.2.0...v0.2.1) - 2026-03-28

### Added

- **api**: add user endpoint ([abc1234](https://github.com/you/repo/commit/abc1234))

### Fixed

- correct null handling in parser ([def5678](https://github.com/you/repo/commit/def5678))
Commit type Section
feat Added
fix Fixed
perf Performance
change Changed
feat! / BREAKING CHANGE Breaking Changes
chore, docs, ci, test, refactor, build, style Excluded

Curated Unreleased notes

Vership promotes hand-written notes under either ## [Unreleased] (the canonical Keep a Changelog form) or ## Unreleased. A fresh canonical ## [Unreleased] heading is left behind for the next release. Duplicate or malformed Unreleased headings stop the release rather than risking incomplete notes.

When the Unreleased section contains curated content, the release keeps those notes and merges in the generated entries they do not cover. A generated entry joins the end of the curated section of the same name (### Fixed under ### Fixed), sections the notes lack are appended in the generated order, and an entry is left out when the notes already cover it, so a hand-written description stands in for the generated line. A note covers a commit in either of two ways: the commit itself added the note under ## [Unreleased] (the usual case, a change that ships with its own release note), or the note cites the commit's hash (fixed the parser (abc1234), for a note written in a later commit). Vership lists every merged, noted and cited entry on stderr, and both vership changelog <level> and vership bump <level> --dry-run show the exact section that would be released.

Set changelog.curated = "replace" in vership.toml to make the curated notes the whole release instead; every generated entry is then dropped and listed. Version link-reference definitions at the bottom of the file ([1.2.0]: https://...) are removed during promotion, and every heading or reference that used one is rewritten to an inline link first.

Version Files

Projects often have version strings scattered across READMEs, docs, and companion packages. vership updates them all during the bump:

# Text mode: search/replace with placeholders
[[version_files]]
glob = "README.md"
search = "rev: v{prev}"       # {prev} = old version
replace = "rev: v{version}"   # {version} = new version

# Field mode: update JSON fields directly
[[version_files]]
glob = "npm/*/package.json"
field = "version"

# Wildcard: update all values in an object
[[version_files]]
glob = "package.json"
field = "optionalDependencies.*"

All matched files are staged and included in the release commit automatically.

Artifacts

Some projects need to regenerate files from the built binary during release (schemas, rule exports, API docs). vership runs these commands and commits the output:

# Capture stdout to a file
[[artifacts]]
command = "cargo run --release -- schema generate-json"
output = "schema.json"

# Or let the command write its own files
[[artifacts]]
command = "make generate-docs"
files = ["docs/api.json"]

Commands run from the project root via sh -c. Output files are staged automatically. If a declared file doesn't exist after the command runs, the release aborts with a clear error.

Configuration

vership works without any configuration. Only create vership.toml if you need to override defaults:

[project]
branch = "main"              # Branch to release from
type = "go"                  # Skip detection: rust, rust-maturin, node, go,
                             # python, gradle, ansible-collection

[hooks]
pre-bump = "make verify"     # Run before version bump
post-push = "echo done"      # Run after push (e.g. trigger Homebrew update)

[checks]
lint = true                  # Run lint checks (default: true)
tests = true                 # Run tests (default: true)
allow_untracked = false      # Require untracked files to be added or ignored
lint_command = "npm run lint" # Override default lint command
test_command = "npm test"     # Override default test command

[changelog]
unconventional = "exclude"   # "exclude", "include", or "strict"
curated = "merge"            # "merge" generated entries into curated Unreleased
                             # notes, or "replace" them with the notes

Independent projects in one repository

Register each independently versioned package in the repository's root vership.toml. The existing root project keeps its v<version> tags and commands.

[projects.agent]
path = "packaging/python-agent"
tag_prefix = "shinyhub-agent-v"
# Optional literal repository-relative files/directories used for commit history.
# Default: the package directory. Shared paths may appear in several projects.
paths = ["packaging/python-agent", "examples/agent", ".github/workflows/publish-agent.yml"]

Give the package its own packaging/python-agent/vership.toml:

[project]
type = "python"

[checks]
lint_command = "uv run ruff check ."
test_command = "uv run pytest"

[verify]
targets = ["tag", "pypi"]

Select the project explicitly, from anywhere inside the repository:

vership --project agent status
vership --project agent changelog minor
vership --project agent bump minor --dry-run
vership --project agent bump minor --prepare
# Review the release commit, then authorize publication:
vership --project agent release
vership --project agent verify
vership --project agent update-local --dry-run

The repository branch and clean-index checks apply to the entire worktree. Manifests, checks, hooks, version-file globs, artifact paths, and CHANGELOG.md are relative to the selected package directory. Named projects inherit the root release branch; an explicit conflicting branch is an error. Calling a project command inside a registered package without --project is an error.

A package's history includes commits that touch any of its paths. Root history excludes commits touching only registered package directories. Mixed commits and explicit shared paths can appear in both changelogs. Compare links use the full project tags, and tags are sorted by semantic version within each namespace. Unmerged higher tags block releases until the history is reconciled.

Named projects must declare verify.targets: workflow inference is disabled for them. Supported targets are tag, release, crates, pypi, npm, homebrew, and ghcr. Registry names come from the selected manifest. Homebrew requires verify.tap and verify.formula; GHCR requires verify.image. --targets narrows this contract and rejects undeclared targets. Root verification and local install discovery exclude registered independent packages, even at equal versions.

Preparation records the reviewed commit in a local refs/vership/prepared/ ref that is never pushed. If HEAD moves afterward, release refuses to tag it. Review the new content and run release --prepare to refresh preparation for the same version, then release. Refresh updates the prepared section for intervening project commits and preserves its curated notes. An amended or rebased preparation also requires explicit refresh. Prepare one project at a time: a release refuses to push another project's unfinished release commit. Refreshing and retrying an unpublished local tag do not replay bump hooks or artifacts. Interrupted runs may resume only declared release files; foreign staged or dirty files are preserved and cause an error. Hook-created undeclared changes also stop the release before committing or pushing. An interruption before the release commit can replay bump hooks and artifact commands on resume; those commands must tolerate retries.

Project directories must exist, remain within the repository, and cannot be nested or duplicated. File paths must stay within their selected project. Independent packages must own their workspace and lockfile; inherited Cargo, Node, pnpm, or uv workspace locks are unsupported. Batch releases and dependency version propagation between independent projects are not supported. Existing repositories without a registry retain their working-directory selection behavior.

Agent Integration

vership is designed to work with AI coding assistants:

# Machine-readable project status
vership status --json

# Full command schema for tool discovery
vership schema

License

MIT

Releasing

Vership owns versioning, changelog generation, release commits, and tags. See the release runbook for the verified workflow and recovery policy.

Metadata

Release files for vership 0.5.28

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for vership 0.5.28
File Size Uploaded
vership-0.5.28.tar.gz 221.6 kB Details

Built distributions (wheels)

Table of built distributions (wheels) for vership 0.5.28
File
vership-0.5.28-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl Python 3 none Linux glibc 2.17+ x86-64 Details
vership-0.5.28-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl Python 3 none Linux glibc 2.17+ ARM64 Details
vership-0.5.28-py3-none-macosx_11_0_arm64.whl Python 3 none macOS 11.0+ ARM64 Details
vership-0.5.28-py3-none-macosx_10_12_x86_64.whl Python 3 none macOS 10.12+ x86-64 Details

Total release size: 11.8 MB

Release files / vership-0.5.28.tar.gz

Download URL vership-0.5.28.tar.gz
Size 221.6 kB
Tags Source
SHA-256 checksum
How to use checksums
2361f6447b2f6fc2d8d5b99f533d7736a833cefd11906b3b4d5c078772ce04f6
BLAKE2b-256 checksum
How to use checksums
6fe720946a23e8be440d7935e4ae654bfdf7d05910dcf235e56cb00832674be5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.13.1 {"installer":{"name":"uv","version":"0.13.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / vership-0.5.28-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL vership-0.5.28-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 3.1 MB
Tags Linux glibc 2.17+ x86-64 Python 3
SHA-256 checksum
How to use checksums
317e93b0941983e7b4fd2f4615705efdee936bef662b1be731195ae39c523cc4
BLAKE2b-256 checksum
How to use checksums
07d7f5536570b8f915a4bf520203a569599873269cae2a686782d6155e698544
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.13.1 {"installer":{"name":"uv","version":"0.13.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / vership-0.5.28-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL vership-0.5.28-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 2.9 MB
Tags Linux glibc 2.17+ ARM64 Python 3
SHA-256 checksum
How to use checksums
e95abba91eed03e7c594c6546f9f94ef4377e4809d9c4a5d332a8af05e811d26
BLAKE2b-256 checksum
How to use checksums
52c253e50d33ed42f5332fdd4fd60e4328374d82272a5395955dfaf1014a2132
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.13.1 {"installer":{"name":"uv","version":"0.13.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / vership-0.5.28-py3-none-macosx_11_0_arm64.whl

Download URL vership-0.5.28-py3-none-macosx_11_0_arm64.whl
Size 2.8 MB
Tags Python 3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
631a935b5768904b0e0204a426f45adaea62aadc44552afcfda40383e6ae6b78
BLAKE2b-256 checksum
How to use checksums
21e8f61ac92dc5765472148a48747be672e127ec209a0b76993456df88d8f21d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.13.1 {"installer":{"name":"uv","version":"0.13.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / vership-0.5.28-py3-none-macosx_10_12_x86_64.whl

Download URL vership-0.5.28-py3-none-macosx_10_12_x86_64.whl
Size 2.9 MB
Tags Python 3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
6fb54b90a09da53ec074fcbeddff8bdc3c9ffdf9e12e14583ff656d88518f8dd
BLAKE2b-256 checksum
How to use checksums
f9f4df834086742c3c4b34d81150cfb61723ae784664341ce85692eb34f4bed2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.13.1 {"installer":{"name":"uv","version":"0.13.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

0.5.29

5 release files

This release

0.5.28 This release

5 release files

0.5.27

5 release files

0.5.26

5 release files

0.5.25

5 release files

0.5.24

5 release files

0.5.20

5 release files

0.5.19

1 release file

0.5.18

1 release file

0.5.17

1 release file

0.5.16

1 release file

0.5.15

1 release file

0.5.14

1 release file

0.5.13

1 release file

0.5.12

1 release file

0.5.11

1 release file

0.5.10

1 release file

0.5.9

1 release file

0.5.8

1 release file

0.5.7

1 release file

0.5.6

1 release file

0.5.5

1 release file

0.5.4

1 release file

0.5.3

1 release file

0.5.2

1 release file

0.5.1

1 release file

0.5.0

1 release file

0.4.5

1 release file

0.4.4

1 release file

0.4.3

1 release file

0.4.2

1 release file

0.4.1

1 release file

0.4.0

1 release file

0.3.1

1 release file

0.3.0

1 release file

0.2.4

1 release file

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page