Contribute¶
The full contributor guide. The root CONTRIBUTING.md is a quick-start summary; this page is the long form.
Before you write code¶
Read these in order:
- The Mission and Architecture.
- The Invariants — the rules nothing in this repo gets to break.
- The Design Rationale — past decisions, in case the question has been settled already.
CLAUDE.md— repo governance, including the authority split between contributors and the project lead.
Before you propose an API change¶
The closed effects taxonomy and the public API are non-negotiable without project sign-off. If your change touches either:
- Open an issue first with a written rationale.
- Wait for a HALT-format response from the project lead before sending a PR.
- Expect to update the parity tests and add a Porting Notes entry.
Without prior agreement, PRs that modify __all__ or extend the Effect enum will be closed with a request to convert to an issue.
Local setup¶
git clone https://github.com/MacFall7/spine-lite-python
cd spine-lite-python
uv venv
uv sync --all-extras --dev
The first sync also fetches Python 3.11 if needed and resolves the lockfile (~70 packages).
Verification gates¶
Before every commit:
nox -s lint typecheck test
Or, if you don't have nox installed and want to invoke the underlying tools directly:
uv run ruff check .
uv run ruff format --check .
uv run mypy src tests
uv run pytest
Before every push, also:
nox -s coverage docs
Or:
uv run pytest --cov=spine_lite --cov-fail-under=95
uv run mkdocs build --strict
Coverage must be at or above 95% on every commit, and at 100% on the modules a phase implements at its exit gate. Docs build with --strict (treats warnings as errors).
Coding standards¶
- Python 3.11+.
from __future__ import annotationsat the top of every module. mypy --strictclean. NoAnywithout a comment justifying the carve-out. The repo has zeroAnytoday; keep it that way.- Google-style docstrings on every public symbol. Include
Examples:for non-trivial functions. - Frozen, slotted, kw-only dataclasses by default. Mutable only with explicit justification in a comment.
- Imports sorted by ruff/isort: stdlib → third-party → first-party. No wildcard imports. Use
if TYPE_CHECKING:for cycle-prone or annotation-only imports. - Pure modules (
effects,manifest,classifier,posture,receipt) contain no I/O, no clocks, no randomness. I/O lives inhookandcli. - Conventional Commits. Subject ≤ 72 chars, imperative mood, no trailing period. Types in use:
feat,fix,docs,chore,test,refactor,ci,perf. - Voice. Direct prose. No marketing tone, no LLM boilerplate, no performative empathy. See
docs/explanation/design-rationale.mdand the existing READMEs for the baseline.
Tests¶
pytest for everything. hypothesis for invariants and determinism properties. Parametrize aggressively; one parametrized test beats six near-duplicates.
Authored fixtures live in tests/fixtures/ (Phase 2 onward). Use them as-is; don't mock. Round-trip parity with byte-stability is the spec.
import pytest
from hypothesis import given, strategies as st
from spine_lite.effects import Effect, most_restrictive, PRECEDENCE
@pytest.mark.parametrize(
("effects", "expected"),
[
({Effect.READ}, Effect.READ),
({Effect.READ, Effect.WRITE}, Effect.WRITE),
],
)
def test_dominance_table(effects: set[Effect], expected: Effect) -> None:
assert most_restrictive(effects) is expected
@given(effects=st.sets(st.sampled_from(list(Effect)), min_size=1))
def test_result_is_member_of_input(effects: set[Effect]) -> None:
assert most_restrictive(effects) in effects
No network calls in tests. Determinism is the runtime contract — the test suite enforces it.
Pull requests¶
- One logical change per PR.
- Reference an issue if one exists.
- Include the verification gate output in the PR description if any cell took unusual time.
- Keep the diff focused — drive-by refactors slow review.
- The project lead reviews every PR; expect a HALT-format message if anything is unclear, and respond in kind.
Architecture rules (recap)¶
The closed effects taxonomy and its precedence ordering are the spec. The five core modules (effects.py, classifier.py, posture.py, manifest.py, receipt.py) are pure — no I/O, no timestamps, no randomness. I/O belongs in hook.py, cli.py, and tests.
If your change cannot be made within those rules, that's a project-level discussion, not a PR.
See also¶
- How-To / Release — the release process.
- Explanation / Invariants — the hard-line rules.
- Explanation / FAQ — common questions about the design.