Contributing¶
Contributions of all kinds are welcome: bug reports, bug fixes, features, documentation, and feedback. This guide covers the development setup and the contribution workflow. A step-by-step setup for a new machine is in the contributor setup guide.
Ways to contribute¶
- Report bugs at https://github.com/TUW-GEO/canvodpy/issues. Include your operating system, Python version, and the steps to reproduce.
- Fix bugs: issues labeled "bug" and "help wanted" are open for pull requests. Comment on the issue first so nobody duplicates work.
- Implement features: issues labeled "enhancement" and "help wanted" are open. Keep the scope narrow: one feature per pull request.
- Improve documentation: docs, docstrings, or external articles. See the development guide.
- Submit feedback: file feature proposals at https://github.com/TUW-GEO/canvodpy/issues. Keep the scope narrow and explain the intended behavior.
Required tools¶
Two tools must be installed before uv sync, which doesn't manage them:
the uv package manager and the
just command runner.
# macOS
brew install uv just
# Linux
curl -LsSf https://astral.sh/uv/install.sh | sh
curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh \
| bash -s -- --to ~/.local/bin
# Windows
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
winget install Casey.Just
The repository needs uv 0.13 (required-version in pyproject.toml), so
every machine writes uv.lock the same way. Install a 0.13 release if uv
refuses to start.
Contribution workflow¶
# 1. Fork on GitHub, then clone your fork
git clone git@github.com:YOUR_USERNAME/canvodpy.git
cd canvodpy
# 2. Check the tools, install dependencies and hooks
just check-dev-tools
git submodule update --init --recursive
uv sync
just hooks
just agent-skills # if you develop with Claude Code: the shared agent workflow
# 3. Create a feature branch
git checkout -b feature/my-feature
# 4. Make changes
# packages/<pkg>/src/canvod/<pkg>/ implementation
# packages/<pkg>/tests/ tests
# 5. Verify
just test && just check
# 6. Commit with Conventional Commits
git add packages/<pkg>/src/... packages/<pkg>/tests/...
git commit -m "feat(readers): add RINEX 4.0 support"
# 7. Push and open a pull request
git push origin feature/my-feature
Commit message format¶
<type>(<scope>): <subject>
Types: feat, fix, docs, refactor, test, chore, perf, ci
Scopes, for example: readers, aux, config, grids, vod, store,
viz, utils, naming, orchestrator, diagnostics, ops, ci, docs,
deps (the list in [tool.commitizen.customize] of pyproject.toml)
git commit -m "feat(vod): add tau-omega calculator"
git commit -m "fix(readers): handle empty RINEX files"
git commit -m "docs: update installation instructions"
git commit -m "feat(viz)!: redesign 3D plotting API" # ! = breaking change
See Conventional Commits for the full specification.
Git hooks¶
just hooks installs Git hooks that run on their own. If a hook before the
commit fails, the commit is rejected: your files stay staged, but no commit
is created.
| Hook | When | What it does |
|---|---|---|
| ruff check --fix | Before commit | Lints Python; fixes what it can (unused imports, style) |
| ruff format | Before commit | Formats Python code |
| uv-lock | Before commit | Checks that uv.lock matches all pyproject.toml files |
| trailing-whitespace | Before commit | Strips trailing whitespace |
| check-added-large-files | Before commit | Blocks files larger than the size limit |
| detect-private-key | Before commit | Blocks committed SSH/PGP keys |
| end-of-file-fixer | Before commit | Ends files with exactly one newline |
| check-agent-docs | Before commit | Checks that paths and just recipes named in AGENTS.md files and skills exist |
| commitizen | After writing the message | Checks the type(scope): subject format |
| ty-check | Before push | Type-checks the code with ty |
| code-graph | After commit, merge, checkout | Updates the code graph in the background |
When a commit is rejected:
- ruff fixed your code: it changed the files in place. Stage the fixes
and commit again:
git add -u, then the samegit commit. - commitizen rejected the message: write it as
type(scope): subject, for examplefix(readers): handle empty observation epochs. - uv-lock is out of date: run
uv sync,git add uv.lock, and commit again.
More cases are in the hook troubleshooting.
Common commands¶
just --list # Show all commands
just test # Run all tests
just test-coverage # With coverage report
just test-package canvod-grids # Specific package
just check # Lint + format + type-check
just docs # Preview documentation
just docs-lint # Language check of the docs (Vale)
just docs-lint-changed # Vale on the pages changed since origin/main
just docs-links # Link check of the built docs (lychee)
Continuous integration¶
Every push and pull request runs these checks on GitHub Actions:
- Python Code Quality: checks that
uv.lockis up to date and runs ruff (lint and format) and ty (types). It also builds and imports every package with only its declared dependencies and checks the paths named inAGENTS.mdfiles and skills. - Test with coverage: pytest with coverage, uploaded to Coveralls and posted as a comment on the pull request.
- Platform Tests: the test suite on Linux, Windows, and macOS.
- Docs Quality: a language check (Vale) of the changed pages and root files, and a link check (lychee) of the built site.
Pull request guidelines¶
just test && just checkpass with no errors.- The change follows
CODING_STANDARDS.md(rules and per-change checklist); the reasons are indocs/adr/. - Include tests for every new feature: every new public function and method
has at least one test, edge cases and error paths (empty inputs, invalid
parameters) are covered, and coverage doesn't decrease. Run
just test-coverageto see which lines are covered. - Update the Zensical documentation in
docs/with every change of behavior (new logic, new code, changed code), public API, or configuration. A pull request without docs to match is declined. The changed pages must pass Vale (just docs-lint-changed) and the built site the link check (just docs-links); CI runs both. - Target Python 3.14.
- Add yourself to
CONTRIBUTORS.mdif this is your first contribution.
Licensing¶
canVODpy is licensed under the
Apache License 2.0. By
submitting a pull request, you agree that your contribution is licensed under
the same terms. The LICENSE and NOTICE files at the repository root apply
to all source files. Per-file license headers aren't required.
Releasing (maintainers)¶
just release-status # commits per package since its last tag
just release canvod-store=1.1.0 canvodpy=1.1.0 # tests, versions, release commit on release/v1.1.0
Each package has its own version; the maintainer chooses them, and canvodpy
is part of every release. Then open a pull request for the release branch;
after it's merged, a maintainer runs just tag-release on main, pushes
the tags, and starts the PyPI publish. The full steps are in the
release process.