There is a moment that sneaks up on people who code with AI. The agent writes a function, it looks right, the tests pass, you hit accept. Do that fifty times and you have a working feature you could not explain in an interview, could not debug at 2am, and could not safely change without asking the AI to change it for you.
That is dependency. Not "I use AI to go faster" — that part is good. Dependency is "the AI is now the only thing that understands this code, including the parts with my name on the commit."
The goal is not to use AI less. The goal is to come out the other side of a fast AI build still able to hold your own system in your head.
Why blindly accepting AI code creates dependency
Reading code and writing code build understanding. Approving code does not. When you accept a diff you have not really read, you skip the step where the design decision, the trade-off, and the shape of the data actually land in your memory.
It compounds three ways:
- Decisions vanish. The AI picked a library, a pattern, a data shape — and the reason lived in a chat window that is now gone. Six weeks later nobody knows why it is built this way, so nobody dares change it.
- The map goes stale. The AI moved a route, split a module, changed how data flows. Your mental model still shows last month's architecture. Every debugging session now starts with re-learning your own app.
- The gap hides. Everything works, so nothing forces you to notice that you could not rebuild this from scratch. The gap only shows up in an incident, and that is the worst time to find it.
The fix is not discipline you have to remember. It is three artifacts the AI maintains for you, plus one habit that takes five minutes.
Part 1 — decisions.md
What it is
One plain-text file at the root of the repo. A running log of the meaningful choices made while building — the ones a future reader would otherwise have to reverse-engineer.
What goes in it
One entry per real decision. Each entry answers four things:
- What changed — the actual change, in a sentence or two.
- Why — the problem it solves. This is the part that disappears otherwise.
- The approach chosen — how it was done, and the constraint that shaped it.
- An alternative considered — the other option, and why it lost. This is what stops someone re-opening the debate in three months.
When the AI should update it
In the same change as the work — not "later". Trigger it on: choosing or swapping a library, picking a pattern or data shape, a schema change, anything with a real trade-off. Not on: renaming a variable, fixing a typo, styling.
What a useful entry looks like
Here is a real entry from my dashboard's decisions.md, lightly trimmed. The change added a length check that warns you before a draft gets silently truncated on publish:
decisions.md — real entry
## 2026-08-30 — Pre-flight length check before publishing ### What changed - The publishing module gained one shared character-limit constant per platform and a read-only length-check function that reports what will happen to a draft before it goes anywhere. - The draft review screen now shows a warning banner when a draft would get silently truncated. - The individual posting scripts dropped their own hard-coded limits and now import the shared constant instead. ### Why One posting script already trimmed an over-length body at a sentence boundary to make room for a trailing link. Nothing surfaced that trim before it ran, so a truncated post looked "ready" when it was not. It bit immediately: the first real draft was over the limit and would have silently lost a chunk of text. ### Approach chosen One shared source of truth for the limits, in the module that already owns the trailing-link logic. Warn, do not block — an over-length draft must stay visible so it can be fixed. Check what the automation will actually do, not a rough guess. ### Alternative considered Enforce the cap inside the shared parser and throw at parse time. Rejected: that parser has no concept of the trailing link (the thing that pushes the body over), so it would either crash the review screen or hand back a pre-truncated body that hides the problem.
Notice the entry is useful even if you never see the code. You know what moved, what problem it solved, and what was ruled out.
Copyable prompt
Paste into Claude Code / Cursor / Codex
Create and maintain a `decisions.md` file at the repo root. It is a human-readable log of meaningful technical decisions. For each decision record, in this order: - What changed - Why we made this change (the problem it solves) - What approach we chose, and the constraint that shaped it - One important alternative we considered, and why we rejected it Only record meaningful implementation or architectural decisions: choosing or swapping a library, a pattern, a data shape, a schema change, anything with a real trade-off. Do NOT record trivial changes (renames, typos, formatting). From now on, whenever you make a significant change, add the entry in the SAME change as the code. Keep entries short. Newest first.
Part 2 — flow.md
What it is
One file that documents how the application actually runs: where it starts, what calls what, and how data moves through it. The map you wish existed every time you open an unfamiliar codebase — except this one is your own.
What it should document
- Entry point — the exact command and file that starts the app, and what it sets up on boot.
- Calls and dependencies — request in, which layer hands off to which (router → handler → service → store), and the key modules involved.
- Data movement — where important data is created, transformed, stored, and returned; which files or tables hold it.
- External services — every API, database, queue, or browser the app talks to, and what for.
- A short list of what must be updated when the code changes — so the file does not rot.
Simple arrow diagrams beat prose here:
flow.md — shape of an entry
Browser (the UI)
| fetch('/api/...')
v
Server (one file, all route handlers inline)
|
+-- read/write local JSON files (no database)
+-- spawn a background script -> drives a real browser
+-- fetch() -> external APIs
Path: "publish a draft"
UI "Publish" -> POST /api/publish {id}
-> claim the item in a status log (state = "publishing") BEFORE any real action
-> spawn the posting script -> drives a logged-in browser
-> script reports its result -> server marks published / needs review / failed
What to update after a code change
Update flow.md in the same change whenever you: add, remove, or rename a route or an entry point; change what a background job is called with; add or move a data file, or change a stored shape; add, remove, or re-auth an external service; or change a core flow like auth or the publish path. Trivial changes do not need an entry — say so explicitly in the file so the AI does not over-document.
Copyable prompt
Paste into Claude Code / Cursor / Codex
Create and maintain a `flow.md` file for this project. First inspect the codebase and understand how it actually works. Then document the important execution and data flow, in plain language: 1. Where the application starts (exact command + file) and what it sets up on boot. 2. What the entry point calls, and the important modules involved. 3. What calls what (router -> handler -> service -> store), with simple arrow diagrams. 4. How data moves through the app: where it is created, transformed, stored, and returned. 5. The key external services / APIs / databases and what each is for. Do not document every file — only the paths someone needs to explain how this works. Verify it against the real code; do not describe architecture that does not exist. Add a short section listing what must be updated when the code changes. From now on, whenever a change affects the architecture, execution flow, or data flow, update `flow.md` in the same change.
Part 3 — the AI quiz (the important one)
Why it matters
decisions.md and flow.md keep the knowledge in the repo. The quiz keeps it in you. It is the checkpoint that turns "the AI understands this" into "I understand this too."
When to trigger it
Before you accept any change that is more than cosmetic: a new dependency, a new module, a change to how data flows, anything touching auth, money, or user data, or any diff big enough that you skimmed it.
How the quiz should work
- Ask the AI for exactly 3 questions, one at a time, about the change it just made.
- One question on what changed, one on why, one on how it fits the existing code and data flow.
- You answer in your own words, out loud or in writing, before seeing the answer.
- The AI tells you if you were right, wrong, or incomplete — in one line, no lecture.
- Plain questions, not multiple choice. Multiple choice lets you pattern-match without understanding.
What counts as understanding
You can explain, without looking: what the change does, what problem it solves, and where it sits in the flow — what calls it, what it touches, what breaks if it is wrong. If you can only repeat the AI's summary back, that is recognition, not understanding.
What to do when you cannot answer
Stop. Do not accept the change. Read the diff properly, ask the AI to walk you through the part you missed, then re-run the quiz. Accepting a change you failed the quiz on is the exact move that builds dependency.
Copyable prompt
Paste before accepting a change
Quiz me on the change you just made, using the actual code, decisions.md, and flow.md as your source of truth. Ask exactly 3 questions, ONE AT A TIME. Wait for my answer before the next one. Test whether I understand: 1. WHAT you changed 2. WHY you made that change 3. HOW it fits into the code / data flow Rules: - Do not explain anything before asking. - Plain questions, not multiple choice. - After each answer: tell me correct / incomplete / wrong in one or two sentences. If I am wrong, point out what I missed briefly — do not give a long explanation. - After question 3, tell me plainly whether I understand this well enough to accept it.
The complete workflow
One change, start to finish
AI proposes a change
v
AI updates decisions.md (same change — what / why / approach / alternative)
v
AI updates flow.md (only if architecture or data flow moved)
v
You review the diff
v
AI quizzes you: 3 questions, one at a time (what / why / how it fits)
v
You can explain it in your own words?
yes -> accept and commit
no -> read it properly, ask for a walkthrough, re-run the quiz
Practical checklist
- decisions.md exists at the repo root
- flow.md exists and matches how the app actually runs today
- The AI knows to update both in the same change as the code
- Every non-trivial decision has a why and one rejected alternative
- You run the 3-question quiz before accepting anything non-cosmetic
- You answer in your own words before seeing the answer
- A failed quiz means stop and understand, not accept anyway
- Once a week: open flow.md and check it still describes reality
Example workflow
The decisions.md entry above is a real one. Here is how it actually went:
- The AI proposed a pre-flight length check so drafts do not get silently truncated on publish.
- It wrote the code, and in the same commit added the four-part entry to
decisions.md. flow.mdgot one line: the draft review screen now shows a warning banner it did not before. Architecture did not change, so that was the whole update.- It ran the quiz. Three questions: what was added to the shared module, what problem it fixed, where the check runs in the request flow.
- I got the "where it runs in the flow" question right and the first two wrong — I said it trimmed the file (it does not, it is read-only) and that the trailing link was at risk (it is not, the body is).
- That is a failed quiz. The move is to re-read the diff and the entry until the "warn, never modify" intent is obvious — then accept.
Without the quiz I would have accepted a change I had backwards in two places. The code was fine. My understanding was not, and only the quiz showed that.
Common mistakes
- Writing decisions.md after the fact. Do it in the same change or it never happens.
- Logging every change. A decisions.md with 200 entries is noise. Meaningful decisions only.
- Letting flow.md rot. A wrong map is worse than no map. Tie its updates to code changes and spot-check it weekly.
- Turning the quiz into multiple choice. You will pass by pattern-matching and learn nothing.
- Reading the answer first. Answer in your own words, then check. Order matters.
- Accepting a failed quiz "just this once". That one is the whole disease.
Final takeaway
AI should make you faster without making you a stranger to your own codebase. Three cheap habits keep both: a decisions.md so the why survives, a flow.md so the map stays true, and a 3-question quiz so the understanding stays in your head and not only in the model's.
Build faster with AI. Keep learning along the way.
// Free newsletter
I send out guides like this every week
Real setups, real sources, no hype. Drop your email and I'll send you the next one.