All articles

The Playbook for Documenting Legacy Codebases with AI

Most legacy systems die not from bugs but from forgotten context. Here is the playbook engineers use to make AI document what nobody bothered to write down.

D

DocPilot Team

AI documentation research and engineering from Onzira Labs.

10 min read

The Playbook for Documenting Legacy Codebases with AI

Every legacy codebase carries an unspoken tax. The original authors are gone, the tribal knowledge evaporated, and the remaining team spends 40-60% of their time just figuring out what the code does before they can change it safely. According to the often-cited Stripe Developer Report, this kind of "developer drag" costs the industry hundreds of billions annually. The irony is that the documentation which would have prevented this tax was always the cheapest thing to produce and the most expensive thing to recreate.

AI documentation tools have changed the economics. What used to require a senior engineer spending three weeks reverse-engineering a payment module can now be sketched in an afternoon and refined over a week. But the tools only work when you point them at the right code, with the right framing, and verify the output with the right instincts. This playbook is the workflow that actually moves the needle when documenting legacy systems with AI.


What "Legacy" Actually Means in 2025

Legacy code is not the same thing as old code. A 15-year-old Java service with thorough docs and consistent patterns is a mature system. Legacy is defined by the gap between the system's current state and the team's current understanding of it. That gap has three common shapes:

  • Tribal codebases — written by a small group, never externalized, where one engineer carries the mental model in their head.
  • Acquired codebases — inherited through a deal, often in a domain the new team does not own.
  • Drifted codebases — once well-documented, but the docs were never updated and the code evolved past them.

All three are the same problem at heart: the documentation is missing, wrong, or both. AI fits naturally here because its core capability — reading code and producing structured natural language — is exactly the missing piece.


Why Legacy Documentation Has Historically Failed

Before going further, it helps to be honest about why past attempts at documentation died. The reasons are structural, not laziness.

Failure ModeRoot Cause
Docs written once, never updatedDocumentation was decoupled from code review and CI
Docs too abstract to be usefulAuthor wrote for stakeholders, not for the next maintainer
Docs too detailed to maintainEvery method got a Javadoc paragraph; nobody could keep up
Docs lost institutional contextThe "why" was never captured, only the "what"
Docs in the wrong placeConfluence pages nobody can find, READMEs that lag by six months

AI documentation tools fail in the same ways when they are used as a fire-and-forget generator. The playbook below treats documentation as a living artifact that is regenerated, reviewed, and versioned alongside the code.


The Four-Phase Playbook

Phase 1: Inventory and Triage

You cannot document a 500k-line monorepo in one pass. The first job is to triage.

Generate a module-level dependency graph. Most languages have a tool for this: mvn dependency:tree, cargo tree, go mod graph, tsc --traceResolution, pydeps for Python. Feed that graph to a tool that can summarize what the modules do at a glance — this is where AI shines, because reading 200 files to produce a one-paragraph module summary is exactly the kind of work that wastes a senior engineer's afternoon.

Rank modules by three signals:

  1. Change frequency — git log --since="6 months ago" --pretty=format: --name-only | sort | uniq -c | sort -rn. The files touched most often are where new developers will stumble.
  2. Bug density — modules with the most recent fixes and the oldest open issues.
  3. Onboarding friction — ask three engineers which modules they dread touching. The overlap is your priority list.

Document the top 20% first. This is where the 80/20 rule actually holds for documentation work.

Phase 2: Capture Context Before Code

This is the step most teams skip and later regret. Before pointing an AI at a module, interview the people who know it.

Run a 30-minute session with the module's owner. Ask three questions:

  1. What does this module do that is not obvious from reading it?
  2. What decisions were made here that you would defend in a code review?
  3. What is the next bug that will land here, and where?

Record the answers as raw notes. Do not transcribe them into prose — the prose is the AI's job, the context is yours. The notes become the "source material" that you feed alongside the code when generating documentation. Without them, AI will produce technically accurate but institutionally empty documentation. With them, you get the "why" that no model can infer from git log.

Store these notes in a structured location — docs/context/<module>.md works fine. They are the seeds the documentation grows from.

