Browse 225 rules, 42 knowledge articles, and 28 prompt templates across security, performance, architecture, and quality.
225 rules
Every interactive element must be operable via keyboard alone (Tab, Enter, Space, Escape). Users with motor disabilities, RSI, or broken trackpads cannot use a mouse — if your app requires mouse interaction, those users are completely locked out.
Text must meet WCAG AA contrast ratio: 4.5:1 for normal text, 3:1 for large text. Low-contrast text is unreadable for users with low vision, color blindness, or anyone using a screen in bright sunlight — affecting up to 8% of male users (color blindness alone).
Write commit messages that explain WHY a change was made, not just WHAT changed. "fix bug" tells future-you nothing — "fix: prevent duplicate form submission on slow connections" tells you the context, the cause, and the scope without reading any code.
Always log errors with structured context: user ID, request ID, input data, stack trace. An error message like "Cannot read property of undefined" with no context is impossible to debug — you don't know which user hit it, what they were doing, or how to reproduce it.
Wrap UI sections in React Error Boundaries to catch rendering errors gracefully. Without error boundaries, a single component crash (a null reference, a failed API parse) takes down the entire page — showing users a white screen with no way to recover or navigate away.
Never use SELECT * in production code. SELECT * fetches every column including large text/blob fields you don't need, wastes bandwidth, breaks when columns are added, and prevents the database from using covering indexes.
Use factory functions (e.g., `createUser({role: 'admin'})`) instead of static JSON fixtures. Factories let you create exactly the data each test needs with sensible defaults, while fixtures force you to maintain large JSON files where a change to one test's data breaks another test.
Every test must be independent — no shared mutable state, no execution order dependencies. When tests share state, they pass in isolation but fail together (or worse, fail randomly), creating flaky CI that wastes hours of debugging time.
Test what the code does (outputs, side effects), not how it does it (internal method calls, private state). Implementation-coupled tests break every time you refactor, even when behavior is unchanged — making tests a liability instead of a safety net.
Define foreign key constraints for all table relationships. Without foreign keys, the database allows orphaned rows (e.g., an order referencing a deleted customer), corrupting data integrity silently.
Normalize your database schema to at least 3NF to eliminate data duplication. Denormalized data means the same fact is stored in multiple places — when you update one copy, the others become stale, creating data inconsistencies that are nearly impossible to track down.
Always use parameterized queries or prepared statements, never string concatenation. String-interpolated SQL is the #1 cause of SQL injection — an attacker can modify your query to read, modify, or delete your entire database.
Add database indexes for columns used in WHERE, JOIN, and ORDER BY clauses. Without indexes, the database scans every row in the table for every query — a table with 1M rows takes seconds instead of milliseconds.
Version your API from day one (URL prefix, header, or query param). Without versioning, any breaking change forces all clients to update simultaneously or breaks them without warning.
Apply rate limiting to all public-facing API endpoints. Without rate limits, a single attacker can overwhelm your server, exhaust your database connections, or brute-force authentication — taking down the service for all users.
Return semantically correct HTTP status codes (400 for bad input, 401 for unauthenticated, 403 for unauthorized, 404 for missing, 500 for server errors). Using 200 for everything hides errors from monitoring, breaks caching, and makes debugging impossible.
Validate and sanitize all request input (body, query params, headers) before processing. Unvalidated input is the root cause of injection attacks, data corruption, and crashes from malformed data.
Avoid `as Type` assertions — they tell TypeScript "trust me" and skip validation. If the runtime value doesn't match, your code crashes with no type error to warn you.
Model mutually exclusive states with discriminated unions, not optional fields. Optional fields allow impossible states (e.g., `status: 'success'` with `error: 'failed'`) that compile but crash at runtime.
Use `unknown` instead of `any` for values with uncertain types. Unlike `any`, `unknown` forces you to narrow the type before using it, keeping type safety intact.
Enable all strict flags in tsconfig.json (strict: true). Without strict mode, TypeScript allows null access, implicit any, and unchecked function calls that will crash at runtime.
Never use `any` as a type annotation. `any` disables all type checking for that value and everything it touches, spreading through your codebase like a virus — one `any` can silently disable type safety across dozens of files.
Extract event handlers defined inline in JSX to named functions or useCallback. Inline functions create new references every render, breaking React.memo and causing unnecessary child re-renders.
Wrap expensive calculations in useMemo and expensive component creation in React.memo. Without memoization, expensive work runs on every render even when inputs haven't changed, causing UI jank and dropped frames.
BeforeMerge scans your pull requests against these rules automatically. Get actionable feedback before code ships to production.