Thanks for helping out. This page covers the contribution flow, the branch naming convention, and how to run the checks locally so your PR is green the first time.
- Fork the repo (external contributors) or create a branch (maintainers).
- Branch from
mainusing the naming convention below. - Make your change; keep it focused (one concern per PR).
- Run the checks locally (
make local-check, plusmake hooksonce so the pre-commit hooks run on every commit). - Open a PR against
main. CI (lint+test) must pass;mainonly takes changes through a PR.
Branches are <type>/<kebab-slug>: a Conventional-Commits type, a slash,
and a short kebab-case description. main and release-please's own release-PR
branches (release-please--…) are exempt.
feat/leaf-only-action-cli fix/poll-lock-lease ci/branch-naming
| type | when to use it |
|---|---|
feat |
a new user-facing capability |
fix |
a bug fix |
perf |
a performance improvement (behavior unchanged) |
refactor |
restructure code; no behavior or API change |
docs |
documentation only |
test |
add or correct tests only |
ci |
CI / workflows / pipeline config |
build |
build system, dependencies, packaging |
chore |
maintenance / tooling that doesn't touch src behavior |
style |
formatting / whitespace only; no logic change |
revert |
revert a previous change |
The slug is lowercase letters, digits, and - . _, starting alphanumeric.
Append ! to the type to mark a breaking change — e.g. feat!/drop-legacy-api —
mirroring the feat!: commit marker that bumps the version (see below).
This is enforced in two places by the same script,
scripts/check-branch-name.sh:
- pre-commit: a commit on a misnamed branch is rejected locally.
- CI: the
branch-namejob validates a PR's source branch.
Rename a branch with git branch -m <new-name>.
Use the same Conventional-Commits types for commit subjects, e.g.
feat(watches): add the activity summary. Not enforced, but it keeps the
history scannable and matches the branch types.
Releases are cut by release-please from the Conventional-Commit history, on two independent tracks:
- Product (the server stack: core, web, email-render) is versioned together
and tagged
v<x.y.z>. The services deploy as a unit, so they share one version (no per-image compatibility matrix to reason about). - The
magpieCLI (apps/cli, published to PyPI asopenmagpie) versions on its own track, taggedcli-v<x.y.z>. It talks to the server over a versioned API (/v1/...), so CLI/server compatibility is governed by the API version, not the build number — a CLI works with any server that still serves an API version it speaks.
Pre-1.0 SemVer: feat: → minor, fix: → patch, a breaking change
(feat!: / BREAKING CHANGE:) → minor (no major bump until 1.0). So the commit
type sets the next version — feat(cli): bumps the CLI, feat(core): bumps the
product. release-please keeps an open "release PR" per track with the pending
version + changelog; merging that PR cuts the release (tag + GitHub Release). You
don't tag by hand.
make build # build + start the stack (Django + Postgres + web)
make local-migrate # migrate, create cache table, bootstrap the OAuth app
make hooks # install the pre-commit hooks (once)
make local-check # the full local gate: lint + types + testsmake local-check mirrors CI: ruff (lint + format), ty (types),
whitespace + file-length, makemigrations --check, and the Django test
suite. See the README for the full dev loop and make help
for every target.