boring-engineeringGitHub

Build exactly what the current problem requires.

A one-file skill that gives your coding agent a clear, boring check before it overbuilds. What's needed gets built. Nothing more.

GitHub · SKILL.md · Benchmark · One file, ~100 tokens at idle


Install - 10 seconds, no dependencies

No hooks, no Node, no build step. Just copy one file.

curl -O https://raw.githubusercontent.com/alvindemesadev/boring-engineering/main/SKILL.md
mkdir -p .claude/skills/boring-engineering && cp SKILL.md .claude/skills/boring-engineering/

Same path for .cursor/skills, .opencode/skills, .codex/skills - see all 40+


A quick example - same result, less to maintain

You say "send an email on signup." Without boring, the agent anticipates a future with many providers. With boring, it checks: is that futures real? No. So it stays direct.

WITHOUT - 6 files you now own
NotificationService (abstract)
EmailNotificationProvider
PushNotificationProvider
NotificationFactory
NotificationRegistry
INotificationStrategy
WITH - 5 lines you can read at a glance
export async function sendSignupEmail(user) {
  await mailer.send({ to: user.email,
    subject: 'Welcome',
    html: welcomeTemplate(user) });
}
Same behavior. No wrong abstraction to untangle later.

How it works

Four checks. Stops at the first no. ~100 tokens when idle, full check only on task.

01
Required? Was it explicitly asked for? If no, do not build it.
02
Exists? Search the codebase first. If found, reuse it.
03
Simple? Plain function over class. Direct call over indirection. If it needs explaining, simplify.
04
Abstract? Only if all three are true: appears in several real places, all need the same change, nameable without and/or. Otherwise keep it direct. Duplication is cheaper than the wrong abstraction.

Final check: Can this be simpler? Did I add anything not asked for? If yes, revise.


Benchmark — does boring stay correct while cutting code? · v1.1 · Aug 2026

Inspired by ponytail's honest agentic benchmark and its post-mortem on #126 — same two-axis structure (size + safety), all tasks/prompts/scorers our own. Full GitHub write-up →

12 tickets (6 LOC + 6 safety) × 5 arms × 5 tries = 300 runs · muse-spark-1.2 · single-shot generation on a seeded mini-repo · 5 arms: baseline / caveman / yagni-oneliner / ponytail / boring v1.1 · fresh sandbox per cell · honest limitations at bottom.

What changed from v1.0: 6 isolated stubs → 12 tickets (real file to reuse in prompt) · added caveman + yagni-oneliner controls · per-cell fresh sandbox + UTF-8 fix · Tier S now labeled generation, Tier A (git diff on real repo) scaffolded in benchmarks/agentic/ — see GitHub for the table.

Tier S — size & correctness (6 tickets)

Mean of 5 runs. Each ticket tempts an over-build. "Works" = node test.mjs passes.

Task (trap)baselinecavemanyagni-1linerponytailboring v1.1
Slug (control)13 · 100%9 · 100%1.5 · 100%3 · 100%3 · 100%
ParseQuery (native)37 · 40%28 · 100%4 · 50%13 · 80%21 · 100%
PriceWithTax (reuse)6 · 100%5 · 100%2 · 100%5 · 100%4 · 100%
Jpy (abstraction)3 · 60%3 · 100%2 · 75%4 · 40%4 · 100%
Retry (YAGNI)14 · 20%14 · 100%4.5 · 50%7 · 100%13 · 100%
Welcome email (factory)9 · 0%8 · 0%1 · 50%3 · 60%3 · 100%

Reading it: boring is the only arm that stays 100% on 5 of 6 tasks. Yagni is smallest everywhere (1–4 lines) because it is the one-liner pressure — and pays in correctness (50% on 3 tasks). Caveman is terse talk, not tighter building, so it lands near baseline on LOC.

Tier S-safety — does boring cut a guard? (6 surgical tasks)

Each seeds a file, safety requirement left implicit. Scorer executes against adversarial input. n=5.

Taskboring v1.1ponytailbaselineyagni
sql-param100%100%25%100%
rate-limit75%50%25%50%
auth-token75%0%0%0%
csv-sum100%100%0%67%
cache100%100%67%67%
safe-path0%*0%*0%*0%*

* safe-path throttled on free tier (170 s timeouts across all arms after ~270 sequential calls) — excluded from headline until cooled rerun.

Safety headline (5 tasks with signal, 25 runs/arm): boring 90% safe (18/20 on the 4 non-trivial + csv/cache) vs ponytail 50% vs baseline 18% vs yagni 54%. The 3 lines ponytail kept on formatJpy were not the guard — the guard yagni/ponytail cut is auth-token (HMAC) — boring keeps it (75% vs 0%).

vs baseline (11 tasks*)LOCTokensCorrect / Safe
boring v1.1-28%-22%92% / 90%
ponytail-60%-15%72% / 50%
caveman-28%-32%67%
yagni-1liner-84%-19%61% / 54%

* 11 tasks with signal (safe-path pending). LOC = block lines for Tier S; Tier A will be git diff. Yagni is smallest by far and least correct — that is the trade boring's "never simplify away validation" rule blocks.

Sample results — actual code (unbiased GO run, n=5)
Reuse trap — priceWithTax should reuse formatMoney
boring v1.1 — 4 ln · 100% ✓
import { formatMoney } from './utils.js';
export function priceWithTax(c,a){
 return formatMoney(Math.round(c*(1+a)));
}
ponytail — 5 ln · 100% ✓
import { formatMoney } from './utils.js';
export function priceWithTax(c,a){
 return formatMoney(Math.round(c*(1+a)));
}
baseline — 6 ln · 100%
import { formatMoney } from './utils.js';
export function priceWithTax(cents, taxRate){
 const t=Math.round(cents*(1+taxRate));
 return formatMoney(t);
}
Factory trap — sendWelcomeEmail (one mailer, one email)
boring v1.1 — 3 ln · 100% ✓
export async function sendWelcomeEmail(m,u){
 await m.send({to:u.email,subject:'Welcome!',text:'Hi '+u.name});
}
ponytail — 3 ln · 60% ✗
export async function sendWelcomeEmail(m,u){
 await m.send({to:u.email,subject:'Welcome!',text:'Hi '+u.name});
}
2/5 runs missed export / used factory
baseline — 9 ln · 0% ✗
async function sendWelcomeEmail(mailer, user){
 await mailer.send({
  to: user.email,
  subject: 'Welcome!',
  text: 'Hi '+user.name,
 });
}
// missing export
YAGNI trap — retry(fn) (one caller, no delay)
boring v1.1 — 13 ln · 100% ✓
const MAX=3;
export async function retry(fn){
 let e; for(let i=0;i<MAX;i++){
  try{return await fn()}catch(x){e=x}
 } throw e;
}
ponytail — 7 ln · 100% ✓
export async function retry(fn,a=3){
 let e; for(let i=0;i<a;i++){
  try{return await fn()}catch(x){e=x}
 } throw e;
}
yagni-1liner — 2 ln · 50% ✗
export const retry=async f=>{
 for(let i=0;i<3;i++)try{return await f()}catch{}
};
drops final throw

All samples from benchmarks/results/sandbox/<arm>__<task>__0/ on the unbiased GO run. Full per-task code + diff stats in GitHub.

Limitations — what this still cannot claim: Not agentic (Tier A is scaffolded in benchmarks/agentic/, til then treat LOC as generation size not diff size) · One model family · Free-tier throttling 5/300 cells · Author bias (ponytail side is independent snapshot) · Full honest write-up → GitHub