If git already stores every line you have changed, with an author, a timestamp and a message attached, what is a separate decisions file supposed to add? That question shows up on every team that tries project memory for the first time, usually from the person who would have to maintain the file.

The useful answer is not "write more documentation." A commit log and a project memory file are shaped to hold different things. Git records state transitions: this tree became that tree, here is the diff. Project memory records judgment: these were the options, this one won, here is what we accepted when it won. So the project memory vs git comparison is less about choosing one and more about knowing which facts each can structurally hold, and which facts fall through the gap between them.

Worth working through properly, because the teams that get this wrong usually fail in one of two directions: they skip the decisions file and lose the reasoning, or they turn it into a prose copy of git log that nobody reads twice.

Git remembers what changed, not what you ruled out

The repository only contains things that exist. If you evaluated three job queue libraries over two days and picked one, the commit history shows a single dependency being added. The two you rejected produced no object in the repo. Neither did the reason, which was probably something specific like "the other one needs a Redis instance we do not want to run in the desktop build."

Abandoned work is worse. A spike that you explored and threw away either never got committed, or lived on a branch you deleted. The reflog keeps it locally for a while, on your machine, until it is garbage collected. Nobody on the team can read it, and neither can an assistant.

This is exactly where AI sessions get expensive. A fresh session reads your code, sees the current approach, and proposes the alternative you spent a day disproving. It is not being careless. The evidence that you considered it was never written anywhere it could read. That is the core argument in project memory for AI coding, and it holds whether your memory lives in a plain Markdown file or anywhere else.

A commit message freezes intent, then the code moves on

Good commit messages do capture reasoning. The limitation is when they are written and how long they stay accurate.

A message is written at the moment of the change, describing that change, in the vocabulary of that moment. Six months later the function it mentions has been renamed, the module it lived in was split, and the message now describes a layout that no longer exists. Git will never update it, correctly, because an immutable record of what you thought on 14 October is a feature.

Then there is the shape of your merge workflow. If you squash, the fourteen commits where the reasoning actually accumulated collapse into "Add billing export (#482)". If you rebase and clean up before merging, the intermediate thinking is gone by design. The reasoning you most want to keep is the part your branch hygiene is built to remove.

What a decision record holds that no git object can

Five things, and none of them have a natural home in a commit:

  • The alternatives and why they lost. The only durable place for "we tested SSE and dropped it because reconnection on mobile was unreliable."
  • Constraints that were true at the time. "The provider's API had no pagination on this endpoint in October 2026." When that stops being true, the decision is open for review. Without the constraint written down, the code just looks arbitrary.
  • Consequences you accepted. "No JSON columns, keep queries portable." This is the part that should shape future work, and a diff cannot express it.
  • Status. Active, superseded, reversed. Git objects do not have a status field, and editing history to add one is not an option.
  • Decisions that produced no code. You decided not to add a caching layer. You decided to stay on the current Node major until the next release cycle. These change what people do next and leave zero commits behind.

Keep them short. A decision record that runs to two pages gets skimmed; the format in lightweight decision records for AI-assisted projects fits in a dozen lines.

Project memory vs git: where the overlap is real

Some questions have one obvious owner, and writing them in both places is pure overhead.

QuestionAnswered by
What changed in this file, line by line?git
Who made the change, and when?git
Which commit introduced this behaviour?git
Why this option and not the two we tested?project memory
What are we deliberately not doing?project memory
Which constraint forced this odd shape?project memory
Where is the migration right now?project memory

The practical rule: if git log -p can answer it, do not write it in memory. "Renamed SyncQueue to WriteQueue" is not a decision record, it is a diff with extra words. The same applies to pasting code into records. Reference the file by path if you must, and know that the reference is fragile, which is the failure mode covered in why a decision record breaks after a refactor.

Rules about how the project works belong in a third place again, your instruction file, and mixing those into memory is the fastest way to a file nobody trusts. Splitting rules from facts is the cleaner division.

A decision record and the change that implemented it should each name the other. Two lines of work, and it is the difference between a decisions file that stays useful and one that slowly becomes folklore.

In the record, name what shipped it:

### 2026-10-14 — Single writer for the sync queue
Decision: One process writes to the queue table; readers poll.
Why: Two writers produced duplicate rows under retry; locking
     the table was slower than serialising the writes.
Rejected: Advisory locks (harder to reason about on restart).
Shipped in: PR #482
Status: Active

Prefer the pull request number over a commit SHA. A squash merge rewrites the SHA you wrote down, and the record then points at a commit that was never on the main branch. The PR number survives.

In the other direction, the PR body carries the path to the record. That is one more reason to write the description before the context evaporates, as in capturing the why in an AI-assisted PR description.

Staying in sync without writing a changelog twice

The sync problem solves itself if you are strict about the trigger. Write a record when you chose between real options, when you rejected something a reasonable person would propose again, or when a constraint pushed the code into a shape that looks wrong without explanation. Everything else is a commit and nothing more. On an active week that might be two records. Some weeks, none.

One record per decision, not per pull request. If a decision takes four PRs to land, it is still one entry, and the entry can list all four.

Where PrjLab fits in this is storage, not process. Your memory files and your source go up in the same prj push, so the record lands in an immutable version alongside the code it describes, independent of how finely you split or squash commits downstream. When a teammate clones the repository or opens it in the browser, the decisions and the files sit next to each other instead of one living in git and the other on somebody's laptop. It sits beside git rather than replacing it; if that split is useful to you, the getting-started guide walks through the first push.

None of this makes git worse at its job. It is an excellent record of what happened to the code. It was never designed to be a record of what you decided, and the gap between those two things is where most of the re-explaining happens.