ZB Field Notes

Shipping my own site at the speed of a prompt

Shipping my own site at the speed of a prompt

The bottleneck was never typing

My career site isn't a static page I forget about — it's a live application I keep evolving: a React SPA gated behind LinkedIn OAuth, a Spring Boot backend, a public server-rendered blog, an AI concierge, visitor analytics, all in one container on a Hetzner box behind Traefik. Every change — a copy tweak, a new section, a whole feature — has to travel the full distance from my editor to production without breaking the thing recruiters are looking at right now.

The slow part of that journey was never the code. It was everything around the code: remembering the exact command to boot both servers, which port the database binds to, that the frontend gets built into Spring's static/ dir, that master is protected, that a schema change needs a matching migration, that CI/CD ships only the image so infra changes need a manual step on the box. That operational knowledge lived in my head and in a long CLAUDE.md. So I did the obvious thing: I turned it into skills — version-controlled slash commands that hand Claude Code the whole playbook.

Skills are operational knowledge, checked into git

A Claude Code skill is just a markdown file with a bit of front-matter. It's not magic — it's a runbook the agent can execute. Each one captures the exact steps, commands, and guardrails for one recurring job, so the agent does it the same careful way every time:

---
name: ship
description: Branch, review, PR, merge, deploy, verify a change to prod
---

1. Open a tracking GitHub issue.
2. Never commit to master — branch off it.
3. Run the code-review gate before committing.
4. Commit with a real what-and-why message.
5. Push, open a PR that closes the issue.
6. Merge and delete the branch.
7. Watch CI/CD to completion.
8. Verify the change is live with a real HTTP request.

The point isn't that the AI types faster than me. It's that the AI never forgets step 7. A tired human skips the boring verification step; a skill that says “poll the log until you see the ready signal, don't sleep blindly” does it identically every single time. Because skills are markdown, they live next to the code, get reviewed in PRs, and improve the moment I hit a new edge case.

/dev — boot the whole stack with one word

The first skill starts both dev servers and reports only when they're actually reachable. It knows the platform quirks, that the default profile auto-starts a database container, and — crucially — that it should tail the logs until each ready signal appears rather than optimistically assume success. If a dependency like Docker is down, it says so precisely instead of flailing, and starts whatever can run so I'm never blocked longer than necessary.

It sounds mundane. It's the skill I lean on most, because “get me to a running app, correctly, in one step” is the tax I used to pay a dozen times a day.

The build loop, with a quality gate

With the app running, the loop is simple: I describe the change in plain language, the agent implements it, and we iterate against the live app until it's right. Then, before anything is committed, a /code-review skill reviews the working tree. This is the part that keeps velocity honest — findings land before the first commit, so the git history stays clean and I never ship a bandaid I'd be embarrassed by later.

I scale the review to the change: a tiny diff gets a focused pass; a meatier one gets a broader, multi-angle sweep. Clear low-risk fixes get applied automatically; anything that's a judgment call gets surfaced to me. The AI proposes; I decide.

/ship — the assembly line

This is the skill that impresses people, and it's really just discipline written down. One command runs the whole pipeline, pausing to ask me only when something is ambiguous or a step fails:

StageWhat it does
TrackOpens a GitHub issue so every ship is traceable
BranchCuts a feature branch off protected master
ReviewRuns the code-review gate; fixes land pre-commit
CommitA real message: what changed and why
PROpens a pull request that closes the tracking issue
MergeMerges and deletes the branch
DeployWatches CI/CD build, push, and restart on the box
VerifyCurls production and checks the response

Watching CI/CD from the terminal

Merging master triggers a GitHub Actions workflow that builds the multi-stage image, pushes it to the registry, and restarts the container. I don't open a browser to watch it — the gh CLI streams it straight into the terminal:

gh run list --workflow=Deploy --branch master --limit 1 \
  --json databaseId,status,headSha -q '.[0]'

gh run watch <id> --exit-status --interval 15

--exit-status is the load-bearing flag: it makes the command fail when the deploy fails. That's what lets the skill stop and surface the broken step instead of blindly reporting success. Automation you can't trust to notice its own failures is worse than none.

“Green” is not “working”

A green pipeline means the container restarted — not that the change does what I intended. So the final step makes a real HTTP request against production and checks the actual response: status code, content type, the bytes I expect. Only then does the skill claim the change is live. This single habit has caught more “successful” deploys of subtly-wrong artifacts than anything else.

Why a human still pulls the lever

None of this is autonomous, and I don't want it to be. The skills make the path fast and repeatable; I stay the one who decides. Review findings that are judgment calls get surfaced, not silently rewritten. master is protected. Even my blog posts publish as drafts by default — including this one — so I read every word before it goes public.

That's the real unlock. It isn't that AI writes my code; it's that I've captured how I ship — the commands, the ordering, the guardrails, the boring verification — into runbooks the agent executes faithfully, with me on the trigger. The payoff is that a one-line tweak and a serious feature travel the exact same well-lit road. The speed doesn't come from skipping steps. It comes from never having to remember them.