Fix Immediately
A failing test after a code change means the change introduced a regression. Fix it now, while the context is fresh. The cost of fixing goes up exponentially with time.
Large migrations fail when teams batch too many changes together. A 2,000-line commit that refactors three services, updates the database schema, and changes the API surface is nearly impossible to review, difficult to roll back, and dangerous to deploy. If something breaks, the blast radius is the entire batch.
Incremental development is the antidote. It is not a velocity technique — it is a risk management discipline.
Every productive modernization session follows the same cycle:
1. Make one small change (one logical unit)2. Verify: tests pass, build succeeds, lint clean3. Commit with a clear message4. RepeatThis rhythm applies whether you are working alone, in a team, or with AI coding agents. The cycle length is minutes, not hours. If more than 30 minutes pass without a commit, the batch is too large.
Modernization amplifies the cost of mistakes:
| Risk Factor | In Greenfield | During Modernization |
|---|---|---|
| Broken build | Blocks new features | Blocks the running production system |
| Failed test | Feature does not work | Parity with legacy is broken |
| Bad deploy | Roll back to previous version | Roll back may require database migration reversal |
| Unclear commit | Annoying to trace | Critical context lost for debugging legacy interactions |
Small changes reduce all four risks. Each commit is a safe rollback point. Each verified step proves that the system still works.
Verification is not optional and not “when convenient.” It happens after every code modification, before reporting progress.
| Check | When | What It Catches |
|---|---|---|
| Tests | After every code change | Regressions, broken parity, logic errors |
| Build | After every code change | Import errors, type mismatches, missing dependencies |
| Lint | Before commit | Style drift, unused imports, potential bugs |
| Type check | Before commit | Type errors that tests might miss |
1. Edit payment.service.ts (extract method)2. Run tests → 42 passed3. Run build → success4. Commit: "refactor: extract validatePaymentInput from processPayment"
5. Edit payment.service.ts (add new validation rule)6. Run tests → 41 passed, 1 FAILED7. Fix the failing test8. Run tests → 42 passed9. Commit: "feat: add currency format validation to payment input"1. Edit payment.service.ts2. Edit payment.controller.ts3. Edit database migration4. Edit 3 test files5. "I think this should work"6. Commit: "refactor payment module"7. Push8. CI fails — but which change broke it?When verification fails, the response is immediate — not deferred:
Fix Immediately
A failing test after a code change means the change introduced a regression. Fix it now, while the context is fresh. The cost of fixing goes up exponentially with time.
Do Not Accumulate
Two failing tests are harder to debug than one. Five are nearly impossible. Never proceed to the next change while tests are red.
Do Not Skip
“I will fix the tests later” is how modernization projects accumulate months of hidden debt. There is no “later” — the next change depends on the current one being verified.
| Ideal | Acceptable | Too Large |
|---|---|---|
| Under 200 lines per commit | 200-500 lines per commit | Over 500 lines per commit |
Each commit should have a single, clear purpose and be independently deployable.
| Good Commit Scope | Bad Commit Scope |
|---|---|
| Extract one method | Refactor entire module |
| Add one validation rule | Add validation + change API + update schema |
| Fix one bug with regression test | Fix three bugs in one pass |
| Add one database migration | Migration + service changes + route changes |
| Rename a concept consistently | Rename + restructure + add features |
If a commit message needs “and” to describe what it does, it should probably be two commits.
Long-lived branches are particularly dangerous during modernization because the codebase is actively changing. A feature branch that lives for two weeks will diverge significantly from main, and the merge will be painful.
| Practice | Guideline |
|---|---|
| Feature branches | Max 2-3 days before merging |
| Direct commits | Acceptable for small, safe changes |
| Release branches | Avoid — use feature flags instead |
| Develop branch | Avoid — adds merge overhead without benefit |
| Main branch | Always deployable, always green |
For migrations that take more than a few days, use feature flags instead of long-lived branches:
// Feature flag controls which implementation runsif (featureFlags.isEnabled('modern-invoice-renderer')) { return modernRenderer.generate(invoice);} else { return legacyRenderer.generate(invoice);}This approach keeps all code on main, enables gradual rollout, and makes rollback instant (flip the flag). It also allows parity testing in production with real traffic.
AI coding agents follow the same incremental rhythm. When an agent makes changes, verification should happen before reporting results:
Agent receives task | vMake change -> Run tests -> Tests pass? | Yes: Commit, report success No: Fix, re-run, then reportStructure agent instructions to enforce this cycle. Include test commands in your project’s AGENTS.md so agents know how to verify their work. See Structuring Context for AI Agents for details.
| Metric | Healthy | Warning |
|---|---|---|
| Average commit size | Under 200 LOC | Over 500 LOC |
| Commits per day (per developer) | 3-8 | Under 2 or over 15 |
| Time between commits | 15-60 minutes | Over 2 hours |
| Revert frequency | Under 5% | Over 10% |
| Main branch red duration | Under 15 minutes | Over 1 hour |
These metrics are indicators, not targets. Gaming them (splitting a logical change into 10 meaningless commits) defeats the purpose. The goal is a sustainable rhythm of verified, meaningful progress.
Every commit should be production-ready. No exceptions. This discipline is especially important during modernization, where a single broken migration can cascade through the system.
The question is not “can I ship this?” but “if this were deployed right now, would the system work correctly?” If the answer is not a confident yes, the commit is not ready.