Hooversion is a single static binary for Conventional Commit linting and
semantic release automation. It is designed to replace separate commitlint,
semantic-release, changelog, release-note, tag, and GitHub release glue —
with no runtime dependencies: no Node, no Bun, no npm packages.
hooversion init
hooversion lint --from origin/main --to HEAD
hooversion plan
hooversion release --dry-run
hooversion release
hooversion verify-release --repository openhoo/hooversion --tag v1.1.1
hooversion doctor
hooversion migrate
hooversion help
hooversion version
hooversion appInstall with Go:
go install github.com/openhoo/hooversion/cmd/hooversion@v1.1.1Or download the hooversion prebuilt static binary from the
GitHub Releases page and put
it on your PATH. Start the GitHub App server from that release binary with
hooversion app; release archives do not contain a standalone versionhoo-app
executable. The same releases power the GitHub Actions integration below.
hooversion init # writes hooversion.yaml (and optional workflows)
hooversion lint --last # lint the most recent commit
hooversion plan # preview the next release plan
hooversion release --dry-run
hooversion release # bump versions, changelogs, tags, push, publish
hooversion doctor # sanity-check config, git, tokensinit [--force] [--no-workflow] [--action-owner-repo <owner/repo>] [--action-ref <ref>] [--hooversion-version <version>]generateshooversion.yamlplus optional GitHub workflows.initrefuses to overwrite an existing configuration without--force;--forcerefuses duplicate config files.--no-workflowgenerates only the config.lint --last | --edit <commit-msg-file> | --from <ref> [--to <ref>]validates Conventional Commits; exactly one selector is required.plan [--config <path>]prints the next release plan.release [--dry-run] [--no-push] [--no-github] [--config <path>].verify-release [--repository <owner/repo>] [--tag <tag>]independently downloads a published release, resolves its tag to a commit, and requires exactSHA256SUMScoverage. Strict flags verify SBOMs, embedded licenses, Sigstore bundles, GitHub attestations, or annotated-tag signatures.--outputwrites a SLSA VSA only after every selected check passes. See Release verification.doctor [--config <path>]printsok:/warning:/error:findings.migrateconverts a legacy TypeScript config (see Migration).help,version.
Hooversion reads its configuration from the current directory: hooversion.yaml,
.hooversion.yaml, hooversion.yml, .hooversion.yml, hooversion.config.json,
or hooversion.json, in that order. An explicit path passed via --config
takes precedence.
| Key | Type | Default | Description |
|---|---|---|---|
branches |
string[] |
["main"] |
Release branches; every entry must be a valid Git branch name. |
tagFormat |
string |
v${version} |
Tag template for single-package repos. Placeholders: ${name}, ${version}. |
independentTagFormat |
string |
${name}@v${version} |
Tag template used when more than one package is configured. |
packages |
list | required, non-empty | One entry per releasable package. |
packages[].name |
string |
manifest name | Falls back to the name read from the manifest. |
packages[].path |
string |
. |
Package root relative to the config directory. |
packages[].type |
string |
required | One of node, rust, python, version-file. |
packages[].manifest |
string |
per type | Defaults to <path>/package.json (node), <path>/Cargo.toml (rust), <path>/pyproject.toml (python), <path>/version (version-file). |
packages[].changelog |
string |
<path>/CHANGELOG.md |
Changelog file updated on release. |
packages[].scopes |
string[] |
[name] |
Commit scopes that route to this package (comma-separated scopes are split). |
packages[].dependencies |
string[] |
[] |
Local package names this package depends on; dependents get patch bumps when a dependency releases. |
packages[].assets |
string[] |
[] |
Files uploaded to the GitHub Release. |
hooks.beforeRelease |
string[] |
[] |
Shell commands run before any files change. |
hooks.afterVersion |
string[] |
[] |
Shell commands run after manifests/changelogs are updated. |
hooks.afterRelease |
string[] |
[] |
Shell commands run after publishing. |
github.enabled |
bool |
true |
enabled: false fully disables GitHub integration. |
github.releases |
bool |
true |
Create GitHub Releases during release. |
github.repository |
string |
origin remote | owner/repo override when it cannot be derived from Git. |
github.apiUrl |
string |
https://api.github.com |
GitHub API base URL (GitHub Enterprise supported). |
outputDir |
string |
.hooversion |
Directory for managed release outputs. |
push |
bool |
true |
Push the release commit and tags. |
Validation is fail-closed: duplicate package names, unknown or self
dependencies, dependency cycles, invalid tag formats, and invalid branch or
package names are rejected before anything runs. See examples/ for a
single-package Node setup and an independent Rust workspace setup. Packages can
use node, rust, python, or version-file manifests. The version-file
type reads and writes a plain text file containing only the semantic version,
which fits container or Go repos that already keep release versions in files
such as transports/version.
featreleases a minor version.fixandperfrelease a patch version.!orBREAKING CHANGE:releases a major version.- Release commits and merge/revert noise are ignored.
- Single-package repos use
v${version}tags. - Independent multi-package repos use
${name}@v${version}tags. - Commits route to packages through changed paths and Conventional Commit scopes.
- Local dependents can release automatically through
dependenciesin config.
Each release plan is bound to its checked-out source SHA. On the initial,
non-resumable attempt, Hooversion requires local HEAD and the remote release
branch to still match that SHA. It updates manifests and changelogs, runs
configured hooks, creates a release commit and tags, and pushes the branch and
all tags in one atomic Git push.
If GitHub publishing fails after that push, rerun the release from the exact release commit and tags: Hooversion rejects remote drift, reuses only a matching existing GitHub Release, and uploads only missing asset names. This makes the post-push GitHub Release/asset step retryable without another branch or tag push.
This repository's release workflow publishes cross-platform archives together with an SPDX SBOM and sorted SHA-256 checksums. Every archive, SBOM, and checksum file receives a keyless Sigstore bundle plus a GitHub artifact attestation. Manual asset recovery rebuilds and reattests the same immutable tag.
Publication and verification remain separate states. verify-release reads the
live tag, release metadata, assets, signatures, and attestations back from
GitHub; a local build, green workflow, or existing tag alone is not accepted as
proof of the published artifact set.
Release outputs are managed files. Each non-dry-run release clears prior
generated output, writes .hooversion/outputs.json and per-tag notes, and
writes .release-version only for a single release (removing it otherwise).
When clearing older payloads, legacy notesPath values are removed only when
they remain contained by the configured output directory. These paths are
generated release state, not user-owned files. Corresponding CI
outputs are written to GITHUB_OUTPUT when available. In protected repositories
that allow GitHub Actions to bypass release-only branch protections, this keeps
releases automatic while human changes to main remain pull-request gated.
Webhook bodies are bounded by VERSIONHOO_WEBHOOK_MAX_BODY_BYTES before
signature verification and durable admission. Every validated handled
workflow_run is fsynced to the bounded file-backed webhook spool before the
app returns HTTP 202. The execution queue remains bounded at 64 running or
waiting jobs and serializes each repository/ref; queue saturation leaves the
durable record pending instead of returning a lossy 503.
VERSIONHOO_WEBHOOK_SPOOL_DIR selects the durable backlog directory and
VERSIONHOO_WEBHOOK_SPOOL_MAX_BYTES bounds its total size (64 MiB by default).
Sequence-numbered records preserve per-repository/ref FIFO and are recovered
and retried after restart. Dedupe reservations remain in memory for 24 hours
and are released after final failure or an admission error, never just because
the in-memory queue is full. Corrupt, oversized, symlinked, and unsafe-path
records are quarantined or skipped without blocking later deliveries.
hooversion migratewhich imports the legacy module (using bun when it is available), normalizes
the result, and writes hooversion.yaml. Without bun installed, migrate
prints guidance for converting manually. Manual mapping is direct: every key in
the old config has the same name in the new YAML schema (see the table above),
with one addition — github: false becomes:
github:
enabled: falseJS API removed: the npm package and its programmatic API are discontinued.
The commit-policy parity surface is now the hooversion lint command together
with policy behavior driven by configuration: the same allowed-type checking,
description requirements, 100-character header cap, and release-type mapping
rules apply through the CLI instead of an imported library.
Hooversion ships composite actions for downstream CI. The actions download a
static binary from GitHub Releases using the version input:
name: Release
on:
workflow_run:
workflows:
- CI
branches:
- main
types:
- completed
workflow_dispatch:
permissions:
contents: read
concurrency:
group: release-${{ github.repository }}
cancel-in-progress: false
jobs:
release:
runs-on: ubuntu-latest
if: >-
(github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'push' && github.event.workflow_run.head_branch == 'main' && github.event.workflow_run.repository.full_name == github.repository) ||
(github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main')
permissions:
contents: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
fetch-depth: 0
ref: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_sha || github.sha }}
- name: Prepare protected-branch release
if: github.event_name == 'workflow_dispatch' || (github.event_name == 'workflow_run' && !startsWith(github.event.workflow_run.head_commit.message, 'chore(release):'))
uses: openhoo/hooversion/actions/prepare-release@ac503b23b9b36ebbf39ee6103713c81f1f18b64d # v1.1.1
with:
version: 1.1.1
install-command: bun install --frozen-lockfile
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Finalize protected-branch release
id: finalize
if: github.event_name == 'workflow_run' && startsWith(github.event.workflow_run.head_commit.message, 'chore(release):')
uses: openhoo/hooversion/actions/release@ac503b23b9b36ebbf39ee6103713c81f1f18b64d # v1.1.1
with:
version: 1.1.1
install-command: bun install --frozen-lockfile
github-token: ${{ secrets.GITHUB_TOKEN }}actions/lint automatically uses the PR base/head range on pull requests and
--last on pushes. actions/release exposes published, version, tag, and
releases-json outputs for downstream package, Docker, or archive publishing
jobs.
Generated workflows keep the immutable action commit pinned to the latest
published Hooversion action, independently of the CLI version selected by
HOOVERSION_VERSION. For a Node package in a subdirectory, the generator
preserves that directory for dependency installation and checks while running
both protected-release action phases from the repository root.
The generated release workflow checks out the successful workflow run's immutable
head_sha and uses repository-scoped concurrency with
cancel-in-progress: false, so a later release never cancels an already-running
release.
Hooversion also ships versionhoo-app (built from cmd/versionhoo-app), a
native server for running releases as an installed GitHub App. The same server
is available as a Docker image. It verifies GitHub webhook signatures, mints
installation tokens from the app private key, clones repositories, runs the
same release engine, pushes release commits and tags, creates GitHub Releases,
and reports a Versionhoo Release check back to GitHub.
The versionhoo-app executable name is reserved for the app's Go and
container routes:
go install github.com/openhoo/hooversion/cmd/versionhoo-app@v1.1.1
go build -o bin/versionhoo-app ./cmd/versionhoo-appThe container route builds the same app from source:
docker build -t versionhoo-app .Use this when main is pull-request gated for humans but a dedicated app should
be allowed to perform release-only writes. See docs/github-app.md for the app
permissions, webhook settings, ruleset bypass setup, and runtime environment.
The App runner deliberately rejects repository dependency installation and all
repository hooks. Hooversion itself has a release hook, so it must not be put
in a versionhoo-app allow-list; use the workflow action for this repository.