Skip to content

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]
  1. Collect the diff. For pre-commit that is the staged changes. For pre-push grip 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 uses PRE_COMMIT_FROM_REF / PRE_COMMIT_TO_REF. Paths matching exclude are dropped and the patch is capped at max_diff_bytes.
  2. 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.
  3. Ask. Questions are shown one at a time on the controlling terminal. You answer in a line; an empty line skips (and scores zero).
  4. 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.
  5. Decide. The Grip Score is the sum, 0 to 100. At or above passing_score the 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_SKIP is set to a truthy value, or git runs with --no-verify;
  • CI is set (hooks in CI have nobody to answer);
  • a hook finds no interactive terminal and require_tty is false (the default); grip quiz typed 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 the fake provider counts only while fake is 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.