In one real release of a widely-used project, 80.1% of all measured churn came from a single generated file. A manager reading that project’s churn figure was reading, four parts in five, the output of a dependency resolver. This paper defines the distinction, measures it on public code, and shows what a churn number means once the machine-written share is separated out.
Churn — how much code changed between two points — is the number managers use to answer questions that matter: where is the risk concentrated, what is being rewritten, is anything moving that should not be. That number is unreliable in a specific and correctable way: modern repositories commit large volumes of machine-generated content — dependency lockfiles, minified bundles, code-generator output — whose churn is counted identically to hand-written work. We measured the public npm/cli release v10.9.0 → v11.0.0: 6,134 logical statements churned, of which 4,916 (80.1%) came from package-lock.json alone — a file no developer edits. TRUE_CHURN, the remainder, was 1,218 statements. Both figures are correct; only one answers a manager’s question. CodeDelta classifies generated content deterministically, subtotals it, and reports authored churn beside the total — nothing is excluded, and every classified file is named with the rule that matched it.
If you ask “how much did this release change?” and the answer is six thousand statements, you might reasonably conclude a great deal happened. In this release, five thousand of those came from one file that a tool rewrote automatically when somebody upgraded a dependency. The developers wrote about twelve hundred. Both numbers are true. Only the second tells you what the team did.
A modern repository is not only source code. It carries content that is derived — produced mechanically from something else, and regenerated rather than edited:
package-lock.json, yarn.lock, Cargo.lock, go.sum, composer.lock. A one-line version bump can rewrite thousands of lines.*.min.js, source maps: the same code, mechanically transformed.@generated, DO NOT EDIT, Code generated by ….All of it is legitimately in the repository. None of it is authored in the sense a manager means when asking what changed. Counted together with real work, it does not merely add noise — at realistic volumes it dominates, and the resulting figure moves with dependency upgrades rather than with development.
A real release of a real project, chosen because it is public, reproducible, and commits its lockfile — as most JavaScript projects do. Both trees are the published release tarballs; the command and hashes are in §05.
| Measure | Statements | Share of total |
|---|---|---|
| Total churn (CRN_LLOC) | 6,134 | 100% |
| Generated churn | 4,916 | 80.1% |
| TRUE_CHURN (authored) | 1,218 | 19.9% |
Per file, the concentration is starker still. 145 files changed in this release; here are the largest by churn:
| File | Churn | Share | Authored? |
|---|---|---|---|
package-lock.json | 4,916 | 80.1% | generated — npm lockfile |
CHANGELOG.md | 861 | 14.0% | authored (release notes) |
workspaces/libnpmhook/README.md | 204 | 3.3% | authored |
workspaces/libnpmhook/CHANGELOG.md | 132 | 2.2% | authored |
.github/workflows/ci-libnpmhook.yml | 116 | 1.9% | authored |
workspaces/arborist/test/arborist/build-ideal-tree.js | 115 | 1.9% | authored (test) |
One file outweighs the other 144 combined by roughly four to one. It is the file least likely to interest anyone asking what the team changed, and it is the file that dominates every unfiltered churn metric, chart and trend line drawn from this repository.
The scan also found 100 generated files in the tree overall — 96 lockfiles across the workspaces plus four carrying DO NOT EDIT markers — totalling 98,715 logical statements of generated content sitting alongside the source.
Rogue development — unreviewed work, changes outside the plan, activity in a component nobody was meant to touch — shows up as churn where churn was not expected. If four fifths of the signal is a dependency resolver, the unexpected activity is a small perturbation on a large noisy baseline. Worse, the noise moves: a routine npm update produces a churn spike indistinguishable in magnitude from a substantial piece of unplanned work.
This is a per-file question, and per-file is exactly where the distortion is worst. A safety-relevant module with 40 statements of careful change is invisible next to a lockfile with 4,916. Ranked lists of “most-changed files” — a standard risk-review artefact — are led by files nobody wrote.
Process signatures such as REWORK (the share of churn that edits existing statements in place) are ratios. Diluting the denominator with generated content compresses every such ratio toward the behaviour of the generator, not the team.
Classification is deterministic — no inference, no scoring — and runs in three passes over each file:
package-lock.json, yarn.lock, pnpm-lock.yaml, Cargo.lock, composer.lock, Gemfile.lock, poetry.lock, uv.lock, go.sum, packages.lock.json, flake.lock..min.js, .min.css, source maps, .pb.go / .pb.cc / _pb2.py and the protobuf family, .g.cs, .freezed.dart, analysis-tool output.@generated, DO NOT EDIT, Code generated by, automatically generated. This is the file declaring itself, in a machine-readable way, and it catches generators no list could enumerate.Teams add their own rules through codedelta_agents.json without waiting for a release.
Generated churn stays inside every existing total. CRN_LLOC, CHG/DEL/ADD, REWORK and the longitudinal database are computed exactly as before, so historical comparisons remain valid and no published figure moves. TRUE_CHURN is a derived companion:
TRUE_CHURN = CRN_LLOC − CRN_GEN_LLOC
Generated churn is not a fourth churn class. A generated file’s statements are classified CHG / DEL / ADD like any other file’s, inside the corresponding headline columns, and subtotalled per component. From the npm/cli measurement:
| CHG | DEL | ADD | CRN | |
|---|---|---|---|---|
| Totals (headline) | 1,242 | 2,242 | 2,650 | 6,134 |
| of which generated | 776 | 1,745 | 2,395 | 4,916 |
| authored = difference (TRUE_CHURN) | 466 | 497 | 255 | 1,218 |
Every classified file is reported by name with the rule that matched it, in the report’s coverage block, the CSV GEN row, the console and a GUI tile. A reader can therefore check the classification rather than trust it — and disagree with it on the evidence.
Two public release tarballs, one command:
curl -L https://codeload.github.com/npm/cli/tar.gz/refs/tags/v10.9.0 | tar xz
curl -L https://codeload.github.com/npm/cli/tar.gz/refs/tags/v11.0.0 | tar xz
codedelta cli-10.9.0 cli-11.0.0 -o report.html --csv metrics.csv
The console prints the split directly:
CHG_LLOC: 1242 DEL_LLOC: 2242 ADD_LLOC: 2650 CHURN: 6134
TRUE_CHURN: 1218 (CHURN minus generated churn)
Generated files: 100 (generator marker (DO NOT EDIT) 4, npm lockfile 96) - 98715 LLOC
Generated churn (subtotal): 776 CHG | 1745 DEL | 2395 ADD | 4916 CRN
-> authored churn: 1218 of 6134 CRN_LLOC
Measured with CodeDelta engine 1.9.1 (build 35) on the unmodified published tarballs. The unit is the logical statement (LLOC), not the physical line — see the companion paper on why that distinction matters before any of this does. Exact, repeatable diff arithmetic: no sampling, no machine learning in the measurement.