How Real Devs Force Quality out of AI (Day 3) - Free 5 Day Course

You're probably using AI but using it all wrong. You'll get some prototypes but never be able to get something to production. You have faults in your workflow. So far we've covered why AI only gets you 80% of the way to a real product (Day 1) and getting to 100% is the hardest part. Day 2 we covered our tools, and now we're going to learn how to use them to create quality and push to 95-100%.

Free 5 Day Course - Forcing Quality from AI

Day 3Here's the core problem: AI is nondeterministic. Ask it the same thing twice and you'll get two different answers. You can't fix that with better prompting alone. What you CAN do is surround the AI with checks that are deterministic, they pass or fail the same way every single time, no opinions involved.

That's the whole strategy for today. Convert as much of your quality bar as possible in to rules a machine enforces, so you're not relying on the AI (or yourself) to remember anything.

Pick a Static Quality Stack

Start with a typed language or a type layer. In the JavaScript world that's TypeScript. Enforce it as strictly as possible, as EARLY as possible. It's way harder to add strictness to a grown codebase than to start with it.

The big one is banning any. When the AI can't figure out a type, any is the escape hatch it reaches for, and every any is a spot where the compiler stops checking anything. In my SaaS both versions are blocked:

// tsconfig.json: "strict": true catches implicit any
// eslint config: explicit any is an error, not a warning
"@typescript-eslint/no-explicit-any": "error"

Now when Claude writes function handle(data: any), the check fails and it has to go figure out the real type. No discussion needed.

What is ESLint?

ESLint is a tool that parses your JavaScript/TypeScript and checks it against a list of rules. Unused variables, unsafe patterns, whatever you configure. Every ecosystem has an equivalent: in Python it's Ruff for linting plus mypy for type checking. Same idea everywhere.

The built-in rules are just the start though. The real power is custom rules specific to YOUR app. Here's one of mine, it bans reading environment variables directly because I route everything through one validated module:

{
  selector: "MemberExpression[object.name='process'][property.name='env']",
  message:
    "Do not read process.env directly. Import `env` from app/env.server.ts " +
    "and use `env.X` (add new vars to its zod schema).",
}

Notice the error message tells you exactly what to do instead. That matters more than ever now, because the AI reads these messages. A good error message is basically a prompt that fires at the exact moment it's needed.

Some other custom rules I run: no raw SQL (it escapes type checking), no hard deletes (we soft-delete everything), every API endpoint must validate its input, and database queries must select specific columns instead of pulling whole rows. Every one of those rules exists because something dumb happened once and I never wanted to review for it again.

Ask the AI What Should Become a Rule

This is the highest value prompt in this whole email:

Look through our codebase and how we've been working. What nondeterministic
things are we doing (style choices, conventions, checks I do by eye) that
could be converted in to deterministic rules? For each one, propose the
lint rule, script, or hook that would enforce it automatically.

Every convention that lives in your head is something you have to catch in review, forever. Every convention that becomes a rule is handled. Run this prompt every so often and your setup keeps getting stricter without you designing it up front.

Formatting

Small one, but do it. Add Prettier (or Ruff's formatter in Python) with a config file so formatting is a settled question:

{
  "semi": true,
  "printWidth": 100,
  "tabWidth": 2,
  "trailingComma": "all"
}

The point isn't the specific settings. The point is that formatting never shows up in a diff again, and the AI's output always matches your codebase style.

Hooks: Where It All Ties Together

Yesterday I mentioned hooks let you tie in to the prompt-response lifecycle. Here's the one that changed everything for me: the stop hook.

A stop hook runs when Claude thinks it's finished. Mine figures out which files the session actually touched, runs ESLint and TypeScript on them, and if there's new debt it BLOCKS the turn and hands the errors straight back to Claude. Claude sees the failures, fixes them, and tries to finish again. It cannot declare victory until the checks pass, and I never even see the intermediate mess.

Registering it is one entry in settings:

"hooks": {
  "Stop": [{
    "hooks": [{ "type": "command", "command": "node .claude/hooks/stop-check.js" }]
  }]
}

Exit code 2 is the entire mechanism. Whatever the script prints goes back to the AI as feedback and it has to keep working. Everything we set up above (TypeScript, ESLint, custom rules, Prettier) plugs in to this one hook.

One practical tip if your codebase already has existing warnings: freeze a baseline. My hook compares against a snapshot of the errors that already existed, and only blocks NEW debt. So old code doesn't have to be perfect, it just can't get worse. It's a quality ratchet, only turning one direction.

I also can run these same checks as git hooks before any commit, so even if something slips past a session, it can't slip in to the repo. However I find this annoying and prefer to have checks on merge or deploy.

Subagents for the Opinion Stuff

Rules catch everything that's black and white. But plenty of quality is a judgment call: is this query efficient, is this the right table design, does this name make sense. You can't write a lint rule for "this SQL is bad".

That's where subagents from yesterday come back in. A subagent can hold an opinionated checklist and review work against it, kinda like a code reviewer that never gets tired.

Here's a skill I'd start with, a simple data layer checker:

Create a skill /db-check that launches a read-only subagent to review our
data layer. Have it look at any queries we changed and flag: queries that
pull whole rows instead of selecting the columns they need, queries running
inside loops, and filters that would benefit from an index. Report findings
in a table with file, problem, and suggested fix. It reviews only, it never
edits code.

Now running /db-check before a commit gives you a second set of eyes on the layer that hurts the most when it's wrong. It's not deterministic but can catch the stuff no rule ever could.

Why This Matters So Much

This is honestly one of the biggest things we focus on inside the Agentic AI course, because it's the actual dividing line between garbage and quality. Two people can use the identical tools, and the one with enforcement gets shippable code while the other gets a demo that falls apart. The course goes much deeper on this: layered verification, review agents, and workflows where the AI checks its own work before you ever see it.

You can check out the course here: https://codebreakthrough.io/agentic-ai

This is a LIMITED TIME offer and closes soon. So please don't miss it. I won't be extending the date.

Or if you have questions first, reply to this email.

Tomorrow we'll cover permissions and security, how to give AI as much freedom as possible without putting yourself at risk.

Until tomorrow,

Caleb


Unsubscribe from marketing emails

Sent via Course Catalyst on behalf of CodeBreakthrough
500 Westover Dr #217334
Sanford, NC 27330

© CodeBreakthrough. All rights reserved.