Contributing#
How to contribute#
The good first issues are the bugs and feature requests to start with.
Setting up a development environment#
The project runs in pixi.
- Install pixi following the official instructions.
- In your clone of the repository, install the environment and the commit hooks:
The hooks run on every commit. They format Python, Markdown, YAML and TOML, lint and type-check the Python, and check the licence headers. These commands run the same checks and the rest of the gate by hand:
pixi run lint: every commit hook, over every file.pixi run test: the test suite.pixi run test-coverageadds coverage.pixi run compile-tex: print every spec in the tree to standalone LaTeX and compile it.pixi run ci: lint, tests, a strict docs build and the LaTeX compile. This is what CI runs. Run it before you push.
Documentation#
The pages under docs/ are Markdown, built by
Zensical from mkdocs.yml. The build is strict: a
dead link or a stale anchor fails it. A page with no nav entry does not fail
the build, and pixi run test is what reports it. pixi run docs-serve builds
the site and serves it at http://127.0.0.1:8000, rebuilding when a page
changes.
I have updated the README.md
The home page includes named sections of the README rather than a copy: the
badges, the example spec and the status note. A section
--8<-- "README.md:name". Edit inside the markers, and the site
follows.
Keep the sections link-free, or link absolutely. A relative link resolves
against docs/index.md on the site and against the repository root on GitHub,
and only one of those can be right.
I have changed what a spec prints
Every page that carries a block a tool writes is listed in
tests/test_docs.py's GENERATED table, and a test compares each block to
its generator. Regenerate rather than edit, and read the diff:
pixi run python -m tools.home_math # docs/index.md and README.md, from examples/dispatch.yaml
pixi run python -m tools.notation # docs/reference/notation.md, from tests/typesetting/golden/model.yaml
pixi run python -m tools.spec_math # the operator table on docs/reference/language/operators.md
pixi run python -m tools.gallery # the example pages, from examples/
Each tool takes --check to report drift without writing.
I want to add a new page
Add a Markdown file under docs/, then add it to the nav key in
mkdocs.yml:
The module pages under Development are generated from the docstrings, so a
new module appears in the next build. A new public name also needs its own
::: entry on the Python API page.
Naming across the layers#
The same construct passes through three layers, and each names it in full. The suffix says which layer:
| Layer | Suffix | Example |
|---|---|---|
YAML block (mathspec.spec) |
Block |
VariableBlock, PiecewiseBlock |
Syntax (mathspec.*_parser) |
Node |
NameNode, UnresolvedComparisonNode |
Program (mathspec.program) |
none / Declaration |
Variable, VariableDeclaration |
A node names the operation, not the verb a file writes. One verb can resolve to two nodes, so the file's spelling cannot decide the name.
| File verb | Node | What the node names |
|---|---|---|
sum(over=) |
Sum |
dims removed from the result |
sum(by=) |
GroupSum |
a sum through a relation |
at(by=) |
Pullback |
a read through a relation |
shift(along=) |
Translate |
a re-index along one dimension |
sum_back(along=) |
WindowSum |
a sum over a trailing window |
Nothing is abbreviated.
Adding an operator#
Start with the grammar, which is usually free because f(x, k=v) already
parses. Then declare the signature in operators.BUILTINS. It holds the number
of arguments and says which arguments name dimensions, and resolution reads it
from there. Then write the node in program.py and how resolution builds it,
the dimension rule in dimensions.py, the degree verdict in degree.py, and
the entry in the language reference.
Submitting changes#
To contribute changes:
- Fork the project on GitHub.
- Create a feature branch to work on in your fork (
git checkout -b new-fix-or-feature). - Test your changes using
pixi run test, orpixi run cifor everything CI will check. - Commit your changes to the feature branch (you should have
pre-commitinstalled to ensure your code is correctly formatted when you commit changes). - Push the branch to GitHub (
git push origin new-fix-or-feature). - On GitHub, create a new pull request from the feature branch.
When you contribute for the first time, ensure your reviewer adds you as a contributor!
Pull requests#
Before submitting a pull request, check whether you have:
- Written the PR title as a conventional commit subject (see below).
- Added its line under
## Upcoming versioninCHANGELOG.mdby hand, if the PR is afeat,fix,perf,refactor,docsorrevert(see below). - Added or updated documentation for your changes (see The docs).
- Added tests if you implemented new functionality.
When opening a pull request, please provide a clear summary of your changes!
The docs#
docs/ is both the site and what you read on GitHub. What a page is for
decides where it goes, in the nav and in the tree: a tutorial (docs/), a
how-to guide (docs/howto/), reference (docs/reference/, and the example pages
in docs/examples/) or explanation (docs/about/) — the four kinds of
Diátaxis — and one page is one kind. A page a spec
writer does not need goes under Development in the nav: building on
mathspec, contributing, or a proof of concept. The rules each kind has to meet, and the sentence-level
bar, are in
the docs-writing skill.
Every page needs a nav: entry in mkdocs.yml, links inside docs/ are
relative, and a link outside it is the full GitHub URL. pixi run docs-build
is --strict and refuses a dead link or a stale anchor; pixi run test is
what refuses a page with no nav entry.
Commit messages#
Merges are squashed, so the PR title becomes the commit on main. Write it
so it also works as the line you add to CHANGELOG.md by hand. It must be a
conventional commit subject:
<type>[(scope)]: <subject>
feat: AST parsing for indexed constraints
fix(parser): where clauses with a trailing comma
docs: describe the two expression tiers
A feat, fix, perf, refactor, docs or revert PR adds its title,
with a link to the PR, under ## Upcoming version in CHANGELOG.md. A
chore, test, ci, build or style PR adds no line. The
Conventional commit subject check enforces the format on every pull request,
and the Changelog line check enforces the line. The label no changelog
opts a PR out of the second.
See RELEASING.md.
Beyond the subject line, write whatever body the change deserves — a paragraph or bullet list covering what changed and its impact.
Code conventions#
Start reading our code and you'll get the hang of it.
We mostly follow the official Style Guide for Python Code (PEP8).
We have chosen to use the uncompromising code formatter and linter ruff.
When run from the root directory of this repo, pyproject.toml should ensure that formatting and linting fixes are in line with our custom preferences (e.g., maximum line length).
To make this a smooth experience, you should run pixi run pre-commit-install after setting up your development environment.
If you prefer, you can also set up your IDE to run these two tools whenever you save your files, and to have ruff highlight erroneous code directly as you type.
Take a look at their documentation for more information on configuring this.
We require all new contributions to have docstrings for all modules, classes and methods. When adding docstrings, we request you use the Google docstring style.
Releases#
A release is a PR that renames ## Upcoming version in CHANGELOG.md to the
version and the day, such as ## 0.1.0 (2026-10-01), and edits the section
into the release notes. Merging it tags the release, opens the GitHub release
and publishes the package to PyPI.
The version is never written down in the source tree — it comes from the git
tag at build time, and mathspec.__version__ reads it back from the installed
package metadata.
See RELEASING.md for the steps, what to do when one fails, and the one-time repository setup.