Stop reviewing the
same bugs

Every PR review is a debate: architecture, tradeoffs, style, quality. Then the PR merges and the debate dies. This distills those comments into rules your AI reviewer enforces on the next PR.

Outputs .cursor/BUGBOT.md and LESSONS.md. Claude Code skill. Runs on GitHub Actions. Works with Cursor Bugbot today.

claude install-skill sscarduzio/pr-war-stories
The pattern this fixes · from a real PR
PR #735 · author dismisses same bot finding repeatedly
"setEditorStatus is a React useState setter — guaranteed referentially stable. Adding it to deps is a no-op."
Without this skill
Next PR touches the same file. Bot re-flags the same “missing dependency”. Author types the same explanation again. Forever.
With pr-war-stories
One rule harvested — author's rationale becomes a scope-level BUGBOT.md rule. The bot stops re-flagging it.

AI reviewers have no memory

🧠

AI reviewers are amnesiac

Cursor Bugbot, CodeRabbit, and Copilot review every PR from scratch. They don't know your team's history or your past incidents.

📝

Knowledge dies on merged PRs

Review comments explaining why something is wrong are the most valuable institutional knowledge. They live on PRs nobody reads again.

🔇

Context overload kills quality

Dumping 50 rules into a config file means every rule competes for attention. The bot starts ignoring everything.

Three memory layers,
each for a different reader

Not all knowledge belongs in the same place. Rules go where they're most effective.

Layer 1 -- Bot memory

.cursor/BUGBOT.md

Read by Bugbot at review time

Hierarchical rules the bot checks against every diff. Token-budgeted: fewer rules = more attention per rule.

"Never use Promise.all on unbounded arrays. Use a concurrency limiter."
Layer 2 -- Developer memory

LESSONS.md

Read by IDE assistants at write time

Universal engineering lessons read by Claude Code and Cursor before developers write code.

"In-memory state is a cache, not a source of truth. Query the database for conflict detection."
Layer 3 -- File memory

Inline comments

Visible in diff when that file changes

Rules that apply to one file, placed in source code. The bot sees them when that file is in a PR.

// WARNING: Runtime-evaluated // JS string. Cannot import. // Do not DRY. (See PR #760)

Every lesson gets triaged
before placement

Not everything belongs in BUGBOT.md. The wrong placement wastes the bot's attention or hides knowledge where nobody sees it.

New lesson from PR review
Can the bot
check this
on a diff?
Yes — reviewable
Which code
does it
affect?
.cursor/BUGBOT.md
Cross-cutting — every PR
apps/frontend/.cursor/BUGBOT.md
App-specific
apps/.../editor/.cursor/BUGBOT.md
Module-specific
packages/.cursor/BUGBOT.md
Shared packages
Bot traverses upward — deeper modules get more context
No — not enforceable
Applies to
one file?
Yes
Inline comment
In the source code itself
No
LESSONS.md
IDE reads before coding

Every merged PR makes the bot smarter

A GitHub Action fires on merge, extracts review comments, and posts a harvest proposal on the PR. One command to classify. Rules apply on the next review.

1

PR Merged

Code lands on main

2

Action Fires

Harvest proposal posted

3

Classify

/pr-war-stories harvest

4

Bot Learns

Enforces on next PR

War stories from production

Actual lessons extracted from merged PRs. Each one prevented the same bug from recurring.

💥
PR #781

Promise.all on 200 files = OOM

BUGBOT.md
- await Promise.all(files.map(upload))
+ await asyncPool(3, files, upload)
🎨
PR #775

LLM CSS change broke schema editor

BUGBOT.md
- .custom-antlayout { height: 100% }
+ .schema-editor-layout { height: 100% }
🚫
PR #735 · 42 dismissals, one recurring theme

setX from useState is stable. Don't flag as missing dep.

BUGBOT.md
- useEffect(() => {...}, [data, setData]) // bot: "missing dep!"
+ one scope rule. Bot stops re-flagging.
PR #741

useState is 1 frame late

LESSONS.md
- const [val, setVal] = useState(x)
+ const valRef = useRef(x) // sync capture

Running on octostar-frontend —
heavy Cursor Bugbot traffic

Private Turborepo monorepo. 8 months of PRs, one extremely active hotspot module. This is where the "author-dismissals are the highest-yield source" doctrine was discovered.

415
Merged PRs mined
8-month window
5
Scoped BUGBOT.md
39 rules total
36%
of rules sourced
from author-dismissals
~17 min
150k tokens
~$0.30–0.60
O

octostar-frontend — knowledge-graph platform UI

React 18 + Vite + TypeScript • Turborepo • two apps (octostar, search-app) + shared packages • 3,114 inline review comments mined

