At this point I work AI-first through the entire lifecycle: planning, design, architecture, implementation, review, deployment. But "AI-first" does not mean "AI-trusting." The mental model that makes it work is treating the AI as an experienced junior developer that I lead and hold accountable — not as a black box I trust blindly.
A junior dev can write good code and still ship the wrong thing, miss the edge case, or misunderstand the business rule. You manage that with specs, small tasks, gates, and review. I run AI exactly the same way. Everything below is the operating system I've built around that idea: the tooling that survived contact with reality, a concrete feature built end to end, and the five places where I deliberately pull back.
The junior-dev model
Working with AI as a junior dev changes three things about how you operate:
- You specify like a lead. Vague prompts get junior-dev output: plausible, confident, wrong in the details. Precise prompts with architecture, conventions, and acceptance criteria get senior output.
- You gate like a lead. No agent proceeds to step N+1 without step N being verified. The cost of a wrong step compounds; the cost of a 30-second check does not.
- You review like a lead. Every diff gets read. Tests passing is necessary, not sufficient — tests only prove what was tested, and the agent chose what to test.
The corollary: AI multiplies leverage in both directions. Good process plus AI ships faster. Sloppy process plus AI ships bugs faster. Most "AI wrote bad code" stories I've seen are actually "nobody led the AI" stories.
Tooling: what survived
My tooling has evolved as I've learned what actually helps versus what just adds friction. The current setup, roughly in the order I adopted it:
Phase 1 — MCP servers. I leaned on GitHub MCP for PR workflows, Figma MCP for design handoff, and Context7 for third-party library docs. Useful in principle. In practice, MCP integrations would intermittently misbehave inside my editor — dropped context, stale results, silent failures — while the plain GitHub CLI just worked:
gh pr diff --name-only # always works
gh pr checkout 247 && npm test # no middleware to misbehaveI switched most of that back to CLI. Boring tools that work beat clever tools that flake.
Phase 2 — skills over servers. I've since moved into a heavier skills-based setup:
- Superpowers for brainstorming and architecture planning on any non-trivial feature. It also enforces test-driven development, which I've come to rely on — tests written first are the contract the implementation is held to.
- A testing-focused skill for agents that verify their own code, so "it works" means "it ran green," not "it reads well."
- A plan-interview skill that interviews me about my own plan until it's actually solid before implementation starts. Cheap insurance against building the wrong thing carefully.
- Anthropic's frontend-design and PR-review-toolkit skills as the foundation for two custom agents I built on top of:
- A full-stack development agent that encodes our actual project architecture and conventions — stack choices, patterns, what "done" looks like here.
- A PR review agent that encodes the same, so review feedback matches how we actually build.
The pattern: generic skills for the discipline, custom agents for the context. The custom layer is where the leverage compounds, because every lesson learned becomes a default for next time.
A concrete example: the analytics-export feature
A recent analytics-export feature shows the whole pipeline. The PRD I received had no mockups — just fragmentary requirements. Here's what happened:
1. Mockups from nothing. I fed the PRD to Claude to generate mockups matching our existing design system, cross-checked the result with ChatGPT for gaps, iterated to a final version, and got product sign-off on that. Two models, different failure modes, one human deciding.
2. Fold the lesson back. I folded the improved prompt back into my full-stack agent, so future PRDs without mockups get this automatically. This is the compounding step most people skip: every workaround should become a default.
3. Stack choice from evidence. I rebuilt the page in Vue 3, TypeScript, Pinia, and Vite — based on a similar migration I'd already done for account-level analytics. Not novelty; precedent.
4. Architecture before code. I brainstormed the architecture with the planning skill, including the trickier decisions like browser-download vs. email-delivery thresholds for large exports, then stress-tested the plan with the interviewing skill until it held up.
5. Gated implementation. I implemented subtask by subtask in fresh sessions, with the agent required to stop and get my sign-off after each one:
Work through the subtasks below ONE at a time, in a fresh context each. After each subtask: stop, summarize what changed and how you verified it, and wait for my sign-off before starting the next. Do not proceed without explicit approval.
I verified each one manually before moving on. Fresh sessions matter — they keep the agent from accumulating stale assumptions across a long build.
6. Review, then ship. Once implementation was done, I ran my PR-review agent against the branch diff, addressed what mattered, did my own final pass on requirements, UX, and performance, then handed it to QA and other engineers before shipping.
Total human decisions: stack, architecture, every subtask gate, final review. Total agent output: mockups, code, tests, review notes. That's the division of labor the junior-dev model produces.
Where I deliberately pull back
Five areas get tighter supervision or no AI-first at all:
| Area | Why |
|---|---|
| Security-critical code | A subtle vulnerability can look completely reasonable. Auth, crypto, access control — I read every line like it owes me money. |
| Payment and billing logic | A one-line mistake can become a five- or six-figure incident, and AI can misread business rules it wasn't given full context on. |
| Migrations and destructive ops | A missing piece of logic here is catastrophic and irreversible. No "looks right" — prove it, preferably on a copy first. |
| Architectural decisions | AI is an advisor that reasons through options. I make the actual call. |
| Production infrastructure | Still done by hand. This category exists because of bugs like the one below. |
The bug that earned the rule
I had AI generate a background worker for Brand Magic's scan queue. Two methods looked completely correct in isolation. Neither would fail in dev or in a serial test suite. Both were broken in production.
Bug one: check-then-act race. One method used a "find or create" pattern to queue a scan; the other fetched pending work for a cron job. The queueing side looked like this:
// Looks correct. Isn't — under concurrency.
$pending = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM scans WHERE site_id = %d AND status = 'pending'",
$site_id
));
if (!$pending) {
$wpdb->insert('scans', ['site_id' => $site_id, 'status' => 'pending']);
}Two concurrent scan requests can both pass the eligibility check, then both try to insert against the unique constraint — and the second one crashes. The fix is to let the constraint do the atomic work instead of checking first:
// The unique constraint is the check. One statement, no race.
$wpdb->query($wpdb->prepare(
"INSERT INTO scans (site_id, status) VALUES (%d, 'pending')
ON DUPLICATE KEY UPDATE id = id",
$site_id
));Bug two: limit-after-load. The cron fetcher pulled all pending and stuck records into memory, then applied its "only take 5" limit in PHP:
// Fine with 12 rows. Fatal with 12,000.
$rows = $wpdb->get_results(
"SELECT * FROM scans WHERE status IN ('pending', 'stuck') ORDER BY created_at ASC"
);
$batch = array_slice($rows, 0, 5);The day thousands of scans queue up at once — say, after a product launch — that's a guaranteed out-of-memory crash. The fix is one word in a different place:
$batch = $wpdb->get_results(
"SELECT * FROM scans WHERE status IN ('pending', 'stuck')
ORDER BY created_at ASC LIMIT 5"
);The lesson generalizes: AI optimizes for code that reads cleanly and passes the tests in front of it. It doesn't reason about what happens at the same millisecond under concurrent load, or with three orders of magnitude more data. So anything touching uniqueness constraints, atomicity, or unbounded queries is now a mandatory human-review category for me — regardless of how clean the generated code looks.
The short version
AI-first works when the "first" refers to leverage, not trust. Specify like a lead, gate like a lead, review like a lead. Keep the tooling boring where reliability matters and custom where context matters. Fold every lesson back into the agents so the system gets stricter over time. And know your five no-go zones before production teaches them to you — I can personally recommend learning the concurrency one from this post instead of from a 2 a.m. page.