Release process¶
Audience: anyone (human or AI agent) committing to vibe-qc, plus users who want to pin a calculation to an exact build. If you only want to install and run, see
installation.md.
Branch model¶
vibe-qc has two long-lived branches with strict roles. All development chats and contributors must respect this split — it’s the mechanism that lets us match a calculation log to the exact code that produced it, and it’s how we keep public users on a stable surface while we iterate.
Branch |
Purpose |
Push policy |
|---|---|---|
|
Active development. Every feature, bugfix, refactor lands here first. CI must pass. Daily commits expected. |
Maintainers + agents, after CI green. |
|
Public-facing snapshot. Advertised install instructions and any binary distribution we ever publish pull from |
Fast-forward only, and only from a tagged commit on |
Any other branch is a topic branch — short-lived, owned by a single
contributor or chat session, gets squashed or rebased into main once
its work lands.
The branch model has been in effect since the v0.4.0 cutover on
2026-04-27. Before that, release did not exist and the docs site
tracked main directly so readers wouldn’t be stuck at a months-
stale v0.1.0 snapshot while v0.2 / v0.3 / v0.4-dev features landed.
That deviation ended when v0.4.0 was tagged and release was
created at the same SHA — the site has rendered release ever
since.
What’s the user-visible difference?¶
Every vibe-qc run prints a banner that identifies the build:
╔════════════════════════════════════════════════════════════════════════╗
║ Release v0.1.0 — Quantum chemistry for molecules and solids ║
║ © Michael F. Peintinger · MPL 2.0 · https://vibe-qc.com ║
║ linked: libint 2.13.1 · libxc 7.0.0 · spglib 2.7.0 ║
╚════════════════════════════════════════════════════════════════════════╝
vs. a development build:
╔════════════════════════════════════════════════════════════════════════╗
║ dev 0.1.0 (main @ abc1234) — Quantum chemistry for molecules and … ║
║ © Michael F. Peintinger · MPL 2.0 · https://vibe-qc.com ║
║ linked: libint 2.13.1 · libxc 7.0.0 · spglib 2.7.0 ║
╚════════════════════════════════════════════════════════════════════════╝
The first form appears only when:
The current commit is exactly tagged (e.g.
v0.1.0).The working tree is clean (no uncommitted changes).
Any other state — branch, dirty tree, detached HEAD, untagged commit —
shows the dev form, including branch name, short SHA, and a dirty
flag if there are uncommitted changes.
Cutting a release — checklist¶
Maintainers only. Run from a clean checkout of main with everything
committed and pushed.
Pre-flight
git fetch origin git checkout main git pull --ff-only .venv/bin/python -m pytest tests/ # 100 % pass .venv/bin/python -c "from vibeqc.banner import build_info; print(build_info())"
The
build_info()output should showdirty: False. If anything else is wrong, fix it onmainfirst.Bump the version
Edit
versioninpyproject.toml(and only that — the banner reads this). Commit:git commit -am "release: vX.Y.Z" git push origin main
Regenerate the bundled reference outputs
The artefacts under docs/_static/examples/ carry the runtime banner from the exact build that produced them. Regenerate against the just-bumped version so banners read
Release vX.Y.Zinstead ofdev X.Y.devN:.venv/bin/python scripts/regenerate_doc_examples.py git diff --stat docs/_static/examples/ git add docs/_static/examples/ git commit -m "docs: regenerate reference outputs for vX.Y.Z" git push origin main
The script refuses dirty trees by default — that’s the protection against shipping
Release vX.Y.Z, dirtybanners in published artefacts. If a job fails or its declared outputs don’t match what the script produced, fix the entry in scripts/doc_examples.toml and re-run. Total wall-clock for the full sweep is ~15 min on a recent laptop (most of it the H₂O trimer optimization and the NH₃ NEB).Skipping this step is the most common release-time mistake — you’ll ship outputs whose banner reads the previous dev SHA, which is misleading to anyone reading them as a reference for the new release. See DOC1 in the v0.5.0 roadmap section for the design rationale.
Tag the release commit on
maingit tag -a vX.Y.Z -m "vibe-qc X.Y.Z" git push origin vX.Y.Z
Fast-forward
releaseto that taggit checkout release # or git checkout -b release if first time git merge --ff-only vX.Y.Z git push origin release
If git refuses the fast-forward, stop.
releasehas somehow diverged frommain’s tag. Investigate before doing anything destructive.Verify on a clean checkout
cd /tmp && rm -rf vibeqc-rel-check git clone -b release https://gitlab.peintinger.com/mpei/vibeqc.git vibeqc-rel-check cd vibeqc-rel-check ./scripts/setup_native_deps.sh python3 -m venv .venv && .venv/bin/pip install -e '.[test]' .venv/bin/python -c "from vibeqc.banner import print_banner; print_banner()"
Banner should read
Release vX.Y.Z. Spot-check one of the bundled reference outputs too — the banner indocs/_static/examples/h2o-rhf/output-h2o-rhf.outshould match (modulo git SHA differences for that specific commit) and definitely should NOT readdev X.Y.devN.Post-release
Update the changelog with the release notes (sourced from the project-root
CHANGELOG.md).Bump the next
0.X.Y+1.dev0version onmainso subsequent dev builds are visibly post-release.The vibe-qc.com docs site auto-deploys via GitLab CI on every push to
release(the.gitlab-ci.ymlfilter isonly: [release]since v0.4.0). The push ofreleasein step 4 already triggered a deploy; the next pipeline at https://gitlab.peintinger.com/mpei/vibeqc/-/pipelines should show green within ~3 minutes and the live site banner readsRelease vX.Y.Z.
Cutting a patch release (vX.Y.Z+1)¶
The minor-release flow above goes forward from current main. A
patch release (vX.Y.Z+1) is the opposite: it cherry-picks
hotfixes onto the previous tag, leaving main’s in-flight
development out of the public surface. Use this for security fixes,
install-script bugs, doc errata, and anything else that’s “we need
this in users’ hands now” but doesn’t justify dragging the rest of
main along.
Identify the fixes to backport. Each one should be a single commit on
main, ideally with a clear self-contained subject line, so cherry-picking is straightforward.Branch off the previous tag (NOT off
release, to keep the workflow uniform regardless of which tag is currently published):git checkout -b hotfix/X.Y.Z+1 vX.Y.Z
Cherry-pick each fix. Resolve any minor conflicts (usually none for self-contained fixes):
git cherry-pick <sha-on-main>
Bump the patch version in
pyproject.tomlto vX.Y.Z+1 and commit:git commit -am "release: vX.Y.Z+1"
Run the test suite to confirm the cherry-picks didn’t silently break anything:
./scripts/setup_native_deps.sh .venv/bin/pip install -e '.[test]' --force-reinstall --no-deps .venv/bin/python -m pytest tests/
Tag and push:
git tag -a vX.Y.Z+1 -m "vibe-qc X.Y.Z+1" git push origin vX.Y.Z+1
Fast-forward
release:git checkout release git merge --ff-only vX.Y.Z+1 git push origin release # ← fires CI, site updates
Update the changelog with the [vX.Y.Z+1] section listing the cherry-picked fixes.
Delete the hotfix branch (it served its purpose; the tag immortalises the state):
git branch -D hotfix/X.Y.Z+1
Cherry-pick anything new from the hotfix back to main if the fix didn’t originate there. (For our typical case — fix lands on main first, then gets backported — main already has it; nothing extra to do.)
The patch-release workflow does not touch the
0.X.Y+1.dev0 version on main — main is post-vX.(Y+1).0.dev0
and that’s correct. Only release and the hotfix tag move; main
is unaffected.
Running calculations against a specific build¶
Common case: testing a fix from a topic branch, comparing accuracy across builds, or reproducing an old result. The banner is the source of truth, so:
# 1. Clone or check out the exact ref you want to test.
git fetch origin
git checkout release # or v0.1.0, or some-topic-branch
# 2. Rebuild the native deps and the python package against that tree.
./scripts/setup_native_deps.sh
.venv/bin/pip install -e '.[test]' --force-reinstall --no-deps
# 3. Confirm the banner shows what you expect.
.venv/bin/python -c "from vibeqc.banner import print_banner; print_banner()"
# 4. Run your calculation. Every persisted SCF log carries the same
# banner at the top, so the result is unambiguously paired to the build.
If a calculation gives a wrong answer, always include the banner in the bug report. The branch+SHA pinpoint the exact source state; the linked-library line pinpoints the native ABI.
CI hooks¶
What’s wired today (.gitlab-ci.yml at repo root):
Docs build + deploy runs on every push to
mainorrelease. Container:python:3.13-slimfor build,alpine:latestfor deploy. Runner:erzherzog-docker(gitlab/gitlab-runner in Docker on the same host as the GitLab CE instance, tagsdocker/sphinx). Deploy uses an rsync-over-SSH push toweb18@vibe-qc.com:/web/, with the ed25519 deploy key stored as the masked, protected$DEPLOY_SSH_KEY_B64CI variable. Build logs surface in GitLab’s pipeline UI.
What’s planned, not yet wired:
releaseshould be marked as a protected branch on the GitLab project (Settings → Repository → Protected branches): only fast-forward from a tag allowed (push rule). Belt-and-suspenders protection on top of the human-process discipline in this document.A nightly job re-runs the test suite against
releaseto catch silent breakage from environment drift (e.g. a newer compiler rejecting our C++). Add as a scheduled pipeline once the test suite runs cleanly inside the CI container (currently needs the C++ toolchain image, which the docs pipeline doesn’t exercise).A manual job tags + pushes the release branch given a tag name. Useful for one-click cutting of a release; today the workflow is the manual step-1-through-6 checklist above.
Why not just use main for everything?¶
We’re a small project, so the temptation is real. The reason
release exists anyway is that the docs site at
https://vibe-qc.com pulls from release, and the
.gitlab-ci.yml filter is only: [release] for the docs-deploy
pipeline. Keeping release separate means casual visitors see
stable docs and stable install instructions blessed by a tagged
release, even as we tear apart main for a refactor.