Rule placement hierarchy

.cursor/BUGBOT.md
10 rules
apps/octostar/.cursor/BUGBOT.md
10 rules
.../components/Apps/.cursor/BUGBOT.md
10 rules
apps/search-app/.cursor/BUGBOT.md
5 rules
packages/.cursor/BUGBOT.md
4 rules
Worst-case bot load (root + octostar + Apps): ~1,620 tokens — 19% under budget.

Lessons the bot can't enforce

useState is 1 frame late — use useRef for synchronous capture in callbacks
💣 Promise.all on unbounded arrays causes OOM — bound concurrency to 3–5
🎨 LLM-generated CSS changes that rename shared classes break unrelated components
🔁 Async effects need a mounted flag or useAsyncCallback
📌 Use as const enum pattern over raw string unions for stable values
🔒 OntologyAPI.subscribe returns an unsub — you MUST call it

Running on octostar-api —
finding the Copilot-filter bug

Private FastAPI / LangChain / ClickHouse backend. 500 merged PRs mined. Surfaced that GitHub Copilot Code Review's Copilot login has no [bot] suffix — the default filter would have misclassified 308 of its comments as human review input. Filter shipped in v0.7.

500
Merged PRs mined
#2 → #608
8
Scoped BUGBOT.md
46 rules total
30%
of rules sourced
from author-dismissals
~12 min
139k tokens
~$0.25–0.55
λ

octostar-api — knowledge-graph platform API

Python 3.12 + FastAPI • LangChain AI + ClickHouse/OpenSearch/MySQL • Kubernetes via Helm • 5,128 inline review comments mined

Rule placement hierarchy

.cursor/BUGBOT.md
7 rules
app/api/v1/common/.cursor/BUGBOT.md
7 rules
app/api/v1/ai/.cursor/BUGBOT.md
6 rules
app/api/v1/apps/.cursor/BUGBOT.md
7 rules
app/api/v1/entities/.cursor/BUGBOT.md
6 rules
+ 3 more scoped files (sets, pipeline, files)
13 rules
Worst-case bot load: 1,965 tokens. Within 2K budget.

Lessons the bot can't enforce

🛠 Use is None, not truthiness, when zero is a valid value in Python
🔒 Never share a SQLAlchemy Session across async handlers and background tasks
⚠️ Narrow-except multi-format parsing is NOT silent exception swallowing
☣️ Always set imagePullPolicy explicitly — defaults vary by tag strategy
🔍 Traefik IngressRoute is a CRD, not a vanilla Ingress
🔑 timbr.* queries get row-level security free — etimbr does not

Running on ReadonlyREST —
Scala Elasticsearch plugin (958★)

Public OSS. Slow-moving, 12-year-old project. JVM stack — completely different from Octostar. Full history mined from PR #6 to PR #1235.

755
Merged PRs mined
full 12-year history
7
Scoped BUGBOT.md
27 rules total
52%
of rules sourced
from author-dismissals
~77 min
175k tokens
~$0.30–0.83
R

ReadonlyREST — Elasticsearch security plugin

Scala 3.3.3 + Java • Public OSS, MIT • Default branch: develop • 175k Claude tokens • 77 minutes wall time

Rule placement hierarchy

.cursor/BUGBOT.md
7 rules
core/.cursor/BUGBOT.md
5 rules
.../accesscontrol/blocks/.cursor/BUGBOT.md
2 rules
.../accesscontrol/factory/.cursor/BUGBOT.md
3 rules
audit/.cursor/BUGBOT.md
3 rules
integration-tests/.cursor/BUGBOT.md
3 rules
ror-tools-core/.cursor/BUGBOT.md
4 rules

Lessons the bot can't enforce

🔐 Never fall back to X-Forwarded-For when TCP remote is missing — trivially spoofable.
⚠️ Use Either[DomainError, _] for expected failures; throw only for invariants.
🛡️ Use NonFatal(e), never catch Throwable — swallows OOM & JVM signals.
📝 Audit rule names are stable identifiers — don't let syntax drift rename them.
♻️ When a metadata-file format needs to change, add a second file. Keep the old one writeable.
📄 Group identity is GroupId, not Group — names drift, IDs don't.

One command to install.
One command to bootstrap.

Install the skill

Installclaude install-skill sscarduzio/pr-war-stories
Setup/pr-war-stories setup
Harvest/pr-war-stories harvest
Audit/pr-war-stories audit
Reads your PR history automatically
Rules scoped to each module
Learns from every merged PR
Won't overload the bot

Works with Cursor Bugbot. Any GitHub repo. Any language. MIT licensed.