β€’

tech

How to prevent AI messing with your code

The same guardrails we created for humans when writing code must be applied to AI. First check is types (Strict Type Safety), then linting, and testing as the ultimate step. A few notes.


Sandro Maglione

Sandro Maglione

Software

With AI, you new objective is to implement automatic solutions to avoid sloppy/buggy code πŸ™Œ

Well, we already have those tools, since we have the same problem with human writing code πŸ’πŸΌβ€β™‚οΈ

Three are key: types, linting, testing.

Let's see how to prevent the AI doing a mess πŸ‘‡


Remainder: Monorepo required

As mentioned in "Full stack with Effect and AI", a monorepo is no more optional with AI.

package.json
{
  "name": "app",
  "private": true,
  "scripts": {
    "dev": "pnpm --parallel -r dev",
    "build": "pnpm -r build",
    "typecheck": "pnpm -r typecheck"
  },
  "dependencies": {
    "typescript": "^5.9.2"
  },
  "packageManager": "[email protected]"
}

A monorepo is key to share strict types (e.g. API signature between client/server), AI agents instructions (AGENTS.md/CLAUDE.md), and linting configuration.

Code will be consistent full-stack, the AI will architect full-stack and have many more examples to copy πŸ—οΈ

Strict Type Safety

Second absolutely necessary requirement: type-safety. Strict Type Safety.

In its most basic form, this comes from adding "strict": true to tsconfig.json, and a few others like exactOptionalPropertyTypes and noUncheckedIndexedAccess:

tsconfig.json
{
  "compilerOptions": {
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true,
    // ...
  },
  // ...
}

Then you must follow a few guidelines for strict type checks:

  • If more than 1 input parameter, use objects
  • Use branded types (for nearly everything)
  • Use pattern matching instead of if/else or switch
  • Use satisfies when the API only accepts primitive values
  • Never re-define a type, always derive (e.g. Pick/Omit)

Instruct the AI to always run tsc after implementing a new feature, and iterate until all type errors are fixed 🫑

Linting

Notice what I mentioned above: "...iterate until all type errors are fixed".

In the AI world, this means as any 🀯

That's where linting comes as another layer of safety. Add rules that are specifically designed to prevent AI from going over the board:

  • no-explicit-any: No more any
  • strict-boolean-expressions: Only checks with boolean
  • prefer-nullish-coalescing: No more ||
  • no-non-null-assertion: No more null!
  • no-floating-promises: A classic 🀝
  • eqeqeq: === and !==

Let's sprinkle in some functional programming as well:

  • no-param-reassign
  • prefer-for-of
  • explicit-function-return-type or no-inferrable-types
  • array-type
  • prefer-function-type
  • prefer-destructuring
  • prefer-as-const

Once again, inform the AI to run lint while implementing a new feature, and not stop until tsc and lint pass 🫡

Testing (optional?)

Types and linting will do wonder to prevent AI mistakes. They should be enough on their own πŸ™Œ

Then there is the last, ultimate level of safety: testing.

For quick AI-iteration purposes I mean mostly unit testing, fast and easy to check by the AI during development.

Do testing as you want/like (if necessary at all), remember to inform the AI to run test.


AIs become faster and better, but if your codebase becomes slower and more buggy there is not reward πŸ’πŸΌβ€β™‚οΈ

Make sure to define strict guardrails, and just sit back and "accept all edits".

See you next πŸ‘‹

Start here.

Every week I dive headfirst into a topic, uncovering every hidden nook and shadow, to deliver you the most interesting insights

Not convinced? Well, let me tell you more about it