Imported from jankneumann/agentic-coding-tools (
skills/simplify/SKILL.md). Install upstream withnpx skills add jankneumann/agentic-coding-tools --skill simplify. Copyright stays with the author.
Simplify
Inspect a focused diff, file, or module for behavior-preserving simplifications: dead code, deep nesting, long functions, premature abstractions, generic names, and isomorphic DRY extracts. The goal is fewer moving parts and faster comprehension — not stylistic preference and not fewer lines for their own sake.
This skill is read → pin → edit: it reviews first, applies a coverage gate, writes characterization tests when needed, gates each candidate against Chesterton's Fence, applies changes one pattern at a time, and dual-runs the suite. Large surface areas are deferred (Rule of 500).
Primary invoke: /simplify
Invocation mode: manual only — operators (or explicit human request) run this skill. Autopilot and implement-feature do not auto-run simplify by default.
When to Use
- After a feature is green and the implementation feels heavier than needed
- During review when readability / complexity is flagged without a behavior bug
- When tech-debt reports local Long Method, Deep Nesting, or local Duplication
- When consolidating duplicated logic that should share one helper
- Optional polish after
/implement-featureor/iterate-on-implementation(separaterefactorcommits)
When NOT to Use
| Situation | Do this instead |
|---|---|
| You do not yet understand the code | Read, blame, and map callers first — then return |
| Behavior or public contracts must change | Feature / fix workflow (implement-feature, TDD) |
| Performance rewrite with different algorithms | /performance-optimization |
| Removing a public or multi-consumer surface | /deprecation-and-migration |
| Hub / coupling / multi-module redesign | /plan-feature (Rule of 500 / structural debt) |
| Cleanup mixed into an in-progress feature | NOTICED BUT NOT TOUCHING: + later /simplify |
| Code is already clear | Stop — do not simplify for its own sake |
Scope
- Run on the current diff, a specified file/module, or a tech-debt finding ID.
- Production edits only after the coverage gate passes.
- Characterization commits may add tests; simplify commits must not change assertion bodies.
- Single-PR / small-batch changes. Cross-cutting refactors follow Rule of 500 or escalate.
Principles
1. Preserve behavior exactly
Same inputs → same outputs, errors, side effects, and ordering. If unsure, do not change it.
2. Tests are the isomorphism proof
Observable equivalence is proven by behavioral tests, not by agent confidence. Prefer state-based tests (inputs/outputs) over interaction mocks so structure can move without rewriting tests — see test-driven-development (Beyoncé Rule, DAMP tests).
3. Follow project conventions
Match neighboring code: imports, naming, error handling, typing depth. External “clever” idioms that fight the codebase are churn, not simplification.
4. Clarity over cleverness and line count
A short nested ternary is not simpler than an explicit branch. Over-inlining that erases a useful name is a failure mode.
5. Scope to what you intended
No drive-by refactors outside the named surface unless the operator broadens scope.
Chesterton's Fence — Pre-Simplification Check
Before removing or refactoring any non-trivial piece of code, answer all three. If any answer is "I don't know," stop and investigate.
- Why does this exist?
git blame, introducing commit message, callers (grep), tests that pin it. - What problem does it still solve? Rate limits, retries, ordering, error masking, security boundaries — load-bearing fences stay.
- What non-obvious invariants does it preserve? Idempotency, transactional boundaries, timezone normalization, injection defense.
If (2) is "nothing — reason is gone," the fence may come down. Otherwise leave it (or document why in a # CHESTERTON: kept because … comment).
Coverage Gate (required)
Surface under edit
│
▼
Existing state-based tests pin inputs/outputs/errors/side effects?
│
yes │ no
│ └──► CHARACTERIZE first:
│ • Write tests that pass on CURRENT code (green-on-baseline)
│ • Prefer real impl / fakes over interaction mocks
│ • Commit: test(<scope>): pin behavior for <surface>
│ • Only then proceed to production edits
▼
Continue to candidate list / Chesterton / edits
If you cannot pin the surface, you cannot simplify it. Hope is not a dual-run.
Characterization tests are not a license to change behavior later in the same PR — they freeze today's behavior so refactors cannot silently drift.
Rule of 500
Simplifications that touch more than 500 lines OR more than 5 files SHALL NOT be done by hand.
When exceeded:
- (a) Automate — codemod / AST tool (libcst, ts-morph, jscodeshift) with reviewable automation.
- (b) Split — one module / one pattern / one PR; repeat.
- (c) Escalate —
/plan-featurefor design + review gates.
Mechanical check (recommended):
python3 "<skill-base-dir>/scripts/check_scope.py" --base <baseline-sha>
# oversized? re-run with --allow-codemod only when a real codemod produced the diff
Pattern catalog
Local clarity (existing)
| Pattern | Signal | Move |
|---|---|---|
| Deep nesting → guard clauses | 3+ levels of if/for/try |
Early return; happy path top-to-bottom |
| Long functions → extract helpers | ~50+ lines or multiple responsibilities | Named steps; outline stays in the parent |
| Nested ternaries → branches / maps | Ternary inside ternary | if/elif or lookup table |
| Boolean flag params → split | do(true, false) switches behavior |
Two named functions or options object |
| Generic names → domain names | data, info, obj, temp, result |
user_record, pending_invoice, … |
| Premature abstraction → inline | One-impl interface / factory-of-one | Inline; re-abstract when a second impl is real |
Isomorphic structure (added)
| Pattern | Signal | Move |
|---|---|---|
| Isomorphic extract | Same ≥~5-line structural block in 2+ sites | Shared helper; both sites call it. Requires characterization (or existing tests) on all sites |
| Dead code removal | Unreachable branches, unused private symbols, commented-out blocks | Remove only after Fence + reference search + tests |
| Redundant intermediate | Wrapper that only forwards, no policy | Inline; do not if public API, documented extension point, or Hyrum-visible |
Rebalance note: Inlining premature abstractions is still valid for single-use abstractions that are not extension points. Extracting real duplication is the dual — do not “inline” away a helper that names a real domain concept used in multiple places.
Workflow
0. Scope
Identify target: git diff, path, module, or tech-debt finding ID. Record the baseline SHA (tip before any simplify production edit; after characterization commits if those land first).
1. Understand (Chesterton's Fence)
Blame, callers, existing tests, edge cases. Read project conventions (AGENTS.md / CLAUDE.md / neighboring modules).
2. Coverage gate
Pin or characterize (see above). Run characterization tests and confirm green on baseline.
3. Candidate list
List opportunities by pattern. Drop any that fail Chesterton's Fence; note fences kept.
4. Rule of 500
Group remaining work. Automate, split, or escalate if over budget.
5. Apply incrementally
For each remaining candidate:
- Make one simplification.
- Run the targeted suite (then broader suite if targeted is green).
- If red → revert that simplification; re-evaluate.
- Commit:
refactor(<scope>): <pattern> — <brief>(e.g.refactor(parser): extract guard clauses from validate_input).
Never mix feat / fix with simplify polish in the same commit.
6. Dual-run verify
# Recommended mechanical dual-run (writes simplify-report.json by default).
# Prefer a project-local interpreter so detached worktrees resolve tools;
# the script also symlinks .venv / node_modules from the main repo when present.
python3 "<skill-base-dir>/scripts/verify_behavior_preservation.py" \
--baseline <baseline-sha> \
--test-cmd "python3 -m pytest -q" # or: .venv/bin/python -m pytest / npm test
# Assertion contract on the simplify range (should be clean for expectation bodies).
# --base MUST be the tip AFTER characterization commits.
python3 "<skill-base-dir>/scripts/check_test_contract.py" --base <baseline-sha>
python3 "<skill-base-dir>/scripts/check_scope.py" --base <baseline-sha>
Source-contribution-only example (this monorepo, not portable to consumers):
skills/.venv/bin/python -m pytest -q skills/tests/simplify/
Manual equivalent: run the same suite on <baseline-sha> and on HEAD; both must pass.
7. Report
Summarize: patterns applied, fences kept, characterization tests added, dual-run evidence (commands + exit codes or report path), Rule of 500 status.
Script helpers
Scripts live in <skill-base-dir>/scripts/ (installed copy under .claude/skills/simplify/scripts/ or .agents/skills/simplify/scripts/). They use only the standard library plus git.
| Script | Purpose | Exit |
|---|---|---|
check_scope.py |
Diff line/file counts vs Rule of 500 | 0 ok, 2 over limit without --allow-codemod, 1 error |
check_test_contract.py |
Detect assertion/expect body changes in test paths | 0 ok, 2 contract break, 1 error |
verify_behavior_preservation.py |
Run tests at baseline and HEAD in detached worktrees; write JSON report | 0 both green, 2 failure, 1 error |
check_test_contract.py expects --base at the tip after characterization commits. Within that range, any +/- assertion line (including deleted test files) is a contract break.
verify_behavior_preservation.py takes a trusted --test-cmd shell string (e.g. pytest -q). Both SHAs are checked out via temporary detached worktrees so a dirty working tree cannot skew results.
Language sketches (clarity, not prescription)
Python — guard clauses
# Before: nested happy path
def process(data):
if data is not None:
if data.is_valid():
return do_work(data)
raise ValueError("invalid")
raise TypeError("missing")
# After
def process(data):
if data is None:
raise TypeError("missing")
if not data.is_valid():
raise ValueError("invalid")
return do_work(data)
TypeScript — redundant boolean
// Before
function isValid(input: string): boolean {
if (input.length > 0 && input.length < 100) return true;
return false;
}
// After
function isValid(input: string): boolean {
return input.length > 0 && input.length < 100;
}
Prefer project idioms when they conflict with these sketches.
Handoffs
| Signal | Route |
|---|---|
| Local complexity / nesting / naming / local dup | Stay on /simplify |
| Tech-debt hub / high coupling / large redesign | /plan-feature |
| Dead public API / multi-consumer removal | /deprecation-and-migration |
| Measured perf bottleneck | /performance-optimization |
| Bug or missing behavior | /test-driven-development + fix (not simplify) |
Common Rationalizations
| Rationalization | Why it's wrong |
|---|---|
| "I don't need blame — this is obviously dead" | "Obviously dead" is the #1 subtle regression source. Blame and callers are free; use them. |
| "It's only 600 lines — I'll be careful" | Rule of 500 is about reviewability and tail risk, not ego. Automate or split. |
| "Tests pass so behavior is preserved" | Tests that never exercised the surface cannot pin it. Coverage gate first. |
| "I'll tweak the assertion — the new code is equivalent" | Expectation edits mean you changed observable behavior or the test was wrong. Revert the simplify; fix with an explicit behavior change outside this skill. |
| "I'll simplify while finishing the feature" | Mixed feat+refactor PRs hide regressions and break revertability. Separate commits/PRs; use NOTICED BUT NOT TOUCHING during implement. |
| "This abstraction will pay off later" | Speculative abstractions are cost without value. Inline until a second real implementation appears. |
| "Fewer lines is always simpler" | Nested one-liners can be harder to read. Optimize for comprehension speed. |
Red Flags
- Production simplify commits without a coverage-gate decision (existing pins or new characterization tests).
- A simplify PR that changes test assertion bodies to go green.
- Diff over 500 lines or 5 files with no codemod / split plan (Rule of 500 violation).
- Removed code with no blame / caller investigation recorded.
feat/fixmixed into the same commit as a clarity refactor.- Autopilot or implement silently running simplify without operator request.
- Inlined helper that deleted a comment documenting a non-obvious invariant (fence lost).
- Isomorphic extract landed without tests covering all rewritten call sites.
Verification
- Cite each pattern catalog entry applied in the PR/report.
- For removed/renamed/inlined constructs, cite blame or introducing commit (Chesterton's Fence).
- Confirm coverage gate: either list existing pinning tests or show the characterization commit (
test(...): pin behavior…) that is green on baseline. - Confirm dual-run: suite green on baseline SHA and on HEAD (attach
simplify-report.jsonfromverify_behavior_preservation.pywhen used). - Confirm assertion contract:
check_test_contract.py --base <baseline>exits 0 for the simplify range (characterization commits may add tests; simplify commits must not mutate expectation bodies). - Confirm scope:
check_scope.py --base <baseline>exits 0, or--allow-codemodwith the codemod named in the report. - Confirm
git diff <baseline>..HEAD --stat(or report) shows intentional surface only — no unrelated drive-by files.