Claude Code config

The agents, commands, skills and hooks I work with every day, packaged as a Claude Code plugin with a one-script restore for a new machine.

Markdown, shell, Python — no runtime of its own; it configures the tool that builds everything else here

What it solves

Every project on this site was built with Claude Code doing most of the typing. That only works if the tool has been taught how I want to work: which model handles which kind of task, what a commit is allowed to contain, how a plan is laid out, when to run tests and when not to bother. Left in ~/.claude/ that knowledge lives on one laptop and drifts. This repo is the version-controlled copy — eight agents, nine slash commands, five skills, three hooks, and the settings that wire them together — so a fresh machine is a clone, a restore.sh, and an /install-plugin away from being the machine I already know.

How it is built

The repo is a Claude Code plugin: a .claude-plugin/plugin.json manifest over agents/, commands/ and skills/, each entry a Markdown file with frontmatter. The agents are role-shaped — architect, implementer, tester, reviewer, security reviewer — and the commands compose them into pipelines: /feature runs spec → architect → plan → implement → test → review → ship; /preflight is the same pipeline stopped before the commit. The hooks are the part that runs without being asked. A PreToolUse hook on every shell command runs the project's build and tests before any git commit is allowed through; a PostToolUse hook formats files on every edit; a Stop hook kicks off a background cost tracker that reads the session transcripts and writes a per-project spend table. The backup/ directory holds the global instructions, settings and cross-project memory, and restore.sh puts them back where the tool expects them.

The hard decision

The pre-commit gate is configured with a filter — if: "Bash(git commit *)" — that is supposed to make the hook fire only on commits. It does not hold. On compound commands, the kind an agent produces constantly (a loop, a ${param} expansion, a commit chained after a build), the filter fails open: the hook is skipped and the commit goes through untested. The tempting fix is to widen the filter. The right one is to stop trusting it. The script now reads the command off stdin and re-checks with its own regex that this really is a git commit before it does any work, and only then runs the build. The filter is kept as an optimisation; the script is the source of truth. A safety check that can be bypassed by the shape of the command it is checking is not a safety check.

What it taught me

Configuration for an agent is code, and it fails the way code fails — silently, on the inputs you did not think of. The hook that guards commits was skipping exactly the commits most worth guarding, and nothing announced it. Once this lived in a repo with a README that says what each piece is for, it became something I could read, doubt and fix, rather than a folder of files I had stopped seeing.