Phase 3: Generate, Then Verify

Now the actual AI work. The most important rule: AI documentation is a draft, not a deliverable.

The generation step itself should follow a layering approach. For each module, produce:

1. README — what is this, who owns it, how do I run it
2. ARCHITECTURE — how the pieces fit together, data flow
3. PUBLIC API — every exported function with parameters, return types, side effects
4. INVARIANTS — what must always be true (transactions, ordering, auth)
5. KNOWN QUIRKS — historical bugs, weird workarounds, "do not refactor this"

A good prompt to an AI agent for this looks roughly like:

You are documenting a legacy module in <language>. The module path is
<path>. Its primary maintainer noted the following context: <context>.
Generate:
1. A README under 400 words with: purpose, owners, entry points,
   runtime requirements, common failure modes.
2. An ARCHITECTURE.md describing the request lifecycle and the
   internal boundaries between submodules.
3. A REFERENCE.md listing every public function with type signatures,
   side effects, and the exceptions thrown.
4. A QUIRKS.md describing anything that looks wrong but is intentional,
   with the date and ticket reference if known.

Do not invent behavior you cannot verify by reading the code. When
unsure, say "UNVERIFIED" rather than guessing.

The verification step is non-negotiable. The senior engineer who knows the module must spend 30-60 minutes reading the AI output and marking every claim as correct, wrong, or "UNVERIFIED but plausible". This is faster than writing from scratch because the AI has done the mechanical work of pulling signatures, file paths, and call graphs.

Phase 4: Wire Docs into the Workflow

Documentation that lives outside the development loop will rot. The fourth phase is structural work to keep them fresh:

  • CI gate for staleness — if a module's source changes in a PR, the corresponding doc must be regenerated or the build fails.
  • PR template — every PR template should ask: "Did documentation need to change?" with a checkbox.
  • Doc review in code review — when a reviewer approves a code change, they also confirm the AI-generated docs reflect the new behavior. A diff of docs/ should be part of every review.
  • Quarterly audit — pick 10 modules at random, regenerate from scratch, diff against the existing docs. Any unaccounted-for delta is a bug in the docs or the code.

Tools like DocPilot, which connect to GitHub and regenerate docs from code via webhooks, automate the first two. The quarterly audit is still a human job.


A Concrete Example: Documenting a Payment Retry Module

To make this less abstract, consider a real-shaped example. Imagine a PaymentRetryService in a Rails app, written in 2019, that handles failed payment retries with exponential backoff. It has 1,200 lines, no tests, and a README that says "see code".

Step 1 — Inventory. git log shows this file gets touched during every payment incident. Onboarding friction: new backend engineers consistently ask about it during their first month. It is in the top 20%.

Step 2 — Context. A 30-minute call with the original author reveals:

  • Retries are capped at 7 because Stripe's idempotency window expires after that.
  • The "random jitter" is not random; it is deterministic per customer to avoid thundering herd in B2B billing.
  • There is a known race condition with webhooks that has been "fixed" three times by retrying the whole job.

Step 3 — Generation. The AI produces a ARCHITECTURE.md showing the retry loop, a REFERENCE.md listing every public method, and a QUIRKS.md noting the idempotency cap, the deterministic jitter, and the webhook race. The maintainer marks one entry as wrong (the AI thought the jitter was cryptographically random) and corrects it.

Step 4 — Workflow. A GitHub Action now regenerates these docs on every PR touching app/services/payment_retry_service.rb. The diff shows up in the PR review.

The total cost: roughly 4 hours of human time spread over a week. The payoff: a new engineer can work on this module safely on day one instead of week three.


Refactoring and Documentation: A Pair You Cannot Separate

Refactoring without documentation is a guessing game. Documentation without refactoring is wishful thinking. The two belong together, and AI makes both cheaper.

A useful pattern is document-then-refactor:

  1. Generate the AI documentation for the module as-is.
  2. Review the documentation. Disagreements between the AI's understanding and the maintainer's are exactly the spots where behavior is non-obvious — and therefore where refactoring is most needed.
  3. Refactor with the documentation as the spec. If the new code does not match the documented behavior, either the docs or the code is wrong; pick one and fix.
  4. Regenerate. The AI should now produce simpler, clearer docs because the underlying code is simpler and clearer.

