Changelog¶
All notable changes to this project are documented here. The format follows Keep a Changelog and the project adheres to Semantic Versioning.
Unreleased¶
0.5.0 - 2026-10-25¶
Upgrading from 0.4: passes remembered by 0.4 are asked once more, because
passed.json now records the score and grader of each pass. grip grade no longer
takes --passing-score; the pass mark comes from the configuration. The Keep A Grip
commands moved under grip platform, and their old names still work for now.
Added¶
retry_questionsoption:same(the default) keeps reusing a diff's questions and board on a retry;newforgets them when a quiz, a whiteboard defense orgrip gradefails, so the next try asks new ones. Grading feedback now says where to look again and what kind of point is missing, instead of naming the rubric point, so a failed quiz can no longer be passed by pasting its feedback back.[[paths]]tables in.grip.toml(or[[tool.grip.paths]]inpyproject.toml) set apassing_score, adifficultyorskip = truefor the files that match theirmatchglobs. A diff takes the strictest pass mark and the hardest difficulty of the files it touches, and a diff made only of skipped files is not quizzed.- A GitHub Action (
uses: guilyx/grip@...): on each pull request it writes five questions about the diff since the merge base and posts them as one comment, updated on every push, for the author or a reviewer to answer in review. Nothing is graded in CI.export-board: truealso uploads the whiteboard as a workflow artifact and links it. - Diffs with nothing to explain are skipped, with the reason printed: only renames or mode
changes, only whitespace (a re-indent still counts), only paths in the new
skip_pathsoption (e.g.["docs/**", "*.md"]), or fewer added and removed lines than the newmin_changed_linesoption (off by default).grip check, the agent push gate andgrip statusagree with the hook;grip quiz --forcequizzes such a diff anyway. - The model reads the messages of the commits being quizzed (pushes,
--unpushed,--branch,--range), up to 4 KB, as delimited data next to the diff, so questions can ask why and grading can weigh what the developer said they meant. Rewording a commit gets new questions.include_commit_messages = falsesends the diff alone. grip appeal <question> "<why my answer was right>"has one question of the last graded quiz graded again by the same provider, with the justification as data. It never lowers a grade, allows one appeal per question per quiz, is saved in the report and the history and shown bygrip last, and records a pass when the new score reaches the pass mark.- grip measures what a quiz costs. Each graded quiz records the wall time of every model
request and, where the provider reports it (the Anthropic and OpenAI-compatible APIs,
not the coding-agent CLIs), the tokens used; the result ends with
took 9.8 s · 3,140 tokens,grip lastshows it per request,grip statusaverages the last 20 quizzes (an appealed quiz counts once), andgrip gradeandgrip last --jsoninclude it. Agrip appealrequest is added to its quiz as anappealcall. The FAQ explains how to turn tokens into a cost with your provider's prices.
Changed¶
- An empty diff in
grip quiz,grip whiteboardandgrip asksays what there is instead and what to run, e.g. "nothing staged. 3 untracked files and 1 modified file:git addthem first; 2 unpushed commits: trygrip quiz --unpushedor--branch".grip askadds the same as data in ahintfield. The "Try it" line at the end ofgrip initsuggests a command that has something to quiz in that repository. grip initwritesprovider = "auto"instead of this machine's coding agent, so the committed.grip.tomlworks for teammates without that tool. It prints whatautomeans on this machine, listsautofirst in its menu, and writes a concrete provider only when you pick one. It now also asks for the difficulty (defaultnormal) and says "wrote AGENTS.md for any coding agent that reads it" instead of "for AGENTS.md readers".--provider autois accepted on every command.- A missing API key for the
anthropicoropenaiprovider is reported before the quiz starts, instead of after the "reading your diff" spinner. The message lists the fixes: set the key variable (api_key_env,ANTHROPIC_API_KEYby default), use a coding agent installed on the machine, or try--provider fake. Theopenaiprovider no longer asks for a key whenbase_urlpoints at a server of your own. - The quiz header names what is being quizzed in words:
3 commits on feat/x → originfor a push,main..HEADas typed for--range,2 unpushed commits on mainfor--unpushed, andeverything since the first commitinstead of the empty tree's hash for a first push. When the diff is overmax_diff_bytesit also says how much the model saw:grip saw the first 200 KB of a 790 KB change (max_diff_bytes). grip --helplists the core commands first (init, quiz, whiteboard, status, skip, resume, last), groups the rest, and ends with examples. The Keep A Grip commands moved undergrip platform(grip platform login,solve,submit, …); the old top-level names still work and print a one-line note that they moved.hook,agent-hook,statusline,askandgradeare no longer listed in the help but work as before.grip configshows where each value comes from (default,pyproject.toml,.grip.toml,env GRIP_…or a flag) and which configuration files it read, and takes--provider,--model,--passing-scoreand--difficultyto preview a run with those flags.grip statusshows a short summary instead of the full table: provider, pass mark, difficulty and hook mode.- Docs: new pages on cost and privacy per provider, rolling grip out to a team, removing
it, and a glossary. The FAQ, honest numbers and home page no longer say that every run
asks new questions or that
anthropicis the default provider, the README says the offlinefakegrader only checks answer length, and the Mitchell Hashimoto quote links to his post. - Whiteboard page: handing in with questions unanswered asks first ("2 unanswered will score 0"); answers show a counter against the 4,000-character limit; the open question lights up the box or arrow it is pinned to, and graded arrows take their colour; an answered pin gets a tick instead of turning black; a score below the pass mark comes with the command to run again and a Copy button. Interview mode says "Score interview" and shows the ticks at full contrast after scoring. Exports show the questions without an answer box. On a phone, a tap within 22px of a pin opens it, the question scrolls into view, and the legend no longer scrolls away with the drawing.
Fixed¶
--range main...HEAD(three dots) no longer fails withbad revision '.HEAD'. It diffs from the merge base, as git does, ingrip quiz,ask,checkandwhiteboard.main..andmain...run up toHEAD, and a malformed range is rejected with the expected form.- Git failures are one plain line instead of git's command line and stderr: "not inside a
git repository (run grip from your project)", "this repository has no commits yet",
"unknown revision 'nope'".
GRIP_DEBUG=1also prints the full git command and output.grip statusworks in a repository with no commits yet. grip quizwithout an interactive terminal (grip quiz </dev/null, a script) exits 2 and points togrip ask/grip gradeorgrip whiteboard, instead of skipping with exit 0. Hooks still skip unlessrequire_ttyis set, and the notice no longer reads "grip: grip needs".- A pasted answer that ends with an empty line ends there, instead of waiting for one more Enter. Empty lines between pasted paragraphs are still kept.
grip checkwithout a scope flag checks what the installed hooks quiz (the unpushed commits for the default pre-push hook) instead of always the staged changes, which saidokwhile the push would block. It says what it checked (grip: ok (unpushed commits)), names a--rangeas typed instead of calling it "the staged changes", and writes "1 file". Two scope flags at once (--branch --unpushed,--rangewith--staged) are a usage error ingrip quiz,ask,checkandwhiteboardinstead of the last one silently winning.- The installed hook runs grip by the absolute path of the grip that ran
grip install, then falls back toPATH, so a push from an IDE or GUI client whosePATHlacks grip is quizzed instead of skipped with "grip: not installed". When that path is gone, the hook says so and suggestsgrip installagain, andgrip statusshowsgrip missing. Re-runninggrip install --appendrefreshes the appended block. - CLI paper cuts: Ctrl-C exits 130 with
grip: stopped, nothing recorded.instead ofAborted!and exit 1;grip forgetsaysno remembered passeswhen there were none; pause times are shown in local time with the time zone instead of UTC; a change where every file is binary is skipped instead of quizzed;grip status,installanduninstallshow hook paths relative to the repository root (.git/hooks/pre-push);grip grade --helpno longer lists--freshand--difficulty, and grading uses the difficultygrip askwrote the questions at; a retry that reuses cached questions no longer shows a "reading your diff" spinner. - A remembered pass can no longer be faked.
.git/grip/passed.jsonnow stores each pass's score, pass mark, provider and model, and the hooks,grip checkand the agent push gate only accept it if the score reaches the current pass mark and, unlessfakeis the configured provider, it was not graded byfake.grip gradeno longer takes--passing-score, andgrip statussays when a pass does not count and why. Passes recorded by older versions are asked again once. - The Claude Code push gate honours
GRIP_SKIP=1 git pushtyped in the command, as its message promised; before, it only read its own environment and denied the push anyway. The/gripskill no longer tells the agent where the rubrics are stored, every skill and the agent rules say not to read.git/grip/, and the docs say plainly that the rubrics are on disk there and that an agent with file access could read them. - Whiteboard answers are no longer lost when grip stops. The open page pings grip every minute, so a long think no longer hits the 20-minute idle timeout; answers are kept as a draft in the tab and survive a reload; and if grip is gone at hand-in, the page says "grip stopped: your answers are below, copy them" with a Copy answers button.
- The whiteboard works from the keyboard and with a screen reader. Boxes, pins and
buttons show a focus ring, focus no longer drops to the top of the page when it
redraws, arrow keys move between pins,
1to5open a question and Esc goes back, and a skip link jumps to the questions. Pins say whether they are answered or how they scored, the score is announced, and the panel no longer re-reads itself on every click. - The whiteboard is readable on real changes. The board is drawn at full size or larger in a window you can drag, scroll and zoom (Ctrl + scroll, pinch, or the buttons; Fit shows it all), and takes the height of the screen on a desktop. Box labels wrap onto two lines and boxes grow to fit, arrows bow around boxes instead of running through them, edge labels sit on a patch of board and move apart instead of overlapping, and changed boxes are tinted. The page now loads its fonts (Archivo, Instrument Sans, JetBrains Mono), which ship with grip under the SIL Open Font License; an export embeds them.
0.4.1 - 2026-10-21¶
Changed¶
- The README and the PyPI page lead with what grip does ("a git hook that won't let you
push code you can't explain"), show the whiteboard first, and start with a 30-second
offline try-out: a throwaway repository and
--provider fake, no hook, no API key.
0.4.0 - 2026-10-20¶
Added¶
grip quiz --branchquizzes the branch since it left the default branch, the diff of its pull request, without typing--range main..feat/x.grip ask,grip checkandgrip whiteboardtake it too.grip statusshows where the branch stands against the default branch, and every row says when and how it was quizzed:branch vs origin/main passed 98/100 at 17:52,failed 40/100 at 17:40,not quizzed (4 files).
Changed¶
- A push with nothing new to quiz says why, and when its commits are already on the
remote but the branch never passed a quiz, says to run
grip quiz --branch. - A failed quiz points to
grip last, on the score line and in the final message.
Fixed¶
- Answers in paragraphs keep their line breaks. A paste in the terminal is one answer,
blank lines included, ending at the next empty line, instead of spilling into the next
questions.
grip gradeaccepts raw line breaks inside the JSON strings, which coding agents write when they copy an answer as it is; the/gripskills say to keep them.grip lastindents the lines of a long answer.
0.3.0 - 2026-10-20¶
Added¶
- A retry reuses the questions. Quizzing the same diff again (after a failed quiz, from the
hook,
grip quizorgrip ask) reuses the questions written for it, so only the grading calls the model;grip whiteboardreuses its board the same way, which skips the slowest call. Entries are keyed by the diff, the task context, the difficulty, the provider and the model, live in.git/grip/cache/and expire aftercache_hours(24 by default, 0 turns it off).--freshasks for new questions once.grip submitnever reuses questions. - The hook can use the whiteboard. With
hook_mode = "whiteboard"(orGRIP_HOOK_MODE=whiteboard), the pre-push and pre-commit hooks open the board in the browser and the change is defended there instead of in the terminal. Where no browser can be opened, over SSH or without a display, they fall back to the terminal quiz.
0.2.2 - 2026-10-19¶
Changed¶
- Releases are automatic. Each pull request carries one label,
release:patch,release:minor,release:majororrelease:none; merging a labelled one bumps the version, dates the changelog, tagsmainand publishes to PyPI and GitHub. A check makes sure every pull request has exactly one. grip initasks, in a terminal, which model to use, the pass mark and whether to quiz before pushes, commits or both, with defaults detected on the machine, then says what the chosen provider still needs.--yestakes every default; scripts are not asked.grip installsays which model will write the questions when none is configured.
Fixed¶
- Pushing a new branch said "nothing to quiz" when its commits were also on a branch that
had been deleted upstream (a merged pull request) but not pruned locally: the stale
origin/...ref made them look pushed. grip now asks the remote withgit ls-remotewhenever the local refs say a new branch has nothing to send. - With no provider configured, grip used the Anthropic API even when Claude Code, Codex or
Gemini CLI was installed, and without an API key it crashed with a traceback. The
default provider is now
auto: the first installed coding agent, else Anthropic. A missing Anthropic key is now a clear error that says what to set. grip quiz --range main..feat/xcut branch names to 12 characters in its header (main..feat/kidnap-); only full hashes are shortened now.
Documentation¶
- A shorter README in the usual open-source shape: what grip is, install, quick start, configuration, links. The long-form material moved to the docs site.
- New walkthrough: one real change, from
git committo an accepted push, with the real questions, grades and screenshots. - New inside grip deep dive: the checks before a quiz, diff collection, prompts, structured output per provider, rubric grading, pass memory, the agent flow, the whiteboard's validation, layout and server, and what is stored or sent.
- The docs navigation is regrouped into Get started, Guides, Deep dive, Reference and Project.
0.2.1 - 2026-10-19¶
grip is on PyPI.
Changed¶
- grip is published to PyPI as
grip-hook:pipx install grip-hookoruv tool install grip-hook. Releases publish there through trusted publishing, and the GitHub release carries the same sdist and wheel. - The prebuilt binaries are gone. They bundled the build machine's Python, so the 0.1.0
and 0.2.0 Linux binaries needed glibc 2.38 and failed on Ubuntu 22.04 and older with
GLIBC_2.38 not found.install.shnow installs the package with uv or pipx, and replaces a binary left by an earlier install. - Windows is supported through the same install.
Fixed¶
- Whiteboard layout: a reply arrow (
allow -> handlerafterhandler -> allow) no longer pushes the board into extra columns, a part that only feeds others (tests, a fixture) sits just before what it feeds instead of across the input's arrow, and each column is ordered by what feeds it so fewer arrows cross. - Whiteboard page: long labels and roles end in an ellipsis inside their box (the full text is in the tooltip and the side panel), long arrow labels wrap onto two lines and slide along their arrow when they would cover another label, a box or a pin, and a pin on an arrow sits on the arrow.
- Whiteboard page: two questions pinned to the same box or arrow were drawn on the same spot, so one was hidden and could not be clicked. They now line up side by side.
--unpusheddescribed its range assha..shawith the same commit twice; the base is now resolved to the parent's hash.
0.2.0 - 2026-10-15¶
The whiteboard release.
Added¶
grip whiteboard: the change drawn as a whiteboard (its parts, how they connect, which inputs are untrusted) with five questions pinned to it, defended in the browser. It asks the two whiteboard questions the quiz does not: what a malicious actor could do and why a data structure was chosen. Graded on the same 100-point scale; a pass counts for the push hook.--interviewshows the rubrics to the person asking and records nothing,--exportwrites a shareable page without rubrics. Served on 127.0.0.1 behind a random token, withHost/Originchecks and a hash-based Content-Security-Policy./grip-whiteboardskill for Claude Code, Codex, Gemini CLI, Cursor and friends.- Providers draw boards (
generate_board); thefakeprovider draws one from the changed files or from a scriptedboard.
Changed¶
- grip is now explained around the whiteboard defense: could you explain and defend this change at a whiteboard? The README, the docs homepage and a new whiteboard defense page map grip's five questions to that bar, and say when to leave grip off (prototypes, demos, experiments).
0.1.0 - 2026-10-15¶
The first public release.
Added¶
grip quiz: five questions about your staged, unpushed or ranged diff, graded to a Grip Score out of 100 with a configurable pass mark.grip hook pre-commit/grip hook pre-push: hook entry points, both for native git hooks and for the pre-commit framework (.pre-commit-hooks.yaml).grip install/grip uninstall/grip status/grip config/grip forget.- Providers: Anthropic (default,
claude-opus-5), any OpenAI-compatible API including Ollama, and an offlinefakeprovider for tests and demos. - Configuration through
.grip.toml,[tool.grip]inpyproject.toml,GRIP_*environment variables and command-line flags. - Passed diffs are remembered for a configurable time so you are not quizzed twice.
- Documentation site built with MkDocs Material and published to GitHub Pages.
- A brand identity under
branding/: the grip mark (an 87.5 percent score ring that reads as a G) in ember orange on warm graphite, Archivo wordmark lockups, a social card, and a palette generated in OKLCH bybranding/build.py, which refuses to write colours that fail WCAG 2 or APCA contrast or that collapse under simulated colour-blindness.tests/test_branding.pykeeps the generated files current. - A redesigned docs site: the generated palette and brand type, tabs for each section, a homepage with an install switcher and feature cards, dark code blocks with checked syntax colours, an announcement bar, social preview tags, and a Brand page.
- Feature clips under
docs/assets/features/: one MP4 and GIF per new feature and a stitched "what's new" cut, recorded byscripts/demo/make_features.pyagainst the real CLI with a scripted offline scenario. - Every recording in the brand: the demo, the feature clips, the launch video and the
Product Hunt stills now use the ember and graphite palette, JetBrains Mono in the
terminal, Archivo and Instrument Sans on the title cards, and the mark. Colours come from
branding/tokens.jsonthroughscripts/demo/brandkit.py, so videos and docs cannot drift. - Two more skills:
grip-reviewquizzes a reviewer on a range of commits (or prints the questions for the author with--questions-only),grip-explainturns the developer's answers from the last quiz into a commit message or pull request description.grip lastshows the last graded quiz, with--jsonfor skills to build on. - An evals harness under
evals/: eight fixture diffs with author notes, three answer arms (blank, generic, notes), repeated grading for noise, a committed fake-provider snapshot the tests keep current, and a Markdown summary. A "Honest numbers" docs page says what is measured, what is not, and where grip gets in the way. CITATION.cffand a Cite section in the README;.github/FUNDING.yml. The README opens with a three-tile summary and a before/after comparison, and its Why section cites the METR, GitClear, DORA and Stack Overflow findings on AI-assisted code.grip skipskips the next hook run once,grip skip --hours Npauses hooks for a while,grip resumecancels. Both the git hooks and the Claude Code push gate honour it; a manualgrip quizdoes not.grip statusgained an activity section: last quiz, whether the staged and unpushed diffs have passed, and the skip state.grip statuslineprintsgrip 92/100 passfor editor and Claude Code status lines.grip init: one command that writes a commented.grip.toml(defaulting to an installed coding agent as provider), installs the pre-push hook and writes the agent rule files.--provider,--passing-score,--difficulty,--stage,--no-hook,--no-agents,--force.config.render_templateproduces the file.grip agents: detects the coding agents installed on the machine (Claude Code, Codex, Gemini CLI, Cursor, Windsurf, Cline, Copilot, OpenCode) and writes, inside the repository, the rule file each one reads so it runsgrip checkbefore pushing. Marked blocks inAGENTS.md,CLAUDE.mdandGEMINI.md, grip-owned rules files for Cursor, Windsurf and Cline, and the plugin enablement merged into.claude/settings.json.--list,--all,--agent,--dry-runand--remove.-
Keep A Grip, the problem platform:
grip login,grip logout,grip whoami,grip problems,grip solve SLUGandgrip submit. Solve a problem with any assistant, get quizzed on the diff, have the score reported with an API token.Diffgained acontextfield that the prompts pass to the model as data, andGit.worktree_diffdiffs everything since a base commit, uncommitted work included. -
Demo recording (
docs/assets/demo.gif,.webm,.cast) and the script that regenerates it,scripts/demo/make_demo.py. - Scripted mode for the
fakeprovider throughGRIP_FAKE_SCRIPT, plusGRIP_FAKE_DELAYfor artificial latency. -
Providers that reuse a coding agent already installed and signed in, so no API key is needed:
claude-code(aliasclaude),codexandgemini. Each runs the CLI in non-interactive mode with tools disabled, in an empty scratch directory. -
Claude Code plugin, served from this repository as a marketplace (
/plugin marketplace add guilyx/grip):/griprelays the quiz through Claude and aPreToolUsehook blocksgit pushuntil the diff has passed. - The quiz skill lives at
skills/grip/SKILL.mdin the Agent Skills layout, sonpx skills add guilyx/gripinstalls it into any agent that readsSKILL.mdfiles. The plugin is served from the repository root (hooks/hooks.json,skills/). grip ask,grip grade,grip checkandgrip agent-hook: the quiz as non-interactive JSON steps so any coding agent can drive it.-
grip study exportandgrip study status: an anonymised JSON summary of every quiz taken on the machine (scores and per-question focus only, never code, paths, questions, answers or feedback) for study platforms. Graded quizzes are now appended to.git/grip/history.jsonland the repository is recorded in$XDG_DATA_HOME/grip/repos.json. Nothing is uploaded by grip. -
Launch assets under
docs/assets/launch/(Product Hunt image, icon, 1080p launch video) anddocs/assets/demo.mp4, generated byscripts/launch/make_launch.py; a Media page on the docs site.
Changed¶
-
grip initreports agent files as "wrote" or "updated" instead of "written", and the genericAGENTS.mdtarget is labelled "AGENTS.md readers". -
The demo now quizzes a ROS 2 change: a
cmd_velsafety filter gaining a lidar scan watchdog, instead of a payment refund. - Distribution:
install.sh(curl -fsSL .../install.sh | sh) downloads a prebuilt, checksum-verified binary from GitHub Releases into~/.local/bin. The release workflow builds binaries for Linux and macOS (x86_64 and arm64) with PyInstaller; nothing is published to PyPI anymore. modelnow defaults to empty, meaning the provider's own default (claude-opus-5for Anthropic, the CLI's default for coding agents).openaiandollamarequire it.- The score table uses rounded borders, matching the panels.
- The "blocked" message prints the report path relative to the working directory.