All WordPress HTML Templates Forms & Webhooks AI & Tools
AI & Tools

Using AI to Write Code You Can Actually Maintain

How to keep ai generated code quality high in 2026: context files, 300-line diff caps, machine-enforced rules and the code review AI can’t do for you.

A real working moment illustrating the theme of an article about ai generated code quality. Wide 16:9 banner, one strong focal point, magazine editorial quality, authentic and unstaged.

The pull request had 41 changed files and a green CI run. It also had three different date formatting helpers, two of which were byte-identical, and a utils.js that had quietly grown to 780 lines. Every individual function in it was fine. The codebase as a whole had gotten measurably worse.

That’s the shape of the problem in 2026. Models are excellent at producing locally correct code and terrible at preserving global coherence, and ai generated code quality is now almost entirely a function of the constraints you put around the generation rather than the model you picked. We’ve shipped client work with agents doing 60 to 70 percent of the typing for about two years now. The teams that get burned aren’t the ones using worse models. They’re the ones with no boundaries.

What follows is the process we actually use, including the parts that slow us down on purpose.

Key Takeaways

  • AI failure is architectural, not syntactic: models rarely produce broken code, they produce duplicated abstractions, inconsistent error handling and files that grow without bound. Lint and CI won’t catch any of that.
  • A committed context file (AGENTS.md, CLAUDE.md .cursorrules) that states your stack versions, naming conventions and forbidden patterns cuts rework more than any prompt phrasing trick.
  • Cap generated diffs at roughly 200 to 300 lines per task. Beyond that, human review quality collapses and people start rubber-stamping.
  • Machine-checkable constraints beat review comments: strict TypeScript, ESLint no-duplicate-imports, PHPCS with the WordPress ruleset, and a hard file-length ceiling do more for maintainable AI code than any amount of “please follow our conventions”.
  • Models are most confidently wrong in security-adjacent WordPress and PHP code: missing nonces, unescaped output, and hand-rolled mail handlers appear constantly and pass tests.

How AI generated code quality actually degrades

It doesn’t fail loudly. Nothing throws. The tests pass because the model wrote the tests too, and it wrote them against the implementation it just produced rather than against the requirement.

The degradation happens along four axes we see over and over:

  • Duplicate abstractions. The model can’t see your whole repo, so it reimplements formatCurrency for the fourth time. Each copy handles a slightly different edge case. Six months later a bug fix lands in one of them.
  • Inconsistent error handling. One module throws, one returns null, one returns { error: string }. All three came from the same agent in the same week.
  • Unbounded file growth. Given a file and an instruction, a model appends. It almost never proposes splitting the file. We’ve watched a single React component go from 120 lines to 600 across five agent tasks with no human ever deciding that was acceptable.
  • Comment rot at speed. Generated comments describe the code as written in that moment. Change the code with a second prompt and half the comments become lies.

None of that is a model quality problem. It’s a context problem, and context is your job.

Write the constraints before you write the prompt

The highest-leverage thing in our workflow is a file committed at the repo root that every agent reads automatically. Depending on the tool it’s AGENTS.md, CLAUDE.md, or .cursorrules. Contents matter more than the filename.

# Project conventions

Stack: WordPress 6.8, PHP 8.2, Bootstrap 5.3, vanilla JS. No jQuery. No build step for admin JS.

Hard rules

  • Never add a dependency without asking. package.json changes need a reason in the commit body.
  • All output escaped at the point of echo: eschtml, escattr, escurl, wpkses_post.
  • Every admin POST handler checks checkadminreferer() and currentusercan().
  • Files over 300 lines must be split. Tell me instead of appending.
  • No new util files. Shared helpers go in inc/helpers.php only.

Naming

  • PHP functions prefixed acme_. Classes Acme\ namespace, PSR-4 via composer.
  • CSS: BEM, no utility soup in templates beyond Bootstrap grid classes.

Before you write

  • Search the repo for an existing function that does this. Cite it if you find one.

