Contributing¶
Note
This page mirrors CONTRIBUTING.md at the repository root, which is the
canonical source. Links below resolve on GitHub.
Contributing to stimela-ninja¶
Thanks for your interest in contributing! stimela-ninja (import name
shinobi, CLI ninja) is a simple but flexible framework for reproducible
radio-astronomy pipelines — Stimela 3.0. It’s early-beta software, so the most
valuable contributions right now are bug reports, focused fixes, tests,
documentation, and feedback on the design.
By participating you agree to abide by our Code of Conduct.
Scope and philosophy¶
Our guiding principle is to keep things simple and robust, while staying flexible and effective. We favour small, focused changes and prefer solving problems with plain Python over adding new layers of machinery.
For background on how the project is put together and the reasoning behind its
current design, see
AGENTS.md.
It’s helpful context when proposing anything that touches the recipe/
orchestration layer. If you’re considering a larger change, opening an issue to
discuss it first is a great way to align before writing code.
Ways to contribute¶
Report bugs and request features via issues.
Improve documentation under
docs/or the docstrings that feed the API reference.Submit code — bug fixes, new cabs/loaders/backends, tests.
Add examples under
examples/.
Development setup¶
The project uses uv:
uv sync --group dev
.venv/bin/pytest
.venv/bin/ruff check src tests
# enable the repo's pre-commit hook (once per clone)
git config core.hooksPath .githooks
(You can equivalently use uv run pytest / uv run ruff check src tests.)
The lockfile¶
uv.lock is committed, and uv sync installs exactly what it pins. CI
runs every job with --locked, so the versions the matrix tests are the
versions you ran locally — not whatever PyPI published that morning. When a
build goes red, that keeps “this branch broke it” and “a dependency broke it”
as separate, answerable questions.
The floors in pyproject.toml still define what a downstream pip install
resolves. The lock binds only this repo’s checkouts and CI.
Two consequences worth knowing:
Change
pyproject.toml, re-runuv lock, commit both. CI’s--lockedrejects the mismatch, and so does the pre-commit hook.Dependency upgrades are a deliberate commit, not a side effect of the clock:
uv lock --upgrade(or--upgrade-package <name>) produces a reviewable diff.
Dependabot (.github/dependabot.yaml) opens those upgrade PRs weekly, updating
pyproject.toml and uv.lock together so they pass the --locked jobs.
Dev-tooling and runtime minor/patch bumps arrive batched; a runtime major gets
its own PR on purpose. Security updates are enabled repo-side and are not
batched or scheduled — those PRs arrive when an advisory does.
The pre-commit hook¶
Enabling it is the only setup step that is not uv’s job — git will not let a
repository turn on an executable hook by itself, which is why the git config
above is manual; skip it and you simply get no hook.
.githooks/pre-commit is tracked in the repo, and does two things when (and
only when) the commit touches pyproject.toml or uv.lock:
checks the lock is in sync with
pyproject.toml;runs
pip-auditover the locked runtime dependencies.
Any other commit skips it in milliseconds — pip-audit is a network round trip
per package, and it cannot tell you anything new about a dependency set you
have not touched. It installs nothing and pins nothing: pip-audit comes from
the audit dependency group via uv run, so it and CI’s audit job check the
same versions with the same commands.
Run that audit by hand any time:
uv export --frozen --no-emit-project --no-default-groups --no-hashes --format requirements-txt |
uv run --group audit pip-audit --no-deps -r /dev/stdin
A finding is usually fixed by uv lock --upgrade-package <name>, raising the
floor in pyproject.toml too if the vulnerable range is one a downstream
install could still land on. If there is no fixed release and the advisory does
not apply here, pass --ignore-vuln <ID> and record why in pyproject.toml
alongside the group. git commit --no-verify bypasses the hook when you
genuinely need to.
Testing¶
Run the suite with:
pytest -q
Most tests are unit/mocked and always run. The live-backend integration
tests (tests/test_docker_live.py, tests/test_kubernetes_live.py,
tests/test_slurm_live.py) auto-skip when the infrastructure isn’t present —
they probe at runtime (shutil.which(...) plus a liveness check such as
docker image inspect, kubectl cluster-info, or sinfo), so you don’t need
to set any opt-in flag. To run them locally, see the docstrings at the top of
each file for the exact setup (a kind cluster for Kubernetes; the throwaway
Slurm cluster in tests/slurm_live/ for Slurm).
New features and bug fixes should come with tests. Follow the existing
layout in tests/ (flat directory, shared fixtures in tests/fixtures/; no
__init__.py — the suite runs in importlib mode with namespace packages).
Code style¶
Lint must be clean:
ruff check src testsshould report no errors. Ruff runs with its default rule set atline-length = 100(seepyproject.toml).ruff formatis available and uses the same line width if you’d like autoformatting.Use type hints and write docstrings on public API — they render into the Sphinx API reference via autodoc.
Match the surrounding code’s naming, comment density, and idiom.
Documentation¶
Docs are built with Sphinx (Furo theme) and hosted on Read the Docs. Build them locally with:
uv sync --group docs
uv run sphinx-build -b html docs docs/_build/html
Please update the docs when you change public API. If you add a documentation
dependency, keep docs/requirements.txt in sync with the docs dependency
group in pyproject.toml (Read the Docs installs from the former).
Pull requests¶
Branch off
mainand keep PRs small and focused — one logical change per PR is much easier to review.Make sure
pytest -qandruff check src testspass locally, and that docs build if you touched public API.Push and open a PR against
main. Reference any related issue (e.g. “Closes #12”).CI must be green. The
testjob runs the suite and lint across Python 3.10, 3.11 and 3.12 — that’s the merge gate. An automatedreviewjob also posts an AI code-review comment; treat it as advisory, not a gate, and weigh its findings with judgement. PerAGENTS.md’s “Reviewing changes: check the tree, not just the diff”, verify any “this doesn’t exist / is unused” claim against the actual tree before acting on it.
Commit messages¶
Write clear, descriptive commit messages explaining why a change is made. No formal convention (Conventional Commits, sign-off/DCO, or CLA) is required.
Provenance for an assistant-assisted commit goes in a commit trailer and never in the PR description — see Attribution: commit trailers yes, PR trailers no.
Versioning and releases¶
The project follows Semantic Versioning. Contributors
don’t cut releases — that’s a maintainer task. Releases are tag-driven: the
maintainer bumps version in pyproject.toml and pushes a vX.Y.Z tag, and
the release.yml workflow verifies the tag matches the package version, builds,
and publishes to PyPI.
License¶
By contributing, you agree that your contributions are licensed under the project’s MIT License.