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.
Files changed (55) hide show
  1. grip_hook-0.2.1/.gitignore +24 -0
  2. grip_hook-0.2.1/CHANGELOG.md +173 -0
  3. grip_hook-0.2.1/LICENSE +28 -0
  4. grip_hook-0.2.1/PKG-INFO +381 -0
  5. grip_hook-0.2.1/README.md +332 -0
  6. grip_hook-0.2.1/branding/README.md +149 -0
  7. grip_hook-0.2.1/evals/README.md +62 -0
  8. grip_hook-0.2.1/evals/cases/clamp-add/after/tests/test_ops.py +13 -0
  9. grip_hook-0.2.1/pyproject.toml +119 -0
  10. grip_hook-0.2.1/src/grip_hook/__init__.py +16 -0
  11. grip_hook-0.2.1/src/grip_hook/__main__.py +6 -0
  12. grip_hook-0.2.1/src/grip_hook/agent.py +235 -0
  13. grip_hook-0.2.1/src/grip_hook/cli.py +1320 -0
  14. grip_hook-0.2.1/src/grip_hook/config.py +255 -0
  15. grip_hook-0.2.1/src/grip_hook/errors.py +31 -0
  16. grip_hook-0.2.1/src/grip_hook/git.py +240 -0
  17. grip_hook-0.2.1/src/grip_hook/hooks.py +140 -0
  18. grip_hook-0.2.1/src/grip_hook/integrations.py +386 -0
  19. grip_hook-0.2.1/src/grip_hook/memory.py +178 -0
  20. grip_hook-0.2.1/src/grip_hook/models.py +134 -0
  21. grip_hook-0.2.1/src/grip_hook/platform.py +357 -0
  22. grip_hook-0.2.1/src/grip_hook/prompts.py +173 -0
  23. grip_hook-0.2.1/src/grip_hook/providers/__init__.py +82 -0
  24. grip_hook-0.2.1/src/grip_hook/providers/agents.py +299 -0
  25. grip_hook-0.2.1/src/grip_hook/providers/anthropic.py +133 -0
  26. grip_hook-0.2.1/src/grip_hook/providers/base.py +45 -0
  27. grip_hook-0.2.1/src/grip_hook/providers/fake.py +255 -0
  28. grip_hook-0.2.1/src/grip_hook/providers/openai_compat.py +137 -0
  29. grip_hook-0.2.1/src/grip_hook/py.typed +0 -0
  30. grip_hook-0.2.1/src/grip_hook/quiz.py +158 -0
  31. grip_hook-0.2.1/src/grip_hook/study.py +267 -0
  32. grip_hook-0.2.1/src/grip_hook/terminal.py +137 -0
  33. grip_hook-0.2.1/src/grip_hook/whiteboard.html +429 -0
  34. grip_hook-0.2.1/src/grip_hook/whiteboard.py +476 -0
  35. grip_hook-0.2.1/src/grip_hook/whiteboard_server.py +208 -0
  36. grip_hook-0.2.1/tests/__init__.py +0 -0
  37. grip_hook-0.2.1/tests/conftest.py +95 -0
  38. grip_hook-0.2.1/tests/test_agent.py +287 -0
  39. grip_hook-0.2.1/tests/test_agents.py +282 -0
  40. grip_hook-0.2.1/tests/test_branding.py +85 -0
  41. grip_hook-0.2.1/tests/test_cli.py +324 -0
  42. grip_hook-0.2.1/tests/test_config.py +75 -0
  43. grip_hook-0.2.1/tests/test_evals.py +82 -0
  44. grip_hook-0.2.1/tests/test_git.py +157 -0
  45. grip_hook-0.2.1/tests/test_hooks.py +86 -0
  46. grip_hook-0.2.1/tests/test_init.py +102 -0
  47. grip_hook-0.2.1/tests/test_integrations.py +237 -0
  48. grip_hook-0.2.1/tests/test_memory.py +60 -0
  49. grip_hook-0.2.1/tests/test_platform.py +393 -0
  50. grip_hook-0.2.1/tests/test_providers.py +363 -0
  51. grip_hook-0.2.1/tests/test_quiz.py +95 -0
  52. grip_hook-0.2.1/tests/test_skip.py +209 -0
  53. grip_hook-0.2.1/tests/test_study.py +344 -0
  54. grip_hook-0.2.1/tests/test_terminal.py +35 -0
  55. 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
@@ -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.
@@ -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
+ [![CI](https://github.com/guilyx/grip/actions/workflows/ci.yml/badge.svg)](https://github.com/guilyx/grip/actions/workflows/ci.yml)
57
+ [![Docs](https://github.com/guilyx/grip/actions/workflows/docs.yml/badge.svg)](https://guilyx.github.io/grip/)
58
+ [![PyPI](https://img.shields.io/pypi/v/grip-hook)](https://pypi.org/project/grip-hook/)
59
+ [![License: BSD-3-Clause](https://img.shields.io/badge/license-BSD--3--Clause-blue.svg)](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
+ ![grip blocking a commit with lazy answers, then letting it through after real ones](https://raw.githubusercontent.com/guilyx/grip/main/docs/assets/demo.gif)
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
+ ![grip whiteboard: the change drawn as parts, with questions pinned to them](https://raw.githubusercontent.com/guilyx/grip/main/docs/assets/whiteboard.png)
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.