Ways of Working¶
This document describes the branch strategy, pull request process, CI pipeline, and release procedure for quadletman. It applies to all contributors — human and AI alike.
Branch Strategy¶
The repository uses a trunk-based development model with short-lived feature branches.
| Branch | Purpose |
|---|---|
main |
Stable, always-green trunk. Every commit on main is releasable. |
feature/<slug> |
New functionality. Branched from and merged back to main. |
fix/<slug> |
Bug fixes. Same lifetime as feature branches. |
chore/<slug> |
Dependency updates, docs, CI, refactoring with no behaviour change. |
Rules:
- No direct pushes to main — all changes land via pull requests.
- Branch names must be lower-kebab-case: feature/connection-monitor, fix/timer-next-run.
- Delete the branch after the PR is merged.
Pull Request Process¶
Opening a PR¶
- Branch off
main: - Work in small, logical commits. Commit messages follow the project convention:
- Before pushing, run the pre-commit suite locally:
- Push and open a PR targeting
main. The PR title becomes the squash-commit message onmain, so it must follow the sameADD / FIX / CHOREprefix convention.
Review checklist (reviewer)¶
- [ ] CI is green (all jobs pass)
- [ ] CLAUDE.md Doc Update Protocol has been followed for any trigger that fired
- [ ] Security Review Checklist in CLAUDE.md has been run for security-relevant files
- [ ] New user-visible strings have accompanying
.po/.moupdates - [ ] Tailwind changes are compiled and committed
Merging¶
- Use squash merge — one commit per PR on
main. - The squash commit message = the PR title.
- Delete the source branch after merge.
CI Pipeline¶
Every push to main and every pull request runs the following GitHub Actions jobs
(defined in .github/workflows/ci.yml):
| Job | What it checks |
|---|---|
| lint | ruff check + ruff format --check |
| test-python | pytest --cov (unit + router tests; E2E excluded) + Codecov upload |
| test-js | npm test (Vitest) |
| tailwind-check | Rebuilds Tailwind; fails if tailwind.css would change |
| babel-check | Recompiles .mo files; fails if they would change |
All jobs must be green before a PR can be merged. The branch protection rule on main
enforces this.
Semantic Versioning¶
quadletman follows Semantic Versioning 2.0.0:
| Segment | Increment when… |
|---|---|
| MAJOR | A backwards-incompatible change is made — removed API, changed DB schema without migration, changed config variable names, or a breaking change to the install/upgrade path. |
| MINOR | New functionality is added in a backwards-compatible way — new UI feature, new endpoint, new config option with a sensible default. |
| PATCH | A backwards-compatible bug fix — no new features, no schema changes. |
Pre-release and dev builds¶
Between releases, hatch-vcs derives the version automatically from git:
- On a tag v1.2.3 → version is 1.2.3
- After a tag (e.g. 4 commits after v1.2.3) → version is 1.2.3.dev4+gabcdef0
This means you never need to manually bump version = in pyproject.toml. The tag
is the single source of truth.
What triggers a MAJOR bump¶
Since quadletman runs as a system service with a SQLite database managed by numbered migrations, the following always require a MAJOR bump:
- Removing a migration (even a corrective one) — the upgrade path would break.
- Renaming or removing a
QUADLETMAN_*environment variable without a deprecation cycle. - Changing the path of the SQLite database or volumes base without providing an automatic migration.
- Dropping support for a Python or Podman version that was previously supported.
Release Process¶
Releases are made by pushing an annotated tag to main. The
.github/workflows/release.yml workflow runs the
full CI suite, builds the wheel, and creates a GitHub Release automatically.
Step-by-step¶
# 1. Make sure main is clean and CI is green
git checkout main && git pull
git status # must be clean
# 2. Update CHANGELOG.md
# - Rename "## [Unreleased]" to "## [X.Y.Z] - YYYY-MM-DD"
# - Add a new empty "## [Unreleased]" section above it
# - Update the comparison links at the bottom of the file
git add CHANGELOG.md
git commit -m "CHORE prepare release vX.Y.Z"
git push origin main
# 3. Create and push the annotated tag
git tag -a vX.Y.Z -m "Release vX.Y.Z"
git push origin vX.Y.Z
Pushing the tag triggers the release workflow, which runs as parallel jobs:
- CI gate — runs the full test suite; all downstream jobs depend on this.
- build-wheel — builds the Python wheel via
uv build --wheel(platform-independent). - build-rpm — builds an RPM inside a Fedora container using
packaging/build-rpm.sh. - build-deb — builds a
.debon Ubuntu usingpackaging/build-deb.sh. - publish — downloads all artifacts, extracts the
## [X.Y.Z]section fromCHANGELOG.mdas release notes, and creates a GitHub Release with the wheel, RPM, and DEB attached.
The VERSION env var is passed to each build script as the tag name with the leading v
stripped (e.g. tag v0.3.1 → VERSION=0.3.1). Local builds fall back to git describe.
See docs/packaging.md for build prerequisites, package structure, and how to build and upgrade packages locally.
Pre-releases¶
Tag with a - suffix to publish a pre-release. Common conventions:
| Tag | Meaning |
|---|---|
v0.2.0-alpha.1 |
Early preview, may be unstable |
v0.2.0-beta.1 |
Feature-complete, needs testing |
v0.2.0-rc.1 |
Release candidate, expected to ship unless bugs found |
The release workflow runs identically to a full release (all packages are built) but GitHub
marks the release with the Pre-release label. Any tag containing a - triggers this
automatically — no workflow change needed.
Add a matching ## [0.2.0-beta.1] section to CHANGELOG.md before tagging if you want
release notes; otherwise the release body falls back to a link to CHANGELOG.md.
Hotfix releases¶
For urgent fixes against an already-released version:
git checkout -b fix/critical-bug vX.Y.Z # branch from the tag
# … make the fix …
git commit -m "FIX critical bug description"
# Cherry-pick or merge back to main as well:
git checkout main && git cherry-pick <commit-sha>
git push origin main
# Tag the hotfix on the fix branch:
git checkout fix/critical-bug
git tag -a vX.Y.(Z+1) -m "Release vX.Y.(Z+1)"
git push origin vX.Y.(Z+1)
GitHub Badges¶
The following badges appear at the top of README.md:
| Badge | Source |
|---|---|
| CI status | GitHub Actions workflow status |
| Release status | GitHub Actions release workflow status |
| Latest release | GitHub Releases API |
| License | Repository metadata |
| Python version | Static (from pyproject.toml requires-python) |
| Coverage | Codecov (populated by the test-python CI job) |
To enable the coverage badge, install the Codecov GitHub App on the repository. No token is needed for public repositories.