Project memory that stays true
Claude Code already gives you a memory directory. This is the discipline that decides whether it's worth reading a year from now — the difference between notes you trust and a pile you ignore.
Running the linter. This ships as a plugin, so its files are not in your folder:
python "${CLAUDE_PLUGIN_ROOT}/skills/memory-system/scripts/memory_lint.py"On Windows type
python; on Mac/Linuxpython3.
The one idea
A memory is a claim about the world that outlives the conversation that produced it. So it has to survive being read by someone with no memory of that conversation — including you in six months. Everything below follows from that.
What earns a memory
Save it when all three are true:
- It will still matter in a month
- It is not already recorded somewhere the code or history can tell you
- Getting it wrong would cost real time or real money
Do NOT save: what the repo already says (structure, past fixes, git history), anything that only matters to the conversation you're in, or a decision you might reverse this week. If someone asks you to remember something in those categories, ask what was non-obvious about it and save that instead — the surprise is the durable part.
The four types
| Type | Holds | The test |
|---|---|---|
user |
Who this person is — role, expertise, standing preferences | Would a new collaborator need telling? |
feedback |
Guidance on how to work: corrections AND confirmed approaches | Did they push back, or say "yes, exactly that"? |
project |
Ongoing work, goals, constraints not derivable from the code | Would the code alone mislead you? |
reference |
Pointers outward — URLs, dashboards, tickets, accounts | Is this a lookup rather than a fact? |
A feedback note carries Why: and How to apply:. Without the why, the rule reads as
arbitrary and gets dropped the first time it's inconvenient. The linter enforces this.
The shape of a note
---
name: <short-kebab-case-slug> # must match the filename
description: <one line — this is what recall actually reads>
metadata:
type: project
---
The claim, in short bullets. Lead with what is TRUE, not what you plan to do.
Link related notes with [[their-slug]] — liberally. A link to a note that does not
exist yet is fine; it marks one worth writing.
**Why:** (feedback notes)
**How to apply:** (feedback notes)
Write bullets, not prose walls. A correct paragraph nobody reads is worth less than a blunt list. Use absolute dates — "last Tuesday" is meaningless to the reader who finds this in March.
The index is the product
MEMORY.md is loaded every session; the notes are not. A note missing from the index is invisible
no matter how good it is. One line per note:
- [Title](file-name.md) — the hook: what this tells you, in a clause
Put the hook in. - [Auth notes](auth.md) tells a reader nothing about whether to open it.
Keeping it true
- When a memory turns out to be wrong, fix or delete it. A stale note is worse than none — it is confidently wrong. Do not leave a correction only in the conversation.
- Verify before acting on a recalled note. It reflects what was true when written. If it names a file, a function or a flag, check that still exists.
- Prefer updating an existing note over adding a near-duplicate. Two notes on one subject drift apart, and then you have to work out which is current.
The gate
Conventions with nothing enforcing them decay quietly. Run the linter:
python "${CLAUDE_PLUGIN_ROOT}/skills/memory-system/scripts/memory_lint.py"
It reports: notes with no frontmatter, a name that doesn't match its filename, a missing
description, an unknown type, feedback notes missing Why:, notes absent from the index,
index entries pointing at files that don't exist, and relative dates that will rot.
--fix-index appends anything unindexed under an "Unfiled" heading so nothing is lost — then move
those lines into the right section yourself.
Exit codes are a contract: 0 clean and it checked something · 1 problems found · 2 could
not run · 3 the audit did not happen (an empty directory is not a pass). Read the code, not
just the last line.
Gotchas
- An empty memory dir passing silently is the failure mode to fear. "Checked, found nothing, said nothing" looks identical to "healthy". That is why exit 3 exists.
- Underscored filenames. A note named
my_note.mdneedsname: my_note— the linter compares them literally, because a mismatch is how a note stops being findable by its own slug. - The linter never edits a note. The only thing it writes, and only when asked, is the index.