{"slug": "i-inherited-60000-lines-of-undocumented-c-i-read-it-with-an-ai-and-assumed-every", "title": "I Inherited 60,000 Lines of Undocumented C++. I Read It With an AI and Assumed Every Answer Was a Lie.", "summary": "A developer who inherited 60,000 lines of undocumented C++ code used an AI as a 'frequently wrong tour guide' to navigate the codebase, employing a 'hypothesis ledger' to verify every AI claim with tools like grep, gdb, and ThreadSanitizer. The method, which treats AI output as hypotheses rather than knowledge, proved useful for reading but not for writing code, with about one in four substantive claims being wrong or incomplete. The developer ran sessions through MonkeyCode, which offers free model access, as part of the company's product outreach.", "body_md": "Three months ago a colleague left, and I inherited his project: roughly 60,000 lines of C++17, no design docs, comments that mostly said `// TODO: clean this up`\n\n, and a build system only he understood. My manager's ask was simple: \"Be able to modify it safely by end of quarter.\"\n\nI did not have a quarter to read it line by line. I also did not trust any LLM enough to let it *change* the code. So I used one for the only job where a wrong answer is cheap: reading. This post is the workflow that came out of it — a method I now call the hypothesis ledger — plus the places where it fell apart.\n\nEvery horror story about AI-generated code shares one shape: plausible output, merged without verification, explosion later. Reading inverts the economics. When a model explains a function to me and gets it wrong, the blast radius is my own misunderstanding — which the compiler, a debugger, or ten minutes of `grep`\n\nwill correct before it costs anyone anything.\n\nThat asymmetry is the entire pitch: use the model where its errors are detectable and cheap, and keep it away from anything that lands in the repo unverified. This is not a story about adopting AI-written code. It is a story about using AI as a very fast, frequently wrong tour guide.\n\nThe core rule: nothing the model says about the codebase counts as knowledge. It counts as a *hypothesis* until verified by a tool that cannot be charmed. I keep a plain Markdown file open while exploring:\n\n```\n| # | Hypothesis (from model) | Source | Verification method | Status |\n|---|------------------------|--------|--------------------|--------|\n| 1 | `RingBuffer::push` overwrites oldest entry when full | LLM reading buffer.cpp | Write probe main.cpp, push 6 items into cap-5 buffer | CONFIRMED |\n| 2 | `OrderBook` is only mutated from the IO thread | LLM summarizing call graph | `grep -rn \"order_book\\.\" src/ | grep -v io_thread` + tsan build | REJECTED — also mutated in metrics.cpp:214 |\n| 3 | Retry backoff is exponential, base 100ms | LLM reading retry.h | Read source myself | PARTIAL — capped at 2s, model missed the clamp |\n```\n\nThree columns do the real work. **Verification method** forces me to decide *how* I would check before I believe anything. **Status** keeps a permanent record of how wrong the model was, which calibrates how much rope to give it next session. After three weeks my ledger showed roughly one in four substantive claims was wrong or incomplete — useful, but nowhere near trustworthy. Exactly the right tool for navigation; exactly the wrong tool for authority.\n\nVague questions got vague, confident nonsense. What worked was a fixed protocol:\n\n`grep`\n\n.`flush()`\n\n, and what does it call?\" Then verify with `ctags -R .`\n\nand `grep`\n\n, or compile with `-Wunused`\n\nand see what the linker actually keeps.For verification I leaned on tools that predate all of this: `gdb`\n\nwatchpoints to test \"this field only changes during shutdown,\" a throwaway `main.cpp`\n\nlinking the suspect object file to test behavior claims, and a ThreadSanitizer build for anything about threads. The model never once mentioned TSan when summarizing concurrency code. The tools did not care about the summary either way.\n\nThis workflow is chatty. A single afternoon of code spelunking is easily 40–60 exchanges: paste a slice, get a structural claim, verify, follow up. On a metered API I would have rationed questions and been worse at the job — the temptation to accept the first plausible answer grows with every dollar.\n\nI ran these sessions through MonkeyCode, which currently offers free model access and a free server option, so the cost of asking one more clarifying question was zero. Disclosure: This article was prepared as part of MonkeyCode's product outreach. Two honest caveats: I am making no claims about which models it serves, its limits, or how long the free tier lasts — verify that the day you set up, not from this article. And the ledger method is deliberately provider-agnostic; it is a Markdown file and a habit, so swapping backends changes nothing about the workflow.\n\nKeeping the ledger meant I also accumulated a catalog of *how* it failed, which turned out to be the most valuable artifact of the quarter:\n\n`grep`\n\ndisagreed three separate times.`std::shared_mutex`\n\nsemantics from a standard the project did not use. Verify language-level claims against the build flags, not the model's memory.Notice what is absent from this list: syntax errors, broken code, anything a compiler catches. Reading-side failures are semantic and social — wrong stories told fluently.\n\n| Task | Model suitable? | Why |\n|---|---|---|\n| \"What calls this function?\" | Yes, then verify with grep/ctags | Cheap falsifiable claim |\n| \"Summarize this class's invariants\" | Yes, as hypothesis only | Invariants are exactly what it invents |\n| \"Is this thread-safe?\" | No — TSan and code review only | Its confidence here is uncorrelated with correctness |\n| \"Why was this written this way?\" | No — it cannot know, but will answer anyway | Intent lives in commit history and people, not syntax |\n| \"Draft a probe program to test hypothesis X\" | Yes, with review | Small, throwaway, compiler-checked |\n\nThe ledger is discipline, and discipline does not scale forever. By week six I was spending real time maintaining it, and on a team of five the per-person ledger would need to become a shared document with conventions. It also assumes you *can* verify — this worked because the project built and ran locally. For code you cannot execute (hardware-dependent firmware, proprietary dependencies), the verification column gets thin and the whole method weakens. Finally, none of this transfers to writing: the moment the model's output goes into the repo, you are back in the risk profile this article explicitly avoided, and you need review gates I have deliberately not discussed here.\n\nIf the codebase is small enough to read in a weekend, read it in a weekend — the ledger is overhead. If you already have the original author available, buy them lunch instead; thirty minutes with a human beats a week of hypothesis testing. And if your employer's policy forbids pasting proprietary code into external services, that constraint settles the question before the workflow starts.\n\nBy end of quarter I could modify the project safely, and the model deserves maybe a third of the credit — the rest goes to `gdb`\n\n, TSan, and the ledger that refused to believe it. That ratio feels like the honest version of AI-assisted legacy work: the model accelerates the asking, and everything else you already owned does the knowing. If you are staring down an inherited codebase right now, start a Markdown table before you paste a single line — the column where you record how wrong it was will teach you faster than any of its answers.", "url": "https://wpnews.pro/news/i-inherited-60000-lines-of-undocumented-c-i-read-it-with-an-ai-and-assumed-every", "canonical_source": "https://dev.to/datacpp_8185/i-inherited-60000-lines-of-undocumented-c-i-read-it-with-an-ai-and-assumed-every-answer-was-a-4l8b", "published_at": "2026-08-12 19:51:45+00:00", "updated_at": "2026-08-12 20:19:40.674483+00:00", "lang": "en", "topics": ["developer-tools", "artificial-intelligence", "large-language-models", "ai-tools"], "entities": ["MonkeyCode", "ThreadSanitizer", "gdb", "grep", "C++17"], "alternates": {"html": "https://wpnews.pro/news/i-inherited-60000-lines-of-undocumented-c-i-read-it-with-an-ai-and-assumed-every", "markdown": "https://wpnews.pro/news/i-inherited-60000-lines-of-undocumented-c-i-read-it-with-an-ai-and-assumed-every.md", "text": "https://wpnews.pro/news/i-inherited-60000-lines-of-undocumented-c-i-read-it-with-an-ai-and-assumed-every.txt", "jsonld": "https://wpnews.pro/news/i-inherited-60000-lines-of-undocumented-c-i-read-it-with-an-ai-and-assumed-every.jsonld"}}