You are handed a system you did not build, and you have days rather than months. The instinct is to start at the entry point and read forward. That is close to the worst available strategy, because it tells you what the code does and almost nothing about what the product is.
Here is what I look at instead, roughly in order, and why each one pays.
Migration history is the build order
Before reading a single route, read the schema's migration history end to end. Not the current schema. The sequence that produced it. Migrations are the only artifact in most repositories that is honestly chronological. Code gets refactored until its history is unreadable; migrations accumulate, because nobody dares rewrite them.
What you get is the product's actual autobiography. The first few migrations tell you what the founders thought they were building. The place where a table suddenly sprouts five nullable columns in one week tells you where a customer pushed back hard. A join table added late tells you that a relationship the team originally modeled as one-to-many turned out not to be. A column named status that later grows a sibling named state tells you two people owned that domain at different times and never reconciled.
Read that sequence and you can usually reconstruct the roadmap, the pivots, and roughly when the team stopped being able to change their mind cheaply. That is context no amount of reading the current code will give you, and it takes an hour.
Uniformity is a tell
Human-written code is uneven. Different authors handle errors differently, name things differently, get bored at different points. That unevenness is information: it marks the seams between people, teams, and eras.
So when error handling is perfectly uniform across a hundred endpoints, with the same wrapper, the same log line, the same shape of response and no drift at all, something other than a hundred human decisions produced it. Same signal when route scaffolding is identical file to file: the same imports in the same order, the same validation block, the same comment structure, varying only in the nouns.
Perfect consistency across a large surface is not evidence of discipline. It is evidence of generation.
This matters practically, not as trivia. Machine-authored scaffolding is cheap to change and rarely load-bearing. You can rewrite a hundred uniform handlers in an afternoon. It also means the volume of the codebase badly overstates the effort behind it, which is exactly the mistake that produces a wrong estimate.
Intellectual property concentrates
Sort every source file by length and look at the top of the list. In nearly every system I have read, the actual proprietary thinking lives in three to six oversized files (a scoring module, a pricing engine, a matching algorithm, a reconciliation routine) surrounded by a large, unremarkable perimeter of endpoints, DTOs, and glue.
Those files are ugly. They have long functions, unfashionable branching, and comments arguing with themselves. That ugliness is the point: they are where the domain refused to be tidy, and where someone made real decisions that no framework was going to make for them. Everything around them is the cost of exposing those decisions over HTTP.
The practical consequence: if you are estimating a rebuild, the hundred endpoints are not the work. Those few files are the work, and they will not compress.
What compresses and what doesn't
The most useful thing I have learned building alongside AI tooling is that the compression is real but wildly uneven, and that averaging it is how estimates go wrong.
Some categories nearly collapse. Governance and boilerplate (auth scaffolding, audit logging, role checks, migrations, seed harnesses) go from weeks to hours, because the correct answer is well known and the work was always transcription. Presentation collapses too. Styling, layout, responsive behavior, empty states, the long tail of interface polish: these have enormous specification-to-effort ratios and AI assistance eats them.
Other categories barely move. Judgment-heavy logic, meaning anything where the difficulty is deciding what the rule should be rather than expressing it, compresses hardly at all, because the bottleneck was never typing. Synthetic data pipelines are the sharpest example I know. Generating plausible records is trivial; generating records whose signal is discriminating enough to prove a system works is genuinely hard, and no amount of tooling shortens the loop of generate, evaluate, discover everything moves together, and redesign the underlying distributions.
The failure mode is to observe the governance layer collapsing by an order of magnitude and apply that ratio to the whole project. The right approach is to segment the estimate: price the collapsible parts near their new cost, and price the judgment-heavy parts near their old one. A blended multiplier will be badly wrong in both directions at once.
The habit underneath all of it
Every one of these reads the same way: look for the artifact that had no reason to lie. Migrations were not written to impress anyone. File length was not a design decision. Uniformity was not intended as a signal. That is exactly why they are trustworthy. They are byproducts, and byproducts are honest in a way that documentation, comments, and architecture diagrams are not.
Read the byproducts first. Then read the code, and it will already make sense.