I’ve mostly kept quiet about how I write code these days. The discourse around AI and programming is exhausting, and I don’t like being yelled at by my friends. It’s felt much easier to do the work and not explain it.
Then I read Claude is not a compiler, and it described the thing I’ve been doing for months better than I’ve managed to describe it to myself. So I’m going to try, and it’s okay to disagree with me!
What I liked
The argument is that treating an LLM as a compiler: natural language in, code out, misses what it’s actually doing. A compiler works inside one layer and makes local decisions. It translates. Claude doesn’t stay in a layer. It moves across all of them at once, from “what should this system even be” down to “which field detects a rolled-back database,” and software is mostly a big pile of decisions like that, made at every layer, each one pinning the whole thing down a little more.
The example the author used is building a distributed DNS system three times over, with parallel agents, then reading the finished implementations against each other to see where independent attempts had quietly diverged. The interesting part wasn’t any one build. It was, in the author’s words, “how many important decisions the agents never asked about but simply made,” and made differently each time. Those divergences got distilled, over a few passes, into what the author calls a “scar-tissue document” that was enough to walk an agent through the important calls at every layer.
And then the line I keep coming back to: “in all the ways that matter, I understand the code.”
How I work now
A CLI that helps humans review LLM-written PRs is something I’m working on, to help show where I think humans are still doing the real work in this loop: not reviewing nits and style, but making decisions about architecture and algorithms. Maybe it’ll be useful to other folks too.
The workflow itself lives in a Claude Code skill I built, ~/.claude/skills/work. Fable runs first and argues with me before anything gets built, pushing on whether I’m even approaching the problem right. That conversation ends in a brief.md, the contract everything downstream has to match.
# human-lead — v1 core + CLI brief.md
**Status:** confirmed by Keith 2026-08-02 — this is the contract for planning
**Date:** 2026-08-01
## The idea in one paragraph
Reviewing agent-written code is now mostly about concepts, algorithm choices, and
architecture — not nil checks, imports, or formatting. `human-lead` takes a unified
diff and identifies the places where a human's taste and expertise are actually
load-bearing. It produces one salience-tagged analysis that drives two renderings:
a **reading diff** (the change, abridged to what's worth reading) and
**annotations** (a small set of markers pinned to file+line, saying "a person needs
to make a judgment call here, and here's why").
The name is a claim about the output: these are the places where the human leads.
## Goal
A TypeScript library plus a CLI that Keith uses on real diffs every day.
The library takes a unified diff (plus optional read-only access to the surrounding
repo) and returns a structured analysis. The CLI wraps it with git plumbing,
caching, and terminal rendering.Once there’s a brief, the skill tracks the project in a state.md so agents can pick up context without me re-explaining it every time. I could swap this for something like beads or beans; a markdown file has been enough so far.
# core-cli
phase: 4
updated: 2026-08-02
## Status
Phases 1–3 complete. `brief.md` confirmed and corrected, three plans drafted,
`review.md` dispositions ruled by Keith. `plan.md` written with 13 packets.
Awaiting Keith's approval of the packet breakdown before phase 5 execution.That brief goes to three separate Opus agents, each told to draft its own implementation plan.
# Plan: core-cli (OPUS-1)
## Approach
Build the constrained-editing architecture the brief specifies, but move as much work
as possible *off* the model and *into* deterministic passes, because that is
simultaneously the quality lever and the cost lever. tree-sitter is not just a
portability upgrade over the prior art's per-language string heuristics: it lets four
hand-written Python scanners (delimiter balance, triple-quote parity, backslashFable reads the three plans against each other, hunting for where they quietly disagree: what one planner assumed that the others never asked about, which calls each of them made differently without flagging it as a decision at all. That comparison becomes a review.md, and it runs long. I’ve seen it surface thirty open questions in one pass, each one asking me to rule on where the planners split.
# Review — three plans for `human-lead` v1
Date: 2026-08-02. Plans: `plans/opus-1.md` (474 lines), `plans/opus-2.md` (420), `plans/opus-3.md` (222).
## Consensus
All three planners independently agreed on the following. These ship to the master plan
unless Keith objects.
1. **No build step.** Node 26 runs `.ts` directly via type-stripping; `tsc --noEmit`
typechecks as a separate step; `node:test` + `node:assert/strict`; `util.parseArgs`
for args; `util.styleText` or a handful of ANSI constants instead of chalk.
Runtime deps hold at three or fewer. All three reached this independently after
the brief invited them to argue for a heavier stack — none did.
2. **Kill the long agent loop.** All three reject the prior art's 24-turn/4-minute
shape. Budgets proposed: 3 calls/90s, 6 calls/120s, 1 call by default with an
opt-in `--deep`. Repo tools default **off**.
3. **Elide deterministically-hidden rows from the transmitted payload** while keeping
original coordinates, with a marker row standing in for each hidden run. (This is
the fix for the brief's factual error, below.)
4. **Repair turns carry the rejection list only — never the diff again.**
....
## Decision table
| # | Decision point | Plan 1 | Plan 2 | Plan 3 | My read |
|---|---|---|---|---|---|
| D1 | Where does tree-sitter get source text? | Reconstruct old/new side text per file **from the diff** | Reconstruct per **hunk** fragment, with an error-node floor | Fetch **real git blobs** (`git show rev:path`), parse genuine files | **P3, with P1 as fallback.** Real blobs give exact ASTs and exact line mapping; diff reconstruction parses incomplete text. But P3's own risk #1 is that blobs are ambiguous for merges/`a...b`/renames. Take blobs when resolvable, fall back to side reconstruction, and report `structureCoverage` per run so we find out which is happening. |
| D2 | The core structural rule | **B3 node alignment** (hidden run = whole sibling nodes) + B3b reparse guard | **syntax-delta** (reparse retained; reject if error nodes exceed the full-side floor) | **atomicNotSplit** (no partial coverage of an `@atomic` node) | **P1 primary, P2 as backstop — they compose.** P1 already proposes exactly this as B3b. Node alignment is precise but P1 names Python indentation as its risk; syntax-delta is general but blunt and P2 names false rejections as *its* top risk. Running both means each covers the other's named failure. |
| D3 | Languages in v1 | TS, JS, Python, **PHP** | TS/TSX, Python **only** | Python, TS, JS, **Go** | **Needs Keith.** P1 picked PHP by guessing his daily repos; P2 argues two languages is the minimum that proves genericity and more is scope. P2's discipline is right in principle, but the binding criterion is daily use, so the answer depends on what Keith actually reviews. |My rulings and whatever the three planners already agreed on get folded into a single plan.md, split into packets. That’s the document that actually gets built from. Sonnet agents each take a packet, and by the time it reaches them there’s nothing left for them to decide.
# Master plan — `human-lead` v1 (core + CLI)
Date: 2026-08-02. Synthesized from `plans/opus-{1,2,3}.md` plus the rulings in
`review.md`. Where the three plans agreed, the consensus ships. Where they diverged, `review.md` records which was taken and why.
## Shape of the system
Five deterministic stages around a small number of model calls:
parse → prepass → model → verify → render
The model's only power is emitting **coordinates** against an immutable numbered diff,
plus salience tags and a one-line summary. It never writes output text. A compiler
applies the plan and rejects it when it would produce dishonest output. Rejections are
data, fed back for repair; when repair is exhausted the system degrades toward showing
*more*, never toward showing something unverified.
**Default output is the annotated full diff.** The abridged reading diff is `--abridge`.
## Layering (non-negotiable)
src/core/ no I/O. Diff in, Analysis out. May not import node:fs,
node:child_process, node:process, or anything under src/cli.
src/renderers/ pure functions: Analysis → string | object. Imports core types only.
src/providers/ the only modules that touch the network.
src/cli/ git, cache, TTY, pager, args. The only layer touching the outside world.
Enforced mechanically by `test/architecture.test.ts`, not by convention.
....
### Packet 1 — Scaffold, conventions, architecture test
**Depends on:** nothing. Blocks everything.
**Files:** `package.json`, `tsconfig.json`, `.gitignore`, `bin/human-lead.js`,
`src/core/errors.ts`, `test/architecture.test.ts`, `README.md` (stub only).
Scaffold per Conventions above. `src/core/errors.ts` defines `HumanLeadError` over the
closed code union (`diff-unsupported`, `diff-too-large`, `model-failed`,
`budget-exhausted`, `verifier-exhausted`, `repo-access-denied`, `no-credentials`).
`test/architecture.test.ts` walks `src/`, extracts import specifiers, and fails if
`src/core/**` imports `src/cli`, `src/providers`, `src/renderers`, `node:fs`,
`node:child_process` or `node:process`, or if `src/renderers/**` imports outside
`src/core`.
**Acceptance:** `npm test` and `npm run typecheck` both pass on an empty source tree.
The architecture test fails when a deliberate illegal import is added, and passes when
it is removed — demonstrated, not asserted.I can open the final code, read it, change anything in it. I don’t usually need to, but the option is right there and I’d know what I was doing. The understanding didn’t come from typing it. It came from building this same thing several times over, watching versions lose to other versions, until one of them was clearly the golden one. I know the tradeoffs that version makes because I watched the alternatives get suggested and thrown out for making different ones.
I can walk through and talk about the decisions made and why each input and output is structured the way it is.

I’m not a compiler either!
The compiler framing assumes I already know what I want, and the job is to translate that down into code. But most of the value here is that I don’t know what I want at the start, not precisely, and neither does Claude, and the many attempts are how we find out. I’m not handing down a finished spec to be compiled. I’m using the attempts to discover what the spec should have been in the first place, and using Claude to find the rough spots without doing it myself.
By the time there’s a doc worth implementing, the hard part is over. The implementation is the easy, boring end of it. So Claude isn’t a compiler in this loop, and neither am I!
I’m not saying this replaces knowing how to program. I read all of it. The only reason the loop converges instead of laundering nonsense into a confident final answer is that I can look at an implementation and tell when it’s quietly wrong. Strip that out and you don’t have three attempts, you have three wrong answers and no way to pick between them or decide why one is right and the other is wrong. I still rely on my judgement and taste hundreds of times a day.
I’m also not saying it’s fast. It burns tokens and it burns hours. When I already know how to write something, this would be a ridiculous way to do it, and I just write it. I run this loop for the problems I don’t know how to write yet (I’m using WASM in this CLI tool!). Those used to be the ones I’d avoid, or spend a week reading other people’s blog posts about before I felt safe starting. Now the reading and the building are the same activity, and I come out the far side actually understanding the thing.
I really do not feel like “AI writes my code.” It’s that I found a way to understand hard problems faster than I used to, and an LLM is what makes running the same problem into the ground many times cheap enough to be worth it.
If you work like this too, or you think I’m kidding myself, I’m @keithlaugh.love.