I built a tool to measure everything I have ever written. It started by telling me I was wrong.
155 projects across 4 drives — games, AI services, data tools, experiments, half-finished ideas. I wanted one honest answer to "what have I built, and what is it worth?" The first count said one project held 27.5 million lines. It held about 95,000. Everything below grew from refusing to accept a number I could not check.
01Why I built it
I write a lot of software, much of it with AI coding agents alongside me, and it lives in a lot of places. After a while I could no longer answer simple questions: have I built something like this before? Which port is free? Which of these three folders is the real one? And the question that started it — if someone asked what I have built, could I show them anything better than a list of folder names?
So I built an atlas: a scanner that reads every project, a set of analyses on top, and a nightly job that keeps it all current. The twist was that every time I pointed it at my own work, it found a number that was wrong — usually because the obvious way to measure something measures something else. The tool became less about counting and more about not lying.
02How it grew
254 commits since 2026-08-17. The suite grew from 744 tests in the first week to 1,416 today, and every one of them pins a defect that actually shipped.
- 2026-08-17The scannerevery project on every drive, measured, not guessed
- 2026-08-17Generated docsfacts and judgement in different files
- 2026-08-19A page that checks itselfbuilt is not the same as right
- 2026-08-19The consoleevery tool behind one local page
- 2026-08-19Grades you can argue withevery score decomposes into its evidence
- 2026-08-23Credentials in historydeleting a secret does not remove it
- 2026-08-23Context packseverything known about a project, for an agent
- 2026-08-25The reference graphwhat breaks if I change this file
- 2026-08-25One walk, three answersthe analyses cannot disagree about files
- 2026-08-28The career storethe resume becomes data
- 2026-08-29Open to AI agentsa read-only MCP server any project can ask
- 2026-10-13The claims registerevery figure carries its evidence
- 2026-10-25Security watchincidents matched against what is installed
- 2026-10-26The vulnerability sweepevery environment, ranked by what runs
- 2026-10-27Measured factsthe resume quotes measurements, not memory
03Counting honestly
A project folder holds four different kinds of thing, and a line counter cannot tell them apart:
Every top-level folder is attributed to one bucket before anything is excluded, and the subtraction is always printed beside the total. Across my estate, 62% of the lines on disk turn out to be real source. When the scanner cannot settle a question it raises a flag rather than guessing, and the answer is written down once, with a reason, so it never has to be asked again.
04Fast, without going stale
Scanning is limited by disk, not by processor — antivirus inspects every file open, so a one-thread scan crawls at about 45 files a second. Sixteen parallel readers fix that. The bigger win is not reading at all: each project is fingerprinted from directory metadata alone, and only projects that moved are opened. A no-change refresh went from 147 seconds to 31.7.
The subtle part is the cache. A stored answer goes stale in two ways: the project changed, or my rule for reading it changed. The second is invisible to any timestamp, so every analysis stores a hash of its own detector definitions, and changing a rule invalidates every answer it ever gave. Nobody has to remember to pass --full.
05One walk, three answers
Three analyses — what depends on what, where complexity and churn pile up, and which code nothing calls — used to read each project separately. They read 61,641 files between them and disagreed about how many files existed. Now one walk feeds all three, which made it a correctness change before it was a speed change: they agree on every project, and a full cold run takes under three minutes instead of about ten.
Languages go through one registry. Python is parsed properly; JavaScript and TypeScript are matched with patterns, and that word — pattern — travels with every number that came from them, because a reader who does not know which they are looking at cannot judge any of it.
06Secrets in the history
Deleting a credential from a file does not remove it from the repository; it is one history command away in every copy. So CodeAtlas reads commit history too. The value never leaves the scanning process — it is redacted at the source and only a file, a line and a kind are kept — and a scan that could not run reports "unknown", never "clean".
07The security sweep
Every Python environment on my machines is read for what is actually installed, not what its manifest claims, and matched against published advisories, the list of vulnerabilities being exploited in the wild, and severity scores. The result is one worklist, ranked by what is running first. Each fix is tested beside the old environment — same tests, same inputs, outputs compared — before anything changes, and recorded in a runbook. The first pass recorded 57 verified fixes and moved six environments to a newer Python so their fixes could land at all.
08Open to AI agents
An AI agent working in one of my projects knows nothing about the other 155 unless something tells it. CodeAtlas does two things about that. A context pack assembles everything already measured about a project — in milliseconds, measuring nothing new — and ends with a ranked "what to do about it". And a small MCP server puts 12 read-only tools in every agent's tool list: search what exists, check which ports are taken, read a project's evidence. It talks over a pipe, not a port, so nothing on the network can reach it.
Every section of a pack says one of three things: found, none, or not measured, and why. Collapsing the last two is how a tool lies to an agent.
09The console
All of it sits behind one local web page. The page never sends a command, only the name of an action, so it cannot be turned into a remote shell. Any tool that declares what its rows are gets a sortable, keyboard-friendly table for free, with every column explaining what it measured — and an empty table must say whether nothing was found or nothing was measured.
10The nightly refresh
At night everything is re-measured, in dependency order, and the very first step is the whole test suite. If a test fails, nothing downstream runs, so a broken rule can never reach the documents or the portfolio. That makes the suite the single most expensive step, which is exactly where it belongs.
11A resume that checks itself
The same measurements now build my portfolio and resume. Every figure in my career records is registered as a claim with its evidence and who may see it, so a number about my day job never reaches a public page by accident. Figures about my own work are not typed at all — the text says {measured:projects}, and the build fills in today's measurement. My resume used to say "140+ projects, 1,200 tests, six machines". It now says what is true, and it will keep saying what is true.
12Twenty lessons
Each of these is a confident wrong answer the tool once gave me. Most of CodeAtlas is the scar tissue.
0127 million lines, give or take 27 million
One project measured 27,570,349 lines. Its real source was about 95,000: the rest was generated HTML and notebook outputs that happened to look like code.
Now: nothing is excluded before it is attributed, and every exclusion carries a written reason.
02Notebooks are mostly pictures
Twenty-five notebooks counted 1,075,236 lines and held 7,746 lines of code — a 139× inflation from images stored inline as text.
Now: only the source of code cells is counted.
03One lowercase, 303 missing files
Lowercasing the exclusion list turned "skip the virtualenv's Scripts folder" into "skip every scripts folder". It hid 303 code files across 27 projects, and one project reported containing no Python at all.
Now: a short, explicitly case-sensitive list, with the reason written beside it.
04The fix that looked like a fix
Recovering include/ folders the same way pulled in 694,189 lines of a vendored GPU library, and one project's authored lines jumped from 841,545 to 1,535,734. It looked exactly like the fix working.
Now: recovering files is only a fix if they are the project's own — check authored lines before and after, not the file count.
05The cap that ate a directory
A 40,000-file safety cap spent its whole budget inside one 50,127-file data folder and never opened the app's code. The project reported 6 Python files against 53.
Now: the walk takes turns across folders, and a truncated walk names what it never reached.
06294 GB of change that wasn't
A freshness check with its own idea of what to skip reported 294 GB of change across 53 projects. Reusing the scanner's own fingerprint said 7 projects had moved.
Now: reuse the scan's answer. A second implementation drifts immediately, not eventually.
07Saved web pages, masquerading as JavaScript
A Python web service measured 97% JavaScript, because it had saved thousands of web pages as data. The same mistake came back twice more, in two different analyses, before I fixed it for all of them at once.
Now: every analysis asks the scan which folders are data; a test fails on a second copy of that logic anywhere.
08FAIL is not fail
A priority band fired for 0 of 168 projects and read like good news. It was comparing against "FAIL" while grading writes "fail". After the fix: 164.
Now: a branch that never fires across the whole estate is a bug until proven otherwise.
09Two right answers, one leaked key
One tool advised putting unversioned projects under version control. Another marked their credentials resolved, because with no repository nothing had leaked. Both true — and followed in order, they commit a live key. Four of nine findings were in exactly that state.
Now: advice from different tools is tested in pairs, and a warning never contains a runnable copy of the thing it warns about.
10Dead code, cautiously
Any name that appears in a string anywhere is never reported as unused: that excludes 17,513 names while still reporting 14,487. A false "delete this" costs a working feature; a miss costs nothing.
Now: confidence comes from the kind of thing, and every row opens the definition before anyone deletes it.
11238 is not a headline
Credentials found only in history started at 238. Minus 20 in committed virtualenvs, 67 in a cloned third-party repository and 16 duplicates across copies: 135 distinct. The virtualenv pattern first missed a folder called venv2, and the duplicate counter reported "0 collapsed" while collapsing 16.
Now: every subtraction is printed, and one that under-reports itself is treated like one that hides.
12+6,600% growth, courtesy of commit one
The trends view counted a scan from the first day, when only two projects were configured, and reported explosive growth. Another tool had already solved this.
Now: import the threshold instead of copying it — and read my own lessons file before writing a new analysis.
13Narrow the run, lose the estate
Analysing one project rewrote the results file with one project in it, quietly discarding 145. Nothing errored. The same bug lived four days longer in a second tool, because the test only read the first one's code.
Now: a narrowed run merges, a full run replaces, and a probe checks every tool of that shape, not one instance.
14Hoarding is not thoroughness
One results file reached 48 MB, saved on every refresh. 84% of it was 112,290 symbols nobody could act on.
Now: keep what can be acted on and count what was dropped. 9.1 MB.
15279.6% typed
The TypeScript counter counted annotations rather than annotated functions, so one function with three typed parameters counted three times.
Now: a measurement that does not apply is "not applicable", never zero — and never 280%.
16Three tools, three realities
Three analyses carried three skip lists and found 17,923, 21,856 and 21,862 files in the same estate. When two say different things, at least one is wrong and nothing says which.
Now: one shared reader. 146 of 146 projects agree.
17The resume aged; the estate did not wait
My resume said 140+ projects, 1,200 tests and six machines. The measurements said 155, 1,397 and seven. And the first version of the fix counted 9 agent tools where there were 12 — caught by checking it against a second, independent count.
Now: figures about my work are quoted from measurements, and two counts of the same thing must agree.
18A stamp is a claim
Marking one new write-up as reviewed silently re-dated every write-up, including 19 that nobody had reviewed against their changed code.
Now: only the write-ups I name are stamped.
19The site was fine; the checker wasn't
The nightly link check reported a live page as dead. The host redirects a missing trailing slash with a status code my checker's library refused to follow. Every browser follows it.
Now: follow redirects the way a browser does — and suspect the instrument before the thing it measures.
20Nine silent nights
A test compared a saved data file with what the code would now produce. One number moved, and the nightly refresh stopped nine nights in a row — at the very step that could have updated the file.
Now: a test that guards the refresh checks what the code does, never the state of the data.