I Committed 518,000 Lines in 30 Days. Production Kept 235,000.
Last week I did something I almost never bother to do: I counted my own commits. Not the ones I remember writing — the ones the repositories actually recorded. Five production repos on my self-hosted Gitea, thirty days, one person: 865 commits, 518,233 lines added, 130,892 deleted.
Then I counted what survived. A clean scan of the current revision of my main production application returns 234,976 lines. The video transcoder that gained 58,240 lines in the same window is 43,050 lines long today.
So where did the other 280,000 lines go? Nothing was lost. They were written, run, judged wrong, and deleted — or replaced by something smaller that did the same job. That loop, not the writing, is what I want to write down, because it changed how I work this year more than any model update did.
The numbers I didn’t believe
Here is the exact commit breakdown across the five repositories, between 17 September and 17 October 2026:
| Repository | Commits (30d) | Additions (+) | Deletions (-) | Net Change | Current Total LOC | Primary Stack |
|---|---|---|---|---|---|---|
| numerology-report | 432 | +331,672 | -98,937 | +232,735 | 234,976 | PHP, Markdown, JSON |
| hevc-transcode | 187 | +58,240 | -15,508 | +42,732 | 43,050 | Python, HTML, Shell |
| dsm-resource-management | 132 | +58,234 | -14,076 | +44,158 | 368,793 | Python, TypeScript, Astro |
| hoelee-blog | 88 | +23,603 | -709 | +22,894 | 55,933 | Astro, TypeScript, Markdown |
| hoelee-agent-dist | 26 | +46,484 | -1,662 | +44,822 | (Dist) | Python, Shell, Markdown |
| Total | 865 | +518,233 | -130,892 | +387,341 | — | — |
Two lines are easy to check yourself; this is the whole measurement:
git -C numerology-report log --since=2026-09-17 --until=2026-10-17 \
--numstat --pretty=format: | awk '{add+=$1; del+=$2} END {print add, del}'
In a human team, churn like that usually means trouble: requirements moving under the code, a rewrite nobody admits to, an architecture that keeps collapsing. None of those applied here. Every one of these five repos is something I run and depend on, and every commit was authored in a session where I could read the diff before it landed.
The churn is what refactoring looks like when generation is cheap. An agent drafts a slice of the change, I run the gates, the gates disagree, and the slice gets thrown away or rewritten smaller. On the days I got the gates right, the size drop came faster than the features arrived.
What the agents actually touch
Most people’s first experience of AI code is a browser tab: paste a function into a chat window, copy the answer back into an editor. The bottleneck there is the clipboard. It also keeps the model far away from the two things that matter — the codebase’s own conventions and the ability to run what it wrote.
So mine run in a terminal, on the machine, with real tools:
- Filesystem and syntax tools. They read existing modules, patch by fuzzy match instead of overwriting whole files, and run syntax or AST checks before anything is saved.
- Test and execution runners. PHPUnit suites, pytest harnesses, and headless Chrome probes, executed in the foreground with the output read back.
- Version control. They read
git diff, stage explicit paths, and look at the history of a file to see why the previous decision was made.
That combination is fast — an order of magnitude faster than me typing the first draft. It is also, on its own, a way to make a codebase rot faster, which is the part nobody warns you about.
Where the bloat comes from
Unsupervised models love to write code. Give one a small edge case and its instinct is to add a private helper class, clone an existing utility with one extra parameter, or wrap a library in three layers of abstraction that only make sense to the diff that introduced them. Left alone for a week, that is a few thousand lines of confident, plausible, unnecessary code.
I know because I have that week in my own history. Two things came out of reading these diffs every day:
- A new class has to earn its place against a standard-library call I already have.
- Anything generated is guilty until a deterministic check says otherwise. “Looks correct” from a model is not evidence; a red test is.
The gates that actually deleted the code
During the V2 architecture overhaul of my main production application, more than 98,000 lines went out in thirty days. Almost none of that was me sitting down to delete things. It went because three gates kept rejecting what came back:
1. Deterministic harnesses instead of “does this look right?”
Asking a model to review its own code mostly produces confidence, not correctness. The useful move is to wire the work to something that cannot be talked out of failing:
- In the PHP application, every public route parameter has to carry an
explicit default, enforced by a reflection probe
(
ControllerAutoRouteSafetyTest.php) — that test exists because a bare route crashed with a 500 in production. - In the report pipeline, a headless PDF extractor counts the rendered Chinese characters before a build is allowed to pass, so a font or layout change that silently drops glyphs fails the build instead of shipping a broken PDF.
- In the transcoder, the ledger compares exact byte counts rather than approximating in megabytes, because rounded numbers hid a size regression for weeks.
If a gate goes red, the agent either fixes the change or drops it. Nothing reaching a commit is exempt from that.
2. Pruning as a standing task, not a cleanup project
A portion of every session goes to deleting. Can a 200-line helper be
replaced by a ten-line call into something that already exists? Is this new
mapper duplicating a function that has lived in app/Libraries/ for a year?
The 130,000 deletions above are not an accident of a rewrite; they are
scaffolding that outlived its purpose, migration layers with one caller, and
logic that arrived twice under two names.
3. Keeping deterministic work deterministic
The trap I keep seeing in AI-assisted projects is a model being asked to do arithmetic it should never touch. If a shell pipeline, a SQL constraint, an idempotent script, or a cron job can answer it, an LLM has no business there.
Models are good at drafting an initial implementation, generating a three-language dictionary, or mapping a third-party API’s quirks. They are not where I put state transitions, money, or queue scheduling, because a probabilistic answer to a deterministic question is a bug with a delay fuse.
What changed in how I work
For most of my career the physical constraint was typing speed and remembering syntax. Output was the metric, and a productive week looked like a lot of new files.
Syntax is free now. I can get a 500-line migration with tests drafted while I read the diff of the previous one. So the constraint moved to the three things a model cannot check for me:
- Boundary design — where components separate, and which state is allowed to cross.
- Test architecture — the harness that catches semantic drift the type checker never sees.
- Pruning discipline — the willingness to delete 280,000 lines of exploratory, redundant, or dead code so 235,000 lines can go to production.
Which is why I no longer read a commit graph as a productivity chart. Half a million added lines in a month tells me the tools were running. The 130,000 deleted lines, the refactor underneath them, and a suite that is still green tell me whether the month was worth anything.
Building something that has to stay up?
I run Hoelee Enterprise, a website design and development studio for Malaysian SMEs. The agent workflow, the test harnesses and the self-hosted infrastructure in this post are the same setup I build and maintain client systems on.
If you need full-stack engineering, workflow automation, or infrastructure that survives its own maintenance:
- WhatsApp: +60 12-797 2969
- Email: [email protected]
- Website: hoelee.com