Contributing

Fork the repository, make the change on a branch, check it as below, and open a pull request against main.

Set up

$ make install-source
$ pipx install pre-commit

make install-source installs yace from the checkout, in editable mode, with pipx, along with pytest and the other development tools. make build install instead builds the wheel and installs it, as CI does.

The tests build and run the check of each target, and skip a target whose toolchain is missing: a C compiler for the C target, a C and a C++ compiler for the C++ target, Cython for the Python target, cargo for Rust, zig and go. yace --toolchains prints the toolchains yace finds. The devenv image, from toolbox/devenv, has all of them, and is what CI runs the tests in:

$ make devenv-run CMD="make install test"

Check a change

$ make format-all
$ make test

make format-all runs pre-commit on every file: black formats the Python, ruff lints it, mypy checks the types, yamlfmt and actionlint check the YAML and the workflows, and REUSE checks every file’s license. make test runs the tests with coverage. make example parses examples/libfoo strictly, and emits and checks every target. CI runs all three, the tests in the devenv with Python 3.12 and 3.13, and on macOS.

What a change carries

A change carries a test that fails without the change. A test of a target parses a header it writes itself, with the parse fixture of tests/conftest.py, and runs the target’s check, so that the test covers what a compiler makes of the emitted code and not only its text.

make docs builds the documentation, and runs kmdo first, which makes again the command output the documentation shows. Commit the output it changes with the change that changed it.

Commits

Commits follow Conventional Commits: a type, as feat, fix or docs, and a summary in the imperative mood. The body says why the change is made, in one to three paragraphs, ideally one. The body does not describe the diff, and does not say what was built, run or tested; that belongs in the pull request description. Lines are at most 78 characters.

A commit message and the source code do not refer to an issue, a pull request or anything else the hosting service numbers or links, and do not use closing keywords such as Fixes #123. Name the behaviour, the function or the commit instead, and name a commit by its abbreviated hash and its summary. The history is cloned, mirrored and read offline, where such a reference names nothing.

Sign off each commit with git commit -s. The sign-off acknowledges the Developer Certificate of Origin: that you wrote the change, or have the right to pass it on under the project’s license. When a coding agent helped make a change, the commit says so in an Assisted-by: trailer before the sign-off, naming the agent and the language model actually used, and any analysis tool after them, separated by spaces:

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Full Name <address>

The trailer is the only disclosure. Do not add a Co-Authored-By: trailer for an agent, and do not add “Generated with” or similar prose to a commit message or a pull request description. You answer for such a change as for any other.

A release is the last commit of its pull request: ver: bump to vX.Y.Z with its section of CHANGELOG.md. Merging that pull request publishes the release to PyPI. The maintainer makes the releases.