How it works¶
The short version. For one real change followed from git commit to an accepted push, see
the walkthrough; for every step in detail, module by module, see
inside grip.
The flow¶
flowchart LR
A[git commit / git push] --> B[hook: grip hook stage]
B --> C{Skip?}
C -- GRIP_SKIP / CI / no TTY / empty diff / recently passed --> Z[exit 0]
C -- no --> D[collect diff]
D --> E[provider: 5 questions + rubrics]
E --> F[ask in the terminal]
F --> G[provider: grade answers]
G --> H{score >= passing_score?}
H -- yes --> I[remember pass, exit 0]
H -- no --> J[show feedback, exit 1]
- Collect the diff. For
pre-committhat is the staged changes. Forpre-pushgrip reads the refs git passes on stdin and diffs each remote SHA against the local SHA; for a brand-new branch it diffs against the parent of the oldest commit not on any remote. Under the pre-commit framework it usesPRE_COMMIT_FROM_REF/PRE_COMMIT_TO_REF. Paths matchingexcludeare dropped and the patch is capped atmax_diff_bytes. - Generate questions. One request to the provider returns a summary plus exactly five questions. Each comes with a hidden rubric: the points a complete answer must cover.
- Ask. Questions are shown one at a time on the controlling terminal. You answer in a line; an empty line skips (and scores zero).
- Grade. A second request sends the diff, the questions, the rubrics and your answers. Each answer gets 0 to 20 points and a sentence of feedback.
- Decide. The Grip Score is the sum, 0 to 100. At or above
passing_scorethe hook exits 0 and the diff's digest is remembered; below it the hook exits 1 and the commit or push is blocked.
The Grip Score¶
| Part | Value |
|---|---|
| Questions per quiz | 5, always |
| Points per question | 0 to 20 |
| Grip Score | sum, 0 to 100 |
| Default pass mark | 70 |
Grading is deliberately rubric-based: the questions are written with the rubric, before you answer, so the grader is checking substance rather than improvising. Spelling, grammar and brevity are explicitly ignored in the grading prompt.
difficulty shifts both ends: easy asks about visible behaviour and grades leniently,
hard probes failure modes and interactions with code outside the diff and grades strictly.
What leaves your machine¶
Only three things are sent to the provider: the diff (after exclusions and truncation),
the messages of the commits being quizzed (when there are commits; up to 4 KB, and
include_commit_messages = false leaves them out) and your answers. No other history, no
other files, no telemetry. The diff and the messages are wrapped in <diff> and
<commits> delimiters and the model is told to treat anything inside as data, never as
instructions.
Reports (questions, answers, grades, score, and calls: the time and token count
of each model request) are written to .git/grip/last-report.json, or to the path given
with --report, and appended to .git/grip/history.jsonl. Passed digests live in
.git/grip/passed.json. grip status reads the history for its averages, and it feeds
grip study export, which only runs when
you ask.
When grip steps aside¶
grip exits 0 without asking anything when:
GRIP_SKIPis set to a truthy value, or git runs with--no-verify;CIis set (hooks in CI have nobody to answer);- a hook finds no interactive terminal and
require_ttyis false (the default);grip quiztyped by hand fails instead; - the diff is empty after exclusions, or every changed file is binary;
- the exact same diff passed within
remember_passes_hours, at or above the current pass mark (a pass graded by thefakeprovider counts only whilefakeis configured).
Provider failures (no key, network down, model unavailable) block by default so a
broken setup is noticed rather than silently bypassed. Set fail_open = true to prefer
availability over strictness.