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:

  1. 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.
  2. 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.
  3. 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.

If you can explain what is changing and why, accept it. If you can't, understand it first.

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:

  1. The AI proposed a pre-flight length check so drafts do not get silently truncated on publish.
  2. It wrote the code, and in the same commit added the four-part entry to decisions.md.
  3. flow.md got 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.
  4. 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.
  5. 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).
  6. 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.