Engineering Rules
Turns Claude into the repo's staff engineer: every diff it produces already passes the review these seven rules describe.
When to Use This Skill
- Implementing any feature, fix, or refactor, in any language
- Self-reviewing a diff before commit or PR
- Cleaning up AI-generated or copy-pasted code
- Writing code samples for skills, docs, or references
- Reviewing someone else's PR against repo standards
- Deciding whether to extract, inline, or delete code
Core Workflow
- Search before writing. Grep the codebase for an existing helper, component, or util that already does the job (search by domain nouns and verbs, not just exact names). Reuse or extend it; only write new code when nothing fits.
- Implement. Small single-responsibility units, follow the project's existing structure and idioms, configuration over hardcoded values, no new global state.
- Run the rules checklist over the diff (the seven rules below, each is concrete and checkable):
- Rule 1, Reusable: no copy-paste variants; shared shape appearing a second time is extracted.
- Rule 2, Maintainable: units small, module boundaries clear, dependencies point one way, nothing hardcoded that belongs in config.
- Rule 3, Readable: intention-revealing names (no
data, tmp, handle, process2), early returns over nesting, reads top-down.
- Rule 4, No AI slop: scan the diff against
references/ai-slop-checklist.md item by item.
- Rule 5, Dead code: everything your change made unreachable is deleted; zero commented-out code; orphaned exports removed.
- Rule 6, Comments: every comment states a non-obvious why; any comment narrating what gets deleted or the code gets rewritten.
- Rule 7, No em dashes:
grep -rn "$(printf '\xe2\x80\x94')" over changed files, in code, comments, docs, and commit messages; returns nothing.
- Fix all violations and re-run the checklist until clean. A single pass is never assumed sufficient; fixes for one rule often introduce violations of another.
- Run the project's formatter and linter (e.g.
prettier --check . + eslint ., ruff format --check && ruff check, gofmt -l . && go vet ./...); fix every reported issue and re-run until clean.
- Verify behavior. Run the project's tests for the touched area; debug failures before declaring done.
Reference Guide
Load detailed guidance only when the task needs it:
| Topic |
Reference |
Load When |
| Reuse and structure (rules 1-2) |
references/reusability-and-structure.md |
Adding a helper/util, seeing duplicated logic, deciding module boundaries or where code lives |
| Readability and naming (rule 3) |
references/readability-and-naming.md |
Naming anything, untangling nested conditionals, code that needs a comment to be understood |
| AI slop checklist (rule 4) |
references/ai-slop-checklist.md |
Self-reviewing any diff, cleaning generated code, reviewing a PR |
| Comments, dead code, em dashes (rules 5-7) |
references/comments-and-dead-code.md |
Writing or deleting comments, removing a feature/flag/branch, touching a file with leftover code |
Key Patterns
Extract on the second occurrence, not the first, not the third:
// Two call sites already format money the same way: extract now.
const formatCents = (cents: number, currency = "USD") =>
new Intl.NumberFormat("en-US", { style: "currency", currency }).format(cents / 100);
Early return instead of nesting:
def ship(order: Order) -> Shipment:
if not order.items:
raise EmptyOrderError(order.id)
if order.status is not Status.PAID:
raise UnpaidOrderError(order.id)
return carrier.dispatch(order)
Comment carries the why, code carries the what:
// Stripe retries webhooks for 3 days; dedupe on event ID or refunds double-apply.
if store.SeenEvent(evt.ID) {
return nil
}
Common Mistakes
- Writing a new helper without searching first. The repo almost always has one; grep for the domain term (
grep -rn "formatCurrency\|toMoney") before writing line one.
- Extracting an abstraction for a single caller. One implementation needs no interface, factory, or base class; wait for the second concrete use.
- "Cleaning up" by commenting code out. Delete it; version control is the archive. Commented-out code fails review outright.
- Adding try/catch that only logs and re-throws, or swallows. Catch only where you can handle or add context; otherwise let it propagate.
- Narrating comments (
// increment counter) and section markers (// helpers). Both are deleted on sight; restructure the code instead.
- Defensive null checks after calls whose types guarantee non-null. Trust the type system; a gratuitous check hides the real contract.
- Leaving the checklist at one pass. A rename fixes rule 3 but can orphan an export (rule 5); loop until a full pass reports nothing.
1---2name: engineering-rules3description: Engineering Rules4---56# Engineering Rules78Turns Claude into the repo's staff engineer: every diff it produces already passes the review these seven rules describe.910## When to Use This Skill1112- Implementing any feature, fix, or refactor, in any language13- Self-reviewing a diff before commit or PR14- Cleaning up AI-generated or copy-pasted code15- Writing code samples for skills, docs, or references16- Reviewing someone else's PR against repo standards17- Deciding whether to extract, inline, or delete code1819## Core Workflow20211. **Search before writing.** Grep the codebase for an existing helper, component, or util that already does the job (search by domain nouns and verbs, not just exact names). Reuse or extend it; only write new code when nothing fits.222. **Implement.** Small single-responsibility units, follow the project's existing structure and idioms, configuration over hardcoded values, no new global state.233. **Run the rules checklist over the diff** (the seven rules below, each is concrete and checkable):24 - Rule 1, Reusable: no copy-paste variants; shared shape appearing a second time is extracted.25 - Rule 2, Maintainable: units small, module boundaries clear, dependencies point one way, nothing hardcoded that belongs in config.26 - Rule 3, Readable: intention-revealing names (no `data`, `tmp`, `handle`, `process2`), early returns over nesting, reads top-down.27 - Rule 4, No AI slop: scan the diff against `references/ai-slop-checklist.md` item by item.28 - Rule 5, Dead code: everything your change made unreachable is deleted; zero commented-out code; orphaned exports removed.29 - Rule 6, Comments: every comment states a non-obvious why; any comment narrating what gets deleted or the code gets rewritten.30 - Rule 7, No em dashes: `grep -rn "$(printf '\xe2\x80\x94')"` over changed files, in code, comments, docs, and commit messages; returns nothing.314. **Fix all violations and re-run the checklist until clean.** A single pass is never assumed sufficient; fixes for one rule often introduce violations of another.325. **Run the project's formatter and linter** (e.g. `prettier --check .` + `eslint .`, `ruff format --check && ruff check`, `gofmt -l . && go vet ./...`); fix every reported issue and re-run until clean.336. **Verify behavior.** Run the project's tests for the touched area; debug failures before declaring done.3435## Reference Guide3637Load detailed guidance only when the task needs it:3839| Topic | Reference | Load When |40|-------|-----------|-----------|41| Reuse and structure (rules 1-2) | `references/reusability-and-structure.md` | Adding a helper/util, seeing duplicated logic, deciding module boundaries or where code lives |42| Readability and naming (rule 3) | `references/readability-and-naming.md` | Naming anything, untangling nested conditionals, code that needs a comment to be understood |43| AI slop checklist (rule 4) | `references/ai-slop-checklist.md` | Self-reviewing any diff, cleaning generated code, reviewing a PR |44| Comments, dead code, em dashes (rules 5-7) | `references/comments-and-dead-code.md` | Writing or deleting comments, removing a feature/flag/branch, touching a file with leftover code |4546## Key Patterns4748**Extract on the second occurrence, not the first, not the third:**4950```typescript51// Two call sites already format money the same way: extract now.52const formatCents = (cents: number, currency = "USD") =>53 new Intl.NumberFormat("en-US", { style: "currency", currency }).format(cents / 100);54```5556**Early return instead of nesting:**5758```python59def ship(order: Order) -> Shipment:60 if not order.items:61 raise EmptyOrderError(order.id)62 if order.status is not Status.PAID:63 raise UnpaidOrderError(order.id)64 return carrier.dispatch(order)65```6667**Comment carries the why, code carries the what:**6869```go70// Stripe retries webhooks for 3 days; dedupe on event ID or refunds double-apply.71if store.SeenEvent(evt.ID) {72 return nil73}74```7576## Common Mistakes7778- Writing a new helper without searching first. The repo almost always has one; grep for the domain term (`grep -rn "formatCurrency\|toMoney"`) before writing line one.79- Extracting an abstraction for a single caller. One implementation needs no interface, factory, or base class; wait for the second concrete use.80- "Cleaning up" by commenting code out. Delete it; version control is the archive. Commented-out code fails review outright.81- Adding try/catch that only logs and re-throws, or swallows. Catch only where you can handle or add context; otherwise let it propagate.82- Narrating comments (`// increment counter`) and section markers (`// helpers`). Both are deleted on sight; restructure the code instead.83- Defensive null checks after calls whose types guarantee non-null. Trust the type system; a gratuitous check hides the real contract.84- Leaving the checklist at one pass. A rename fixes rule 3 but can orphan an export (rule 5); loop until a full pass reports nothing.