pedantic-coder/skills/dead-code-intolerance/SKILL.md
This skill should be used when the user's code contains commented-out code blocks, unused imports, unreachable branches, TODO/FIXME comments that have been sitting for more than a day, or comments referencing removed functionality. Covers the elimination of dead code and the rule that version control remembers so you don't have to.
npx skillsauth add oborchers/fractional-cto dead-code-intoleranceInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Dead code is not harmless. It is actively harmful. It confuses new developers who do not know whether it is intentional. It gets updated during refactors by people who do not realize it is unused. It triggers false positives in grep and IDE searches. It creates the illusion of complexity where none exists. And it tells every reader: "The author of this codebase does not clean up after themselves."
Version control exists. Git remembers everything. You do not need to.
Delete it.
Commented-out code is not "kept for reference." It is not "just in case." It is not "might need later." It is dead weight that tells the reader nothing useful and everything bad. If you need the old implementation, git log has it. If you need to compare approaches, create a branch. If you think you might need it tomorrow, you will not. And if you do, git log still has it.
Zero exceptions. Not even one line. Not even "temporarily." The moment code is commented out, it starts rotting -- the surrounding code evolves, the commented code does not, and within a week it no longer works anyway.
# BAD -- every one of these must be deleted
# user = get_user(user_id)
# if user.is_admin:
# return admin_dashboard()
# Old implementation (keeping for reference)
# def calculate_tax(amount):
# return amount * 0.08
# Commented out until we fix the race condition
# await sync_inventory()
# GOOD -- the code that exists is the code that runs. Nothing else.
user = get_user(user_id)
if user.is_admin:
return admin_dashboard()
An unused import is a lie. It tells the reader "this module is used here" when it is not. It increases startup time. It creates false dependency graphs. And "but I might need it later" is what autocomplete is for.
// BAD -- three of these imports are unused
import { useState, useEffect, useCallback, useMemo, useRef } from "react";
import { format } from "date-fns";
import { clsx } from "clsx";
function Counter() {
const [count, setCount] = useState(0); // only useState is used
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
// GOOD -- import exactly what you use
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
# BAD -- os and sys are unused
import os
import sys
from datetime import datetime, timedelta
def days_until(target: datetime) -> int:
return (target - datetime.now()).days
# GOOD
from datetime import datetime
def days_until(target: datetime) -> int:
return (target - datetime.now()).days
Code that can never execute is not a safety net. It is a hallucination. if (false), debug flags that are never set, catch blocks for exceptions that cannot be thrown, default cases in exhaustive switches -- if the runtime will never reach it, the reader should never see it.
// BAD -- DEBUG is a compile-time constant set to false
const DEBUG = false
func processRequest(req *Request) {
if DEBUG {
log.Printf("Processing: %+v", req) // unreachable
}
// ...
}
// GOOD -- if you need debug logging, use a proper logging framework with levels
func processRequest(req *Request) {
slog.Debug("processing request", "request_id", req.ID)
// ...
}
// BAD -- the type system guarantees status is "active" | "inactive"
function handleStatus(status: "active" | "inactive") {
switch (status) {
case "active":
return activate();
case "inactive":
return deactivate();
default:
// This can never execute. TypeScript knows it. You know it. Delete it.
throw new Error("Unknown status");
}
}
// GOOD -- exhaustive switch, no dead default
function handleStatus(status: "active" | "inactive") {
switch (status) {
case "active":
return activate();
case "inactive":
return deactivate();
}
// TypeScript's exhaustiveness checking handles the rest
const _exhaustive: never = status;
}
TODO comments are where good intentions go to die. They sit in the codebase for months. Years. They reference tickets that were closed long ago. They describe bugs that were fixed in a different way. They accumulate until searching for TODO returns 400 results and nobody reads any of them.
The rule is simple: either fix it right now, or create a tracked issue in your project management tool and delete the comment. A TODO without a linked ticket is an untracked bug. An untracked bug is a bug that will never be fixed.
# BAD -- these TODOs are never getting done
# TODO: optimize this query
results = db.query("SELECT * FROM users")
# FIXME: handle edge case where amount is negative
total = sum(amounts)
# TODO(john): refactor this when we migrate to v2
# (John left the company 8 months ago)
# GOOD -- the code is clean, the issue tracker has the work items
results = db.query("SELECT id, email, name FROM users WHERE is_active = true")
total = sum(amount for amount in amounts if amount >= 0)
Comments that describe what the code used to do, what was previously tried, or what was removed are git commit messages that someone pasted into the wrong place. The code describes what it does now. The history of what it used to do belongs in version control.
// BAD -- none of this helps the reader understand the current code
// This used to be configurable but we hardcoded it in Q3
const TIMEOUT_MS = 5000;
// We removed the old validation because it was too slow
// (see commit abc123 for the original implementation)
function validateInput(input: string): boolean {
return input.length > 0 && input.length <= 1000;
}
// Was previously using Redis but switched to in-memory cache
const cache = new Map<string, CacheEntry>();
// GOOD -- the code says what it does. Git says why it changed.
const TIMEOUT_MS = 5000;
function validateInput(input: string): boolean {
return input.length > 0 && input.length <= 1000;
}
const cache = new Map<string, CacheEntry>();
A feature flag that has been at 100% rollout for more than one sprint is dead code in disguise. The flag check, the old code path, and the configuration all need to go. Feature flags are for safe rollouts, not permanent architecture.
# BAD -- this flag has been 100% for six weeks
def get_pricing(user: User) -> PricingPlan:
if feature_flags.is_enabled("new_pricing_engine", user):
return new_pricing_engine.calculate(user)
else:
# Nobody has hit this branch since November
return legacy_pricing.calculate(user)
# GOOD -- the flag is removed, the old path is deleted
def get_pricing(user: User) -> PricingPlan:
return pricing_engine.calculate(user)
If nothing calls it and nothing imports it, it does not exist. It just has not been deleted yet. "But someone might need it" is not a reason to keep code. It is a reason to have good git history. Unused code costs you in every refactor, every search, every onboarding.
// BAD -- FormatLegacyDate is not called anywhere in the codebase
func FormatLegacyDate(t time.Time) string {
return t.Format("01/02/2006")
}
func FormatDate(t time.Time) string {
return t.Format(time.RFC3339)
}
// GOOD -- only the code that is used exists
func FormatDate(t time.Time) string {
return t.Format(time.RFC3339)
}
Dead code is not free. Every line of dead code:
The fix is always the same: delete it. If you were wrong and you need it back, git log and git checkout are right there.
Working implementations in examples/:
examples/dead-code-patterns.md -- Multi-language examples showing dead code identification and removal across Python, TypeScript, and Go, covering commented-out code, unused imports, unreachable branches, stale TODOs, and dead feature flagsWhen reviewing code for dead code:
if (false), no dead defaults in exhaustive switches, no catch blocks for impossible exceptionstools
This skill should be used when the user invokes any /plan-* command from the planning-tools plugin (/plan-context, /plan-master, /plan-open-questions, /plan-verify, /plan-tick, /plan-progress, /plan-delete), asks how Claude Code's plan files work, asks where plans are stored, asks to author or audit a multi-phase master planning document, asks how to walk through a plan's Open Questions interactively, asks how to write progress entries, or mentions ~/.claude/plans/ or .claude/planning-tools.local.md. Provides the index of planning-tools commands, the master-plan workflow lifecycle, the v0.3.0+ list-shape mandate (phases and questions as headings + bulleted scope items, never tables), the v0.3.2+ plain-bullet shape (no `- [ ]` checkboxes — heading emoji is the sole tick signal), the progress-entry methodology, and the mechanics of Claude Code's plan-mode file storage.
testing
This skill should be used by the plan-verifier agent and the /plan-verify command to audit a drafted master plan against a fixed checklist. Covers universal-core completeness, the v0.3.0+ no-tables-for-phases-or-questions rule, trigger-based section-coverage gaps, phase actionability (heading + per-phase TL;DR + bulleted scope + exit criteria), the v0.3.1+ per-phase TL;DR requirement, the v0.3.2+ plain-bullet scope shape (legacy `- [ ]`/`- [x]` accepted silently), the v0.3.3+ context-block shape (plan-level `**TL;DR:**` + bulleted metadata, legacy `>` blockquote accepted silently), integer phase numbering enforcement, dependency traceability, citation resolution, callout/evidence convention compliance, Open Questions placement, and the one-PR-per-master-plan rule. Single-owner of the audit checklist.
tools
This skill should be used when authoring, reviewing, or modifying a multi-phase master planning document via the planning-tools plugin (especially the /plan-master and /plan-verify commands). Codifies the universal core sections, trigger-based optional sections, integer-only phase numbering, Open Questions placement, one-PR-per-plan rule, status conventions, evidence attribution, callouts, cross-reference formats, the v0.3.0 list-shape mandate (phases and questions are heading + bulleted list, never markdown tables), the v0.3.1 per-phase TL;DR requirement (1–3 sentence what/why summary under each phase heading for glance-ability), the v0.3.2 plain-bullet scope shape (`- <action>` items, no `- [ ]` checkboxes — the phase status emoji is the sole tick signal), and the v0.3.3 context-block shape (a plan-level `**TL;DR:**` + a bulleted metadata list instead of a `>` blockquote; legacy blockquote blocks accepted silently). Project-agnostic — no ticket-prefix or plan-type taxonomy.
testing
This skill should be used when the user is adjusting spacing, padding, margins, content density, section gaps, vertical rhythm, or separation between elements. Also applies when reviewing whether a design feels cramped or too sparse, choosing between borders and whitespace for separation, or defining a spacing system. Covers the 4px/8px spacing system, macro vs micro whitespace, content density spectrum, separation techniques (whitespace > background shifts > borders), and vertical rhythm.