# I built a local-first LLM code reviewer in Go. Here's the entire pipeline.

> Source: <https://dev.to/muhammetsafak/i-built-a-local-first-llm-code-reviewer-in-go-heres-the-entire-pipeline-11g>
> Published: 2026-06-24 04:40:00+00:00

**CommitBrief is a local-first CLI that runs an LLM review over your git diff before a teammate — or your future self — sees it.** There's no server and no telemetry; the diff leaves your machine only for the provider *you* chose, and with a local model like Ollama it never leaves at all.

The interesting engineering isn't "call an LLM." It's everything that has to happen *around* that call so the review stays cheap, safe, and reproducible. Here's the whole path from `commitbrief --staged`

to the findings on your screen.

**TL;DR**

`git diff`

range) with the provider you pick: Claude, GPT, Gemini, or a fully local Ollama model.**Key facts**

`commitbrief commit`

, and even that only runs one `git commit`

of already-staged changes — it never edits a file.`brew install CommitBrief/tap/commitbrief`

, `scoop install commitbrief`

, or `go install github.com/CommitBrief/commitbrief/cmd/commitbrief@latest`

.Every review walks one linear pipeline. Here it is at altitude before we zoom in:

| Stage | What happens | Why it's here |
|---|---|---|
| 1. Resolve context | Walk up for `.git` , merge config (built-in < global < repo), apply env + flags |
One deterministic config per run |
| 2. Load rules |
`./COMMITBRIEF.md` or the embedded default; validate the output template first |
Fail on a broken template before spending a token |
| 3. Acquire diff | Hybrid go-git + `exec git` fallback |
Worktree state is git's, not a reimplementation's |
| 4. Parse + filter | Three ignore layers, then an optional allowlist | Don't pay to review lock files |
| 5. Pre-send guard | Refuse to leak `.commitbrief/**` ; scan for secrets |
The diff is about to leave the machine |
| 6. Build prompt | Four XML blocks + an immutability guard | Structured and injection-resistant |
| 7. Cache lookup | SHA-256 of the exact inputs | A re-run is a disk read, not a bill |
| 8. Cost preflight | Estimate tokens, warn over a threshold | No surprise spend |
| 9. Provider call | Structured JSON, or verbatim text for CLI providers | The actual review |
| 10. Render + gate | Cards / JSON / Markdown, then `--fail-on`
|
Human output or a CI exit code |

Five of these carry most of the weight. Let's take them in order.

You'd think reading a diff is trivial. It is — until you need staged-vs-unstaged, a worktree comparison, and `git diff main...feature`

to all behave *exactly* like git, on Windows too.

CommitBrief runs a hybrid: a primary `go-git`

implementation with a `git`

CLI fallback (ADR-0002). Range operations that go-git models cleanly — commit-vs-first-parent, merge-base range diffs, branch diffs — stay in-process. Staged, unstaged, and arbitrary `git diff <args>`

passthrough shell out to `git`

with `--no-color --no-ext-diff`

for stable parsing. The CLI stays the source of truth for index and worktree state; reimplementing that plumbing is exactly the kind of subtle drift you don't want under a review tool.

```
commitbrief --staged                 # the index
commitbrief --unstaged               # the working tree
commitbrief diff main...feature      # args forwarded verbatim to git diff
```

A diff is mostly signal *and* a pile of things no reviewer should read. Filtering is three composed layers (ADR-0006):

`vendor/**`

, `node_modules/**`

, generated code, build artifacts, binaries, IDE/OS noise, and `.commitbrief/cache/**`

.`.commitbriefignore`

`!pattern`

line can re-include something a built-in dropped.`COMMITBRIEF.md`

("don't flag generated mocks"), applied by the model itself.After the ignore layers, `--file`

/ `--dir`

apply a narrower allowlist. If nothing survives, the run exits `0`

having spent nothing — an empty diff is a success, not an error.

Stage 5 is the one I care about most, because it sits on the boundary where your code is about to leave the machine.

Two checks run before the provider call. First, a guard refuses to quietly ship your own config: if any path in the diff starts with `.commitbrief/`

, it prompts, and auto-aborts when there's no TTY. Second, a secret scanner runs over **added lines only** against eight built-in patterns — AWS access keys, GitHub/GitLab tokens, Anthropic/OpenAI keys, JWTs, Stripe live keys, PEM private keys — and you can add your own through `guard.secret_patterns`

.

One detail I'm proud of: a match records only `{Line, Patterns}`

— never the matched substring. The scanner that exists to stop a leak can't itself become one through a log line, stderr, or a cache file.

The bypass policy is deliberate, too. `--allow-secrets`

skips the scan; `--yes`

does **not**. Auto-confirming prompts in a pipeline should never silently approve shipping a credential to a third party.

Reviewing the same diff twice should be free. The cache key is a SHA-256 over the exact inputs that could change the answer:

```
sha256( diff "::" systemPrompt "::" provider ":" model ":" lang ":" schemaVersion [":ctx"] [":mode:"+mode] )
```

Every input earns its place. The fully-assembled system prompt is in the key, so editing `COMMITBRIEF.md`

invalidates stale reviews. The schema version is in the key, so bumping the output contract invalidates everything at once. `--with-context`

and the commit-message mode each get a suffix so they never collide with a plain review. Entries are written atomically — temp file, `0600`

, then rename — to `.commitbrief/cache/<key>.json`

: one file per response, no index.

The payoff lands at stage 8. Cost preflight estimates input tokens with a conservative `(len+3)/4`

heuristic, clamps expected output to `[200, 1500]`

tokens, multiplies by a per-model price table, and prompts only when the estimate clears your `cost.warn_threshold_usd`

(default `$0.50`

). A cache hit skips preflight entirely — re-running a review on an unchanged diff costs one disk read.

For API providers, CommitBrief asks for structured findings and parses them into a fixed contract — `severity`

, `file`

, `line`

, `title`

, `description`

, `suggestion`

— emitted as JSON schema v1. If the model returns something unparseable, it retries once; if it's still bad, it degrades to rendering the raw text as Markdown and prints a warning instead of crashing. CLI-backed providers (`claude-cli`

, `gemini-cli`

, `codex-cli`

) run as read-only subprocesses and stream their text verbatim.

Output is Cards by default, or:

```
commitbrief --json --fail-on=high       # CI gate: exit 1 on any high+ finding
commitbrief diff main...feature --markdown -o review.md
```

Exit codes stay simple: `0`

for success — including a clean review or a `--fail-on`

threshold that wasn't breached — and `1`

for any error or a breached gate.

It's the zeroth reviewer, not a replacement for a human one. It catches the obvious-but-easy-to-miss class: injection, missing nil checks, swallowed errors, a guard clause that's now unreachable. It does not catch intent-level design problems, and it won't tell you whether the feature should exist. That conversation stays with your reviewer.

I won't assert quality at you, either. There's a reproducible eval harness scoring real output against a known-answer corpus — run it yourself:

```
COMMITBRIEF_EVAL_PROVIDER=<name> make eval-live
```

If you want a second pair of eyes on your diff before anyone else gets one — locally, with the provider you already trust — that's the whole point:

```
commitbrief setup     # pick a provider, paste a key, ping it
commitbrief init      # optional: write project-specific rules
git add .
commitbrief --staged
```

Repo and install instructions: **github.com/CommitBrief/commitbrief**.

*This is part 1 of **Building CommitBrief**. Next: how one Go interface fans out to 10 LLMs across three transport classes — native APIs, OpenAI-compatible endpoints, and subprocess-backed CLIs.*