This is the virtuous cycle: clearer code produces clearer docs, which forces clearer code. The opposite — undocumented code, refactored without spec — produces the worst kind of legacy: code that looks clean but nobody understands.

A second pattern is test-extraction. While the AI is reading the module, also use it to extract a list of behaviors that look testable. Treat that list as a starting point for characterization tests. A module with both documentation and characterization tests is essentially immune to the kind of silent regression that haunts legacy work.


Common Pitfalls

A few failure modes worth naming explicitly:

  • Hallucinated APIs — AI models will sometimes invent a method that does not exist. Always cross-reference the generated reference against grep or your IDE's symbol index.
  • Plausible-but-wrong invariants — concurrency claims, transaction boundaries, and "this is always called within a lock" statements are the most common lies. They are exactly the kind of thing humans also assume incorrectly, so verify them.
  • Documentation that flatters the code — AI models tend to describe code as "clean" and "well-structured" even when it is neither. This is more dangerous than obvious errors because it goes unchallenged.
  • Treating it as a one-time project — the docs will rot the moment someone ships a PR without the regeneration hook. Make staleness a build failure.
  • Skipping the context step — without the maintainer's notes, you get technically correct, contextually misleading documentation. The most expensive kind, because it is harder to catch.

Measuring Whether the Playbook Worked

Documentation work is famously hard to justify because its absence is invisible. A few metrics that actually correlate with downstream outcomes:

  • Time-to-first-PR for new engineers on documented modules vs. undocumented ones. A drop of 30-50% is realistic.
  • Mean time to understand (MTTU) during incident response — track how long it takes to find the answer to "why does this work this way" during a real incident.
  • Documentation PR coverage — the percentage of code PRs that include a docs change. Target 60-80% for actively maintained systems.
  • Drift detection — automated diff between generated docs and regenerated docs should be empty. Any non-empty diff is either a code change that was not documented or a doc that needs updating.

None of these are perfect. They are all easier to defend than "we spent 6 weeks on docs and feel better about the codebase."


A Two-Week Starting Plan

If you want to start next week, here is a concrete schedule:

Week 1

  • Day 1: Run the dependency graph and change-frequency analysis.
  • Day 2: Pick the top 3 modules by the priority score. Schedule 30-minute context interviews.
  • Day 3: Capture interview notes. Generate first-pass docs with AI.
  • Day 4-5: Review, correct, commit. Set up the regeneration hook in CI.

Week 2

  • Day 1-2: Expand to the next 5 modules.
  • Day 3: Add the PR-template checkbox and the staleness gate.
  • Day 4: Run the quarterly audit against the first 3 modules to validate the workflow.
  • Day 5: Write a one-page team playbook so the process survives beyond you.

By the end of two weeks you will have documented roughly a third of the modules that matter, with a system that keeps them current automatically. The remaining two-thirds will follow at the same pace.


Closing Thought

Legacy code is not a punishment. It is the accumulated decisions of people who were solving real problems under real constraints. The job of documentation is not to apologize for those decisions but to make them legible to the next person. AI does not replace the judgment required to do that well, but it removes the mechanical work that used to make it uneconomic.

If you are staring at a codebase where nobody dares touch the payments module and the only person who understands the auth flow is on parental leave, the playbook above is the one that will actually move you forward. Start with three modules, capture the context, generate, verify, wire it in. The cycle compounds.

DocPilot was built for exactly this workflow. It connects to a GitHub repo, reads the code, generates READMEs, architecture docs, and API references, and regenerates them on every push via webhooks — so the documentation you produce in this playbook never drifts from the code it describes. The free tier covers small repos, and the paid tier handles monorepos and private installations. If you are about to spend a quarter untangling a legacy system, it is worth a weekend to see how much of that quarter you can give back.

Ship documentation that keeps up

Try DocPilot on your repository — free for one repo.

Connect your first repository in about a minute. No credit card. DocPilot writes and maintains the documentation from your code.

Read next