Coding Rules that operationalize the YAGNI (You Aren’t Gonna Need It) principle for modern TypeScript projects using React, Node.js and CI/CD.
Every feature you build "just in case" becomes technical debt the moment you ship it. These Cursor Rules implement the YAGNI (You Aren't Gonna Need It) principle as executable guardrails in your TypeScript development workflow, helping you ship faster by building less.
You know the drill: a simple feature request becomes a two-week engineering project because you're solving problems that don't exist yet. That flexible architecture you built for "when we scale"? It's been maintaining itself for six months while your actual users wait for basic functionality.
The hidden productivity killers:
These rules transform YAGNI from a philosophy into enforced development practices that keep your codebase lean and your delivery fast.
These aren't just coding guidelines—they're automated guardrails that prevent over-engineering at the point of creation. Instead of debating whether you need that abstraction in code review, these rules guide you to build exactly what's needed, when it's needed.
Build in Vertical Slices: Every change delivers user value immediately. No infrastructure without features, no abstractions without concrete use cases.
Fail-Fast Development: Validate requirements early, challenge assumptions continuously, and delete speculatively written code before it becomes technical debt.
Evidence-Based Complexity: Introduce abstractions only when you have concrete evidence they're needed—not when you think they might be.
// Speculative over-engineering
interface UserPreferences {
theme: 'light' | 'dark' | 'auto' | 'custom'; // "custom" never implemented
notifications: NotificationSettings; // complex nested object
privacy: PrivacySettings; // built for GDPR that never came
accessibility: A11ySettings; // empty interface "for later"
}
// Helper utility used in one place
export const isValidEmail = (email: string): boolean => {
// 50 lines of regex for edge cases we don't handle
};
// Generic error hierarchy for future use cases
class BaseError extends Error {}
class ValidationError extends BaseError {}
class NetworkError extends BaseError {}
class DatabaseError extends BaseError {} // no database yet
// Build exactly what's needed today
interface UserPreferences {
theme: 'light' | 'dark'; // only implemented themes
emailNotifications: boolean; // single boolean, not nested object
}
// Inline validation at point of use
function validateSignupForm(email: string) {
if (!email.includes('@')) {
throw new Error('Invalid email format');
}
// Add complexity when requirements demand it
}
// Single error type until second type is needed
class UserError extends Error {
constructor(message: string) {
super(message);
this.name = 'UserError';
}
}
Ship vertical slices that deliver user value immediately instead of building complete feature sets upfront. Deploy working software in days, not weeks.
Automated cleanup rules delete unused exports, empty interfaces, and TODOs older than one sprint. Your codebase stays lean and navigable.
Built-in YAGNI checklist questions eliminate debates about premature optimization and speculative features. Focus reviews on actual requirements.
CI enforces that every commit delivers complete, tested functionality. No more "work in progress" branches that never ship.
// ❌ Speculative component with unused props
interface ButtonProps {
variant: 'primary' | 'secondary' | 'tertiary' | 'ghost'; // only primary used
size: 'xs' | 'sm' | 'md' | 'lg' | 'xl'; // only md implemented
loading?: boolean; // never used
leftIcon?: ReactNode; // never used
rightIcon?: ReactNode; // never used
}
// ✅ YAGNI-first component
interface ButtonProps {
variant: 'primary'; // add variants when designs require them
children: ReactNode;
}
function Button({ variant, children }: ButtonProps) {
return <button className="btn-primary">{children}</button>;
}
// ❌ Over-engineered from day one
app.get('/api/users/:id',
authMiddleware, // no auth requirements yet
rateLimitMiddleware, // no scaling problems yet
validationMiddleware, // complex validation for unused fields
cacheMiddleware, // no performance issues yet
async (req, res) => {
// Supports filtering, sorting, pagination that aren't used
}
);
// ✅ Start simple, add complexity when needed
app.get('/api/users/:id', async (req, res) => {
const user = await getUserById(req.params.id);
if (!user) {
throw new UserError('User not found');
}
res.json(user);
});
// ❌ Redux setup for simple form
const userSlice = createSlice({
name: 'user',
initialState: { /* complex state tree */ },
reducers: { /* actions for future features */ }
});
// ✅ Local state until sharing is required
function UserProfile() {
const [user, setUser] = useState<User | null>(null);
// Introduce Context/Redux when 3+ components need this state
return (
<form onSubmit={handleSubmit}>
{/* form implementation */}
</form>
);
}
# Add to your .cursorrules file
curl -o .cursorrules https://example.com/yagni-typescript-rules
// tsconfig.json - Enable strict mode
{
"compilerOptions": {
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true
}
}
// package.json - Add YAGNI-enforcing scripts
{
"scripts": {
"lint:dead-code": "ts-unused-exports tsconfig.json",
"test:coverage": "jest --coverage --coverageThreshold='{\"global\":{\"lines\":90}}'",
"clean:todos": "grep -r 'TODO\\|FIXME' src/ && exit 1 || exit 0"
}
}
# .github/workflows/yagni-checks.yml
name: YAGNI Enforcement
on: [pull_request]
jobs:
yagni-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Check for dead code
run: npm run lint:dead-code
- name: Validate test coverage
run: npm run test:coverage
- name: Clean old TODOs
run: npm run clean:todos
## YAGNI Checklist
- [ ] Does every new function/class solve a current user story?
- [ ] Are all interfaces used by at least one concrete implementation?
- [ ] Have I removed any dead code I discovered while working?
- [ ] Do my tests cover actual requirements, not speculative edge cases?
- [ ] Can I simplify this change without losing functionality?
"We shipped our MVP 40% faster after implementing YAGNI rules. Instead of building a complete user management system, we shipped user registration and added features as users actually needed them." — Senior Full-Stack Developer
"Our code reviews went from 2-hour architecture discussions to 20-minute requirement validations. The YAGNI checklist eliminated most bikeshedding." — Engineering Team Lead
These rules don't just change how you code—they change how you think about software development. Every line becomes intentional. Every feature solves a real problem. Every abstraction earns its place through usage, not speculation.
Ready to ship faster by building less? Install these YAGNI-First TypeScript Rules and transform your development workflow from speculative architecture to user-focused delivery.
Your users don't need your perfect future system. They need your working current solution.
You are an expert in TypeScript, Node.js (Express), React, Jest, ESLint, Prettier, GitHub Actions, and Agile/XP delivery.
Key Principles
- Build ONLY what is demonstrably required today—defer every "nice-to-have" until it becomes a real, prioritised need.
- Ship in "vertical slices": the smallest test-covered change that delivers user value.
- Ruthlessly delete dead code, TODOs older than one sprint, and unused abstractions.
- Continuously collaborate with stakeholders to validate actual needs; challenge every "what-if" assumption in grooming and code-review.
- Prefer composition over generalisation; resist premature framework or library adoption.
- Optimise later; profile-driven improvements only after a proven bottleneck.
- CI must stay green at all times—no half-finished speculative branches on main.
TypeScript
- Use strict mode (`"strict": true`) to surface issues early; avoid speculative type unions "just in case".
- Start with concrete interfaces; generalise only when a second concrete use-case appears.
- Keep functions < 40 LOC; split once additional, not-yet-needed params creep in.
- Reject empty enum/empty interface placeholders during PR review.
- Module layout: `index.ts` exports exactly what is live; remove legacy exports within same PR as their last usage.
- Prefer inline type-guards over creating global util "isFooBarBaz" until >1 call site.
Error Handling and Validation
- Fail‐fast: validate inputs at top of function and `throw` early.
- Distinguish user-facing vs developer errors (`UserError`, `SystemError`). Do not pre-define an error hierarchy until >2 custom error classes are required.
- Leverage `zod`/`io-ts` only once schemas are reused across at least two endpoints/components.
- Unit tests must assert error paths for every current validation rule; omit tests for speculative rules.
React (Frontend Framework)
- Functional components only; introduce a custom hook once logic is duplicated.
- Limit local state to present requirements; postpone Context/Redux/MobX until state is shared by ≥3 sibling branches.
- CSS-in-JS: start with scoped module CSS; migrate to styled-components/tailwind only once style reuse pain is explicit.
- Accessibility (a11y) checks are mandatory and never deemed "premature".
Node.js / Express (Backend Framework)
- Single responsibility routers; split when file >300 LOC or route groups exceed 7.
- Middleware pipeline: add middlewares incrementally; avoid global error-logger until errors need cross-cutting enrichment.
- Use in-memory data store for prototyping; introduce Redis/Postgres only when persistence/scaling becomes a stated requirement.
Testing
- 100 % automated tests for all implemented behaviour; do not speculatively test future edge-cases.
- Use Jest with `--coverage`; fail CI if coverage of touched lines drops.
- Contract tests for external APIs only after an outage/regression is experienced.
Performance
- Baseline with Lighthouse & clinic.js after first release;
optimise only bottlenecks with >100 ms user-visible impact.
- No webpack/js bundle splitting until ‑-analyze shows >250 kB initial chunk.
Backlog & Process
- Groom weekly; close or re-estimate any ticket older than two sprints with no owner.
- Definition of Ready excludes "future phases" tasks.
- PR template includes a YAGNI checklist: "Does this line/service/dep solve a current story?" ✔/✖.
Security
- Always patch known vulnerabilities immediately; security fixes are never YAGNI.
- Use OWASP top-10 as non-negotiable acceptance criteria.
Documentation
- README covers only how to run, test, and deploy today’s functionality.
- ADRs are created only for decisions with proven long-term impact (e.g., DB selection).
CI/CD
- GitHub Actions: lint → test → build → deploy stages; introduce canary rollout only after a failed prod deploy.
- Auto-delete feature branches once merged.
Common Pitfalls & Guardrails
- "Helper" utils folder grows >10 files → refactor to domain-specific locations or delete.
- Dependency count watch: CI fails if new transitive deps >5 without justification.
- Reject PRs adding feature flags without a ticket linking to an immediate use-case.