AI AgentTypeScriptCI/CDCode QualitySoftware Engineering

Keeping AI Code from Becoming a Debt Factory: My 5 Automated CI Rules

··5 min read

Late one night last month, I asked an AI agent to refactor a data transformation module. Three minutes later, it submitted a PR: 12 files changed, 18 unit tests added, and an unbroken wall of green PASS marks across the terminal.

I thought I was ready to call it a night.

Then I opened the diff. Deep in the third layer of the core pipeline, it had encountered an incompatible type boundary and smoothly typed `as unknown as Record<string, any>`. When handling optional fields, it bypassed our shared `formatDate` utility and hand-rolled a date parser with zero timezone awareness. Worst of all, to make a failing test pass, it made a critical error state return an empty array instead of throwing.

This is the signature failure mode of AI-generated code: **it optimizes to satisfy the prompt in the present moment, not to sustain the system in the future.** Without rigid external constraints, AI flows like water along the path of least resistance—and that path is almost always technical debt.

Rule 1: Lock tsconfig down completely and remove escape hatches

Many teams only enable basic TypeScript checks. That might be tolerable for humans, but for AI agents, it leaves loopholes everywhere. Wherever the type system leaves ambiguity, an AI will exploit it to get a passing build.

In all my projects, `tsconfig.json` enforces these four strict compiler flags:

json
{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true
  }
}

`noUncheckedIndexedAccess` is especially critical. By default, `array[index]` or `record[key]` assumes the element exists. Turning this flag on forces the return type to include `| undefined`. That single change compels the AI to write defensive checks for every array and object access rather than assuming data is always pristine.

Rule 2: Semantic linters to stop "plausible code smells"

Syntactic validity is not good design. When pressed by a developer to "fix this error," an AI frequently reaches for three shortcuts: slapping on `@ts-ignore`, casting to `any`, or removing hook dependencies in React.

These shortcuts pass basic unit tests with flying colors, but trigger nasty race conditions and memory leaks in production. My CI pipeline flags them as fatal errors at the ESLint layer:

Disallowed PatternAI Habitual MotiveCI Enforcement
@ts-ignore / @ts-nocheckBypassing complex generic mismatches quicklyStrictly blocked; only @ts-expect-error with issue references allowed
explicit anyAvoiding typing for third-party or unknown payloadsFails build; requires unknown with Type Guards
react-hooks/exhaustive-depsSilencing infinite re-render warnings by deleting depsTreated as error; zero tolerance for ignored warnings
no-duplicate-importsFragmented imports created during cut-and-paste refactorsAutomatic autofix or PR gate rejection
ESLint enforcement matrix for AI-generated code patterns

Rule 3: Domain Architecture Gates

Unit tests can verify whether a function computes an output correctly, but they cannot verify whether system architecture boundaries were broken.

Take keeponfirst.com as an example: we have strict publishing rules for SEO and AdSense. Ads must only appear on article canonicals, prerendered HTML must never be empty shells, and every article requires bilateral localization. If you merely tell an AI in a prompt "remember not to show ads on the homepage," it will forget two times out of ten during large refactors.

My solution is dedicated validation scripts wired into the final gate of `npm run build`:

javascript
// scripts/check-publisher-scope.mjs
const htmlFiles = await listHtml(distDir);
for (const file of htmlFiles) {
    const html = await readFile(file, 'utf8');
    if (!html.includes(ADSENSE_SRC)) continue;

    const canonical = html.match(/<link rel="canonical" href="https:\/\/keeponfirst\.com([^"#?]*)"/i)?.[1] ?? '';
    // Build aborts immediately if ads appear outside /article/
    if (!/^\/(?:en\/)?article\//.test(canonical)) {
        failures.push(`${relative(distDir, file)}: AdSense loaded outside an article canonical`);
    }
}

These lightweight architectural gates do not require heavy testing frameworks and execute in milliseconds. If an AI violates a business domain boundary, CI halts immediately with the exact file and violation reason.

Rule 4: Contract Testing over self-fulfilling unit tests

Asking an AI to write unit tests for code it just generated has a fatal flaw: **it will craft tests tailored precisely to fit its own flawed implementation.**

If it replaced a division-by-zero exception with a silent `return 0`, it will happily write `expect(divide(5, 0)).toBe(0)`. The CI dashboard shines green, while the system contract has been quietly corrupted.

The remedy: **Test contracts must be defined by humans or immutable domain invariants.** Using invariant testing or property-based fuzzing, we generate 1,000 randomized boundary inputs to verify that the state machine invariants hold true under all conditions.

Rule 5: Refocusing Human Code Reviews

In the era of AI-assisted engineering, developers spending time nitpicking formatting, naming styles, or syntax sugar are wasting their attention. The Linter should have caught those seconds ago.

When reviewing an AI-generated PR, I focus exclusively on three things:

1. **Failure boundaries**: When the network drops, an API returns 500, or a JSON payload is malformed, how does it fail? Does it degrade gracefully, or swallow the error silently?

2. **State contracts**: Does this change corrupt schema migrations or cached data structures? Will older clients crash against the updated API?

3. **Architectural duplication**: Does a clean utility already exist in the codebase? Did the AI reinvent the wheel because it lacked context?

Conclusion: The AI is the engine, CI is the roll cage

People often ask: "With AI writing code so fast, will we stop writing tests?"

My answer is the exact opposite: **The more horsepower an engine delivers, the stronger the chassis and roll cage must be.**

When the cost of generating code drops to near zero, the efficiency of verifying and filtering code becomes the ultimate bottleneck. Let automated CI robots police the rigid boundaries so human attention can remain anchored to high-leverage architecture and tradeoffs.

Sources