Local Review

2026-07-11 · page · source ↗ · source summary

A local, single-user git review tool that gives you GitHub-style review ergonomics for branches you haven't pushed — or don't want to push — entirely on your machine. Point it at a folder of repos, pick a branch, comment on any line or range, reply in threads, resolve what's done, and export the review as clean markdown for a coding agent to act on. Ships as a single Go binary with the React frontend embedded; no runtime dependencies beyond git.


Architecture

A single Go binary (main.go, 143 lines) serves a JSON API over net/http (Go 1.22+ method+path routing) and mounts an embedded React/TypeScript frontend via //go:embed all:web/dist. The server listens on 127.0.0.1:7777 and opens the browser automatically.

Four internal packages handle the core logic:

The React frontend (web/src/, ~1,100 lines) is a three-column resizable layout: hierarchical file tree (left) → per-file diff view (center) → comment overview panel (right). Uses Shiki for syntax highlighting (~235 languages, JS regex engine to avoid WASM load failures), IntersectionObserver for viewport-mounting of lazy files, and markdown-it for export preview rendering.

Key Techniques

Drift-resistant comment anchoring

Comments survive branch rebasing and further commits through a two-tier system:

  1. Precise git line tracking: annotateByDiff() runs git diff <commit_sha> head -- path and maps the original line range through the diff hunks with MapOldLine(). Every line surviving contiguously → current or moved (relocated range provided); any line deleted or modified → outdated. This is preferred because it distinguishes genuine moves from coincidental reappearances of the same lines — something snippet matching structurally can't do.

  2. Snippet matching fallback: annotateComment() compares the captured code snippet against the current file (working tree or head, depending on the worktree flag recorded at comment time). Unambiguous re-match → moved (with new line range); gone or ambiguous → outdated.

Diffs and file reads are cached per (commit_sha, path) or per path within a single review read, so the O(N×M) cost is amortized.

Derived state, never persisted

Comment anchor status and reviewed-file validity are recomputed on every read, never trusted from stored flags. The anchorStatus/currentStartLine/currentEndLine fields on the Comment struct have omitempty JSON tags; the store writes and reads zero values for them. annotateReview() fills them in the API layer before serializing to JSON. This means force-pushes, rebases, and interleaved commits are always reflected correctly — no stale "reviewed" checkmarks after new work.

Agent handoff as API, not just export

The tool supports two agent handoff modes:

Either way, agent replies appear live in the UI via SSE, so the human can read them, resolve what's addressed, and hand off what's left.

Live multi-tab sync via SSE

The SSE hub recognizes that a dropped ping is harmless when every ping triggers a full refetch — at most one refresh is owed per client. Size-1 buffered channels with non-blocking sends mean a stalled tab never blocks a mutation handler. Keepalive comments (every 25s) turn half-open connections into write errors that trigger unsubscribe. The frontend supplements with a focus/visibility refetch for the reconnect gap.

Backtick-safe markdown export

fenceFor() scans each captured snippet for the longest consecutive run of backticks, then emits a fence one longer (minimum 3). This prevents a reviewed markdown file containing triple-backtick code fences from prematurely closing the export's code block — a concrete edge case handled by a general algorithm.

Responsiveness for large change-sets

Four independent mechanisms keep the UI responsive on large diffs: - LazyFile viewport-mounting via IntersectionObserver — off-screen files never fetch, tokenize, or render - Auto-collapse for files with >500 changed lines - Highlight skip for files >2000 lines - DOM-level panel resize during drag (writes grid-template-columns directly, no React re-render per mousemove)

Design Decisions

Shells out to git binary rather than using go-git. Correctness on real repos (renames, .gitattributes, submodules) beats theoretical portability. The tool's entire purpose is reading git state; using the real git is the obvious call.

Single SQLite connection (SetMaxOpenConns(1)). foreign_keys pragma is per-connection in SQLite — a pooled DB would have some connections miss it and silently skip ON DELETE CASCADE. Single connection makes the pragma authoritative and gives transactions full atomicity. DB access is low-frequency (git operations don't hit SQLite), so serialization costs nothing.

Backend as canonical source of truth. The frontend is a cache that refetches on SSE pings. This means derived state (comment staleness, reviewed-file validity) is computed server-side on every read — no conflict resolution logic in the client, no risk of stale client-side caches. The trade-off is network round trips, but on localhost that's negligible.

Recompute staleness on read rather than persist at write time. Updating all comments on every push would be expensive and fragile. Computing on read means a freshly pulled branch always shows correct anchor status. The cost is O(comments × diffs) per read, which is fine for human-scale reviews (tens to hundreds of comments).

Two-level threading (comment + replies) rather than arbitrary nesting. Simple, predictable, no edge cases. FK cascades ensure replies never orphan. GitHub-style deep threading is complex to render and rarely needed for agent-bound review feedback.

Comparison Notes

Unlike OpenCodeReview, which uses AI to generate review comments, local-review is for humans to write comments that an AI coding agent then acts on. The direction is inverted.

Unlike Bram, which layers a two-gate approval workflow over coding agent sessions, local-review is purely a review artifact generator — it produces markdown, which can feed into any agent but doesn't execute or approve anything itself.

Unlike DeltaDB, which traces every line to the conversation that produced it, local-review only captures the review moment — a snapshot of human judgment at a point in time.

Unlike Metis — ARM AI Security Code Review and brooks-lint, which use deterministic analysis or prompt engineering to find issues, local-review makes no attempt to understand code semantics. It operates at the diff and line level, leaving all understanding to the human reviewer and the receiving coding agent.

The tech stack echoes Memento (Go + SQLite + SSE runtime), though for a different domain — Memento is a knowledge layer; local-review is a review layer.

Placed in the Agent Coding Workflow pipeline, local-review sits at the human review step between code generation and agent iteration — the artifact that closes the loop between "AI wrote this" and "human verified this, now fix these things."

Hunk occupies the complementary role in the pipeline: where Local Review captures human feedback for agents to consume, Hunk renders agent annotations for humans to read. Together they form the two halves of a bidirectional review loop — agent→human (Hunk) and human→agent (Local Review) — with Subspace providing an alternative human→agent path for structured terminal-based feedback.


Sources: raw/local-review Tags: #tool #project #developer-tools #code-review #git Last updated: 2026-07-11