How I Ship Code When AI Writes 80% of It

About 80% of the code I ship is now written by AI: controllers, unit tests once I’ve described what I want, services once I’ve described the main business rules, mappers, database migration scripts. The remaining 20% is edge cases. Explaining to the AI why a case works a certain way takes longer than writing the if-else myself.

That didn’t make the job easier. The effort moved to what happens around the generation: understanding the code before prompting, giving the right context, and reviewing what comes out. Treat an LLM as a replacement for your own understanding and it generates technical debt faster than any developer could.

Investigate before you prompt

I mainly use Claude Code from my IDE, and the order matters:

  1. Investigate the code myself: which files are involved, how the data flows through the methods, and the constraints (performance, security, legacy).
  2. Pin the relevant files.
  3. Explain what I want, with context.
  4. Let Claude Code do the work.
  5. Make the small fixes by hand and ship to the development environment.

If I skip the first step and can’t describe the solution in plain English, the AI confidently produces a complex, broken implementation, and I spend hours debugging it.

Scope the context

Giving the AI the whole repository dilutes the context and leads to generic or conflicting suggestions. I name the files explicitly. Instead of “Refactor the login logic”, I write “Refactor login.ts using the UserSession interface in types/auth.ts and the wrapper in lib/crypto.ts.”

Rules that apply to every prompt go in a context file, so I don’t repeat them. Claude Code reads CLAUDE.md; most other agents read AGENTS.md. Mine keeps growing: naming strategy, unit test strategy, database migrations, REST naming conventions, what belongs in the frontend and what belongs in the backend. Rules for an authentication module look like this:

# Authentication module rules
- Use only the `UserSession` interface defined in types/auth.ts.
- Do not use external libraries for hashing; use the internal lib/crypto.ts wrapper.
- Handle all errors through the `AuthErrorHandler` class.
- Never include sensitive keys in log outputs.

I also added a glossary file with every business name, its description, and its synonyms, so the AI uses one name for one concept across the codebase.

Write the prompt like a specification

As I write less code, I write more instructions. A prompt I’d give for a feature has four parts:

  1. Context: “We are migrating the dashboard from REST to GraphQL.”
  2. Constraint: “Don’t use the LegacyFetch wrapper.”
  3. Input and output: “From this JSON schema, generate a TypeScript interface and the matching resolver.”
  4. Expectation: “Keep the logic functional and don’t mutate state outside the resolver.”

It does exactly what you said

Once I described a calculation in detail, and the AI implemented exactly that: an overcomplicated method that followed my description step by step. A colleague would have asked whether I meant something else, or pointed out that it could be simpler. The AI has no judgment to say either.

Versions are the other weak spot. When I created a Terraform project recently, the AI picked a version so recent it was still in beta, while all my other projects use the same stable one. Another time, in the middle of upgrading all our GitHub runners, it went back to a runner version from five years ago that GitHub is deprecating. Listing the versions you use in the context file is cheaper than catching this in review.

Review it like a junior’s code

I treat the AI like a very productive and overconfident junior developer. Nobody lets a junior push to production without a review, and I review AI code twice. It sometimes produces elegant solutions, and sometimes code that looks right and fails on the first edge case.

  • If I don’t understand a line, it doesn’t ship.
  • If it picked a library or pattern that doesn’t fit the architecture, I reject it.
  • I check the side effects myself. The AI often misses how a change in one file breaks a hidden dependency in another.

Comments

Leave a Reply