How to Move Fast with AI Coding
Speed is not the enemy of quality. Carelessness is. When I started shipping AI-assisted code at a higher cadence, the question was never “how do I slow down to be safe?” It was “how do I build a safety net that makes speed sustainable?”
The answer is a layered CI pipeline. Six workflows, each catching a different class of failure, each running automatically on every pull request — bound by one rule: nothing merges unless all of them are green. Not because I enjoy configuring GitHub Actions, but because I want to merge without thinking twice.
The stack here is the one I reach for most — a Next.js frontend and a Python backend — so the specific tools below are TypeScript- and Python-flavored: ESLint and the TypeScript compiler on the web side, Ruff, Black, and Bandit on the Python side. Do not read the tools as the point. Every language ships its own equivalents — Rust has cargo fmt, Clippy, and cargo test; Go has gofmt and go vet— and the gate is the same shape regardless. Swap the tooling, keep the layers.
I run some version of these six in every project, which is why this is a reasonable reference to copy from. They are not all there on day one — the integration suite usually comes later, once there is a real database and service wiring worth testing against. But the quality checks and the changelog gate go in from the first commit. They cost almost nothing to stand up, and they are what keep a young codebase from rotting before it is old enough to defend itself.
Layer 1: Backend Quality — Ruff, Black, Bandit
Three tools run over only the Python files a PR changed. Ruff catches dead imports, undefined names, and the un-Pythonic patterns a reviewer should never have to flag by hand. Black runs in check mode, so style stops being a code-review argument — the formatter is the single source of truth. Bandit scans for risky patterns: shell injection, hardcoded secrets, weak crypto. These are the cheapest class of bug to catch and the most expensive to argue about, so I let a machine settle them.
Layer 2: Frontend Quality — ESLint, TypeScript
ESLint and a full TypeScript type-check run over the changed web files. The type-check is the heavy one: it proves the types line up across the whole app — “this prop can be undefined,” “this signature changed” — before a user ever hits it. It is effectively a free, exhaustive test suite for a whole category of bugs, and AI-generated code is exactly where shape mismatches sneak in.
Layer 3: Backend Unit Tests
The backend is where logic lives. Unit tests confirm that individual functions, handlers, and service methods do what they claim — fast and hermetic, with an in-memory database so nothing external has to be stood up. I track coverage not as a vanity metric but as a pressure gauge. When it drops, something new got written without a corresponding test. That is the signal.
Layer 4: Frontend Unit Tests
The frontend has its own test surface — components, hooks, utility functions — a Jest suite kept separate from the backend. The separation is intentional. When a frontend test fails, I want to know immediately, isolated from any backend noise. Types and lint cannot tell you a button does the wrong thing when clicked; only a test can.
Layer 5: Integration Tests
The heavyweight. It spins up real service containers — Postgres and Redis — applies the database migrations against a fresh database, and runs tests against the live stack instead of mocks. This is the only layer that catches what mocks hide: broken migrations, bad SQL, connection-pool bugs, wiring mistakes between services. Applying migrations on a clean database every run is what catches the “works on my machine, breaks on deploy” bug before it reaches an environment I cannot easily roll back.
Layer 6: Changelog Check
Every PR has to add an entry to the changelog, with a label as the escape hatch for dependency bumps and other PRs that genuinely do not warrant a note. It sounds like bureaucracy; it is not. A changelog written as part of the change — while the author still remembers the why — becomes a searchable history of what shipped and the reasoning behind it. Required-by-CI is the only thing that keeps it honest: an optional changelog is an empty changelog within a month.
The Merge Gate
All six checks have to pass before a PR can merge. No exceptions for “this is a small change” — especially not for AI-generated changes, which can look clean but carry surprises. The gate is the gate.
Why this matters for AI-assisted development specifically
AI writes plausible code. That is both its strength and its risk. The output looks right. It follows conventions, handles edge cases, fills in boilerplate fluently. But “looks right” and “is right” are different things, and the gap grows wider as the codebase grows larger.
The six-layer pipeline is not there to second-guess the AI. It is there because I do not want to second-guess myself. When every PR goes through the same gates, I can merge confidently at the pace AI coding enables. Without those gates, speed becomes a liability.
I have seen teams ship faster with AI assistance for two months and then spend three months unwinding the debt. The pipeline is what prevents that. It makes the speed durable.
The Other Front: Dependencies
The six layers guard the code I write. They say nothing about the code I depend on — and that is the front where AI is quietly raising the volume. Libraries ship faster, so version bumps arrive more often. Automated scanners disclose vulnerabilities faster, so security patches arrive more often too. Left alone, the dependency backlog grows until a one-line upgrade has compounded into a multi-day migration.
The first move is dependabot.yml. It is GitHub-native and genuinely plug-and-play: drop it in .github/, point it at your dependency directories, and GitHub opens a grouped pull request — one per ecosystem, weekly — whenever something is out of date. I keep the config conservative on purpose: minor and patch bumps grouped into a single PR, and semver-major updates ignored, because breaking changes deserve a human reading the migration notes, not an auto-PR. The payoff is that every bump flows through the same six-layer gate above before it can land.
One catch worth saying plainly: the file is not enough on its own. Dependabot’s alerting lives in repository settings — the dependency graph, Dependabot alerts, security updates, version updates — and those switches do not travel with the file. You enable them once, in the repo’s settings UI, for every repo you add the config to. Commit the YAML to a fresh repo and nothing happens until you flip them on.
Triaging the Bumps: A Weekly Claude Code Routine
Dependabot opens the PRs. It does not tell me which are safe to merge — and the two easy answers are both wrong. Rubber-stamp them and a bad bump lands unreviewed; ignore them and the security patches rot. What I actually want is the judgment a human would apply to each one, done consistently, without me opening and building ten branches by hand every week.
So I hand that to a Claude Code routine — a scheduled agent, not a GitHub Action. Once a week, the day after Dependabot’s scan, it walks every open Dependabot PR and does what I would: it checks out the branch and merges current main locally first, so it tests the merged result rather than the stale base the PR was opened against; it runs the CI gates for that ecosystem; it pulls the changelog highlights for the bumped versions; and it posts a single verdict comment — merge, human glance, or hold. It is strictly report-only: it never approves, merges, or pushes anything. Every merge decision stays mine.
That is the shape of moving fast that lasts. The pipeline keeps my own changes honest; Dependabot keeps the dependencies current; and the weekly routine turns a growing pile of update PRs into a triaged queue I can clear in a few minutes. Speed stays durable on both fronts — the code I write and the code I lean on.
Build From These
The artifacts from this article. Grab them, run them, adapt them.
GitHub Actions Workflow Pack
Sanitized, copy-paste versions of the CI workflows we run in production. Python quality (Ruff/Black/Bandit), frontend quality (ESLint + TypeScript), backend and frontend unit-test suites, integration tests against real Postgres/Redis, and a changelog check — plus the composite actions that drive them. Swap the marked placeholders for your paths and secret names, and the same gate system runs on your repo.