Contributing to grip¶
Thanks for helping keep a grip on things. This document covers the workflow; the Code of Conduct covers how we treat each other.
Development setup¶
git clone https://github.com/guilyx/grip
cd grip
uv venv && source .venv/bin/activate # or: python -m venv .venv
uv pip install -e ".[dev,docs]" # or: pip install -e ".[dev,docs]"
pre-commit install --hook-type pre-commit --hook-type pre-push
Yes, grip quizzes you before you push to grip. Set GRIP_SKIP=1 if you are pushing
work in progress to a draft branch and know what you are doing.
Day-to-day commands¶
| Task | Command |
|---|---|
| Lint and format | ruff check . && ruff format . |
| Type-check | mypy |
| Tests | pytest (add --cov for coverage) |
| Try it offline | grip quiz --provider fake |
| Docs preview | mkdocs serve |
CI runs the same four checks on Linux, macOS and Windows across supported Python versions, so running them locally first saves a round trip.
Making changes¶
- Open an issue first for anything bigger than a bug fix, so we can agree on the approach before you spend time on it.
- Branch from
main. Keep pull requests focused: one change per PR. - Add or update tests. New behaviour without a test will be asked to grow one.
- Add a line under
UnreleasedinCHANGELOG.md. - Update the docs in
docs/if the user-facing behaviour changed. - Open the pull request; the template lists what reviewers look for.
Style¶
- Python 3.11+, fully typed (
mypy --strictpasses), Google-style docstrings. ruffis the single source of truth for formatting and linting.- Prompts live in
src/grip_hook/prompts.pyonly. Changing them is a behaviour change: say what you changed and why in the PR, and try it against a few real diffs.
Adding a provider¶
Implement the Provider protocol from grip_hook.providers.base, register a factory in
grip_hook.providers.REGISTRY, and add tests that exercise it without network access
(see tests/test_providers.py for the pattern). Keep new runtime dependencies to a
minimum; optional ones belong in an extra.
Regenerating the demo¶
The README animation is produced from a scripted, offline session so it is reproducible:
pip install -e ".[demo]"
python scripts/demo/make_demo.py all # record -> docs/assets/demo.cast, render -> .gif + .webm
python scripts/launch/make_launch.py # demo.mp4 + the launch image, icon and video under docs/assets/launch
record builds a throwaway repository, installs the hook with the fake provider driven
by scripts/demo/scenario.json, and types the answers in make_demo.py into a real
bash session. render replays the asciinema-format cast through pyte and Pillow.
Edit the scenario or the answers, re-run, and commit the three assets together. The
cast also plays with asciinema play docs/assets/demo.cast.
Releasing (maintainers)¶
Releases are prebuilt binaries on GitHub Releases; nothing goes to PyPI.
- Bump
__version__insrc/grip_hook/__init__.pyand move theUnreleasedsection ofCHANGELOG.mdunder the new version. - Commit, tag
vX.Y.Z, push the tag. - The
releaseworkflow builds single-file binaries with PyInstaller for Linux and macOS (x86_64 and arm64), writesSHA256SUMS, and creates the GitHub release with the changelog section as notes.install.shfetches from there.
To build a binary locally: pip install -e ".[build]" && scripts/build/package.sh, then
GRIP_DOWNLOAD_BASE=file://$PWD/some-dir sh install.sh to exercise the installer against
it (CI does exactly this on every push).