Drop a uniform version / version:bump-* task interface onto any repo, regardless of stack.
All bump logic lives in one installed file (.mise/scripts/versioning.sh); mise tasks are thin
wrappers. Every version-bearing file in the repo is kept at the same semver — the highest wins.
The contract this skill guarantees
After init, the repo exposes exactly these mise tasks (and nothing the user must learn beyond them):
| task | effect |
|---|---|
version |
print current version as vX.Y.Z (the highest across all tracked files) |
version:bump / version:bump-patch |
vX.Y.Z → vX.Y.(Z+1) (alias — same task) |
version:bump-minor |
vX.Y.Z → vX.(Y+1).0 |
version:bump-major |
vX.Y.Z → v(X+1).0.0 |
version:check |
exit nonzero + list drift if files disagree |
version:sync |
force every file up to the highest version |
Every bump rewrites all tracked files in their native format and (if tracked) creates a
vX.Y.Z git tag, so a frontend package.json and a backend pyproject.toml never diverge.
Running init
init is the single entry point. It is idempotent — safe to re-run.
bash scripts/init.sh # run from inside the target repo (cwd anywhere within it)
# flags: --force (re-discover the manifest) --seed X.Y.Z (initial version) --no-git-tag
What it does, in order:
- Ensure mise. If
mise.tomlis absent, create a minimal one. - Install the engine. Copy
assets/versioning.sh→.mise/scripts/versioning.sh(chmod +x). - Discover version files → write
.mise/version-files.conf(a<type> <path>manifest). Skipped if a manifest already exists unless--force. - Merge the tasks into
mise.tomlinside a managed# >>> mise-versioning >>>block (replaced wholesale on re-run, never duplicated). - Resolve parity. Run
version:check; if files disagree,version:syncto the highest. - Wrap the build. If
mise.tomlhas a[tasks.build], adddepends = ["version:bump-patch"]. - Flag hardcoded self-reported versions. Scan source for semver literals fed to
--version-style surfaces (.version("X.Y.Z"),__version__ = …,version: "X.Y.Z") and warn — bumps never rewrite these, so the artifact would lie about itself. Fixing is yours (see below); init only detects.
After running, review .mise/version-files.conf — it is hand-editable and authoritative.
Add or remove entries the auto-discovery missed before relying on bumps.
After init: judgment the script leaves to you
init handles the mechanical 90%. Finish these by hand when they apply:
- Foreign build runners. init only auto-wires an existing mise
[tasks.build]. If the repo builds vianpm run build, a Makefile,cargo build, etc., wrap it so the version bumps first:
Invoke[tasks.build] depends = ["version:bump-patch"] run = "pnpm build"mise run buildthereafter, not the underlying tool. - Build task that already had
depends. init injects a# NOTE:rather than guessing merge order. Combine into one array with the bump first:depends = ["version:bump-patch", "…"]. - Unusual version locations. If a config file's version isn't matched (indented TOML, a
non-standard property), prefer a canonical
plainVERSIONfile over fighting the matcher. See references/file-types.md. - Self-reported versions — the artifact must derive, not duplicate. A literal like
commander's
.version("1.0.0")orMcpServer({ version: "1.0.0" })is invisible to the manifest and lies a little more every bump (real incident: a CLI shipped as 1.1.3 while--versionsaid 1.0.0). Make the program read its version from the tracked manifest at runtime (Node walk-up topackage.json, Pythonimportlib.metadata, Rustenv!("CARGO_PKG_VERSION"), Go ldflags) — recipes and a verify ritual in references/self-reported-version.md. Then prove it:<artifact> --versionmust equalmise run version. Never "fix" this by regex-tracking the source file in the conf.
How parity & "highest wins" work
The canonical version is computed fresh on every call as the max semver across all manifest
entries. There is no separate "source of truth" file — any tracked file can carry the version, and
they are reconciled upward. This is what lets init adopt a repo whose files already disagree:
version:sync pulls the laggards up to the leader. Bumps always start from the canonical value, so
a stray un-bumped file can never silently lower the version.
Supported file types
json · toml · cargo · csproj · gradle · plain · gittag. Read/write rules, the
column-0 anchoring that protects dependency pins, and how to add a new type are in
references/file-types.md.
Troubleshooting
When a bump touches the wrong line, check flags drift after init, the build doesn't bump, or
current reads v0.0.0 in a versioned repo — see references/gotchas.md.
Out of scope
- Changelogs / release notes. This skill moves version numbers; it does not write release
prose. Use the
product-changelogskill. - Publishing / releasing. No
npm publish,uv publish,cargo publish, GitHub Releases, orgit push --tags. Bumps and tags stay local; wire release steps separately. - Deriving versions from commit history (Conventional Commits, semantic-release, release-please). This workflow is explicit-bump, not commit-inferred. If the user wants commit-driven versioning, that's a different tool — don't bolt it on here.
- Non-version mise configuration (tool pinning, env, unrelated tasks). init touches only its
managed block and an existing
buildtask.