That last line is worth more than the rest combined. “Search first, cite what you found” converts a generator into something closer to a reader. On a mid-size WordPress plugin it dropped our duplicate-helper rate to near zero within a sprint.

Keep it under about 60 lines. Long convention files get compressed into vagueness by the model’s own attention budget, and you end up with rules it ignores. Delete anything you don’t actually enforce in review.

Small tasks, hard boundaries

Ask for a feature and you get a sprawl. Ask for one function with a stated signature and you get something you can read in 90 seconds.

Our working rule: if the expected diff is over roughly 300 lines, split the task. This isn’t about model capability. It’s about you. Reviewer attention falls off a cliff somewhere around 400 lines, and every senior developer we know has approved a large generated diff they didn’t fully read. Once that becomes normal on a team, the codebase is on autopilot.

Concretely, we scope agent tasks like this:

  1. Describe the change in one sentence. If you need “and”, it’s two tasks.
  2. Name the files it may touch. Explicitly forbid the rest.
  3. State the interface: function signature, hook name, expected return shape.
  4. Say what happens on failure. Models default to swallowing errors.

Point 4 catches a lot. “On network failure, throw a FetchError with the status code, do not return an empty array” is the difference between a debuggable system and one where empty states are indistinguishable from outages.

Make the rules machine-checkable

Review comments don’t scale against a generator that produces 2,000 lines a day. Anything you’d say twice in review, encode it.

// eslint.config.js - the rules that specifically catch agent output
export default [
  {
    rules: {
      'max-lines': ['error', { max: 300, skipBlankLines: true, skipComments: true }],
      'max-lines-per-function': ['error', { max: 60 }],
      'no-duplicate-imports': 'error',
      'complexity': ['error', 12],
      // agents love empty catch blocks and console.log breadcrumbs
      'no-empty': ['error', { allowEmptyCatch: false }],
      'no-console': ['error', { allow: ['warn', 'error'] }],
    },
  },
];

max-lines at 300 is the single most useful rule we’ve added. It turns “the file quietly grew” into a red CI run, and it forces a human to make the split decision. On the PHP side, PHPCS with WordPress-Extra plus WordPress.Security catches escaping and nonce misses that a model will produce with total confidence.

Add a duplication detector too. jscpd with a threshold of about 1.5 percent, run in CI, catches the copy-paste helpers before they calcify:

npx jscpd src --min-lines 8 --threshold 1.5 --reporters console,json

The code review AI can’t do for you

Using an LLM as a first-pass reviewer is genuinely useful, and we do it. It catches off-by-one errors, missing null checks, and inconsistent naming within a diff. It misses everything that requires knowing your system.

So we split the review into two passes. The machine pass runs on the diff. The human pass asks five questions the machine structurally cannot answer:

  • Does this belong here? Not “is it correct”, but is this the right module, the right layer, the right lifecycle hook.
  • Does it duplicate something? Search the repo yourself for the two or three key nouns in the diff.
  • What happens at 100x? Models write the naive query. A getposts with postsper_page => -1 is fine at 200 posts and fatal at 40,000.
  • Who owns the failure path? If the third-party API is down at 3am, what does the user see.
  • Would a new hire understand why? Generated code explains what. It never explains why this approach was chosen over the obvious alternative.

The code review ai layer is a filter, not a gate. Treat its approval as “no obvious defects”, which is not the same as “safe to merge”.

Where models are most confidently wrong: WordPress and PHP

Training data for WordPress skews old. Ask for an admin form handler and there’s a real chance you get code that would have been fine in 2014.

<?php
// What we frequently get back. Three problems, zero errors.
addaction( 'adminpostacmesave', function () {
    $name = $_POST['name'];                       // unsanitised
    updateoption( 'acmename', $name );          // no capability check
    wpredirect( adminurl( 'admin.php?page=acme' ) );
    exit;
} );
<?php
// What ships.
addaction( 'adminpostacmesave', function () {
    if ( ! currentusercan( 'manage_options' ) ) {
        wpdie( eschtml__( 'Insufficient permissions.', 'acme' ), 403 );
    }
    checkadminreferer( 'acmesavesettings' );  // dies on a bad or missing nonce

    $name = isset( $POST['name'] ) ? sanitizetextfield( wpunslash( $_POST['name'] ) ) : '';
    updateoption( 'acmename', $name );

    wpsaferedirect( addqueryarg( 'updated', '1', admin_url( 'admin.php?page=acme' ) ) );
    exit;
} );

