The File That Makes My AI Builds Get Better Over Time
I kept seeing the same review failures. Sprint after sprint, Opus would flag the same class of mistakes — missing prefers-reduced-motion, direct state mutation in React, hardcoded API URLs. Gemini would fix them. Next sprint, Gemini would make the same mistakes again.
The AI wasn't learning. It had no memory between sprints.
So I created a file called PATTERNS.md and gave it a simple rule: every time Opus flags something during review, log it. Every time Gemini starts a build, read it first.
How it works
After each review, a retro workflow runs. It reads Opus's review output, extracts every FAIL, and categorises it:
## PATTERNS.md
### A11Y-001: Missing prefers-reduced-motion
- **Count:** 4
- **Severity:** high
- **Pattern:** Animated components missing motion preference check
- **Fix:** Always wrap animations in useReducedMotion() or CSS media query
- **First seen:** Sprint 02 | **Last seen:** Sprint 05
### TS-003: Direct state mutation
- **Count:** 3
- **Severity:** critical
- **Pattern:** Mutating state object before setState call
- **Fix:** Always spread or structuredClone before modifying
- **First seen:** Sprint 03 | **Last seen:** Sprint 06Each pattern has a count. Every new occurrence increments it. The categories are short codes: A11Y (accessibility), DSC (design system compliance), TS (TypeScript), ERR (error handling), PERF (performance), ARCH (architecture), TEST (testing), CNT (content).
The promotion rule
When a pattern hits 3 occurrences, it gets promoted. It moves from PATTERNS.md (which is a reference file Gemini reads) into project-rules.md (which is injected as a hard constraint). After promotion, the pattern isn't a suggestion anymore. It's a rule.
# project-rules.md (auto-promoted)
RULE: All animated components MUST check prefers-reduced-motion.
RULE: Never mutate React state directly. Use spread or structuredClone.
RULE: No hardcoded API URLs. Use environment variables.Gemini reads this before writing a single line of code. The mistakes that kept recurring? They stopped.
The flywheel
Here's what actually happens over time:
Sprint 2: Gemini builds. Opus finds 14 issues. 14 patterns logged. Sprint 4: Gemini reads PATTERNS.md. Opus finds 8 issues. 5 are new, 3 are repeats (promoted). Sprint 8: Opus finds 4 issues. All new edge cases. Zero repeats. Sprint 12: Opus finds 2 issues. The codebase is clean enough that reviews are mostly nitpicks.
The builder genuinely gets better because it reads its own mistake history. It's not intelligence — it's just a feedback loop that persists between sessions.
What makes this different from linting
Linters catch syntax and formatting. PATTERNS.md catches judgment errors — architectural decisions, UX oversights, logic patterns that technically work but are wrong. Things like "don't use in-memory queues in serverless environments" or "every modal needs a keyboard escape handler." No linter catches those.
The real numbers
Across 14 sprints on one project (the Uptrail website), PATTERNS.md accumulated 31 unique patterns. 9 were promoted to hard rules. Review failure rate dropped from roughly 40% of items in Sprint 2 to under 10% by Sprint 10.
I didn't teach the AI anything. I just gave it a file to read and a process to update it. The improvement was mechanical, not magical.
Setting it up
You need three things:
- A
PATTERNS.mdfile in your project root - A retro workflow that extracts review failures and logs them
- A build workflow that reads PATTERNS.md before starting
The retro runs right after the review (in the same session, so Opus has the FAIL context). The build reads PATTERNS.md from Sprint 02 onward.
That's it. No framework, no database, no fancy tooling. Just a markdown file that accumulates mistakes and a build process that reads it.
The best part of this system is how boring it is. There's no AI magic happening. A file gets written to. A file gets read. Mistakes stop repeating. If that's not the most unglamorous form of machine learning, I don't know what is.