The Lifecycle of a Code Change
AI writes most of my code now. Here's where I let it, and where I don't.
In the last year, AI has seeped its way into every part of my development process: design docs, code, tests. It automated most of the toil I never enjoyed day-to-day, fixing presubmits, linting files, refactoring old code. It also raised the ceiling on what I could get done in a day.
Everything felt different. I stopped writing the code. I started owning the decision that it was the right code. None of it came from a new tool I was handed. It came from rewiring how I work.
The process is not automated, though, and it is further from automated than the hype suggests. The human in the loop matters more than ever, and so does your taste. The code these tools write becomes your code the moment you mail it for review. Getting the human parts right is the whole game.
So here is what my workflow actually looks like now, stage by stage.
Requirements
This is one of the few areas that's primarily human driven. Driving requirements mainly entails:
- Identify requirements
- Define scope and non-goals
- Gather stakeholders
- Set success criteria
- Alignment and sign-off
AI can identify requirements; but based on what? AI can "identify" stakeholders, but the only "gathering" it can do is a calendar invite. AI can draft a scope line or a success metric, but it cannot feel the deadline that decides which half to cut. AI can help with alignment if you paste in the comments from your design doc; but can it distill a meeting full of conflicting opinions and make the correct judgment call?
Barring a huge change in the research landscape, we as humans will have to still write prompts and guide the agents to a desired outcome. How do you expect to know the destination if you don't know what it looks like?
So my advice here is to lean on the people around you. Get input from all the humans. Put forward multiple proposals and see what sticks. Be ready to be asked and ask yourself "Why", a lot.
Design
The thinner the context, the more generic your results will be. It's much cheaper to update the design than the implementation; there are few worse feelings than exhausting your model usage on an incorrect solution.
If there's a place I invest the best models, the largest research tasks, and the most tokens, it's this design phase. They usually have the longest prompts and the most back and forth. I really recommend going back-and-forth with the model for several iterations to make sure your architecture is clear.
I'd recommend setting up skills or MCP servers that point to your corporate corpus of information. That way existing infrastructure gets reused, company standards get met, and the model stops inventing APIs that don't exist in your codebase.
Development
Since 2025, almost all of the code I submitted at work was AI generated. I felt sneaky at first, the models weren't quite perfect, and outputs needed a lot of refinement. But the models improved, and so did my confidence in them. I knew there was something here when I kept showing my teammates the AI tooling available and they started adopting it.
If the requirements are gathered and the design is documented, most of this work will be oversight. I'll inactively monitor the agents and gently nudge them in the direction I think is right. When they're done, I read the summary of what they accomplished, and probe about the high-level details. Finally, I will manually test the new feature out to make sure it behaves as expected.
I find that the agents work best when they have a metric or goal they can loop over.
Testing
Testing has never mattered more. When you wrote every line yourself, the tests confirmed what you already believed. When an agent wrote the line, the tests are how you find out what it actually did. They are also the metric the agent loops against: hand it a failing test and a way to run it, and it will grind until the bar goes green.
Whether you write the tests first (test-driven development) or after is a matter of taste. With agents I write them first more often than I used to, because a concrete, runnable definition of done is the cleanest prompt there is.
Aim for a good mix of unit, integration, and end-to-end (e2e) tests. I target roughly 70% unit, 20% integration, and 10% e2e across the whole codebase, not per commit.
At work I read every line of code I ship to production. On personal projects, I mostly read the tests and trust them to catch the rest.
Preflight Checks
Everything up to here was about building the right thing. Everything from here to review is about not embarrassing yourself: the unglamorous gate between "works on my machine" and "someone else now has to read this." Three habits do most of the work.
A Proper Readover
Your code reviewer should never be the first person to read your code. It does not matter if the change is a single line; you read it first.
Read it in the same tool your reviewer will use, the diff view, not your editor. The diff is a different lens. Things that looked fine while you were writing them look wrong sitting next to everything else that changed: the debug print you forgot, the commented-out block, the variable you renamed everywhere but one place, the function that quietly grew three arguments past where it should have been split.
This matters more, not less, when an agent wrote the code. You are no longer the author who remembers every keystroke; you are the first reviewer of a change you mostly watched happen. Read it like a stranger wrote it, because in a sense one did.
The Commit
Your commit messages are an index into your work; how accurate is your index? The code tells you what the system does today. The history tells you how it got there, and why. Six months from now, staring at a line you do not remember writing, the blame trail and its messages are the only narrator you have. git bisect, code archaeology, the post-incident "why is this even here": all of it reads your messages, not your cleverness.
A good commit message says exactly what was done. A great one says why. Your dead ends, failed attempts, and wrong assumptions don't belong in the code. They belong in the message, where the next person (probably you) can find them.
My messages follow a fixed shape, wired into git through commit.template so the scaffold is always in front of me:
<type>(<scope>): <subject>
<body>
<footer>
- type is one of
feat,fix,docs,refactor,perf,test,build,ci,chore,revert. - subject is imperative and fits in 50 characters, no trailing period.
- body explains what and why, wrapped at 72 columns.
- footer carries breaking changes and issue references.
On top of that scaffold, one trailer goes into about nine of every ten messages: Tested:. To fill it, I ask:
- How did I convince myself this change was correct?
- What manual steps did I run? What logs can I attach as proof?
- Is this a visual change? Should I attach screenshots or a video?
- Did I write any throwaway scripts worth keeping?
- What did I deliberately not test, and why was that safe?
I've even gotten into the habit of writing Tested: nope, to show I thought about it and chose to skip it.
Your future self (and your agents) will thank you.
Presubmits
You know the ones. The automated gates that run before your change is allowed anywhere near main:
- Linters and formatters (run in check mode, so CI fails on unformatted code)
- Static analyzers and type checkers
- Unit and integration tests
- Build and compile checks
- Code-coverage thresholds
- Security and dependency scans (SAST, vulnerable-package checks)
- License and policy checks
Run all of them locally before you send for review, not after. The fastest review is the one where the machine has already caught everything a machine can catch, so your reviewer spends their attention on what only a human can see. Wire them into a pre-commit hook and you never have to remember.
The readover, the commit, and the presubmits live as one tickable list in The Commit Checklist.
Code Review
You did your own readover so your reviewer wouldn't have to be the first. Now they're the second, and this is where the change stops being yours alone.
A reviewer is not a linter with a pulse. The machine already caught the formatting, the dead code, the failing test. What's left is the part only another person can see: that this is the third time someone reinvented this helper, that the team agreed last month not to add config flags here, that the edge case you waved off is the one that paged them in March. They carry the context you can't, because you've been staring at this for two days and they haven't.
This is also the stage where AI quietly changed who I argue with. An agent will defend its code, cite the diff, and revise on command, but it has no standing. It can't approve the change, because approval is accountability: a second name that says this should land. When a reviewer's comment and the agent's instinct disagree, the agent doesn't get a vote. You read the comment, decide who's right, and own the call either way. Treat the thread as a conversation, not a checklist: push back when you're right, concede fast when you're not.
Deployment
Just because the code has shipped, that doesn't mean it's landed. Merged is not deployed, deployed is not in front of users, and in front of users is not the same as working.
How the change reaches production is its own discipline. Roll out behind a flag, so you can turn it off without another deploy. Ship to a canary first, a thin slice of traffic, and watch it before you widen. Stage the rollout, 1% then 10% then everyone, with a metric you are watching at each step and a threshold that triggers a rollback.
Then actually look. Have you exercised the feature in production yourself, with a real account, not just in staging? Are the dashboards and alerts that would tell you it broke wired up before you need them? What is the feedback from your users, your clients, your peers?
This is another stage where AI helps least. It will happily write the rollout config and the alert rules. It cannot tell you the latency graph looks wrong, or that a customer has gone quiet.
Maintenance
Most code spends almost none of its life being written and almost all of it being maintained. The change you are proud of today is the one someone debugs at 2am next year. Possibly you.
Maintenance is the long tail: bug reports, dependency bumps, the migration when the API you built on gets deprecated, the page at 3am when your feature meets traffic you did not predict. When it breaks, stop the bleeding first (roll back, mitigate) and understand it second (root cause, a test that would have caught it, a postmortem that blames the system and not the person).
AI is a real help here, more than I expected. Point an agent at a stack trace, the relevant logs, and the commit that introduced the regression, and it is good at the narrow, well-scoped question: what changed, and why does it fail. The messages you wrote back in the commit stage are what make that answer possible. Your index, read back to you.
The Human Parts
The pattern repeats at every stage. The agent takes the toil: the boilerplate, the failing test, the rollout config, the first draft of almost everything. What stays with you is the judgment: whether the requirement is the right one, whether the design survives contact, whether the diff reads clean, whether the latency graph looks wrong. The tools improved every month I worked this way, and the better they got, the more what was left was the part only a person could do.
I write less code than I have in years, and I have never been more on the hook for it. When it breaks at 2am, the agent that wrote it won't be the one paged. I will.



