Thanks for considering a contribution. This is a small, solo-maintained library, so keep proposals in scope with a quick issue first if you're planning something larger than a bug fix.
- Fork, then PR. Branches can't be pushed to this repository — repository rulesets
block branch creation for everyone but the maintainer. Fork the repo, work on a branch
in your fork, and open a pull request against
main. - All merges are squash merges. Your PR lands as a single commit whose message is the PR title — that's why the Conventional Commits title format below is enforced: the changelog is generated directly from those commits.
- Every PR needs the maintainer's review and the required checks green before merge: Checks (ruff format/lint, mypy strict, zizmor workflow lint), Test Suite (pytest across Python 3.12–3.14 with coverage), Documentation (docs build, when docs change), and All Checks Passed (PR metadata, size, coverage diff, breaking-change scan, dependency review). Checks that aren't relevant to your change (e.g. docs checks on a code-only PR) pass automatically as skipped.
- Pushing new commits after an approval dismisses it — the updated PR needs re-review.
git clone https://github.com/<your-username>/pymediate.git
cd pymediate
uv sync --all-extras --group test
uvx pre-commit install # recommended: commit-time format/lint gateuv sync alone only installs the default dev group (ruff, mypy, poethepoet) — the
--group test above is required to get pytest and friends. See README.md
for the full command list.
pre-commit install wires the checks in .pre-commit-config.yaml
to run at every commit: format check, lint, and generated-context freshness. They invoke the
same poe tasks CI runs (tool versions come from uv.lock), so a commit that passes locally
won't bounce off CI for formatting. Optional but recommended — CI enforces the same checks
either way.
All dev commands go through Poe the Poet (tasks.toml), so
local results match CI exactly. Don't invent bespoke pytest/ruff/mypy invocations.
uv run poe dev # fix formatting/lint + fast test run — the standard inner loop
uv run poe pr # full check before opening a PR (fix, strict mypy, lint, tests+coverage)Run uv run poe with no arguments to see every available task.
- Tests: new behavior needs test coverage. The CI-enforced floor is 95% (
poe test:releaseis the exact gate a release runs). - Types:
src/pymediate/is checked withmypy --strict— zero untyped defs. - PR title: must follow Conventional Commits
(
feat:,fix:,docs:,chore:, etc.) — this is enforced by CI and also feeds the auto-generated changelog. - Public API changes: diffs to
__all__insrc/pymediate/__init__.py, theRequestHandlerclass, or theServiceProviderprotocol are treated as potential breaking changes. Nontrivial design or API changes should include an ADR indocs/adr/(see existing ones for the template). - Docstrings:
src/pymediate/docstrings (outside_internal/) are public-facing and mirrored by the API reference pages (docs/content/docs/api/) — seeCLAUDE.mdfor what belongs in them (and what doesn't), which sections to use, and why every example needs to be verified to run, not just look plausible.
tests/typing/snippets/errors/*.py are deliberately type-invalid — the harness in
tests/typing/ asserts that mypy and basedpyright each fail on every file there, with the
expected diagnostics pinned in tests/typing/expectations.py. If you see a type error under
errors/, that's the test working correctly. Never add # type: ignore or an exclusion for
those files. Snippets under valid/ must be clean under mypy --strict and basedpyright's
strictest mode, and must also execute at runtime — see tests/typing/README.md.
Open an issue — happy to discuss design direction before you invest time in a larger change.