YAGNI — build only what's needed, and make it work
Cut scope, never correctness. "Needed" means: satisfies the accepted task and every commitment it inherits — preserved behavior, durable data, public compatibility, supported environments, security, privacy, accessibility, observability, recovery, rollback, accepted assurance gates — and runs. The latest prompt need not repeat those for them to bind.
When to use
- Before the first edit of any behavior-affecting task, alongside
test-driven-development, and again before calling the work ready. - Skip a throwaway spike answering one question, and non-behavioral config or content: nothing there has task behavior to scope.
Step 1: Before the first edit
Write the completion checklist from the accepted task, the commitments above, and the project's
coding-standardsexemplar. No exemplar → the nearest file in the codebase that does the same kind of thing. List names, structure, idioms, where a non-obvious why is explained.Trace the real flow. Change the owner of the behavior, not the first path that shows the symptom. Unknown cause → invoke
debuggingfirst.For each piece of the change, walk the ladder and stop at the first rung that holds:
- Speculative need → skip it and say so.
- Something in the codebase fits, same semantics, owner, dependency, lifecycle → reuse it.
- Standard library → use it.
- A native feature or constraint → use it over owned machinery.
- An installed dependency → use it. Add none for a few lines.
- One line → one line.
- Only then, the minimum code that fully works.
Arguable rung → read
references/yagni-in-depth.md.Add no abstraction for hypothetical variation. One real volatile or external boundary earns a seam only for measured impedance, a failure policy, or test isolation.
Lasting surface (new dependency, service or process, generalized abstraction, public extension point or config knob, verification system), or a strict challenge requested → dispatch
references/yagni-challenger.mdread-only and wait.reviseordecision→ blocked as written.inconclusive→ not clearance. Local, easily reversed choices stay inline.
Step 2: Before calling it ready
- Read every changed line against the Step 1 checklist. A green test waives no convention visible in the file. A neighbour already drifted → report it; do not migrate it.
- Sort every candidate cut into two lists. Cut only from the first.
- Minimal: removes abstractions, files, dependencies, or lines while every guarantee holds.
- Unfinished: removes behavior or an inherited commitment, or leaves a stub, TODO, unhandled path, untested logic, unreadable code.
- Delete only what you proved unused: no static, runtime, reflection, config, generated, or external consumer, or a completed deprecation. Unknown → stays, or goes to migration or refactor ownership.
- REQUIRED SUB-SKILL: invoke
verifying-completionbefore the claim leaves this skill.
When you're tempted to call it done
| The thought | The reality |
|---|---|
| "Simplest version: just stub this / return a placeholder" | A stub is an unsolved task, not a simpler solution. |
| "I'll keep it minimal and leave a TODO" | YAGNI defers unneeded features, never needed behaviour. |
| "Shortest diff wins, so I'll touch the smallest spot" | Smallest diff in the wrong place is a second bug. |
| "Skipped the error/edge handling to stay lean" | Correctness is never the thing you minimise. |
| "Simple enough to be obviously right — didn't run it" | Unverified is not done. Run it. |
| "Deleted that code, it looked unused" | Removing needed behaviour to shrink the diff is under-delivery. |
| "Ship the quick version, clean it up later" | Later never comes; every future change pays the re-reading cost. Readable now is the cheaper path. |
| "Clear names and comments are gold-plating" | Gold-plating is unneeded features. Clarity is maintenance cost — the thing this skill exists to protect. |
| "My usual style beats this file's conventions" | A codebase in one voice is cheaper to change than your personal best practice. Match it. |
Hard stops
- Minimal ≠ incomplete. Smaller breaks ties only between solutions that both solve the task and run.
- Before done: real checks pass, implied inputs work, and no required path has a
stub/TODO/placeholder; confirm through
verifying-completion. - Never cut trust-boundary validation, data-loss prevention, security, accessibility, or explicit user scope.
- Never minimise away: preservation, compatibility/parity, durable-data safety, observability, recovery/rollback, or a required migration/assurance gate. Their owning contracts define the guarantee; YAGNI cannot silently weaken it.
- Minimise only ceremony owning no guarantee: duplicate gates, speculative abstractions, and uncommitted platforms/knobs.
- Never compress domain names, non-obvious why, or governing conventions.
- Never delete by confidence. Prove static/runtime/reflection/config/generated/ external consumer absence or completed deprecation; unknowns stay or route to migration/refactor ownership.