The harness I built around Claude Code

(Updated on )

I use AI to write a lot of my code now. The model isn’t the interesting part though, the harness around it is. On its own a model guesses. Wired into the tools you already use, your test suite, and a few checks it can’t argue with, it becomes something I actually rely on every day, and this site is one of the things it built.

This is the overview: the shape of the thing, and why each piece is there. I’ll go deeper on the parts in later posts.

The four layers

Only the bottom one comes out of the box. The rest I put there.

Deterministic gates tests, lint, typecheck, build Native swaps tsgo instead of the default language service Skills I wrote One per tool, loaded before the agent runs it Claude Code The base I started from
The four layers, base at the bottom.

Each skill teaches the agent one tool I already use and the kind of question that tool answers. The swaps are for the places where the default was too heavy to keep in the loop.

Finding things in the code

This is the layer I lean on most, and it’s where I part ways with how a lot of setups do it. The common move is a RAG index: embed the whole repo, search the vectors. I don’t. An index is stale the moment someone pushes, it costs resources to build and host and keep current, and it still answers a precise question with a fuzzy guess.

So the routing is boring and exact instead. One kind of question, one tool, and a rule that the agent loads the skill before it runs anything.

QuestionGoes toWhy that one
A literal string, an error message, a keyripgrepFast, respects gitignore, and there’s no index to keep fresh
A shape of code: a decorator, a call signatureast-grepMatches structure, so formatting variants don’t slip past
Who actually calls this functionthe language serverFollows re-exports and injected wiring that a text search misses
When this line showed up, and who touched itgit history (log -S, log -G, blame)Searches time, not just the current checkout
Whether anything still imports this exportknipSweeps the whole project for exports and files nothing reaches
A diff drowning in reformattingdifftasticCompares syntax, so moved code reads as moved instead of rewritten

None of those guess, and that’s the whole point. It’s also the instinct behind the swap to tsgo, the native rewrite of the TypeScript compiler: I want the agent reading the types as they are right now, not a snapshot from an hour ago.

The gates

I don’t trust an agent that tells me it’s done. I trust an exit code.

The agent changes whatever it wants tests, lint, typecheck, build the exit code non-zero: back to the agent zero: I read it, then I decide
Nothing in that path asks the model for its opinion.

So the checks that matter are deterministic and git-tracked, not left to the model’s judgment. When I want a rule enforced it becomes a script that returns zero or non-zero, not a line in a prompt asking the agent to please remember. Same thing I’d want from a person: show me, don’t tell me.

What I don’t hand over

The bot that orders my groceries at home sits behind a Telegram approval step, because I don’t fully trust it with my money yet, and the same instinct carries into how I code. The agent can change whatever it wants. It just doesn’t get to decide the change was good. The tests and the gates decide that, and then I do.

Each of these is worth its own post, and I’ll write them as the pieces settle. The short version is that the model was the easy part to get, and most of my time now goes into the rig around it.