grip-hook 0.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- grip_hook-0.2.1/.gitignore +24 -0
- grip_hook-0.2.1/CHANGELOG.md +173 -0
- grip_hook-0.2.1/LICENSE +28 -0
- grip_hook-0.2.1/PKG-INFO +381 -0
- grip_hook-0.2.1/README.md +332 -0
- grip_hook-0.2.1/branding/README.md +149 -0
- grip_hook-0.2.1/evals/README.md +62 -0
- grip_hook-0.2.1/evals/cases/clamp-add/after/tests/test_ops.py +13 -0
- grip_hook-0.2.1/pyproject.toml +119 -0
- grip_hook-0.2.1/src/grip_hook/__init__.py +16 -0
- grip_hook-0.2.1/src/grip_hook/__main__.py +6 -0
- grip_hook-0.2.1/src/grip_hook/agent.py +235 -0
- grip_hook-0.2.1/src/grip_hook/cli.py +1320 -0
- grip_hook-0.2.1/src/grip_hook/config.py +255 -0
- grip_hook-0.2.1/src/grip_hook/errors.py +31 -0
- grip_hook-0.2.1/src/grip_hook/git.py +240 -0
- grip_hook-0.2.1/src/grip_hook/hooks.py +140 -0
- grip_hook-0.2.1/src/grip_hook/integrations.py +386 -0
- grip_hook-0.2.1/src/grip_hook/memory.py +178 -0
- grip_hook-0.2.1/src/grip_hook/models.py +134 -0
- grip_hook-0.2.1/src/grip_hook/platform.py +357 -0
- grip_hook-0.2.1/src/grip_hook/prompts.py +173 -0
- grip_hook-0.2.1/src/grip_hook/providers/__init__.py +82 -0
- grip_hook-0.2.1/src/grip_hook/providers/agents.py +299 -0
- grip_hook-0.2.1/src/grip_hook/providers/anthropic.py +133 -0
- grip_hook-0.2.1/src/grip_hook/providers/base.py +45 -0
- grip_hook-0.2.1/src/grip_hook/providers/fake.py +255 -0
- grip_hook-0.2.1/src/grip_hook/providers/openai_compat.py +137 -0
- grip_hook-0.2.1/src/grip_hook/py.typed +0 -0
- grip_hook-0.2.1/src/grip_hook/quiz.py +158 -0
- grip_hook-0.2.1/src/grip_hook/study.py +267 -0
- grip_hook-0.2.1/src/grip_hook/terminal.py +137 -0
- grip_hook-0.2.1/src/grip_hook/whiteboard.html +429 -0
- grip_hook-0.2.1/src/grip_hook/whiteboard.py +476 -0
- grip_hook-0.2.1/src/grip_hook/whiteboard_server.py +208 -0
- grip_hook-0.2.1/tests/__init__.py +0 -0
- grip_hook-0.2.1/tests/conftest.py +95 -0
- grip_hook-0.2.1/tests/test_agent.py +287 -0
- grip_hook-0.2.1/tests/test_agents.py +282 -0
- grip_hook-0.2.1/tests/test_branding.py +85 -0
- grip_hook-0.2.1/tests/test_cli.py +324 -0
- grip_hook-0.2.1/tests/test_config.py +75 -0
- grip_hook-0.2.1/tests/test_evals.py +82 -0
- grip_hook-0.2.1/tests/test_git.py +157 -0
- grip_hook-0.2.1/tests/test_hooks.py +86 -0
- grip_hook-0.2.1/tests/test_init.py +102 -0
- grip_hook-0.2.1/tests/test_integrations.py +237 -0
- grip_hook-0.2.1/tests/test_memory.py +60 -0
- grip_hook-0.2.1/tests/test_platform.py +393 -0
- grip_hook-0.2.1/tests/test_providers.py +363 -0
- grip_hook-0.2.1/tests/test_quiz.py +95 -0
- grip_hook-0.2.1/tests/test_skip.py +209 -0
- grip_hook-0.2.1/tests/test_study.py +344 -0
- grip_hook-0.2.1/tests/test_terminal.py +35 -0
- grip_hook-0.2.1/tests/test_whiteboard.py +530 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
dist/
|
|
6
|
+
/build/
|
|
7
|
+
.venv/
|
|
8
|
+
venv/
|
|
9
|
+
.eggs/
|
|
10
|
+
|
|
11
|
+
# Tooling
|
|
12
|
+
.mypy_cache/
|
|
13
|
+
.ruff_cache/
|
|
14
|
+
.pytest_cache/
|
|
15
|
+
.coverage
|
|
16
|
+
.coverage.*
|
|
17
|
+
coverage.xml
|
|
18
|
+
htmlcov/
|
|
19
|
+
site/
|
|
20
|
+
|
|
21
|
+
# Editors / OS
|
|
22
|
+
.idea/
|
|
23
|
+
.vscode/
|
|
24
|
+
.DS_Store
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. The format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project adheres to
|
|
5
|
+
[Semantic Versioning](https://semver.org/).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [0.2.1] - 2026-10-19
|
|
10
|
+
|
|
11
|
+
grip is on PyPI.
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- grip is published to PyPI as `grip-hook`: `pipx install grip-hook` or
|
|
16
|
+
`uv tool install grip-hook`. Releases publish there through trusted publishing, and
|
|
17
|
+
the GitHub release carries the same sdist and wheel.
|
|
18
|
+
- The prebuilt binaries are gone. They bundled the build machine's Python, so the 0.1.0
|
|
19
|
+
and 0.2.0 Linux binaries needed glibc 2.38 and failed on Ubuntu 22.04 and older with
|
|
20
|
+
`GLIBC_2.38 not found`. `install.sh` now installs the package with uv or pipx, and
|
|
21
|
+
replaces a binary left by an earlier install.
|
|
22
|
+
- Windows is supported through the same install.
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- Whiteboard layout: a reply arrow (`allow -> handler` after `handler -> allow`) no longer
|
|
27
|
+
pushes the board into extra columns, a part that only feeds others (tests, a fixture)
|
|
28
|
+
sits just before what it feeds instead of across the input's arrow, and each column is
|
|
29
|
+
ordered by what feeds it so fewer arrows cross.
|
|
30
|
+
- Whiteboard page: long labels and roles end in an ellipsis inside their box (the full
|
|
31
|
+
text is in the tooltip and the side panel), long arrow labels wrap onto two lines and
|
|
32
|
+
slide along their arrow when they would cover another label, a box or a pin, and a pin
|
|
33
|
+
on an arrow sits on the arrow.
|
|
34
|
+
- Whiteboard page: two questions pinned to the same box or arrow were drawn on the same
|
|
35
|
+
spot, so one was hidden and could not be clicked. They now line up side by side.
|
|
36
|
+
- `--unpushed` described its range as `sha..sha` with the same commit twice; the base is
|
|
37
|
+
now resolved to the parent's hash.
|
|
38
|
+
|
|
39
|
+
## [0.2.0] - 2026-10-15
|
|
40
|
+
|
|
41
|
+
The whiteboard release.
|
|
42
|
+
|
|
43
|
+
### Added
|
|
44
|
+
|
|
45
|
+
- `grip whiteboard`: the change drawn as a whiteboard (its parts, how they connect, which
|
|
46
|
+
inputs are untrusted) with five questions pinned to it, defended in the browser. It asks
|
|
47
|
+
the two whiteboard questions the quiz does not: what a malicious actor could do and why
|
|
48
|
+
a data structure was chosen. Graded on the same 100-point scale; a pass counts for the
|
|
49
|
+
push hook. `--interview` shows the rubrics to the person asking and records nothing,
|
|
50
|
+
`--export` writes a shareable page without rubrics. Served on 127.0.0.1 behind a random
|
|
51
|
+
token, with `Host`/`Origin` checks and a hash-based Content-Security-Policy.
|
|
52
|
+
- `/grip-whiteboard` skill for Claude Code, Codex, Gemini CLI, Cursor and friends.
|
|
53
|
+
- Providers draw boards (`generate_board`); the `fake` provider draws one from the changed
|
|
54
|
+
files or from a scripted `board`.
|
|
55
|
+
|
|
56
|
+
### Changed
|
|
57
|
+
|
|
58
|
+
- grip is now explained around the whiteboard defense: could you explain and defend this
|
|
59
|
+
change at a whiteboard? The README, the docs homepage and a new
|
|
60
|
+
[whiteboard defense](https://guilyx.github.io/grip/whiteboard-defense/) page map grip's
|
|
61
|
+
five questions to that bar, and say when to leave grip off (prototypes, demos,
|
|
62
|
+
experiments).
|
|
63
|
+
|
|
64
|
+
## [0.1.0] - 2026-10-15
|
|
65
|
+
|
|
66
|
+
The first public release.
|
|
67
|
+
|
|
68
|
+
### Added
|
|
69
|
+
|
|
70
|
+
- `grip quiz`: five questions about your staged, unpushed or ranged diff, graded to a Grip
|
|
71
|
+
Score out of 100 with a configurable pass mark.
|
|
72
|
+
- `grip hook pre-commit` / `grip hook pre-push`: hook entry points, both for native git
|
|
73
|
+
hooks and for the pre-commit framework (`.pre-commit-hooks.yaml`).
|
|
74
|
+
- `grip install` / `grip uninstall` / `grip status` / `grip config` / `grip forget`.
|
|
75
|
+
- Providers: Anthropic (default, `claude-opus-5`), any OpenAI-compatible API including
|
|
76
|
+
Ollama, and an offline `fake` provider for tests and demos.
|
|
77
|
+
- Configuration through `.grip.toml`, `[tool.grip]` in `pyproject.toml`, `GRIP_*`
|
|
78
|
+
environment variables and command-line flags.
|
|
79
|
+
- Passed diffs are remembered for a configurable time so you are not quizzed twice.
|
|
80
|
+
- Documentation site built with MkDocs Material and published to GitHub Pages.
|
|
81
|
+
- A brand identity under `branding/`: the grip mark (an 87.5 percent score ring that
|
|
82
|
+
reads as a G) in ember orange on warm graphite, Archivo wordmark lockups, a social card,
|
|
83
|
+
and a palette generated in OKLCH by `branding/build.py`, which refuses to write colours that fail WCAG 2 or APCA contrast or
|
|
84
|
+
that collapse under simulated colour-blindness. `tests/test_branding.py` keeps the
|
|
85
|
+
generated files current.
|
|
86
|
+
- A redesigned docs site: the generated palette and brand type, tabs for each section, a
|
|
87
|
+
homepage with an install switcher and feature cards, dark code blocks with checked
|
|
88
|
+
syntax colours, an announcement bar, social preview tags, and a Brand page.
|
|
89
|
+
- Feature clips under `docs/assets/features/`: one MP4 and GIF per new feature and a
|
|
90
|
+
stitched "what's new" cut, recorded by `scripts/demo/make_features.py` against the real
|
|
91
|
+
CLI with a scripted offline scenario.
|
|
92
|
+
- Every recording in the brand: the demo, the feature clips, the launch video and the
|
|
93
|
+
Product Hunt stills now use the ember and graphite palette, JetBrains Mono in the
|
|
94
|
+
terminal, Archivo and Instrument Sans on the title cards, and the mark. Colours come from
|
|
95
|
+
`branding/tokens.json` through `scripts/demo/brandkit.py`, so videos and docs cannot drift.
|
|
96
|
+
- Two more skills: `grip-review` quizzes a reviewer on a range of commits (or prints the
|
|
97
|
+
questions for the author with `--questions-only`), `grip-explain` turns the developer's
|
|
98
|
+
answers from the last quiz into a commit message or pull request description. `grip last`
|
|
99
|
+
shows the last graded quiz, with `--json` for skills to build on.
|
|
100
|
+
- An evals harness under `evals/`: eight fixture diffs with author notes, three answer
|
|
101
|
+
arms (blank, generic, notes), repeated grading for noise, a committed fake-provider
|
|
102
|
+
snapshot the tests keep current, and a Markdown summary. A "Honest numbers" docs page
|
|
103
|
+
says what is measured, what is not, and where grip gets in the way.
|
|
104
|
+
- `CITATION.cff` and a Cite section in the README; `.github/FUNDING.yml`. The README opens
|
|
105
|
+
with a three-tile summary and a before/after comparison, and its Why section cites the
|
|
106
|
+
METR, GitClear, DORA and Stack Overflow findings on AI-assisted code.
|
|
107
|
+
- `grip skip` skips the next hook run once, `grip skip --hours N` pauses hooks for a
|
|
108
|
+
while, `grip resume` cancels. Both the git hooks and the Claude Code push gate honour
|
|
109
|
+
it; a manual `grip quiz` does not. `grip status` gained an activity section: last quiz,
|
|
110
|
+
whether the staged and unpushed diffs have passed, and the skip state.
|
|
111
|
+
`grip statusline` prints `grip 92/100 pass` for editor and Claude Code status lines.
|
|
112
|
+
- `grip init`: one command that writes a commented `.grip.toml` (defaulting to an
|
|
113
|
+
installed coding agent as provider), installs the pre-push hook and writes the agent
|
|
114
|
+
rule files. `--provider`, `--passing-score`, `--difficulty`, `--stage`, `--no-hook`,
|
|
115
|
+
`--no-agents`, `--force`. `config.render_template` produces the file.
|
|
116
|
+
- `grip agents`: detects the coding agents installed on the machine (Claude Code, Codex,
|
|
117
|
+
Gemini CLI, Cursor, Windsurf, Cline, Copilot, OpenCode) and writes, inside the
|
|
118
|
+
repository, the rule file each one reads so it runs `grip check` before pushing.
|
|
119
|
+
Marked blocks in `AGENTS.md`, `CLAUDE.md` and `GEMINI.md`, grip-owned rules files for
|
|
120
|
+
Cursor, Windsurf and Cline, and the plugin enablement merged into
|
|
121
|
+
`.claude/settings.json`. `--list`, `--all`, `--agent`, `--dry-run` and `--remove`.
|
|
122
|
+
- Keep A Grip, the problem platform: `grip login`, `grip logout`, `grip whoami`,
|
|
123
|
+
`grip problems`, `grip solve SLUG` and `grip submit`. Solve a problem with any assistant,
|
|
124
|
+
get quizzed on the diff, have the score reported with an API token. `Diff` gained a
|
|
125
|
+
`context` field that the prompts pass to the model as data, and `Git.worktree_diff` diffs
|
|
126
|
+
everything since a base commit, uncommitted work included.
|
|
127
|
+
|
|
128
|
+
- Demo recording (`docs/assets/demo.gif`, `.webm`, `.cast`) and the script that
|
|
129
|
+
regenerates it, `scripts/demo/make_demo.py`.
|
|
130
|
+
- Scripted mode for the `fake` provider through `GRIP_FAKE_SCRIPT`, plus `GRIP_FAKE_DELAY`
|
|
131
|
+
for artificial latency.
|
|
132
|
+
- Providers that reuse a coding agent already installed and signed in, so no API key is
|
|
133
|
+
needed: `claude-code` (alias `claude`), `codex` and `gemini`. Each runs the CLI in
|
|
134
|
+
non-interactive mode with tools disabled, in an empty scratch directory.
|
|
135
|
+
|
|
136
|
+
- Claude Code plugin, served from this repository as a marketplace
|
|
137
|
+
(`/plugin marketplace add guilyx/grip`): `/grip` relays the quiz through Claude and
|
|
138
|
+
a `PreToolUse` hook blocks `git push` until the diff has passed.
|
|
139
|
+
- The quiz skill lives at `skills/grip/SKILL.md` in the Agent Skills layout, so
|
|
140
|
+
`npx skills add guilyx/grip` installs it into any agent that reads `SKILL.md` files.
|
|
141
|
+
The plugin is served from the repository root (`hooks/hooks.json`, `skills/`).
|
|
142
|
+
- `grip ask`, `grip grade`, `grip check` and `grip agent-hook`: the quiz as
|
|
143
|
+
non-interactive JSON steps so any coding agent can drive it.
|
|
144
|
+
- `grip study export` and `grip study status`: an anonymised JSON summary of every quiz
|
|
145
|
+
taken on the machine (scores and per-question focus only, never code, paths, questions,
|
|
146
|
+
answers or feedback) for study platforms. Graded quizzes are now appended to
|
|
147
|
+
`.git/grip/history.jsonl` and the repository is recorded in
|
|
148
|
+
`$XDG_DATA_HOME/grip/repos.json`. Nothing is uploaded by grip.
|
|
149
|
+
|
|
150
|
+
- Launch assets under `docs/assets/launch/` (Product Hunt image, icon, 1080p launch video)
|
|
151
|
+
and `docs/assets/demo.mp4`, generated by `scripts/launch/make_launch.py`; a Media page
|
|
152
|
+
on the docs site.
|
|
153
|
+
|
|
154
|
+
### Changed
|
|
155
|
+
|
|
156
|
+
- `grip init` reports agent files as "wrote" or "updated" instead of "written", and the
|
|
157
|
+
generic `AGENTS.md` target is labelled "AGENTS.md readers".
|
|
158
|
+
|
|
159
|
+
- The demo now quizzes a ROS 2 change: a `cmd_vel` safety filter gaining a lidar scan
|
|
160
|
+
watchdog, instead of a payment refund.
|
|
161
|
+
- Distribution: `install.sh` (`curl -fsSL .../install.sh | sh`) downloads a prebuilt,
|
|
162
|
+
checksum-verified binary from GitHub Releases into `~/.local/bin`. The release workflow
|
|
163
|
+
builds binaries for Linux and macOS (x86_64 and arm64) with PyInstaller; nothing is
|
|
164
|
+
published to PyPI anymore.
|
|
165
|
+
- `model` now defaults to empty, meaning the provider's own default (`claude-opus-5` for
|
|
166
|
+
Anthropic, the CLI's default for coding agents). `openai` and `ollama` require it.
|
|
167
|
+
- The score table uses rounded borders, matching the panels.
|
|
168
|
+
- The "blocked" message prints the report path relative to the working directory.
|
|
169
|
+
|
|
170
|
+
[Unreleased]: https://github.com/guilyx/grip/compare/v0.2.1...HEAD
|
|
171
|
+
[0.2.1]: https://github.com/guilyx/grip/compare/v0.2.0...v0.2.1
|
|
172
|
+
[0.2.0]: https://github.com/guilyx/grip/compare/v0.1.0...v0.2.0
|
|
173
|
+
[0.1.0]: https://github.com/guilyx/grip/releases/tag/v0.1.0
|
grip_hook-0.2.1/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, Erwin L.
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
grip_hook-0.2.1/PKG-INFO
ADDED
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: grip-hook
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: Pass the whiteboard defense before you push: a git hook that quizzes you on your own diff.
|
|
5
|
+
Project-URL: Homepage, https://github.com/guilyx/grip
|
|
6
|
+
Project-URL: Documentation, https://guilyx.github.io/grip/
|
|
7
|
+
Project-URL: Repository, https://github.com/guilyx/grip
|
|
8
|
+
Project-URL: Issues, https://github.com/guilyx/grip/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/guilyx/grip/blob/main/CHANGELOG.md
|
|
10
|
+
Project-URL: Releases, https://github.com/guilyx/grip/releases
|
|
11
|
+
Author-email: "Erwin L." <erwin.lejeune15@gmail.com>
|
|
12
|
+
License-Expression: BSD-3-Clause
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Keywords: code-review,git,hook,llm,pre-commit,pre-push,quiz,understanding
|
|
15
|
+
Classifier: Development Status :: 4 - Beta
|
|
16
|
+
Classifier: Environment :: Console
|
|
17
|
+
Classifier: Intended Audience :: Developers
|
|
18
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
19
|
+
Classifier: Operating System :: OS Independent
|
|
20
|
+
Classifier: Programming Language :: Python :: 3
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
25
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
26
|
+
Classifier: Topic :: Software Development :: Version Control :: Git
|
|
27
|
+
Classifier: Typing :: Typed
|
|
28
|
+
Requires-Python: >=3.11
|
|
29
|
+
Requires-Dist: anthropic<2,>=1.0
|
|
30
|
+
Requires-Dist: click>=8.1
|
|
31
|
+
Requires-Dist: pydantic>=2.5
|
|
32
|
+
Requires-Dist: rich>=13.7
|
|
33
|
+
Provides-Extra: demo
|
|
34
|
+
Requires-Dist: fonttools>=4.40; extra == 'demo'
|
|
35
|
+
Requires-Dist: imageio-ffmpeg>=0.5; extra == 'demo'
|
|
36
|
+
Requires-Dist: pillow>=10.0; extra == 'demo'
|
|
37
|
+
Requires-Dist: pyte>=0.8; extra == 'demo'
|
|
38
|
+
Provides-Extra: dev
|
|
39
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
40
|
+
Requires-Dist: pre-commit>=3.7; extra == 'dev'
|
|
41
|
+
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
42
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
43
|
+
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
44
|
+
Provides-Extra: docs
|
|
45
|
+
Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
|
|
46
|
+
Requires-Dist: mkdocs<2,>=1.6; extra == 'docs'
|
|
47
|
+
Requires-Dist: mkdocstrings[python]>=0.25; extra == 'docs'
|
|
48
|
+
Description-Content-Type: text/markdown
|
|
49
|
+
|
|
50
|
+
**Pass the whiteboard defense before you push.** grip is a git hook that pulls you aside
|
|
51
|
+
at the moment you ship and asks what a senior engineer would: what does this do, why this
|
|
52
|
+
way and not another, where does it fail, what could go wrong. Five questions written from
|
|
53
|
+
your own diff, a Grip Score out of 100, and a pass mark your team sets. Below the mark,
|
|
54
|
+
nothing goes upstream.
|
|
55
|
+
|
|
56
|
+
[](https://github.com/guilyx/grip/actions/workflows/ci.yml)
|
|
57
|
+
[](https://guilyx.github.io/grip/)
|
|
58
|
+
[](https://pypi.org/project/grip-hook/)
|
|
59
|
+
[](https://github.com/guilyx/grip/blob/main/LICENSE)
|
|
60
|
+
|
|
61
|
+
<table>
|
|
62
|
+
<tr>
|
|
63
|
+
<td align="center" width="33%"><h3>5 questions</h3>written from <em>your</em> diff, graded against a hidden rubric, 100 points</td>
|
|
64
|
+
<td align="center" width="33%"><h3>12 → 92</h3>the same diff blocked with lazy answers, through with real ones (<a href="https://github.com/guilyx/grip/blob/main/docs/assets/demo.mp4">demo</a>)</td>
|
|
65
|
+
<td align="center" width="33%"><h3>0 new tools to learn</h3>a git hook, or <code>/grip</code> inside Claude Code, Codex, Gemini CLI, Cursor and friends</td>
|
|
66
|
+
</tr>
|
|
67
|
+
</table>
|
|
68
|
+
|
|
69
|
+
<table>
|
|
70
|
+
<tr>
|
|
71
|
+
<th width="50%">Without grip</th>
|
|
72
|
+
<th width="50%">With grip</th>
|
|
73
|
+
</tr>
|
|
74
|
+
<tr>
|
|
75
|
+
<td valign="top">
|
|
76
|
+
|
|
77
|
+
```text
|
|
78
|
+
$ git push
|
|
79
|
+
Enumerating objects: 14, done.
|
|
80
|
+
...
|
|
81
|
+
To github.com:acme/robot.git
|
|
82
|
+
3f2a1c9..8e7d4b2 main -> main
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Four hundred lines the assistant wrote, nobody on the team can explain, now upstream.
|
|
86
|
+
|
|
87
|
+
</td>
|
|
88
|
+
<td valign="top">
|
|
89
|
+
|
|
90
|
+
```text
|
|
91
|
+
$ git push
|
|
92
|
+
grip 5 questions about your push (pre-push). Pass mark: 70/100.
|
|
93
|
+
|
|
94
|
+
Q1/5 (behaviour) What happens to a cmd_vel message
|
|
95
|
+
whose linear.x exceeds the new limit?
|
|
96
|
+
> it is clamped to the limit, not dropped, so the
|
|
97
|
+
> robot keeps moving at the max speed
|
|
98
|
+
|
|
99
|
+
...
|
|
100
|
+
|
|
101
|
+
Grip Score: 92/100 PASS
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Same push, one minute later. You can explain it in the review because you just did.
|
|
105
|
+
|
|
106
|
+
</td>
|
|
107
|
+
</tr>
|
|
108
|
+
</table>
|
|
109
|
+
|
|
110
|
+

|
|
111
|
+
|
|
112
|
+
<sub>A commit blocked at 12/100, then accepted at 92/100. [Video version](https://github.com/guilyx/grip/blob/main/docs/assets/demo.mp4). Recorded with the offline scripted provider, so questions are canned; a real model writes them from your diff.</sub>
|
|
113
|
+
|
|
114
|
+
## The whiteboard defense
|
|
115
|
+
|
|
116
|
+
> I should be able to pull you aside at any moment and ask you to explain any
|
|
117
|
+
> customer-facing system you've shipped. [...] I don't expect line-level familiarity with
|
|
118
|
+
> the code. [...] But if I ask "why did you do X instead of Y?", "what happens if this
|
|
119
|
+
> actor behaves maliciously?", "what data structure did you use here and why?", or "where
|
|
120
|
+
> does this fail?" you should be able to answer confidently.
|
|
121
|
+
>
|
|
122
|
+
> Mitchell Hashimoto
|
|
123
|
+
|
|
124
|
+
We found this post after we built grip, and it is the clearest statement of what grip is
|
|
125
|
+
for. (Mitchell is not involved with grip.) The bar is not line-level recall. It is being
|
|
126
|
+
able to explain and defend a system you put in front of customers, however much of it a
|
|
127
|
+
model wrote.
|
|
128
|
+
|
|
129
|
+
grip runs that conversation at the last moment it is cheap: before the push.
|
|
130
|
+
|
|
131
|
+
| At the whiteboard | What grip asks |
|
|
132
|
+
| --- | --- |
|
|
133
|
+
| How does it work? | **behaviour**: what the change does, in your words |
|
|
134
|
+
| Why X instead of Y? | **motivation**: why it is built this way |
|
|
135
|
+
| Where does this fail? | **edge case**: the input or state that breaks it |
|
|
136
|
+
| What could go wrong? | **risk**: side effects, and what else it touches |
|
|
137
|
+
| How do you know? | **testing**: how you would show it works |
|
|
138
|
+
|
|
139
|
+
The questions never ask for line numbers, exact strings or function names. A model writes
|
|
140
|
+
them from the diff with a hidden rubric, then grades your answers against it.
|
|
141
|
+
|
|
142
|
+
### `grip whiteboard`: the whole defense
|
|
143
|
+
|
|
144
|
+
`grip whiteboard` draws your change the way you would on a whiteboard: its parts, how they
|
|
145
|
+
connect, which inputs are untrusted. It pins five questions to the drawing, including the
|
|
146
|
+
two the quiz does not ask directly, **what a malicious actor could do** and **why this data
|
|
147
|
+
structure**, and opens it in your browser. Click a part, answer, hand in. Same score, same
|
|
148
|
+
pass mark, and a pass counts for the push hook.
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
grip whiteboard # defend your staged change in the browser
|
|
152
|
+
grip whiteboard --interview # for the person asking: rubrics visible, tick what you hear
|
|
153
|
+
grip whiteboard --export board.html # a shareable copy, no rubrics
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+

|
|
157
|
+
|
|
158
|
+
**Prototype freely, defend what ships.** For a spike, a demo or an experiment, generate
|
|
159
|
+
all of it: `grip skip` lets one push through, `grip skip --hours 2` pauses grip for a
|
|
160
|
+
session, or leave the hook out of that repository. Keep the hook where customers are.
|
|
161
|
+
|
|
162
|
+
## Why now
|
|
163
|
+
|
|
164
|
+
Generated code, big refactors, late-night pastes: it is easy to ship a change you could
|
|
165
|
+
not explain in a code review. grip makes you explain it *before* it leaves your machine.
|
|
166
|
+
It is a lightweight forcing function, not a gate for correctness.
|
|
167
|
+
|
|
168
|
+
The gap grip aims at is measured, not imagined. In METR's 2025 randomised trial,
|
|
169
|
+
experienced open-source developers using AI tools were [19% slower while believing they
|
|
170
|
+
were 20% faster](https://metr.org/blog/2025-07-10-early-2025-ai-experienced-os-dev-study/).
|
|
171
|
+
GitClear's analysis of 153 million changed lines found [code churn on track to double
|
|
172
|
+
since AI assistants arrived](https://www.gitclear.com/coding_on_copilot_data_shows_ais_downward_pressure_on_code_quality).
|
|
173
|
+
The [2024 DORA report](https://dora.dev/research/2024/dora-report/) linked higher AI
|
|
174
|
+
adoption to lower delivery stability, and the [2025 Stack Overflow survey](https://survey.stackoverflow.co/2025/ai)
|
|
175
|
+
put developer trust in AI output at a low. Every one of these tools checks the code. grip
|
|
176
|
+
is the only one that checks the human.
|
|
177
|
+
|
|
178
|
+
## Install
|
|
179
|
+
|
|
180
|
+
grip is on PyPI as [`grip-hook`](https://pypi.org/project/grip-hook/). Install it as a
|
|
181
|
+
command-line tool with pipx or uv (Python 3.11+):
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
pipx install grip-hook # or: uv tool install grip-hook
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Plain `pip install grip-hook` works too, inside a virtualenv. You get `grip` and a
|
|
188
|
+
`git grip` alias, on Linux, macOS and Windows. Upgrade with `pipx upgrade grip-hook` (or
|
|
189
|
+
`uv tool upgrade grip-hook`).
|
|
190
|
+
|
|
191
|
+
Neither pipx nor uv? `curl -fsSL https://raw.githubusercontent.com/guilyx/grip/main/install.sh | sh`
|
|
192
|
+
uses whichever one it finds and tells you how to get uv when there is neither.
|
|
193
|
+
|
|
194
|
+
Then, in your repository:
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
grip init
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
That writes a commented `.grip.toml`, installs the pre-push hook and tells the coding
|
|
201
|
+
agents on your machine to run the quiz before they push. Commit the files and the team
|
|
202
|
+
shares the setup. Prefer to do it by hand? Two ways to wire it into git:
|
|
203
|
+
|
|
204
|
+
### Option A: native git hook
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
cd your-repo
|
|
208
|
+
grip install # pre-push (recommended)
|
|
209
|
+
grip install --stage pre-commit --stage pre-push # both
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Option B: pre-commit framework
|
|
213
|
+
|
|
214
|
+
```yaml
|
|
215
|
+
# .pre-commit-config.yaml
|
|
216
|
+
repos:
|
|
217
|
+
- repo: https://github.com/guilyx/grip
|
|
218
|
+
rev: v0.2.1
|
|
219
|
+
hooks:
|
|
220
|
+
- id: grip # runs at pre-push
|
|
221
|
+
# - id: grip-commit # runs at pre-commit
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
pre-commit install --hook-type pre-push
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Inside a coding agent
|
|
229
|
+
|
|
230
|
+
grip is an [Agent Skill](https://agentskills.io). One command installs it into Claude
|
|
231
|
+
Code, Codex, Gemini CLI, Cursor, Windsurf, Cline, Copilot and friends:
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
npx skills add guilyx/grip -g
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Then type `/grip` before you push. The agent relays the five questions, you answer, grip
|
|
238
|
+
grades; the skill forbids the agent from answering for you. `/grip-review main..feat`
|
|
239
|
+
quizzes a reviewer on someone else's branch, `/grip-explain` turns your answers into the
|
|
240
|
+
commit message, and `/grip-whiteboard` opens the whiteboard in your browser so you answer
|
|
241
|
+
there. Claude Code users can take the
|
|
242
|
+
plugin instead, which also blocks `git push` until the diff has passed:
|
|
243
|
+
|
|
244
|
+
```text
|
|
245
|
+
/plugin marketplace add guilyx/grip
|
|
246
|
+
/plugin install grip@grip
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
`grip agents` writes the rule file each agent on your machine reads (`AGENTS.md`,
|
|
250
|
+
`CLAUDE.md`, `GEMINI.md`, Cursor, Windsurf, Cline, Copilot) so it runs `grip check` before
|
|
251
|
+
every push. See the [docs](https://guilyx.github.io/grip/agents/) for the raw
|
|
252
|
+
`grip ask` / `grip grade` flow any agent can drive.
|
|
253
|
+
|
|
254
|
+
### Provider
|
|
255
|
+
|
|
256
|
+
Already using Claude Code, Codex or Gemini CLI? grip can reuse it, so there is no API key
|
|
257
|
+
and no extra bill:
|
|
258
|
+
|
|
259
|
+
```toml
|
|
260
|
+
# .grip.toml
|
|
261
|
+
provider = "claude-code" # or "codex", or "gemini"
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Otherwise grip talks to Anthropic by default: export `ANTHROPIC_API_KEY` (or run
|
|
265
|
+
`ant auth login`). For a local, free setup use Ollama:
|
|
266
|
+
|
|
267
|
+
```toml
|
|
268
|
+
# .grip.toml
|
|
269
|
+
provider = "ollama"
|
|
270
|
+
model = "qwen3:14b"
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Any OpenAI-compatible API works with `provider = "openai"` plus `base_url` and
|
|
274
|
+
`OPENAI_API_KEY`.
|
|
275
|
+
|
|
276
|
+
## Use
|
|
277
|
+
|
|
278
|
+
Nothing to do: commit or push as usual. When grip runs, answer five questions in the
|
|
279
|
+
terminal. Pass the mark and the commit or push continues; fail it and it is blocked with
|
|
280
|
+
per-question feedback so you know what to go read.
|
|
281
|
+
|
|
282
|
+
```bash
|
|
283
|
+
grip quiz # quiz your staged changes right now, no hook needed
|
|
284
|
+
grip quiz --unpushed # quiz everything not yet on a remote
|
|
285
|
+
grip quiz --provider fake # try the flow offline with a dummy grader
|
|
286
|
+
grip whiteboard # draw the change and defend it in the browser
|
|
287
|
+
grip status # hooks, last score, what has passed, configuration
|
|
288
|
+
grip skip && git push # bypass once (GRIP_SKIP=1 and --no-verify work too)
|
|
289
|
+
grip skip --hours 1 # pause the hooks for a demo; grip resume cancels
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
A diff that passed is remembered for 24 hours, so a pre-push right after a pre-commit
|
|
293
|
+
does not ask again.
|
|
294
|
+
|
|
295
|
+
## Practice on Keep A Grip
|
|
296
|
+
|
|
297
|
+
[Keep A Grip](https://keepagrip.vercel.app) is a catalogue of ROS 2, robotics and AI
|
|
298
|
+
problems built for this loop: solve one with any assistant, then let grip quiz you on the
|
|
299
|
+
diff. Scores land on your dashboard; anyone can add a problem.
|
|
300
|
+
|
|
301
|
+
```bash
|
|
302
|
+
grip login kag_… # token from your dashboard
|
|
303
|
+
grip solve cmd-vel-safety-filter # a repo with PROBLEM.md, pass mark preset
|
|
304
|
+
grip submit # tests, five questions, score reported
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
Only the score leaves your machine. See the [docs](https://guilyx.github.io/grip/keepagrip/).
|
|
308
|
+
|
|
309
|
+
## Configure
|
|
310
|
+
|
|
311
|
+
`.grip.toml` at the repository root, or `[tool.grip]` in `pyproject.toml`. Environment
|
|
312
|
+
variables (`GRIP_PASSING_SCORE=80`) and flags (`--passing-score 80`) override files.
|
|
313
|
+
|
|
314
|
+
```toml
|
|
315
|
+
passing_score = 70 # 0-100, the Grip Score you need
|
|
316
|
+
difficulty = "normal" # easy | normal | hard
|
|
317
|
+
provider = "anthropic" # anthropic | claude-code | codex | gemini | openai | ollama | fake
|
|
318
|
+
model = "" # empty = provider default (claude-opus-5 for anthropic)
|
|
319
|
+
effort = "medium" # low | medium | high | xhigh | max | none
|
|
320
|
+
exclude = ["*.lock", "*.snap"]
|
|
321
|
+
max_diff_bytes = 200000
|
|
322
|
+
remember_passes_hours = 24
|
|
323
|
+
require_tty = false # fail instead of skipping in non-interactive runs
|
|
324
|
+
fail_open = false # let commits through when the provider is down
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
Full reference: [guilyx.github.io/grip](https://guilyx.github.io/grip/).
|
|
328
|
+
|
|
329
|
+
## How the score works
|
|
330
|
+
|
|
331
|
+
- The model reads the diff and writes exactly five questions, each with a hidden rubric.
|
|
332
|
+
- You answer each in a sentence or two.
|
|
333
|
+
- The model grades all five against the rubrics: 0 to 20 points each, summed to a
|
|
334
|
+
Grip Score out of 100.
|
|
335
|
+
- Score at or above `passing_score` lets the commit or push through.
|
|
336
|
+
|
|
337
|
+
Only the diff (after `exclude`) and your answers are sent to the provider. Reports are
|
|
338
|
+
saved to `.git/grip/last-report.json`.
|
|
339
|
+
|
|
340
|
+
## When grip gets in the way
|
|
341
|
+
|
|
342
|
+
A one-line typo fix does not deserve five questions, and grip cannot tell a rename from a
|
|
343
|
+
rewrite. That is what the escape hatches are for: `grip skip` for one push,
|
|
344
|
+
`grip skip --hours 1` for a pairing session, `exclude` for generated files,
|
|
345
|
+
`remember_passes_hours` so a passed diff is not asked twice. Questions and grading come
|
|
346
|
+
from a language model, so they are sometimes unfair; the per-question feedback shows you
|
|
347
|
+
why, and `grip quiz` gives a fresh set. What has and has not been measured about the
|
|
348
|
+
quiz, and where it is known to be annoying, is on the
|
|
349
|
+
[honest numbers](https://guilyx.github.io/grip/honest-numbers/) page; the
|
|
350
|
+
[evals harness](https://github.com/guilyx/grip/blob/main/evals/) behind it runs offline. Treat the Grip Score as a prompt to
|
|
351
|
+
re-read, not a verdict.
|
|
352
|
+
|
|
353
|
+
## Cite
|
|
354
|
+
|
|
355
|
+
If grip is part of a study or a course, please cite it ([CITATION.cff](https://github.com/guilyx/grip/blob/main/CITATION.cff)):
|
|
356
|
+
|
|
357
|
+
```bibtex
|
|
358
|
+
@software{lejeune2026grip,
|
|
359
|
+
author = {L., Erwin},
|
|
360
|
+
title = {grip: keep a grip on your code},
|
|
361
|
+
year = {2026},
|
|
362
|
+
url = {https://github.com/guilyx/grip}
|
|
363
|
+
}
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
## Contributing
|
|
367
|
+
|
|
368
|
+
Issues and pull requests are welcome. See [CONTRIBUTING.md](https://github.com/guilyx/grip/blob/main/CONTRIBUTING.md) for the
|
|
369
|
+
development setup, and [SECURITY.md](https://github.com/guilyx/grip/blob/main/SECURITY.md) for what data grip touches.
|
|
370
|
+
|
|
371
|
+
## Media
|
|
372
|
+
|
|
373
|
+
A [launch video](https://github.com/guilyx/grip/blob/main/docs/assets/launch/grip-launch-1080p.mp4), the
|
|
374
|
+
[Product Hunt image](https://raw.githubusercontent.com/guilyx/grip/main/docs/assets/launch/grip-producthunt-1270x760.png) and the icon live
|
|
375
|
+
under `docs/assets/launch/`, all generated from the demo recording by
|
|
376
|
+
`scripts/launch/make_launch.py`. Use them freely. More on the
|
|
377
|
+
[media page](https://guilyx.github.io/grip/media/).
|
|
378
|
+
|
|
379
|
+
## License
|
|
380
|
+
|
|
381
|
+
[BSD-3-Clause](https://github.com/guilyx/grip/blob/main/LICENSE). Copyright (c) 2026, Erwin L.
|