The xberg-io toolchain baseline for mise: one set of tool and language versions that every repository installs locally and in CI.
| Path | Purpose |
|---|---|
baseline.toml |
Canonical versions, in mise [tools] syntax. Renovate bumps them here first. |
scripts/toolchains.py |
check reports drift from the baseline and the task sets; sync rewrites a repository's pins and shared tasks. Standard library only, Python 3.11+. |
task-sets/ |
Named sets of mise tasks that sync writes into a repository (see Task sets). |
default.json |
Renovate preset for consumers: turns off upstream bumps for baseline tools, groups repo-owned ones. |
A repository commits:
mise.tomlwith the tools it needs, using the baseline's exact key, version and table options (node = "26.12.1","github:Goldziher/poly" = "0.29.0"). An alias of a baseline tool (nodejs,core:node,aqua:…) is drift;mise.lock, generated bymise lock, coveringlinux-x64,linux-arm64,macos-arm64andwindows-x64;rust-toolchain.tomlfor Rust, which stays authoritative for components and targets. Declaringrustinmise.tomlis an error; the file'schannelis checked against the baseline'srust;toolchains.tomlrecording the baseline tag it follows, and any override:
baseline = "v1.0.0"
[overrides.node]
version = "22.20.0"
reason = "engines floor smoke test runs on the oldest supported Node"
[overrides."github:Goldziher/poly"]
version = "0.28.4"
reason = "example: hold the previous release until a regression fix ships"Only mise.toml is read; profile files such as mise.ci.toml are not checked. A task's own tools pin of a
baseline tool in mise.toml (tools = { node = "26.13.0" } under [tasks.setup], or a [tasks.setup.tools]
table) must state the override, else the baseline version, too; sync moves it, and fails without writing for
any other spelling.
Tools the baseline does not list (for example wasm-bindgen-cli, which must match Cargo.lock) are owned by the
repository. Renovate updates them when the repository extends github>xberg-io/toolchains.
ai-rulez is not pinned anywhere: tasks run npx -y ai-rulez@latest, which needs the baseline's node.
governance:hooks:install allows the poly hooks' npm channel first, so they run npx -y ai-rulez@latest too, and
falls back to an ai-rulez on PATH.
git clone --depth 1 --branch v1.1.0 https://github.com/xberg-io/toolchains /tmp/toolchains
python3 /tmp/toolchains/scripts/toolchains.py sync --baseline /tmp/toolchains/baseline.toml --ref v1.1.0
mise lock
python3 /tmp/toolchains/scripts/toolchains.py check --baseline /tmp/toolchains/baseline.tomlsync also moves the version files mise does not read (see the table below), so a baseline bump is one
sync; it prints Dockerfile tags that still disagree, since it does not rewrite them.
mise lock after sync is required, not optional: shared tasks can pin their own tools (governance:check and the
plugin tasks run on a task-level node = "26.13.0"), and a lock without that version fails --locked CI with
"No lockfile URL found for node@26.13.0". With MISE_TASK_RUN_AUTO_INSTALL=false and a stale lock, the task falls
back to whatever node is on PATH without a warning. check exits 1 when a committed mise.lock lacks a version a
selected task pins.
check exits 1 on drift and 2 when nothing could be checked: a missing mise.toml, or neither a declared tool nor
a version file covered by the baseline. It lists the repository-owned tools it skipped. A pin of ai-rulez under any backend
(github:Goldziher/ai-rulez, npm:ai-rulez) is drift: use npx -y ai-rulez@latest instead. It also exits 1 when
the repository root still holds go-task files, since the org runs its tasks with mise: a Taskfile.yml,
Taskfile.yaml, Taskfile.dist.yml or Taskfile.dist.yaml in any letter case, or a .task/ directory. In a git
checkout only tracked files count, so an ignored .task/ cache passes; elsewhere the files on disk are checked.
Version files mise does not read must agree with the baseline as well, or with the tool's override in
toolchains.toml. In a git checkout the tracked files count, outside node_modules, vendor, vendored,
third_party and third-party directories; a checkout git cannot read (for example one it reports as dubious
ownership) is an error, exit 2, never a walk of the disk. A directory without .git, such as a git archive
export, is walked on disk, skipping those directories and .venv, target, dist and build too. check lists
the files it compared and the ones it could not, and exits 1 when one disagrees:
| File | Compared with | Match | sync |
|---|---|---|---|
.nvmrc, .node-version |
node |
exact; a leading v is ignored, a bare major (26) or lts/* is drift |
rewrites, keeping a v |
.python-version |
python |
exact, first version line | rewrites the first line |
.ruby-version |
ruby |
exact; a ruby- prefix is ignored |
rewrites, keeping ruby- |
.tool-versions |
each baseline tool it names (nodejs, golang included) |
exact, first version; system, lts and latest are skipped, java only when temurin-<x.y.z>+<build> |
no |
package.json packageManager |
pnpm |
exact; a +sha512.… suffix is ignored, other package managers are skipped |
rewrites; a +sha512.<hex> suffix is recomputed from the npm registry's dist.integrity for the new release, and sync fails rather than drop it when the registry cannot be read |
nested rust-toolchain.toml, any rust-toolchain |
rust |
exact, like the root file: 1.99 is drift against 1.99.1; named channels (nightly, nightly-2026-01-01, stable, beta) are listed as not checked; invalid TOML is drift |
rewrites numeric channels |
Dockerfile, Dockerfile.<name>, *.Dockerfile, Containerfile |
node, python, go, ruby |
major.minor of official node, python, golang, ruby images |
no, prints the drift |
Exact matching keeps every place that installs a runtime on the version CI locks. That includes
.python-version: 3.13 is drift against 3.13.18 on purpose, since pyenv and uv resolve it to whatever patch
is installed. The root rust-toolchain.toml is checked with the mise pins and must name the baseline channel
exactly; a repository that needs another toolchain for fuzzing or Miri keeps it in a nested file.
A version file that must stay off the baseline on purpose, such as a nested rust-toolchain.toml that pins a crate's
MSRV, is listed under file_overrides in toolchains.toml by its repository-relative path, with a reason:
[file_overrides]
".github/toolchains/fetcher-msrv/rust-toolchain.toml" = "the fetcher crate's MSRV job builds on its oldest supported Rust"sync leaves the file alone and check lists it as not checked. check exits 1 when an entry has no reason,
names a path the repository does not have (in a git checkout, a tracked one), or names a file it does not compare
as a version file; the root rust-toolchain.toml is overridden with [overrides.rust] instead.
Dockerfile tags are compared on major.minor only, because the official images publish patch tags after the release
the baseline already follows: FROM node:26.13-slim and FROM node:26.13.1 pass against 26.13.0; FROM node:26,
node:latest and an untagged node are drift. Dockerfile.<name> with a template or document extension (.j2,
.jinja, .tmpl, .tpl, .template, .md, .txt) is not a Dockerfile. An ARG default in the same file
resolves a ${VAR} tag, and ${VAR:-x} or ${VAR-x} falls back to x; a tag that still names an argument
without a value is listed as not checked. Other registries (ghcr.io/…/node) and other images are not compared.
sync writes nothing when any pin cannot be rewritten (for example a [tools.node] subtable) or when table options
differ from the baseline; fix those by hand.
Tasks that several repositories define with the same body live here as named sets, so a fix lands once and
repositories pick it up on the next baseline move. A repository opts in from toolchains.toml:
baseline = "v1.6.0"
task_sets = ["governance", "governance-check", "lint-format"]
[task_overrides]
lint = "runs tflint over terraform/, not poly"sync (same command as in Moving to a new baseline) then writes, all generated and
committed:
mise-tasks/shared.toml: the selected sets' tasks, under a header naming the toolchains tag;mise-tasks/.shared/<set>/…: the sets' file tasks, executable, plus.manifest, the list of files sync owns. The directory is hidden because mise registers every executable under an included directory as a task;"mise-tasks"and"mise-tasks/shared.toml"inmise.toml's[task_config] includes, adding the table when it is missing.includesreplaces mise's default scan, so sync also keeps any default task directory (.mise-tasks,.mise/tasks, …) the repository has.
Dropping a set deletes the files sync wrote for it, as listed in the manifest; dropping the last one also takes
"mise-tasks/shared.toml" out of includes. Sync writes and deletes only regular files under those two paths, never
through a symlink, and stops without writing when the manifest names anything else or a shipped path already holds a
file sync did not write. Do not edit the generated files: change the set here and tag a release.
A task listed under task_overrides is left out of shared.toml (with its aliases) and stays the repository's own,
for a body that must differ. Every other task a selected set provides must not be defined locally: check exits 1
when mise.toml [tasks], a TOML file mise loads (an includes file, or any *.toml under an included directory),
or an executable file task defines one of those names or aliases. It also exits 1 on an unknown set; an override
without a reason, for a task no selected set provides, or for a task the repository does not define; a missing
include; and any generated file that differs from what sync would write, including its exec bit. In a repository
that selects a set or has a manifest, every file under mise-tasks/.shared/ must be one sync wrote.
Repository specifics come from the environment with a default, never a repository name: set DOCS_DIR (default
docs-site), BATS_LIB_REF or the deps-refresh variables in mise.toml's [env].
| Set | Tasks | Notes |
|---|---|---|
governance |
governance:hooks:install |
poly hooks for AI governance |
governance-check |
governance:check |
npx -y ai-rulez@latest on the baseline node |
lint-format |
lint, format, format:check |
repository-wide poly |
docs-site |
docs:build, docs:serve |
DOCS_DIR |
docs-snippets |
docs:snippets:audit, docs:snippets:gaps |
alef snippets; DOCS_DIR |
rust |
rust:format, rust:clean |
Cargo workspace |
alef |
alef:regenerate, alef:verify, alef:verify:strict, alef:snippets, e2e:generate |
alef-generated outputs |
alef-bindings |
alef:build, test:bindings |
alef language bindings |
alef-publish |
publish:{build,package,prepare,validate} |
alef release packaging |
alef-pin |
alef:pin:check |
the alef_version pin in alef.toml |
alef-version |
version:show, version:bump:{major,minor,patch} |
the repository defines version:sync |
plugin |
plugin:generate, plugin:verify, plugin:skills:test |
npx -y ai-rulez@latest plugin and public skills scripts |
plugin-runtime |
plugin:launcher:test, plugin:hermes:test, plugin:test |
MCP launcher, Hermes bridge and OpenCode test scripts |
bats-lib |
test:scripts:lib:check, test:scripts:lib:sync |
the repository's scripts/task/sync-bats-lib.sh; BATS_LIB_REF in [env] |
bats-suite |
test:scripts |
Bats suites under scripts/tests/bats |
deps-refresh |
deps (deps <update|upgrade> [lang]) |
DEPS_LANGS, DEPS_<LANG>_DIR, DEPS_NODE_DIRS, DEPS_RUST_MANIFESTS in [env] |
A task joins a set when its body is identical in at least two repositories and in at least half of the repositories that define the name, so overrides stay the exception. A set also gives no repository a task it does not define: a member only some of a set's repositories define moves to a set of its own, or stays local when that set would ship no file task and replace fewer than six local definitions. Counts are repositories with that exact body / repositories defining the name, from every repository's default branch on 2026-10-29, after each repository removed the tasks it does not use (remote file tasks count by source file).
| Set | Members (identical / defining) |
|---|---|
governance |
governance:hooks:install 17/17 |
governance-check |
governance:check 15/16; apart from governance because one repository removed it and still installs the hooks |
lint-format |
format 10/13, format:check 8/8, lint 8/14; no lint:check alias, which would hand the name back to the repositories that removed it; check tasks call lint |
docs-site |
docs:build 7/8, docs:serve 7/8 |
docs-snippets |
docs:snippets:audit 3/4, docs:snippets:gaps 3/4; apart from docs-site because the docs-only repositories have no alef |
rust |
rust:format 5/5, rust:clean 5/5 |
alef |
alef:regenerate 4/4, alef:verify:strict 4/4, alef:verify 4/5, alef:snippets 3/4, e2e:generate 3/5 |
alef-bindings |
alef:build 4/4, test:bindings 4/4; apart from alef because one alef repository defines neither |
alef-publish |
publish:* 3/3 each; apart from alef because two alef repositories define none of them |
alef-pin |
alef:pin:check 2/2, a file task |
alef-version |
version:show 5/5, version:bump:* 4/5 each |
plugin |
plugin:generate 6/6, plugin:verify 6/6, plugin:skills:test 6/6 |
plugin-runtime |
plugin:launcher:test 3/3, plugin:hermes:test 3/3, plugin:test 2/4; apart from plugin because three plugin repositories do not define the first two |
bats-lib |
test:scripts:lib:check 5/5, test:scripts:lib:sync 5/5, with the BATS_LIB_REF three of them set on the task moved to [env] |
bats-suite |
test:scripts 3/4, a file task; apart from bats-lib because two of its repositories name or split their suites differently |
deps-refresh |
deps 5/5, already one shared file |
Removed on 2026-10-29: governance:generate (1/2), docs:lint:prose (2/5), rust:licenses and the versions:sync
alias (no repository defines them). Left local although they meet the count, because a set would give them to
repositories that do not define them and a set of their own would replace fewer than six definitions:
docs:build:strict (2/2), docs:snippets:list (3/3), docs:snippets:list:lang (2/2), alef:docs (2/2),
readme:generate (2/2), alef:format (2/2), alef:sync (2/3), version:set (2/4), rust:format:check (3/4).
Left out because the bodies diverge per repository: rust:build, rust:test, rust:doc, rust:lint,
docs:lint:links, docs:snippets:validate, docs:snippets:all, alef:generate, version:sync, setup. Also
left out: default (mise tasks ls in 9/9, nothing to maintain), build (5/10, a wrapper for the repository's own
rust:build) and the per-language elixir:*, go:* and ruby:* tasks (two or three repositories, each tied to
its package layout).
CI locks the whole baseline for all four platforms. These cannot be locked and are allowed by name: dotnet and
rust (installed by script, no checksummed artifact), aws-cli, bats and ruby on Windows, cargo-sort on
linux-arm64. Jobs on those platforms keep their existing setup action for that tool.
Edit baseline.toml, keep default.json's disabled list equal to its keys (a test enforces this), and tag a
release. Patch versions follow the newest patch of the line in use; moving a minor or major line is a reviewed
change.