Thanks for considering a contribution. This document covers setup and the
quality gates; the design intent lives in PLAN.md and the hard coding
rules live in CODING_STYLE.md — read that one before writing code, because
its functional-core / imperative-shell rules are enforced in review.
With uv (recommended):
uv sync # creates .venv, installs rigorloop editable + dev tools (locked)
uv run pytest # sanity checkWithout uv (pip ≥ 25.1):
python -m venv .venv && source .venv/bin/activate
pip install -e . --group devOptional but recommended:
uv run pre-commit installEverything CI runs is reachable through the justfile (or run the underlying
commands directly):
just lint # ruff check + ruff format --check
just typecheck # mypy --strict over src and tests
just test # pytest with coverage gates: 95% core / 80% overall
just check # all three
just build # sdist + wheelA PR must be green on all of them. Strict typing is load-bearing here: the
exhaustive-match and dev/val/test split-type guarantees are enforced by mypy,
so # type: ignore needs a very good justification.
src/rigorloop/core/ is 100% pure — no I/O, no clock, no randomness that
isn't an injected seed, no subprocesses. It must be testable with plain
inputs and asserted outputs, zero mocks. src/rigorloop/shell/ performs the
effects the core describes (claude subprocess calls, files, sandboxed script
execution) and stays as thin as possible. If your change needs a mock to test
core logic, the boundary is in the wrong place — restructure.
Two invariants are non-negotiable and covered by dedicated tests in
tests/test_leakage.py:
- No validation/test example content ever enters a strategy or executor prompt (the agent-context channel).
- Directives may carry the champion solution's content forward — never scores, mistakes, or per-example failures.
- Core changes: add pure unit tests (and hypothesis property tests where the logic is algebraic — splitting, statistics).
- Shell changes: use the stub-CLI / fake-deps patterns in
tests/conftest.py; never call the realclaudeCLI in tests. - Anything touching the run protocol: extend the fake-agent E2E tests in
tests/test_e2e.py.
The git tag is the version: hatch-vcs derives it from the latest
vX.Y.Z tag, so there is no version number to bump in any file. The only file
you edit for a release is CHANGELOG.md; pushing the tag does the rest.
- Land the change the normal way — branch, PR,
just checkgreen, add an entry under## [Unreleased]inCHANGELOG.md, merge tomain. - On
main(git checkout main && git pull), promote the changelog: rename[Unreleased]to[X.Y.Z] - YYYY-MM-DD, add a fresh empty[Unreleased]above it, commit, and push. The tag will point at this commit, somainmust be green first (branch protection requiresci-ok). - Tag and push — this triggers
release.yml:git tag vX.Y.Z && git push origin vX.Y.Z - Approve the
pypienvironment deployment when prompted (the publish job is gated on manual approval). The workflow then rebuilds from the tag, publishes to PyPI via OIDC Trusted Publishing (no token), and cuts a GitHub Release with generated notes and the built artifacts. - Verify
pip install rigorloop==X.Y.Zin a scratch venv, thenrigorloop --version.
Version choice while 0.x: patch (v0.1.1) = fixes only; minor
(v0.2.0) = features or breaking changes (call breakage out in the changelog);
v1.0.0 when rigorloop.toml, the CLI, and the run-directory format are
declared stable.
A version publishes to PyPI exactly once — never re-push or edit a tag. If a
release fails partway, fix forward with a new patch tag rather than reusing the
old one. To rehearse the full pipeline first, push a prerelease tag
(e.g. v0.2.0rc1) against TestPyPI.