Note wpunslash before sanitising. Models skip it constantly, and it silently mangles apostrophes in production. The same pattern of stale training shows up with custom fields, where you’ll get addmeta_box boilerplate when the project would be better served by registered meta and block bindings. State your intended approach in the prompt or you’ll get the 2016 answer.

One more that costs real money: ask for a contact form and you’ll get a mail() or wp_mail() handler with no rate limiting, no spam defence and no delivery guarantee. It works on your machine and lands in spam in production. For static sites and JAMstack builds we point that at WebForms instead and skip the backend entirely, which also removes the endpoint an agent might otherwise leave unauthenticated.

Where AI doesn’t pay off

Being honest about this matters more than the enthusiasm.

Novel CSS layout debugging is a bad fit. The model can’t see the rendered box model, and it will confidently suggest overflow: hidden on a container that has nothing to do with your problem. DevTools and a systematic process will get you there faster. We wrote that up in our CSS layout debugging guide.

Performance work is another. Models optimise what looks slow rather than what is slow. Every recommendation should follow a measurement, not precede one. We’ve had agents suggest memoising a component that rendered twice per session while ignoring a 900ms uncached database query on the same page.

Then there’s anything with a genuinely novel domain model. If the business rules aren’t in the training data and aren’t in your repo, you’ll spend more time correcting a plausible-looking wrong answer than you would writing the right one. Design it yourself, then hand over the implementation.

Frequently Asked Questions

Should I disclose AI-generated code in commits or PRs?

Yes, in the commit body or PR description, not the subject line. It changes how a reviewer reads the diff: they’ll look harder for duplicated helpers and swallowed errors. Some clients also have contractual requirements about it, so establish the policy before the first commit rather than after an audit.

Do AI-written tests count as real test coverage?

Only if you wrote the assertions or reviewed them line by line. Models tend to write tests that mirror the implementation, so a bug in the code becomes a bug in the test and coverage numbers stay green. A good compromise: write the test names and expected behaviours yourself, then let the model fill in the setup boilerplate.

How do I stop an agent from rewriting files I didn’t ask it to touch?

Name the allowed files explicitly in the task and add a hard rule in your context file. Beyond that, run agents on a branch and review the file list before the diff. If unrelated files appear, reject the whole task and re-scope rather than cherry-picking, because the cherry-picked version usually leaves half-applied changes.

Is a bigger model the answer to maintainable AI code?

It helps with reasoning quality but not with architecture, because architecture is about your repo, not the model’s knowledge. A stronger model with no context file still duplicates your helpers. Spend your effort on retrieval, conventions and task sizing first, then upgrade the model.

How much of a legacy codebase should I let an agent refactor at once?

One module, with tests in place before you start. Large refactors are exactly where generated code looks most convincing and breaks most subtly, since behaviour changes hide inside plausible restructuring. If there are no tests, write characterisation tests first even if the agent writes them, then review those tests unusually carefully.

What to do on Monday

Pick one repo. Add a 40-line context file with your stack versions, your escaping rules and a “search before you write” instruction. Turn on max-lines at 300 and run jscpd once against the existing code to see how much duplication is already there. That single measurement usually settles the argument about whether this matters.

Then cap your next five agent tasks at 300-line diffs and review each one against the five questions above. If it feels slower for a week, that’s the point. You’re paying for the maintenance you were about to defer.

AGENTS.md context file conventions ai code duplication detection jscpd ai generated code quality code review ai workflow eslint max-lines rule for ai code limiting ai generated pull request size maintainable ai code wordpress nonce escaping ai generated php