Contributing¶
Python 3.11–3.15 are supported, and CI runs the full matrix.
Setup¶
The project uses mise and Poetry.
mise install
poetry install
vekna cast comes from the optional rituals group, which a plain
poetry install leaves out. On Python 3.11–3.14, cast a ritual through mise,
which installs the group first:
mise run cast <ritual>
A cast pushes through an https remote, since it has no terminal for an ssh key passphrase. A fresh clone adds one once, substituting its own URL on a fork:
git remote add https-origin https://github.com/fancysnake/tingle.git
gh auth login && gh auth setup-git
Checks¶
Tasks are defined in mise.toml; mise tasks lists them all.
mise run test:py # the test suite
mise run lint:py # ruff, mypy, pylint, import-linter, codespell, vulture
mise run format # black, ruff --fix, taplo
tingle dogfoods its own CI gate — tingle check runs on every pull request,
so a branch that takes on debt fails the build.
Tests¶
tests/ is split by test type, and the layer under test picks the type — not
convenience, and not whichever is easier to reach coverage with.
| Layer | Type | Where |
|---|---|---|
mills, specs, pacts, and pure helpers from any layer |
unit | tests/unit/ |
links, gates, inits — anything that touches the filesystem, git, or a terminal |
integration | tests/integration/ |
Unit tests mock the protocols in pacts and assert how they were called;
tests/unit/ is organised by convenience rather than mirroring src/.
Integration tests run against the real thing — a git repository in a tmpdir, a
typer CliRunner, a textual pilot — and assert side effects.
An uncovered line belongs to the test type that owns its layer: never raise
links or gates coverage with a mock-everything unit test. Those two suites
are also what the test:unit and test:int tasks run separately, so a
boundary change can be checked without the whole suite.
Docs¶
The documentation site is MkDocs + Material, built from docs/ and
published to GitHub Pages from main.
mise run site:dev # live-reloading preview on localhost:8000
mise run site:build # build into site/; strict, so a broken link fails it
The docs dependency group is optional, so a plain poetry install does not
pull in MkDocs. Both tasks install it on demand.
Architecture¶
The source follows the GLIMPSE layout, and the layer boundaries are enforced
by import-linter contracts in pyproject.toml — a violating import fails
CI.
| Layer | Responsibility |
|---|---|
pacts |
contracts — depends on nothing |
specs |
invariants — depends only on pacts |
mills |
logic, no IO |
links |
IO adapters |
gates |
CLI |
inits |
wiring |
specs is imported only by mills; gates and inits reach it indirectly
through mills, which is by design.