About unslop
unslop is an agent skill for removing confirmed, behavior-preserving noise from code. It gives coding agents a catalog of patterns and a scoped workflow that distinguishes safe cleanup from changes that belong elsewhere.
Key features
- 01Catalog of 19 code-noise patterns
- 02Behavior-preserving cleanup rule
- 03Diff-scoped review workflow
- 04Verification guidance
- 05Findings linked to numbered patterns
Use cases
- 01Remove redundant comments and names from a diff
- 02Identify unnecessary wrappers or defensive handling
- 03Separate cleanup from behavior-changing work
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ howells/ skills --skill unslopAfter installing, type/unslopin Claude Code to confirm it loaded.
Bundled filesagents/openai.yaml
Details
From the project READMEUnslop
The code-side counterpart to deslop. Same instinct, different material: deslop takes the tells out of writing, unslop takes them out of code.
Agent-written code carries a signature. Not bugs - it typechecks, the tests pass, it works. It is padded with ceremony that exists because a model was being thorough rather than because anything needs it, and the cost lands on whoever reads it next.
The rule that decides every call: nothing here changes behaviour. Remove a tell and the tests still pass unchanged. If a cleanup would alter what the program does, it is not this skill's - a fallback that hides a failure belongs to fail-fast, a file too big to hold in your head belongs to componentize. Say so and move on rather than smuggling a behaviour change into a tidy-up.
Read the surrounding code before calling something a tell. Local conventions and the purpose of an abstraction matter: a one-caller wrapper can still name a meaningful operation or preserve a useful boundary. The catalogue gives you patterns to investigate, not automatic deletions. Prefer the smallest edit that removes confirmed noise; do not rewrite neighbouring code to match your taste.
The tells
Cite by number in findings, so a reviewer can see the pattern rather than the instance.
Comments and names
- Narrating comments.
// loop over the users,// Phase 1: build the cards. The code says it already. The assertion string or the log line is the only documentation a step needs. - Doc comments that restate the signature. "@param userId - the user id". If the doc adds nothing the type doesn't, delete it. If the symbol is exported and deserves real hover help, that is
marginalia, not a deletion. - Commented-out alternatives. The other approach, kept "just in case". Git has it.
- Over-explicit names.
userDataObject,handleClickEventHandler,configurationOptionsMap, or the kind of thing restated in a type name:UserShape,UserInterface. Say the noun.
Ceremony
- One-caller wrappers. A function, class or module that exists to be called from exactly one place and adds nothing on the way through. Inline it.
- Interfaces with one implementation. Written for a second implementation that never arrived.
- Options nobody passes. Parameters whose default is the only value ever used, config objects with one live key, feature switches with one state.
- Symmetry for its own sake. An empty
else, adefault:that cannot be reached, afinallythat does nothing, a wrapper that only awaits. - Files holding one constant. Unless the boundary is the point, put it where it is used.
Defensive noise
- Redundant defensive handling. A null check on a value that cannot be absent, a runtime
typeofafter the compiler already narrowed, or atry/catchthat only rethrows the same error. Establish that the path is trusted; a type annotation alone does not validate runtime input. Keep boundary validation and catches that add meaningful context or cleanup. Catches that swallow errors or supply fallbacks belong tofail-fastwhen removing them would change behaviour. - Redundant assertions.
as anyused only to silence a type error,as unknown as T, a non-null!where narrowing would do the job honestly, a known value assigned tounknownor a broad record and then asserted back to the type it already had. Use the correct type or narrowing rather than another assertion. If fixing the mismatch requires a runtime change, report it instead. - Re-implemented standard library. A hand-rolled
groupBy,chunk,debounce, or date formatter beside one that already ships. Replace it only after confirming equivalent semantics for its callers, including timing, defaults, errors, locale and edge cases; otherwise refer. - Redundant async plumbing. Remove async syntax only after tracing callers and preserving Promise returns, rejection behaviour and scheduling. An
asyncfunction withoutawaitstill returns a Promise and converts throws to rejections; awaiting a plain value still suspends continuation. Neither is redundant merely because the syntax looks unnecessary. Refer uncertain changes instead of guessing.
Output and leftovers
Published CLI output, error messages and logs consumed by scripts, users or monitoring are observable contracts. Keep them unchanged unless their alteration was requested separately. Do not update snapshots merely to make a cleanup pass; refer changes whose compatibility you cannot establish.
- Decorated console output. Emoji, box-drawing, ✅/❌ status theatre, banner headers in logs and CLI output.
- Congratulatory strings. "Successfully completed!", "All done!". Report the outcome or say nothing.
- Error messages that restate the stack. Say what failed and what the caller should do; the stack is already there.
- Dead scaffolding.
TODOplaceholders nobody owns, example blocks, and any mock, stub or fixture that leaked into a production path. - Tests asserting only incidental mock details. Inspect what the assertion protects: a call count or argument can be a real contract, such as preventing duplicate writes. Preserve meaningful interaction checks. Report tests that prove no behaviour rather than silently deleting coverage or starting a test rewrite outside the request.
Control flow
- Unnecessary nesting. Newly introduced layers of
if/elsethat an early return would make easier to follow. Flatten only when execution order, side effects, cleanup and return values stay the same. Leave nesting alone when it expresses the logic clearly or fits the surrounding code.
Steps
Scale the workflow to the diff. For a small cleanup, keep the scope, reasoning and verification brief; a separate task list is optional.
- Fix the scope. Default to the diff - the branch against its merge base, or the working tree. A whole-repo sweep is a different job and needs saying out loud, because it will touch code nobody asked you to touch.
- Choose relevant verification. Name the behaviour that could change and use the narrowest existing check that could catch it. For comment-only changes, inspect the diff and any applicable documentation check. Record unrelated baseline failures and continue safe independent work. Preserve repository-required final checks; do not start a workspace-wide gate ladder.
- Pass over the diff against the catalogue. Read nearby code to establish local conventions, then record one finding per confirmed instance, each carrying the tell number and a
file:line. Do not rewrite as you read - a pass that edits while it scans loses the thread and starts improving things. - Sort the findings into remove and refer. Remove is anything behaviour-preserving. Refer is anything that would change what the program does, named with the skill that owns it. Never do a refer inline.
- Apply the removals, then verify the affected contract. Run the selected checks and inspect return values, side effects and timing where relevant. Passing tests support equivalence; they do not prove it. Investigate changed results, distinguish flaky or pre-existing failures, and revert any removal that changes behaviour.
- Report. For a small cleanup, use one to three sentences: what changed, anything referred elsewhere, and the gate result before and after. Include tell numbers and file locations when needed to explain a finding; reserve counts by tell for a requested audit or a larger diff that benefits from them.
What this is not
- Not a style pass. Formatting follows the repository’s formatter.
- Not a refactor. Moving logic between modules or extracting a package is
componentize. - Not a hardening pass. Deleting a fallback so it fails loudly is
fail-fast, and that one changes behaviour on purpose. - Not a linter. Rules a machine can check on every file - a type assertion with no stated reason,
unknownin parameters and returns, module mocking,Reflectproperty access - belong in the repository's lint configuration;howells-lintsets up@howells/lint, which carries them. If the lint does not load them, refer that rather than policing each site by hand. - Not a review. Correctness bugs are a different job; if you find one, report it and leave it.