Development
The four-command loop, and the local hooks that guard every commit.
This page covers the working loop for contributing to overpower: getting the environment, and the four commands you run over and over. It also covers the hooks that run without being asked, and what each one rejects.
Before you start
uv installed, and a clone of the repository. There is no task runner, and the
absence is deliberate: uv already does what one would add.
The steps
Run the four-command loop
The same four run in CI, from the same lockfile, so it passed locally and it passed in CI mean the same thing.
uv run ruff format --check . # formattinguv run ruff check . # lintuv run --group typecheck pyright # types, strictuv run pytest # testsArm the hooks, once per clone
A worktree does not need its own
lefthook install: it shares.git/hookswith the clone that owns it.lefthook installAsk what has no test, when the question comes up
Coverage is a diagnostic, not a gate. No threshold lives in
pyproject.toml, no badge, nothing in CI.uv run --with pytest-cov pytest --cov=src/overpower --cov-report=term-missing
Checking it
pyright runs strict, against the floor Python, 3.12, never the version your
own interpreter happens to be. pytest runs the whole suite, on every platform in
the matrix. There is no marker for slow and no tier that runs on one OS only,
because what the suite tests is disk behaviour, and disk behaviour is exactly what
diverges between platforms.
The hook is the shortcut, not the gate. The gate is the ruleset on main, and a
ruleset has no --no-verify: the lists of who can bypass it are empty, including
for the repository's owner. What the hook buys is speed, catching the cheap error
cheaply before a push pays for a CI job.
The two hooks
The pre-commit hook runs over the staged set, in parallel, and rejects four
things: formatting outside the standard on staged .py files, lint on the same
files, anything under src/overpower/content/ hidden from git, and a secret found
by gitleaks over the staged diff.
The commit-msg hook runs commitlint, checking Conventional Commits with a
lower-case subject.
A subject-less run, where the directory does not exist or git tracks nothing under it, is treated as failure, not as a vacuous pass. A gate that passes for having nothing to check is a gate that disappears the day the path is renamed.
lefthook, gitleaks and the tooling commitlint needs are equipment of the
machine, not of the repository. A clone on a machine without them loses the
shortcut by design, and still meets the same gate on the pull request. When a hook
rejects a commit, it prints the failing command's own output straight to the
terminal, at the moment of the rejection, so there is no separate log to go find.