Claude Cursor Skill

deslop

Use when the user says deslop, debloat, tidy, simplify, clean a diff, or deslop branch diff, or remove dead code or config. Not for remote, credential, publish, deploy, or irreversible changes.

LLM Mart · 0 points · 11 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download OutlineDriven-odin-claude-plugin-plugins_odin-code_skills_deslop-f73ec79.zip · 22 KB
Part of outlinedriven/odin-claude-plugin — 120 skills

Install

skills CLI npx skills add https://github.com/OutlineDriven/odin-claude-plugin/tree/main/plugins/odin-code/skills/deslop
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install outlinedriven-odin-claude-plugin@llmmart
Git git clone https://github.com/OutlineDriven/odin-claude-plugin.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole outlinedriven/odin-claude-plugin collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Deslop

Four modes share one spine: bound scope, verify with the repo's own command, rollback on regression, atomic commits separate from behavior changes.

Contract

Field Bound contract
Trigger The user says deslop, debloat, tidy, simplify, clean up this diff, cleanup codebase, deslop a branch diff, remove dead code, find placeholders or stubs, remove dead fields, redundant wrappers, or stale config, or the slop skill routes code findings here
Authority Reversible local: writes only production source files or prose artifacts (may run the repo verifier and git restore on regression); rollback is version control or undo. No remote mutation.
Side effect Local writes to production source files or prose artifacts; no edits to tests, fixtures, mocks, examples, generated, vendored, or lockfile/build artifacts
Done Mode-specific done predicate holds; verifier green or rollback confirmed

Refusal

Not for behavior changes, new abstractions, or refactors that introduce patterns: a tidy pass that would change observable behavior stops and reports a candidate for a separate refactor. Not for remote, credential, publish, deploy, or irreversible changes. Not for duplication across artifacts or drift: those are handed off, not force-compressed. Not for non-code targets (memory, git workspace, docs): route to their owners. Not for opportunistic sweeps across untouched files in tidy mode: the candidate must lie in code already under edit.

Mode selection

User says Mode Target
deslop, remove debug code, find placeholders or stubs, remove dead code, slop routed here slop Production source files
debloat, tighten this, too long bloat One padded prose artifact (document, skill, spec)
tidy this up, simplify, clean up this diff, polish my changes, make this simpler tidy Code in the working tree
dead field, redundant wrapper, stale config, duplicate state, speculative abstraction tidy Code already under edit
cleanup codebase tidy Code already under edit
deslop this branch diff, remove AI debris from my branch, clean up added lines only diff Added/modified lines in a branch diff

Shared spine

  1. Bound scope. Prefer changed files unless the user requested a full sweep. Exclude tests, fixtures, mocks, examples, generated output, vendored code, lockfiles, build artifacts, and minified bundles: **/test/**, **/tests/**, **/__tests__/**, *.test.*, *.spec.*, *_test.*, *Test.java, **/fixtures/**, **/mocks/**, **/testdata/**, **/examples/**, **/benches/**, dist/**, build/**, target/**, coverage/**, vendor/**, node_modules/**, *.min.*, generated/protobuf/openapi outputs. Keep Markdown out of whitespace cleanup: trailing spaces can be semantic line breaks. Done when: the file set is enumerated and exclusions applied.

  2. Verify. Run the repo's own test command after fixes. Derive it from manifests in this order: package script (test, then check, then typecheck), cargo test, go test ./..., pytest, mvn test, gradle test, dotnet test, bundle exec rspec or rake test, composer test or phpunit, swift test, or the project's documented command. If no command exists, run the narrowest parser/type check available, state the limitation, and treat every fix as unverified. Done when: the verifier has run or the limitation is stated.

  3. Rollback on regression. If verification fails, immediately git restore -- <file...> every changed file, rerun the verifier to confirm baseline, and report the failed fix group as blocked with file/line and failing command. Never suppress tests, rewrite expectations, or keep partial results. Done when: baseline is confirmed restored or fixes are verified green.

  4. Commit separately. Cleanup commits are always separate from behavior commits. Use atomic commits with clear messages naming what was removed. If a cleanup is mixed into a behavior commit, split it with git move --fixup or git split before merging. Done when: each commit has exactly one concern and the diff is net-deletion or inline-and-delete only.

Slop mode

Certainty-graded mechanical slop removal from production source. Full category catalog, per-language instances, and autofix strategy semantics: references/slop-catalog.md.

  1. HIGH deterministic scan. Use search for line patterns and ast-grep where syntax shape matters. Record {file, line, pattern, certainty: HIGH, strategy} for each finding. Categories: debug output (stream-writing mechanism left behind after debugging: exclude output that is the product: CLIs, loggers, entrypoints), placeholder or unimplemented body (empty block, no-op, not-yet-implemented throw, TODO-marked panic), swallowed failure (catch/except/rescue that discards the error so the unhappy path continues with invalid state), crash-on-failure shortcut (forced unwrap, unchecked cast, abort-on-error where failure is recoverable: flag only), hardcoded credential (sk-, ghp_/github_pat_, AKIA, Bearer <token>, JWT strings, private-key blocks: flag only), placeholder text (lorem ipsum, asdf asdf, foo bar baz, replace this, TODO: implement), privilege and supply-chain hazard (chmod 777, piping download into shell: flag only), whitespace artifact (mixed tabs+spaces on one indentation prefix, trailing whitespace outside Markdown). Done when: every HIGH category has been scanned.

  2. MEDIUM contextual scan. Use codegraph first when indexed; otherwise combine ast-grep, search, and direct reads of the narrow files. Report only, no auto-fix: comment bloat (doc-to-code ratio >3 for a real function with ≥3 code lines, or >2 comments per code line inside a function; filler/hedging/buzzword comments), dead or unreachable code (statements after return/throw/break/continue that are not a language-required fallthrough), commented-out code (consecutive comment lines whose content is code), mutable global state (module-level binding named as constant but declared mutable, or mutable global collection outside settings/constants), missing safety justification (escape-hatch construct entered without the adjacent comment its convention requires), suppression escape (warning or type-check suppression applied instead of fixing the finding), over-engineering indicators (file/export ratio >20, lines/export >500, directory depth >4 without real module boundaries), unsubstantiated capability claim ("production-ready", "secure", "enterprise-grade", "scalable" with fewer than two concrete supporting code signals), infrastructure without implementation (Client/Connection/Pool/Service/Provider/Manager/Factory/Repository/Gateway/Queue/Cache/Store values created but never used beyond setup/export), stub return values (function whose only significant body line returns 0/null/undefined/None/nil/false/true/[]/{}/""/empty collections/Default::default()/Optional.empty(): escalate attention when adjacent TODO/FIXME/STUB text exists, keep auto-fix disabled). Done when: MEDIUM findings are recorded.

  3. LOW optional CLI scan. Run only tools already available in the repo or PATH; never install. Record findings as LOW and flag-only: jscpd for duplication, madge for cycles, and the linter the project already declares (derived from its manifest or config: eslint, clippy, golangci-lint, ruff, ktlint, rubocop, phpstan, swiftlint, the .NET analyzers, and equivalents). If a tool is absent, write missing: <tool> and continue. Done when: available tools have run.

  4. Prioritize. Sort HIGH before MEDIUM before LOW; then severity; then scope proximity to changed files; then fix strategy. Keep a separate fixes list containing only HIGH findings with remove-line, remove-block, replace-whitespace, or add-comment strategies. Exclude every flag-only finding from automatic edits. Done when: the fix list is ordered and flag-only findings are excluded.

  5. Fix HIGH only. Apply the smallest edit that removes the deterministic slop: remove-line (debug prints, trailing whitespace, isolated commented-out code blocks), replace-whitespace (convert mixed indentation to the file's dominant style; strip trailing spaces), add-comment (empty catch/except blocks only when the correct behavior is intentionally swallowing the error and the surrounding code proves that intent: otherwise flag, do not invent logging), remove-block (placeholder block only when it is unreachable/dead and removal cannot change API behavior: stubs on live API surfaces are report-only), flag-only (hardcoded secrets, crash-on-failure shortcuts, placeholder implementations, dead code requiring control-flow judgment, architectural smells). Done when: all HIGH non-flag fixes are applied.

Done when: HIGH fixes applied and verified, MEDIUM/LOW findings left flagged for manual inspection, verifier green or rollback confirmed.

Bloat mode

Compress one padded but fully binding prose artifact in place. Every rule present before the pass is present after; the artifact is materially denser.

  1. Read end to end. Note in one line what each section must convey. No second artifact is read or written. Done when: the artifact's load-bearing structure is mapped.

  2. Find padding. A needless qualifier, a sentence fusing three ideas, an enumeration better expressed as a rule plus a short list, a nearby restatement, litigation history where the rule alone suffices. Done when: padding candidates are listed.

  3. Compress in place. Cut the padding, split fused sentences, replace excessive enumerations with a rule and short list, keep repeated points once. Move nothing to another artifact and re-derive nothing. Done when: the artifact is materially denser.

  4. Keep every load-bearing claim. If cutting a word would lose one, keep the word. Do not accept the loss. Done when: every prior rule and claim is confirmed present.

  5. Hand off non-bloat. Duplication across artifacts and drift are not bloat; do not force-compress them. State that they were handed off rather than removing them. Done when: non-bloat defects are named and left for their owner.

  6. Review and cut again with fresh eyes. The first pass always leaves some. Done when: a second pass finds nothing genuine to cut.

Done when: every prior rule and claim remains, the artifact is materially denser, and non-bloat problems are handed off.

Tidy mode

Remove constructs that do not earn their keep from code in the working tree, then verify. Branch-specific detection patterns: references/dead-fields.md (dead fields and members), references/dead-config.md (dead flags, env vars, branches), references/redundant-wrappers.md (inline-then-delete wrappers).

  1. Confirm scope. In cleanup-codebase trigger shape (dead field, redundant wrapper, stale config), the candidate must lie in a file already touched by the active change. If it does not, stop: opportunistic sweeps across untouched files are out of scope. In tidy trigger shape (tidy this up, simplify), the scope is the user-named target, the active file, or the current diff. Done when: the exact files and functions in scope are identified.

  2. Read end to end. Understand what each function, type, and module in scope must do or convey. Note the behavioral contract each piece serves. Done when: the scope's contracts are mapped.

  3. Classify candidates. For each construct in scope:

    • Dead code: unreachable paths, unused imports, unexported helpers with zero callers, commented-out blocks, stale feature-flag branches that are always-on or always-off.
    • Redundant construct: duplicated logic, a wrapper that only forwards, a variable assigned once and immediately consumed, a conditional whose guard is always true or false in context, a type alias that adds no clarity.
    • Special case: a branch that handles one input shape identically to the general case, a guard that duplicates the default, a fallback that cannot trigger.
    • Ceremony: a factory/builder/adapter with one real implementation, a generic parameter with one concrete use, an abstraction layer with no real boundary behind it.
    • Not a candidate: live behavior, public API contracts, real boundary seams (process, network, untrusted input, FFI, async/sync, test/production where mocks substitute), code that is verbose but not wrong. A swappable-implementation contract counts only when more than one real implementation ships today.

    Indirection earns its keep only at a real boundary: public API surfaces, process or network seams, untrusted-input boundaries, async/sync seams, runtime FFI seams, or test/production seams where mocks substitute. Internal modules in the same package, same-file helpers, and cross-module calls without a co-change constraint are not boundaries. Done when: every construct in scope is classified.

  4. Confirm dead across all consumers. Run git --no-pager grep -n (or ast-grep) for every reference to the candidate in code, tests, docs, configs, error messages, and log lines. A field is dead only if it is never read after assignment; a wrapper is redundant only if it adds nothing but a rename or forward. If any consumer is unverified, stop and investigate rather than delete. Done when: every candidate is confirmed dead or redundant across all consumers.

  5. Check coupling. Determine whether removal breaks the build or forces a refactor of the only consumer. If it does, that is a separate decision; record it and do not delete in this pass. Done when: coupling effects are assessed and blocking couplings are recorded.

  6. Remove in place. Delete dead code. Inline single-use wrappers at every call site, then delete the wrapper. Collapse special cases into the general path. Fuse duplicated logic into one copy. Remove ceremony that does not protect a real boundary. Never comment out code as a substitute for deletion. Never introduce new patterns, abstractions, or dependencies: the diff must be net-deletion or inline-and-delete only. Done when: all confirmed candidates are removed.

  7. Search for ghosts. Grep docs, error messages, config keys, env vars, and log lines for string references to the removed concept. Leftover references mean the cleanup is incomplete: remove them, or roll back if they indicate the candidate was not actually dead. Done when: no ghost references remain.

Done when: working tree has fewer dead/redundant/special-cased constructs, every removal is confirmed dead across all consumers, build/lint passes, and no ghost references remain.

Diff mode

Remove AI-generated debris from a branch diff, bounded to added/modified diff lines only. The shared spine applies; the diff-specific steps below resolve the branch scope and bound every edit to the lines the branch introduced.

  1. Resolve the branch diff scope. Capture every commit since the branch diverged from its base plus staged and unstaged changes. Try base refs in order and use the first that resolves: git merge-base HEAD origin/main, then origin/master, then main, then master, then @{upstream}. Run git diff <base> (no ..HEAD, so working-tree changes are included). If none resolves, check HEAD: if git rev-parse --verify HEAD fails, HEAD is unborn and the scope is user-named files only; if HEAD^ fails, HEAD is the root commit and the scope is the working tree (git diff HEAD); otherwise stop and request an explicit base, because falling back to git diff HEAD on a local-only branch with committed work would silently drop that work. Done when: a base ref resolves and git diff <base> runs, or the scope is confirmed as user-named files / working tree / root commit, or the skill stops to request an explicit base.
  2. Enumerate changed files from the diff. Exclude tests, fixtures, mocks, examples, benchmarks, generated output, vendored code, lockfiles, and build artifacts; they may intentionally contain placeholders, fake tokens, and debug output. Done when: the changed-file list is produced with exclusions applied.
  3. Read local style context. For each changed file, read the diff hunks plus enough surrounding unchanged lines to judge the file's dominant indentation, naming, comment density, and import ordering. "Matching local style" is the done predicate, so this context is required before any edit. Done when: per-file dominant indentation, naming, comment density, and import ordering are recorded.
  4. Identify AI-generated debris in added lines only. Scan the lines the branch introduced, not unchanged context. Debris classes: debug output left after debugging (console/print/debug macros/shell tracing), excluding output that is the product (CLIs, loggers, entrypoints); placeholder or unimplemented bodies (empty block, no-op, not-yet-implemented throw/abort, TODO: implement); commented-out code blocks; restating-the-code comments and motivational or hedging comments ("Let me...", "Now we...", "Here we..."); placeholder text in string literals (lorem ipsum, foo bar baz, replace this); unused imports or variables introduced by the change; redundant defensive guards that duplicate a check already present in the same path; mixed tabs+spaces or trailing whitespace on added lines. Done when: every added line is scanned and findings are listed by debris class.
  5. Classify each finding before editing. Apply only removals that cannot change behavior: delete debug prints, delete restating/hedging/motivational comments, delete commented-out code blocks, delete unused added imports and variables, and normalize added-line indentation and trailing whitespace to the file's dominant convention. Flag-only, no edit: placeholder implementations on live API surfaces, hardcoded credentials, crash-on-failure shortcuts (forced unwrap, unchecked cast, abort-on-error where failure is recoverable), and dead code requiring control-flow judgment. Done when: each finding is marked remove or flag-only.
  6. Bound every edit to added/modified diff lines. Never edit unchanged context lines, never reformat the whole file, and never introduce new logic, imports, or abstractions. The diff must shrink or stay focused; it must not grow. Done when: edits are applied only on added/modified lines and the diff has not grown.

Done when: focused diff matching local style, verifier green or rollback confirmed.

Failure and recovery

Failure mode Rule
Scope violation (auto-fix touches an excluded file) Revert that file with git restore; do not widen scope
Certainty violation (MEDIUM or LOW finding edited in slop mode) Revert the edit; the finding stays report-only
Behavior regression (verifier fails after fixes) git restore -- <file...> every changed file, rerun the verifier, report the failed fix group as blocked with file/line and failing command. Keep no partial cleanup
Lost-claim cut (bloat mode, cutting a word would lose a load-bearing claim) Keep the word. Do not accept the loss
Not-bloat problem (bloat mode, defect is duplication across artifacts or drift) Hand it off; do not mutate the artifact for it
Unverified consumer (tidy mode, a consumer was not in the original grep) Do not delete. Investigate and either preserve the candidate or migrate the consumer first
Candidate might be live behavior (tidy mode) Classify as "not a candidate" and skip. Do not remove to find out
Mixed-concern commit (cleanup bundled with behavior change) Split with git move --fixup or git split before merging; never merge a mixed commit
New abstraction introduced during cleanup Cleanup must be net-deletion. Separate the abstraction into its own commit with independent justification, or drop it
No verifier available Treat every fix as unverified; state the limitation; do not claim the done predicate holds
Nothing to improve A pass that finds nothing genuine to improve changes nothing
Empty change-set (diff mode) No diff after all resolutions and no user-named files. Stop, report pass-through, make no edits
No base ref resolves and committed history exists (diff mode) Stop and request an explicit base (deslop against <ref>). Do not fall back to working-tree-only, which would silently drop committed branch work
Finding requires behavior or control-flow judgment (diff mode) Flag-only, no edit. Never swallow the failure or pretend the done predicate holds

Partial-result rule: applied fixes that verify stay; any fix whose verification is unconfirmed or failed is reverted and reported as blocked. In tidy mode, a pass that confirms some candidates dead and leaves others unverified lands only the confirmed deletions; unverified candidates stay untouched. Non-mutation rule (tidy mode): nothing is deleted until the dead-confirmation grep covers code, tests, docs, configs, and error messages. Never swallow an error or pretend the done predicate holds.

Output

A compact report naming the mode, changed files, fixes applied (HIGH only in slop mode), findings left for manual inspection, the verifier command and its result, and any rollback action taken. Bloat mode: the artifact rewritten in place plus a one-line summary of what was cut and which non-bloat problems were handed off. Tidy mode: removed/fixed/skipped counts with one-phrase skip reasons. Diff mode: resolved base ref (or working-tree/root-commit note), changed files cleaned, debris classes removed with file/line, flag-only findings left for manual review, the verifier command and its result, and any rollback action taken. If nothing needed doing: Deslop: nothing to do.

Files (odin-claude-plugin)
  • agents
    • openai.yaml 201 B
      interface:
        display_name: "Deslop"
        short_description: "Use when the user says deslop, debloat, tidy, simplify, clean a diff, cleanup codebase, or deslop branch diff, or remove dead code or config."
      
  • references
    • dead-config.md 5.2 KB
      # Dead config: flags, env vars, branches
      
      Configuration ages worse than code. A feature flag introduced for a migration becomes permanent the moment everyone forgets it exists. An environment variable for a debug mode no one uses ships in production for years. An `if env.staging` branch full of stale logic stays untouched because no one wants to be the person who broke staging.
      
      The fix: while you are in nearby config code, audit. Delete what is dead. The hardest part is proving it dead: config consumers can be implicit (read by tools, dashboards, infra-as-code, deployment scripts) and grepping the source tree alone is not enough.
      
      ## Table of contents
      
      - Categories of dead config: always-on / always-off feature flags · stale
        environment variables · dead config branches · dead defaults that never
        trigger · stale infrastructure config
      - Detection workflow
      - Caveats
      
      ## Categories of dead config
      
      ### Always-on / always-off feature flags
      
      ```python
      FEATURES = {
          "new_pricing_engine": True,        # set in 2023, every env enables it
          "legacy_payment_path": False,      # disabled in every env for > 1 year
          "experimental_caching": True,      # experiment ended; this is now the default
      }
      ```
      
      ```go
      var Features = map[string]bool{
      	"new_pricing_engine":   true,  // set in 2023, every env enables it
      	"legacy_payment_path":  false, // disabled in every env for > 1 year
      	"experimental_caching": true,  // experiment ended; this is now the default
      }
      ```
      
      If a flag is unconditional in every environment, the *flag* is dead. Pick the winning branch, inline it, delete the flag. The losing branch becomes deletable dead code (which is also a cleanup-codebase concern).
      
      ### Stale environment variables
      
      ```bash
      export OLD_DB_HOST=...   # replaced by DATABASE_URL three migrations ago
      export DEBUG_VERBOSE=1   # disabled, no consumer reads this
      ```
      
      ```go
      var oldDBHost = os.Getenv("OLD_DB_HOST")      // replaced by DATABASE_URL three migrations ago
      var debugVerbose = os.Getenv("DEBUG_VERBOSE") // disabled, no consumer reads this
      ```
      
      Search the source tree, the deployment scripts, the IaC (terraform, pulumi, helm), the runbooks, and the dashboards. Only when all are clean is the env var truly dead. **This is the audit point most people skip.**
      
      ### Dead config branches
      
      ```python
      def get_database_url() -> str:
          if config.use_legacy_db:
              return f"postgres://{config.legacy_host}/{config.legacy_db}"
          return config.database_url
      ```
      
      ```go
      func DatabaseURL(cfg Config) string {
      	if cfg.UseLegacyDB {
      		return fmt.Sprintf("postgres://%s/%s", cfg.LegacyHost, cfg.LegacyDB)
      	}
      	return cfg.DatabaseURL
      }
      ```
      
      If `use_legacy_db` is dead (always false), the entire legacy branch is dead, including `legacy_host`, `legacy_db`, and any code reachable only through that path. Delete the branch, the flag, and the dependent fields together.
      
      ### Dead defaults that never trigger
      
      ```rust
      #[derive(Deserialize)]
      struct Config {
          #[serde(default = "default_timeout")]
          timeout_ms: u64,
          #[serde(default = "default_max_legacy_retries")]
          max_legacy_retries: u32,  // legacy flag, no longer consulted
      }
      
      fn default_max_legacy_retries() -> u32 { 3 }
      ```
      
      ```go
      type Config struct {
      	TimeoutMs        uint64 // read on every connection attempt; default 5000
      	MaxLegacyRetries uint32 // legacy flag, no longer consulted; default was 3
      }
      ```
      
      The default value and the field both exist for a setting nothing reads. Delete both.
      
      ### Stale infrastructure config
      
      - Terraform module variables with no resource referencing them
      - Helm chart values with no template substitution
      - Docker `ARG` declarations with no `${…}` interpolation
      - CI matrix entries for runners / OSes you no longer ship to
      
      These are config too, and the same audit applies: if no consumer reads it, it does not earn its keep.
      
      ## Detection workflow
      
      1. **List all config keys**: `grep`/`ast-grep` over the config file(s) to enumerate every setting.
      2. **For each key, find consumers**: search source code, but also: deployment scripts, dashboards (links and queries), runbooks (search the wiki), IaC, monitoring/alerting rules, CI configs.
      3. **Classify**:
         - Live: read in production code; conditional value drives real behavior
         - Dead-on: value is always-true across every environment, branch always taken; flag is dead
         - Dead-off: value is always-false across every environment, branch never taken; flag and the gated branch are dead
         - Unknown: ambiguous; investigate further or leave alone
      4. **Delete dead**: atomic commit per concern (one flag = one commit), grep for ghost references afterward.
      
      ## Caveats
      
      - Implicit consumers: dashboards, alerts, support runbooks, third-party integrations may read a config key without it appearing in the source tree. The audit must extend beyond the repo.
      - Migration in progress: a flag that *will be* dead next quarter is still live now. Coordinate with whoever owns the migration before deleting.
      - Time-bounded enable/disable: flags that flip on at a specific date (e.g., GDPR rollout, holiday rate limits) are not dead even when currently off. Look for date-based logic.
      - External-facing config: anything customers, partners, or downstream services configure is a public API. Removing it is a compat-breaking change, not cleanup.
      
    • dead-fields.md 3 KB
      # Dead fields, props, and members
      
      A dead field is one that is *written* but never *read*, or *read* but only to forward to another field that is itself never used. Dead fields mislead readers, bloat memory layout, and survive every grep someone does looking for "where is this used."
      
      ## Detection pattern
      
      For any field `Foo.x`:
      
      1. `git --no-pager grep -nF '.x'` (or the appropriate selector for the language): look for read sites.
      2. `ast-grep run -p '<self-ref>.x' -l <lang>`: find self-references inside the type. The self-reference token is not universal; parameterize per language:
      
         | Language | Self-reference form |
         |---|---|
         | Python, Rust | `self.x` |
         | TypeScript, Java, Kotlin | `this.x` |
         | Go | named receiver (e.g. `s.x`); the name is chosen per type, so grep the field name across the package instead of relying on one fixed pattern |
      
      3. If the only references are *writes* (assignments, constructors), the field is dead.
      4. Standing limit: frameworks that read fields reflectively (see Caveats) are invisible to both checks above; check the framework's marker before deleting.
      
      ## Per-language instances
      
      | Language | Dead-field shape | Fix |
      |---|---|---|
      | Python | `@dataclass` field set only in `__post_init__` (e.g. a generated token), never read elsewhere | Delete the field, its assignment, and the generator call if otherwise unused |
      | TypeScript | Optional interface prop set by legacy middleware, no read site (`legacyTenantId?: string`) | Delete the prop and the middleware that set it |
      | Rust | Struct field only ever produced by `Default::default()`, no method reads it | Delete the field; `#[derive(Default)]` regenerates without it |
      | Go | Struct field set at construction, no method on the receiver reads it | Delete the field and the construction-site assignment |
      | Java | Field with a generated setter, no getter, no internal read, common after a deserialized-config change | Delete field + setter; add `@JsonIgnoreProperties(ignoreUnknown = true)` or drop the JSON key if deserialization complains |
      | Kotlin | `data class` component never read outside the constructor, left behind by a library swap | Delete the parameter and every call site that supplied it |
      
      ## Caveats
      
      - Frameworks that read fields reflectively: `serde`, Jackson, Gson, pydantic, attrs, Spring, Dagger, and Hilt (among others) read fields via derive macros, decorators, annotations, or DI wiring (`#[derive(Serialize)]`, `@JsonProperty`, `@Autowired`, `@Inject`) with no direct read site anywhere in source. This is a standing limit, not a checklist to exhaust: reflective, DI, and serialization reads are not resolvable by static analysis. Treat a field under such a marker as unprovable-dead by grep alone; require an explicit allowlist or scope exclusion before deleting it.
      - Tests: a field read only by tests may indicate the field exists *for* the tests; purging the test is the other direction, not this one.
      - External consumers: if the type crosses a process boundary (DTO, event payload), removing a field breaks a published contract; that is a compat-breaking change, not cleanup.
      
    • redundant-wrappers.md 5.5 KB
      # Redundant wrappers: inline, then delete
      
      A redundant wrapper is a function whose body adds no semantic value over its underlying call. Common patterns:
      
      - Renaming the call without changing arguments
      - Single-line passthrough with no transformation, validation, or error mapping
      - "Convenience" wrapper that just rearranges arguments cosmetically
      - Adapter between two equivalent local interfaces (when both are yours)
      
      The fix is always the same: inline the wrapper at all call sites, then delete the wrapper definition. If the wrapper has 1-2 callers, this is a small, mechanical change. If the wrapper has 50 callers, the wrapper might be earning its keep as a cohesion point; pause and consider before inlining.
      
      ## Detection pattern
      
      The shape to search for: a function or method whose entire body is one call, forwarding the arguments it received, with no added logic. `ast-grep` pattern syntax is not portable across languages: a pattern written for one language's grammar will not parse, let alone match, in another. Each pattern below omits the visibility modifier, so it catches exported and private wrappers alike; prepend `pub` / `export` / `public` to narrow the sweep to exported ones.
      
      | Language | `ast-grep` seed pattern | Also run |
      |---|---|---|
      | Python | `def $NAME($$$PARAMS) $$$RT: return $INNER($$$ARGS)` | none |
      | TypeScript | `function $NAME($$$PARAMS) $$$RT { return $INNER($$$ARGS); }` | none |
      | Rust | `fn $NAME($$$PARAMS) $$$RT { $INNER($$$ARGS) }` | none |
      | Kotlin | `fun $NAME($$$PARAMS) $$$RT = $INNER($$$ARGS)` | block body: `fun $NAME($$$PARAMS): $RET { return $INNER($$$ARGS) }`, and again without `: $RET` |
      | Go | `func ($$$RECV) $NAME($$$PARAMS) $$$RET { return $INNER($$$ARGS) }` | plain function: drop `($$$RECV)` · void: drop `return` |
      | Java | `$RET $NAME($$$PARAMS) { return $RECV.$INNER($$$ARGS); }` | bare call: `return $INNER($$$ARGS);` · void: drop `return` |
      
      These are seeds, not exhaustive detectors. Three things bound what a single run proves:
      
      - **`$$$RT` in the return-type position absorbs the annotation.** In Python, TypeScript, Rust, and Kotlin expression bodies, one run matches the annotated and unannotated forms: `def f(id):` and `def f(id) -> User:`, `fn f(x) {` and `fn f(x) -> T {`. Hardcoding `-> $RET` silently skips every wrapper that omits the return type, including the usual Rust unit form written without `-> ()`. The shortcut does not extend to a Kotlin *block* body, where the annotated and unannotated forms need separate runs.
      - **Go and Java need the extra runs, on different axes.** Go splits on the *declaration*: a method carries a receiver (`func (s *Store) Get(...)`), a different grammar node from a plain function, so neither form finds the other's shape. Java splits on the *call*: `return repo.findById(id)` is a qualified invocation that a bare `$INNER($$$ARGS)` will not match. In both, a void wrapper forwards without `return` and needs its own run. The Python, TypeScript, Rust, and Kotlin seeds match bare and receiver-forwarded bodies alike as written.
      - Proof that the arguments are unchanged: a typed parameter list (`id: int`) and its untyped call site (`id`) are different text, so one reused metavariable can't assert identity the way it can within a single side. Treat a structural match (single-call body) as a candidate, and confirm by eye that the call forwards what it received.
      
      ## Per-language instances
      
      | Language | Wrapper shape | Keep when |
      |---|---|---|
      | Python | `def get_user(id): return repo.get(id)` | Validates, maps errors, logs/traces, or `repo` is what tests would otherwise have to mock directly |
      | TypeScript | `function fetchData(url) { return api.get(url); }` | `api` is the test seam; production code depends on the swappable `fetchData` name instead of the global `api` |
      | Rust | `fn validate(x: &Input) -> Result<(), E> { x.validate() }` | Exists for trait-object dispatch, `&dyn` ergonomics, or to keep `Input` out of a public API |
      | Go | `func (s *Store) Get(id int64) (*User, error) { return queryUser(s.db, id) }` | `queryUser` is unexported; `Store.Get` is the only sanctioned entry point |
      | Java | `class UserService { User findById(long id) { return repo.findById(id); } }` | `UserService` is a DI boundary (`@Service`, a Dagger module), or its other methods add real behavior |
      | Kotlin | `class UserService(val repo: Repo) { fun findById(id: Long) = repo.findById(id) }` | Same as Java: DI boundary, or the rest of the class earns its keep |
      
      ## When to keep a wrapper
      
      A wrapper earns its keep when it does any of:
      
      - Removes coupling: callers depend on the wrapper's signature, not the underlying library; switching the library is a one-place change.
      - **Adds validation, error mapping, instrumentation, or retry logic**. Even a small error-remapping step is real work the wrapper does: `except RepoError as e: raise UserNotFound(...) from e` (Python), `if err != nil { return nil, fmt.Errorf("...: %w", err) }` (Go), `.map_err(UserError::from)` (Rust), `try { ... } catch (RepoError e) { throw new UserNotFound(e); }` (C-family).
      - Bridges a real boundary: process, network, async/sync seam, FFI, untrusted input.
      - Provides a stable seam for testing: the wrapper is the mock point for tests that need to stub the underlying call.
      - Names a non-obvious operation: `findUserByEmail` over `db.query("SELECT ... WHERE email = ?", email)` adds semantic value; `isRateLimited` over a bare `redis.incr(key) > threshold` does the same. The name *is* the abstraction.
      
      If the wrapper does none of these, it is dead weight. Inline and delete.
      
    • slop-catalog.md 22.5 KB
      # Slop catalog
      
      Use this catalog to classify findings before editing. `HIGH` means deterministic presence; it does **not** always mean auto-removable. Autofix strategies are constrained to `remove-line`, `add-comment`, `remove-block`, `replace-whitespace`, or `flag-only`.
      
      Categories are behavioral, not per-language: one category names a runtime behavior, and its table lists the instance each language surfaces. Read a category top-to-bottom to see how the same defect looks across the stack; read a single row when you already know the language in front of you.
      
      ## Category index
      
      CWE anchors are optional cross-walk metadata for tools that key on them (Semgrep, CodeQL, Sonar, SARIF `taxa`). They do not change certainty or autofix strategy. `—` means no CWE fits; do not invent one. All 19 categories live in one table under [Slop categories](#slop-categories); filter on the Category column for the rows below.
      
      | Category | Rows | CWE anchor |
      |---|---:|---|
      | Debug output | 11 | CWE-489 Active Debug Code; CWE-215 Sensitive Info in Debugging Code |
      | Placeholder or unimplemented body | 19 | CWE-1071 Empty Code Block; CWE-546 Suspicious Comment (TODO-marked only) |
      | Swallowed failure | 14 | CWE-1069 Empty Exception Block; CWE-390 Error Condition Without Action; CWE-391 Unchecked Error Condition; CWE-396 Catch of Generic Exception |
      | Crash-on-failure shortcut | 5 | - (no CWE anchor; this category is ours) |
      | Stub return value | 6 | - |
      | Hardcoded credential | 7 | CWE-798 Hard-coded Credentials; CWE-259 Password; CWE-321 Cryptographic Key |
      | Placeholder text | 1 | - |
      | Whitespace artifact | 2 | - |
      | Mutable global state | 2 | - |
      | Missing safety justification | 1 | - |
      | Suppression escape | 1 | - |
      | Privilege and supply-chain hazard | 2 | - |
      | Dead or unreachable code | 2 | CWE-561 Dead Code (parent CWE-1164 Irrelevant Code) |
      | Commented-out code | 1 | - (CWE-546 covers TODO-style comments, not commented-out code) |
      | Comment bloat | 4 | - |
      | Over-engineering | 1 | - |
      | Unsubstantiated capability claim | 1 | - |
      | Infrastructure without implementation | 1 | - |
      | Residual: external tool signals | 6 | - |
      
      Supporting sections: [Global exclusions](#global-exclusions) · [Certainty rules](#certainty-rules) · [Slop categories](#slop-categories) · [Autofix strategy semantics](#autofix-strategy-semantics) · [Report shape](#report-shape).
      
      ## Global exclusions
      
      Exclude these from automatic fixes: tests, fixtures, mocks, examples, generated code, vendored code, lockfiles, minified bundles, build output, coverage output, and Markdown whitespace.
      
      Common exclude selectors:
      
      ```text
      **/test/** **/tests/** **/__tests__/** *.test.* *.spec.* *_test.* *Test.java
      **/fixtures/** **/mocks/** **/testdata/** **/examples/** **/benches/**
      dist/** build/** target/** coverage/** vendor/** node_modules/** *.min.* *.lock
      *.generated.* *.pb.* openapi/** generated/**
      ```
      
      ## Certainty rules
      
      | Certainty | Rule | Edit policy |
      |---|---|---|
      | HIGH | Direct regex/AST pattern; file is in production scope; match identifies a concrete leftover | Auto-fix only when strategy is mechanical and behavior-preserving |
      | MEDIUM | Requires control-flow, codegraph, ratio, or cross-file reasoning | Report only |
      | LOW | Optional external CLI heuristic or noisy smell | Report only |
      
      ## Slop categories
      
      One table across all 19 categories. Read a category's rows top-to-bottom to see how the same defect looks across the stack; filter by Language when you already know what's in front of you. `Any` means the category is keyed to something other than a language (a provider secret shape, a codegraph ratio, an external tool) and applies across the whole file set regardless of source language.
      
      | Category | Language | Instance | Certainty | Detection recipe | Autofix strategy |
      |---|---|---|---:|---|---|
      | Debug output | JavaScript / TypeScript | Debug console output | HIGH | `ast-grep pat="console.log($$$ARGS)"`; `ast-grep pat="console.debug($$$ARGS)"`; exclude CLIs/scripts/entrypoints/tests | `remove-line` when expression statement is standalone |
      | Debug output | Python | Debug print / breakpoint | HIGH | `search pattern="\\b(print\\(|breakpoint\\(|import pdb|import ipdb)" paths=["**/*.py"]` excluding tests/conftest/CLI output scripts | `remove-line` for standalone debug lines |
      | Debug output | Rust | Debug macros | HIGH | `search pattern="(println!|dbg!|eprintln!)\\(" paths=["**/*.rs"]` excluding tests/examples/benches and binaries that intentionally print | `remove-line` for standalone debug macros |
      | Debug output | Go | Debug `fmt.Print*` | HIGH | `search pattern="fmt\\.(Print|Println|Printf)\\(" paths=["**/*.go"]`; require debug label or non-CLI/non-main context | `remove-line` when standalone |
      | Debug output | Java | `System.out/err.println` | HIGH | `search pattern="System\\.(out|err)\\.println\\(" paths=["**/*.java"]` excluding CLI/main/tests | `remove-line` when standalone debug output |
      | Debug output | C / C++ | Debug prints | HIGH | `search pattern="(printf|fprintf|std::cout|std::cerr).*(DEBUG|TRACE|HERE|TODO)" paths=["**/*.{c,h,cpp,cc,cxx,hpp,hxx}"]` | `remove-line` when standalone |
      | Debug output | Shell | Debug tracing | HIGH | `search pattern="^\\s*set\\s+-[xv]\\b" paths=["**/*.{sh,bash,zsh}"]` excluding test scripts | `remove-line` |
      | Debug output | C# | `Console.WriteLine` / `Debug.WriteLine` | HIGH | `search pattern="(Console|Debug|Trace)\\.Write(Line)?\\(" paths=["**/*.cs"]` excluding console entrypoints/tests | `remove-line` when standalone |
      | Debug output | Ruby | `puts` / `p` / debugger entry | HIGH | `search pattern="^\\s*(puts|p|pp)\\s|binding\\.(pry|irb)|byebug" paths=["**/*.rb"]` excluding rake tasks/CLI/tests | `remove-line` for standalone debug lines |
      | Debug output | PHP | Dump helpers | HIGH | `search pattern="\\b(var_dump|print_r|dd|dump|error_log)\\s*\\(" paths=["**/*.php"]` excluding CLI scripts/tests | `remove-line` when standalone |
      | Debug output | Swift | `print` / `debugPrint` | HIGH | `search pattern="\\b(print|debugPrint|dump)\\s*\\(" paths=["**/*.swift"]` excluding CLI targets/tests | `remove-line` when standalone |
      | Placeholder or unimplemented body | JavaScript / TypeScript | Placeholder throw | HIGH | `search pattern="throw\\s+new\\s+Error\\s*\\(\\s*['\"].*(TODO|implement|not\\s+impl)" paths=["**/*.{js,jsx,ts,tsx,mjs,cjs}"]` | `flag-only`; implementation missing |
      | Placeholder or unimplemented body | JavaScript / TypeScript | Empty function body | HIGH | `ast-grep pat="function $NAME($$$ARGS) { }"`; also check arrow bodies `($$$ARGS) => { }` | `flag-only`; public surface may depend on it |
      | Placeholder or unimplemented body | Python | `raise NotImplementedError` | HIGH | `search pattern="raise\\s+NotImplementedError" paths=["**/*.py"]` | `flag-only` |
      | Placeholder or unimplemented body | Python | Function body only `pass` | HIGH | `ast-grep pat="def $NAME($$$ARGS): pass"` or regex fallback `def\s+\w+\s*\([^)]*\)\s*:\s*(pass|\n\s+pass)\s*$` | `flag-only`; implementation missing |
      | Placeholder or unimplemented body | Python | Function body only ellipsis | HIGH | `search pattern="def\\s+\\w+\\s*\\([^)]*\\)\\s*:\\s*(\\.\\.\\.|\\n\\s+\\.\\.\\.)\\s*$"` excluding `.pyi` | `flag-only` |
      | Placeholder or unimplemented body | Rust | `todo!()` / `unimplemented!()` | HIGH | `ast-grep pat="todo!($$$ARGS)"`; `ast-grep pat="unimplemented!($$$ARGS)"`; regex fallback `\b(todo|unimplemented)!\s*\(` | `flag-only` |
      | Placeholder or unimplemented body | Rust | `panic!("TODO")` placeholder | HIGH | `search pattern="\\bpanic!\\s*\\(\\s*['\"].*(TODO|implement)" paths=["**/*.rs"]` | `flag-only` |
      | Placeholder or unimplemented body | Go | `panic("TODO")` | HIGH | `search pattern="panic\\s*\\(\\s*['\"].*(TODO|implement|not\\s+impl)" paths=["**/*.go"]` | `flag-only` |
      | Placeholder or unimplemented body | Go | Empty TODO function | HIGH | AST/read body: function contains only comments with TODO/FIXME/STUB or no statements | `flag-only` |
      | Placeholder or unimplemented body | Java | `UnsupportedOperationException` placeholder | HIGH | `ast-grep pat="throw new UnsupportedOperationException($$$ARGS)" paths=["**/*.java"]` | `flag-only` |
      | Placeholder or unimplemented body | Java / Kotlin | TODO throw | HIGH | `search pattern="(RuntimeException|IllegalStateException|NotImplementedException)\\s*\\(.*(TODO|not implemented)" paths=["**/*.{java,kt,kts}"]` | `flag-only` |
      | Placeholder or unimplemented body | Kotlin | `TODO()` | HIGH | `search pattern="\\bTODO\\s*\\(" paths=["**/*.{kt,kts}"]` | `flag-only` |
      | Placeholder or unimplemented body | Java | `return null; // TODO` | HIGH | `search pattern="return\\s+null\\s*;\\s*//.*(TODO|FIXME|STUB)" paths=["**/*.java"]` | `flag-only` |
      | Placeholder or unimplemented body | C / C++ | Not implemented | HIGH | `search pattern="assert\\s*\\(\\s*false.*(TODO|not implemented)|throw\\s+.*(runtime_error|logic_error).*not implemented"` | `flag-only` |
      | Placeholder or unimplemented body | Shell | Placeholder function | HIGH | search function bodies containing only `:`/`true` plus TODO/not implemented comment | `flag-only` |
      | Placeholder or unimplemented body | C# | `NotImplementedException` | HIGH | `search pattern="throw\\s+new\\s+NotImplementedException\\s*\\(" paths=["**/*.cs"]` | `flag-only` |
      | Placeholder or unimplemented body | Ruby | `NotImplementedError` / empty method | HIGH | `search pattern="raise\\s+NotImplementedError|def\\s+\\w+[!?]?\\s*(\\([^)]*\\))?\\s*\\n\\s*end" paths=["**/*.rb"]` | `flag-only` |
      | Placeholder or unimplemented body | PHP | TODO throw | HIGH | `search pattern="throw\\s+new\\s+\\\\?(RuntimeException|LogicException|BadMethodCallException)\\s*\\(.*(TODO|not implemented)" paths=["**/*.php"]` | `flag-only` |
      | Placeholder or unimplemented body | Swift | `fatalError` placeholder | HIGH | `search pattern="fatalError\\s*\\(\\s*\"[^\"]*(TODO|unimplemented|not implemented)" paths=["**/*.swift"]` | `flag-only` |
      | Swallowed failure | JavaScript / TypeScript | Empty catch | HIGH | `ast-grep pat="try { $$$BODY } catch ($E) { }"` or `search pattern="catch\\s*(\\([^)]*\\))?\\s*\\{\\s*\\}"` | `add-comment` only if intentional swallow is proven; otherwise `flag-only` |
      | Swallowed failure | Python | Empty `except: pass` | HIGH | `search pattern="except\\s*[^:]*:\\s*pass\\s*$" paths=["**/*.py"]` | `add-comment` only when intentional; otherwise `flag-only` |
      | Swallowed failure | Python | Bare `except:` | MEDIUM | `search pattern="^\\s*except\\s*:"` | `flag-only` |
      | Swallowed failure | Rust | Empty error match arm | HIGH | `search pattern="Err\\s*\\([^)]*\\)\\s*=>\\s*\\{\\s*\\}" paths=["**/*.rs"]` | `flag-only` unless surrounding invariant proves intentional |
      | Swallowed failure | Go | Empty error branch | HIGH | `search pattern="if\\s+err\\s*!=\\s*nil\\s*\\{\\s*\\}" paths=["**/*.go"]` | `flag-only` unless intended ignore is proven, then `add-comment` |
      | Swallowed failure | Go | Discarded error | MEDIUM | `search pattern="_\\s*=\\s*.*\\("`; verify callee returns error | `flag-only` |
      | Swallowed failure | Java / Kotlin | Empty catch | HIGH | `search pattern="catch\\s*\\([^)]*\\)\\s*\\{\\s*\\}" paths=["**/*.{java,kt,kts}"]` | `add-comment` only if intentional; otherwise `flag-only` |
      | Swallowed failure | Java | `printStackTrace()` | HIGH | `search pattern="\\.printStackTrace\\s*\\(" paths=["**/*.java"]` | `flag-only`; replace with project logger manually |
      | Swallowed failure | C++ | Empty catch | HIGH | `search pattern="catch\\s*\\([^)]*\\)\\s*\\{\\s*\\}" paths=["**/*.{cpp,cc,cxx,hpp,hxx}"]` | `add-comment` only if intentional; otherwise `flag-only` |
      | Swallowed failure | Shell | Empty trap | HIGH | `search pattern="trap\\s+['\"]\\s*['\"]" paths=["**/*.{sh,bash,zsh}"]` | `flag-only` |
      | Swallowed failure | C# | Empty / generic catch | HIGH | `search pattern="catch\\s*(\\(\\s*(System\\.)?Exception[^)]*\\))?\\s*\\{\\s*\\}" paths=["**/*.cs"]` | `add-comment` only if intentional; otherwise `flag-only` |
      | Swallowed failure | Ruby | Empty or nil-swallowing rescue | HIGH | `search pattern="rescue[^\\n]*\\n\\s*end|rescue\\s+nil\\s*$" paths=["**/*.rb"]` | `add-comment` only if intentional; otherwise `flag-only` |
      | Swallowed failure | PHP | Empty catch | HIGH | `search pattern="catch\\s*\\(\\s*\\\\?[A-Za-z_\\\\]+\\s+\\$\\w+\\s*\\)\\s*\\{\\s*\\}" paths=["**/*.php"]` | `add-comment` only if intentional; otherwise `flag-only` |
      | Swallowed failure | Swift | Empty catch / discarded `try?` | HIGH | `search pattern="catch\\s*\\{\\s*\\}|^\\s*_\\s*=\\s*try\\?" paths=["**/*.swift"]` | `add-comment` only if intentional; otherwise `flag-only` |
      | Crash-on-failure shortcut | Rust | Bare `.unwrap()` | HIGH | `ast-grep pat="$X.unwrap()"`; regex fallback `search pattern="\\.unwrap\\(\\s*\\)"` then ignore `unwrap_or*`/method-chain false positives by inspection; exclude tests/examples/benches | `flag-only`; requires error-path design |
      | Crash-on-failure shortcut | Rust | Bare `.expect(...)` | HIGH | `ast-grep pat="$X.expect($MSG)"`; exclude tests/examples/benches | `flag-only`; requires error-path design |
      | Crash-on-failure shortcut | Go | Unchecked type assertion | HIGH | `search pattern="\\.\\([A-Za-z_][A-Za-z0-9_]*\\)"`; inspect for missing comma-ok form | `flag-only` |
      | Crash-on-failure shortcut | Go | Panic for recoverable error | MEDIUM | `search pattern="panic\\("` outside init/test/main invariant code | `flag-only` |
      | Crash-on-failure shortcut | Swift | Force unwrap / forced cast | HIGH | `search pattern="try!\\s|\\bas!\\s|\\w\\!\\." paths=["**/*.swift"]` excluding tests; inspect for optional-binding alternative | `flag-only`; requires error-path design |
      | Stub return value | JavaScript / TypeScript | Stub return only | MEDIUM | `ast-grep pat="function $NAME($$$ARGS) { return $VALUE; }"`; the semicolon-less variant `ast-grep pat="function $NAME($$$ARGS) { return $VALUE }"` catches the same shape; check `$VALUE` in `0/null/undefined/true/false/[]/{}/""` and no other significant statements | `flag-only` |
      | Stub return value | C / C++ | Stub return only | MEDIUM | `ast-grep pat="$RET $NAME($$$ARGS) { return $VALUE; }"`; use this form, not the JavaScript `function ...` one: that pattern parses in C++ but binds `function` as the return *type*, so it only matches functions literally returning a type named `function` | `flag-only` |
      | Stub return value | Python | Stub return only | MEDIUM | inspect `def` body with one significant line returning `None/0/True/False/[]/{}/""` | `flag-only` |
      | Stub return value | Rust | Stub return only | MEDIUM | function body only returns `None/0/true/false/String::new()/Vec::new()/vec![]/()/""/Default::default()` | `flag-only` |
      | Stub return value | Go | Stub return only | MEDIUM | function body only returns `nil/0/""/false/true/[]T{}/map[...]T{}/&T{}` | `flag-only` |
      | Stub return value | Any | Stub return values | MEDIUM | Function body has exactly one significant return of placeholder value and optional comments | `flag-only` |
      | Hardcoded credential | Any | Hardcoded OpenAI-style key | HIGH | `search pattern="sk-[A-Za-z0-9]{32,}"` | `flag-only`: require rotation + env/config replacement |
      | Hardcoded credential | Any | Hardcoded GitHub token | HIGH | `search pattern="ghp_[A-Za-z0-9]{36}|gho_[A-Za-z0-9]{36}|ghu_[A-Za-z0-9]{36}|ghs_[A-Za-z0-9]{36}|ghr_[A-Za-z0-9]{36}|github_pat_[A-Za-z0-9_]{80,}"` | `flag-only`: require rotation |
      | Hardcoded credential | Any | Hardcoded AWS access key / secret | HIGH | `search pattern="AKIA[0-9A-Z]{16}|aws_secret_access_key\\s*[:=]\\s*['\"][A-Za-z0-9/+=]{40}['\"]"` | `flag-only`: require rotation |
      | Hardcoded credential | Any | Bearer token literal | HIGH | `search pattern="Bearer [A-Za-z0-9._~+/-]{20,}"` | `flag-only`: require rotation |
      | Hardcoded credential | Any | JWT-looking token | HIGH | `search pattern="eyJ[A-Za-z0-9_-]{10,}\\.eyJ[A-Za-z0-9_-]{10,}\\.[A-Za-z0-9_-]{10,}"` | `flag-only`: require rotation |
      | Hardcoded credential | Any | Private key block | HIGH | `search pattern="-----BEGIN (RSA )?PRIVATE KEY-----"` | `flag-only`: require rotation/revocation |
      | Hardcoded credential | Any | Generic secret assignment | HIGH | `search pattern="(password|secret|api[_-]?key|token|credential|auth)[_-]?(key|token|secret|pass)?\\s*[:=]\\s*['\"][^'\"\\s]{8,}['\"]"` excluding test/mock/example/masked values | `flag-only` |
      | Placeholder text | Any | Placeholder text | HIGH | `search pattern="(lorem ipsum|test test test|asdf asdf|foo bar baz|replace (this|me)|todo:?\\s+implement|this is a placeholder)"` in code comments/strings, excluding docs and fixtures | `flag-only` unless isolated comment can be removed |
      | Whitespace artifact | Any | Trailing whitespace outside Markdown | HIGH | `search pattern="[ \\t]+$" paths=[source globs excluding *.md]` | `replace-whitespace`: strip suffix only |
      | Whitespace artifact | Any | Mixed indentation on one line | HIGH | `search pattern="^\\t+ +|^ +\\t+" paths=[source globs excluding Makefile, *.mk]` | `replace-whitespace`: normalize to file-dominant indent |
      | Mutable global state | JavaScript / TypeScript | Mutable all-caps global | MEDIUM | `search pattern="^(let|var)\\s+[A-Z][A-Z0-9_]*\\s*="` excluding config/tests/constants | `flag-only` |
      | Mutable global state | Python | Mutable all-caps global collection | MEDIUM | `search pattern="^[A-Z][A-Z0-9_]*\\s*=\\s*(\\[|\\{|dict\\(|list\\(|set\\()"` excluding settings/constants/tests | `flag-only` |
      | Missing safety justification | Rust | Unsafe block without safety comment | MEDIUM | `search pattern="unsafe\\s*\\{"` then inspect adjacent lines for `SAFETY:` | `flag-only` |
      | Suppression escape | Java | Raw generics / suppress annotations | LOW | existing linter or search for `@SuppressWarnings` / raw generic declarations | `flag-only` |
      | Privilege and supply-chain hazard | Shell | `chmod 777` | HIGH | `search pattern="chmod\\s+777"` | `flag-only`; requires permission design |
      | Privilege and supply-chain hazard | Shell | Curl pipe shell | HIGH | `search pattern="curl .*\\|\\s*(sh|bash)|wget .*\\|\\s*(sh|bash)"` | `flag-only`; security remediation |
      | Dead or unreachable code | JavaScript / TypeScript | Dead code after return/throw | MEDIUM | AST/control-flow scan for statements after terminating statement in same block | `flag-only` |
      | Dead or unreachable code | Any | Dead code after terminator | MEDIUM | AST/control-flow: statements after `return`, `throw`, `break`, `continue`; verify no label/fallthrough semantics | `flag-only` |
      | Commented-out code | Any | Commented-out code block | MEDIUM | ≥5 consecutive comment lines matching code-like tokens | `remove-block` only when isolated and verifier passes; otherwise `flag-only` |
      | Comment bloat | JavaScript / TypeScript | Doc-to-code ratio >3 | MEDIUM | Compare JSDoc block line count to function body lines for functions with ≥3 code lines | `flag-only` |
      | Comment bloat | JavaScript / TypeScript | Verbose comments ratio >2 | MEDIUM | Count inline/comment lines vs code lines inside functions | `flag-only` |
      | Comment bloat | Any | Doc-to-code ratio >3 | MEDIUM | Read the function and count contiguous doc/comment lines immediately preceding it vs body lines; skip tiny functions (<3 code lines) | `flag-only` |
      | Comment bloat | Any | Verbosity ratio >2 | MEDIUM | Count comments vs code inside one function/class; ignore license/API docs and generated declarations | `flag-only` |
      | Over-engineering | Any | Over-engineering | MEDIUM | Use codegraph/files: file/export ratio >20, lines/export >500, depth >4 without module boundary | `flag-only` |
      | Unsubstantiated capability claim | Any | Buzzword inflation | MEDIUM | Search claims `production-ready`, `production-grade`, `enterprise-grade`, `secure`, `scalable`, `highly available`; require fewer than 2 concrete evidence hits | `flag-only` |
      | Infrastructure without implementation | Any | Infrastructure without implementation | MEDIUM | Find setup names ending `Client/Connection/Pool/Service/Provider/Manager/Factory/Repository/Gateway/Queue/Cache/Store`; codegraph callers/callees or search shows no use outside setup/export. Where the language has a `new`-expression an AST match is more precise, but it needs **both** forms: the assignment form `ast-grep pat="$NAME = new $TYPE($$$ARGS)"` matches only bare assignments (`client = new Client()`, `this.store = new Store()`) and silently misses the far more common declaration, so pair it with the declaration form for the language: `var`/`let`/`const $NAME = new $TYPE($$$ARGS)` as three separate runs (JavaScript/TypeScript; the keyword is literal, so one run per keyword), and `$TYPE $NAME = new $CTOR($$$ARGS)` (Java, C++, C#; in C# this also covers `var` declarations, since `var` is itself a type identifier). Verified matching in JavaScript/TypeScript, Java, C++, and C#; Go and Python have no `new`-expression, so use codegraph or the name search there | `flag-only` |
      | Residual: external tool signals | Any | Duplicate code | LOW | Run existing `jscpd` only if present; parse duplicated blocks | `flag-only` |
      | Residual: external tool signals | JavaScript / TypeScript | Dependency cycles | LOW | Run existing `madge` only if present | `flag-only` |
      | Residual: external tool signals | JavaScript / TypeScript | Existing lint findings | LOW | Run repo-local `eslint` script/config only if already present | `flag-only` |
      | Residual: external tool signals | Rust | Existing lint findings | LOW | Run `cargo clippy` only when Rust project already uses it or user asks | `flag-only` |
      | Residual: external tool signals | Go | Existing lint findings | LOW | Run `golangci-lint` only if repo config/binary exists | `flag-only` |
      | Residual: external tool signals | Any | High complexity | LOW | Existing complexity tool only; threshold >10 cyclomatic flags, >20 severe | `flag-only` |
      
      ## Autofix strategy semantics
      
      | Strategy | Allowed change | Never do |
      |---|---|---|
      | `remove-line` | Delete a standalone debug/log/trace line or isolated whitespace-only artifact | Delete a call expression whose return value or side effect is used |
      | `replace-whitespace` | Strip trailing spaces; normalize mixed indentation line-by-line | Reformat unrelated code |
      | `add-comment` | Add a short intentional-ignore comment to an already-empty handler when surrounding code proves the swallow is intended | Invent logging, swallow more errors, or hide unknown intent |
      | `remove-block` | Delete isolated commented-out code or unreachable placeholder block proven disconnected from public behavior | Delete live API stubs or exported symbols |
      | `flag-only` | Report exact evidence and recommended human action | Apply an edit |
      
      ## Report shape
      
      Use this compact shape for handoff:
      
      ```text
      HIGH applied:
      - path:line pattern strategy
      
      HIGH flagged:
      - path:line pattern reason
      
      MEDIUM manual inspection:
      - path:line pattern evidence
      
      LOW advisory:
      - tool pattern evidence
      
      Verification:
      - command: <repo verifier>
      - result: pass|fail
      - rollback: none|git restore -- <file...>
      ```
      
  • SKILL.md 21.5 KB
    ---
    name: deslop
    description: 'Use when the user says deslop, debloat, tidy, simplify, clean a diff, cleanup codebase, or deslop branch diff, or remove dead code or config. Not for remote, credential, publish, deploy, or irreversible changes.'
    ---
    
    # Deslop
    
    Four modes share one spine: bound scope, verify with the repo's own command, rollback on regression, atomic commits separate from behavior changes.
    
    ## Contract
    
    | Field | Bound contract |
    |---|---|
    | Trigger | The user says deslop, debloat, tidy, simplify, clean up this diff, cleanup codebase, deslop a branch diff, remove dead code, find placeholders or stubs, remove dead fields, redundant wrappers, or stale config, or the slop skill routes code findings here |
    | Authority | Reversible local: writes only production source files or prose artifacts (may run the repo verifier and `git restore` on regression); rollback is version control or undo. No remote mutation. |
    | Side effect | Local writes to production source files or prose artifacts; no edits to tests, fixtures, mocks, examples, generated, vendored, or lockfile/build artifacts |
    | Done | Mode-specific done predicate holds; verifier green or rollback confirmed |
    
    ## Refusal
    
    Not for behavior changes, new abstractions, or refactors that introduce patterns: a tidy pass that would change observable behavior stops and reports a candidate for a separate refactor. Not for remote, credential, publish, deploy, or irreversible changes. Not for duplication across artifacts or drift: those are handed off, not force-compressed. Not for non-code targets (memory, git workspace, docs): route to their owners. Not for opportunistic sweeps across untouched files in tidy mode: the candidate must lie in code already under edit.
    
    ## Mode selection
    
    | User says | Mode | Target |
    |---|---|---|
    | deslop, remove debug code, find placeholders or stubs, remove dead code, slop routed here | slop | Production source files |
    | debloat, tighten this, too long | bloat | One padded prose artifact (document, skill, spec) |
    | tidy this up, simplify, clean up this diff, polish my changes, make this simpler | tidy | Code in the working tree |
    | dead field, redundant wrapper, stale config, duplicate state, speculative abstraction | tidy | Code already under edit |
    | cleanup codebase | tidy | Code already under edit |
    | deslop this branch diff, remove AI debris from my branch, clean up added lines only | diff | Added/modified lines in a branch diff |
    
    ## Shared spine
    
    1. **Bound scope.** Prefer changed files unless the user requested a full sweep. Exclude tests, fixtures, mocks, examples, generated output, vendored code, lockfiles, build artifacts, and minified bundles: `**/test/**`, `**/tests/**`, `**/__tests__/**`, `*.test.*`, `*.spec.*`, `*_test.*`, `*Test.java`, `**/fixtures/**`, `**/mocks/**`, `**/testdata/**`, `**/examples/**`, `**/benches/**`, `dist/**`, `build/**`, `target/**`, `coverage/**`, `vendor/**`, `node_modules/**`, `*.min.*`, generated/protobuf/openapi outputs. Keep Markdown out of whitespace cleanup: trailing spaces can be semantic line breaks. Done when: the file set is enumerated and exclusions applied.
    
    2. **Verify.** Run the repo's own test command after fixes. Derive it from manifests in this order: package script (`test`, then `check`, then `typecheck`), `cargo test`, `go test ./...`, `pytest`, `mvn test`, `gradle test`, `dotnet test`, `bundle exec rspec` or `rake test`, `composer test` or `phpunit`, `swift test`, or the project's documented command. If no command exists, run the narrowest parser/type check available, state the limitation, and treat every fix as unverified. Done when: the verifier has run or the limitation is stated.
    
    3. **Rollback on regression.** If verification fails, immediately `git restore -- <file...>` every changed file, rerun the verifier to confirm baseline, and report the failed fix group as blocked with file/line and failing command. Never suppress tests, rewrite expectations, or keep partial results. Done when: baseline is confirmed restored or fixes are verified green.
    
    4. **Commit separately.** Cleanup commits are always separate from behavior commits. Use atomic commits with clear messages naming what was removed. If a cleanup is mixed into a behavior commit, split it with `git move --fixup` or `git split` before merging. Done when: each commit has exactly one concern and the diff is net-deletion or inline-and-delete only.
    
    ## Slop mode
    
    Certainty-graded mechanical slop removal from production source. Full category catalog, per-language instances, and autofix strategy semantics: `references/slop-catalog.md`.
    
    1. **HIGH deterministic scan.** Use `search` for line patterns and `ast-grep` where syntax shape matters. Record `{file, line, pattern, certainty: HIGH, strategy}` for each finding. Categories: debug output (stream-writing mechanism left behind after debugging: exclude output that is the product: CLIs, loggers, entrypoints), placeholder or unimplemented body (empty block, no-op, not-yet-implemented throw, TODO-marked panic), swallowed failure (catch/except/rescue that discards the error so the unhappy path continues with invalid state), crash-on-failure shortcut (forced unwrap, unchecked cast, abort-on-error where failure is recoverable: flag only), hardcoded credential (`sk-`, `ghp_`/`github_pat_`, `AKIA`, `Bearer <token>`, JWT strings, private-key blocks: flag only), placeholder text (lorem ipsum, `asdf asdf`, `foo bar baz`, `replace this`, `TODO: implement`), privilege and supply-chain hazard (`chmod 777`, piping download into shell: flag only), whitespace artifact (mixed tabs+spaces on one indentation prefix, trailing whitespace outside Markdown). Done when: every HIGH category has been scanned.
    
    2. **MEDIUM contextual scan.** Use codegraph first when indexed; otherwise combine `ast-grep`, `search`, and direct reads of the narrow files. Report only, no auto-fix: comment bloat (doc-to-code ratio >3 for a real function with ≥3 code lines, or >2 comments per code line inside a function; filler/hedging/buzzword comments), dead or unreachable code (statements after `return`/`throw`/`break`/`continue` that are not a language-required fallthrough), commented-out code (consecutive comment lines whose content is code), mutable global state (module-level binding named as constant but declared mutable, or mutable global collection outside settings/constants), missing safety justification (escape-hatch construct entered without the adjacent comment its convention requires), suppression escape (warning or type-check suppression applied instead of fixing the finding), over-engineering indicators (file/export ratio >20, lines/export >500, directory depth >4 without real module boundaries), unsubstantiated capability claim ("production-ready", "secure", "enterprise-grade", "scalable" with fewer than two concrete supporting code signals), infrastructure without implementation (`Client`/`Connection`/`Pool`/`Service`/`Provider`/`Manager`/`Factory`/`Repository`/`Gateway`/`Queue`/`Cache`/`Store` values created but never used beyond setup/export), stub return values (function whose only significant body line returns `0`/`null`/`undefined`/`None`/`nil`/`false`/`true`/`[]`/`{}`/`""`/empty collections/`Default::default()`/`Optional.empty()`: escalate attention when adjacent TODO/FIXME/STUB text exists, keep auto-fix disabled). Done when: MEDIUM findings are recorded.
    
    3. **LOW optional CLI scan.** Run only tools already available in the repo or PATH; never install. Record findings as LOW and `flag-only`: `jscpd` for duplication, `madge` for cycles, and the linter the project already declares (derived from its manifest or config: `eslint`, `clippy`, `golangci-lint`, `ruff`, `ktlint`, `rubocop`, `phpstan`, `swiftlint`, the .NET analyzers, and equivalents). If a tool is absent, write `missing: <tool>` and continue. Done when: available tools have run.
    
    4. **Prioritize.** Sort HIGH before MEDIUM before LOW; then severity; then scope proximity to changed files; then fix strategy. Keep a separate `fixes` list containing only HIGH findings with `remove-line`, `remove-block`, `replace-whitespace`, or `add-comment` strategies. Exclude every `flag-only` finding from automatic edits. Done when: the fix list is ordered and flag-only findings are excluded.
    
    5. **Fix HIGH only.** Apply the smallest edit that removes the deterministic slop: `remove-line` (debug prints, trailing whitespace, isolated commented-out code blocks), `replace-whitespace` (convert mixed indentation to the file's dominant style; strip trailing spaces), `add-comment` (empty catch/except blocks only when the correct behavior is intentionally swallowing the error and the surrounding code proves that intent: otherwise flag, do not invent logging), `remove-block` (placeholder block only when it is unreachable/dead and removal cannot change API behavior: stubs on live API surfaces are report-only), `flag-only` (hardcoded secrets, crash-on-failure shortcuts, placeholder implementations, dead code requiring control-flow judgment, architectural smells). Done when: all HIGH non-flag fixes are applied.
    
    Done when: HIGH fixes applied and verified, MEDIUM/LOW findings left flagged for manual inspection, verifier green or rollback confirmed.
    
    ## Bloat mode
    
    Compress one padded but fully binding prose artifact in place. Every rule present before the pass is present after; the artifact is materially denser.
    
    1. **Read end to end.** Note in one line what each section must convey. No second artifact is read or written. Done when: the artifact's load-bearing structure is mapped.
    
    2. **Find padding.** A needless qualifier, a sentence fusing three ideas, an enumeration better expressed as a rule plus a short list, a nearby restatement, litigation history where the rule alone suffices. Done when: padding candidates are listed.
    
    3. **Compress in place.** Cut the padding, split fused sentences, replace excessive enumerations with a rule and short list, keep repeated points once. Move nothing to another artifact and re-derive nothing. Done when: the artifact is materially denser.
    
    4. **Keep every load-bearing claim.** If cutting a word would lose one, keep the word. Do not accept the loss. Done when: every prior rule and claim is confirmed present.
    
    5. **Hand off non-bloat.** Duplication across artifacts and drift are not bloat; do not force-compress them. State that they were handed off rather than removing them. Done when: non-bloat defects are named and left for their owner.
    
    6. **Review and cut again with fresh eyes.** The first pass always leaves some. Done when: a second pass finds nothing genuine to cut.
    
    Done when: every prior rule and claim remains, the artifact is materially denser, and non-bloat problems are handed off.
    
    ## Tidy mode
    
    Remove constructs that do not earn their keep from code in the working tree, then verify. Branch-specific detection patterns: `references/dead-fields.md` (dead fields and members), `references/dead-config.md` (dead flags, env vars, branches), `references/redundant-wrappers.md` (inline-then-delete wrappers).
    
    1. **Confirm scope.** In cleanup-codebase trigger shape (dead field, redundant wrapper, stale config), the candidate must lie in a file already touched by the active change. If it does not, stop: opportunistic sweeps across untouched files are out of scope. In tidy trigger shape (tidy this up, simplify), the scope is the user-named target, the active file, or the current diff. Done when: the exact files and functions in scope are identified.
    
    2. **Read end to end.** Understand what each function, type, and module in scope must do or convey. Note the behavioral contract each piece serves. Done when: the scope's contracts are mapped.
    
    3. **Classify candidates.** For each construct in scope:
       - Dead code: unreachable paths, unused imports, unexported helpers with zero callers, commented-out blocks, stale feature-flag branches that are always-on or always-off.
       - Redundant construct: duplicated logic, a wrapper that only forwards, a variable assigned once and immediately consumed, a conditional whose guard is always true or false in context, a type alias that adds no clarity.
       - Special case: a branch that handles one input shape identically to the general case, a guard that duplicates the default, a fallback that cannot trigger.
       - Ceremony: a factory/builder/adapter with one real implementation, a generic parameter with one concrete use, an abstraction layer with no real boundary behind it.
       - Not a candidate: live behavior, public API contracts, real boundary seams (process, network, untrusted input, FFI, async/sync, test/production where mocks substitute), code that is verbose but not wrong. A swappable-implementation contract counts only when more than one real implementation ships today.
    
       Indirection earns its keep only at a real boundary: public API surfaces, process or network seams, untrusted-input boundaries, async/sync seams, runtime FFI seams, or test/production seams where mocks substitute. Internal modules in the same package, same-file helpers, and cross-module calls without a co-change constraint are not boundaries. Done when: every construct in scope is classified.
    
    4. **Confirm dead across all consumers.** Run `git --no-pager grep -n` (or `ast-grep`) for every reference to the candidate in code, tests, docs, configs, error messages, and log lines. A field is dead only if it is never read after assignment; a wrapper is redundant only if it adds nothing but a rename or forward. If any consumer is unverified, stop and investigate rather than delete. Done when: every candidate is confirmed dead or redundant across all consumers.
    
    5. **Check coupling.** Determine whether removal breaks the build or forces a refactor of the only consumer. If it does, that is a separate decision; record it and do not delete in this pass. Done when: coupling effects are assessed and blocking couplings are recorded.
    
    6. **Remove in place.** Delete dead code. Inline single-use wrappers at every call site, then delete the wrapper. Collapse special cases into the general path. Fuse duplicated logic into one copy. Remove ceremony that does not protect a real boundary. Never comment out code as a substitute for deletion. Never introduce new patterns, abstractions, or dependencies: the diff must be net-deletion or inline-and-delete only. Done when: all confirmed candidates are removed.
    
    7. **Search for ghosts.** Grep docs, error messages, config keys, env vars, and log lines for string references to the removed concept. Leftover references mean the cleanup is incomplete: remove them, or roll back if they indicate the candidate was not actually dead. Done when: no ghost references remain.
    
    Done when: working tree has fewer dead/redundant/special-cased constructs, every removal is confirmed dead across all consumers, build/lint passes, and no ghost references remain.
    
    ## Diff mode
    
    Remove AI-generated debris from a branch diff, bounded to added/modified diff lines only. The shared spine applies; the diff-specific steps below resolve the branch scope and bound every edit to the lines the branch introduced.
    
    1. **Resolve the branch diff scope.** Capture every commit since the branch diverged from its base plus staged and unstaged changes. Try base refs in order and use the first that resolves: `git merge-base HEAD origin/main`, then `origin/master`, then `main`, then `master`, then `@{upstream}`. Run `git diff <base>` (no `..HEAD`, so working-tree changes are included). If none resolves, check HEAD: if `git rev-parse --verify HEAD` fails, HEAD is unborn and the scope is user-named files only; if `HEAD^` fails, HEAD is the root commit and the scope is the working tree (`git diff HEAD`); otherwise stop and request an explicit base, because falling back to `git diff HEAD` on a local-only branch with committed work would silently drop that work. Done when: a base ref resolves and `git diff <base>` runs, or the scope is confirmed as user-named files / working tree / root commit, or the skill stops to request an explicit base.
    2. **Enumerate changed files from the diff.** Exclude tests, fixtures, mocks, examples, benchmarks, generated output, vendored code, lockfiles, and build artifacts; they may intentionally contain placeholders, fake tokens, and debug output. Done when: the changed-file list is produced with exclusions applied.
    3. **Read local style context.** For each changed file, read the diff hunks plus enough surrounding unchanged lines to judge the file's dominant indentation, naming, comment density, and import ordering. "Matching local style" is the done predicate, so this context is required before any edit. Done when: per-file dominant indentation, naming, comment density, and import ordering are recorded.
    4. **Identify AI-generated debris in added lines only.** Scan the lines the branch introduced, not unchanged context. Debris classes: debug output left after debugging (console/print/debug macros/shell tracing), excluding output that is the product (CLIs, loggers, entrypoints); placeholder or unimplemented bodies (empty block, no-op, not-yet-implemented throw/abort, `TODO: implement`); commented-out code blocks; restating-the-code comments and motivational or hedging comments ("Let me...", "Now we...", "Here we..."); placeholder text in string literals (lorem ipsum, `foo bar baz`, `replace this`); unused imports or variables introduced by the change; redundant defensive guards that duplicate a check already present in the same path; mixed tabs+spaces or trailing whitespace on added lines. Done when: every added line is scanned and findings are listed by debris class.
    5. **Classify each finding before editing.** Apply only removals that cannot change behavior: delete debug prints, delete restating/hedging/motivational comments, delete commented-out code blocks, delete unused added imports and variables, and normalize added-line indentation and trailing whitespace to the file's dominant convention. Flag-only, no edit: placeholder implementations on live API surfaces, hardcoded credentials, crash-on-failure shortcuts (forced unwrap, unchecked cast, abort-on-error where failure is recoverable), and dead code requiring control-flow judgment. Done when: each finding is marked remove or flag-only.
    6. **Bound every edit to added/modified diff lines.** Never edit unchanged context lines, never reformat the whole file, and never introduce new logic, imports, or abstractions. The diff must shrink or stay focused; it must not grow. Done when: edits are applied only on added/modified lines and the diff has not grown.
    
    Done when: focused diff matching local style, verifier green or rollback confirmed.
    
    ## Failure and recovery
    
    | Failure mode | Rule |
    |---|---|
    | Scope violation (auto-fix touches an excluded file) | Revert that file with `git restore`; do not widen scope |
    | Certainty violation (MEDIUM or LOW finding edited in slop mode) | Revert the edit; the finding stays report-only |
    | Behavior regression (verifier fails after fixes) | `git restore -- <file...>` every changed file, rerun the verifier, report the failed fix group as blocked with file/line and failing command. Keep no partial cleanup |
    | Lost-claim cut (bloat mode, cutting a word would lose a load-bearing claim) | Keep the word. Do not accept the loss |
    | Not-bloat problem (bloat mode, defect is duplication across artifacts or drift) | Hand it off; do not mutate the artifact for it |
    | Unverified consumer (tidy mode, a consumer was not in the original grep) | Do not delete. Investigate and either preserve the candidate or migrate the consumer first |
    | Candidate might be live behavior (tidy mode) | Classify as "not a candidate" and skip. Do not remove to find out |
    | Mixed-concern commit (cleanup bundled with behavior change) | Split with `git move --fixup` or `git split` before merging; never merge a mixed commit |
    | New abstraction introduced during cleanup | Cleanup must be net-deletion. Separate the abstraction into its own commit with independent justification, or drop it |
    | No verifier available | Treat every fix as unverified; state the limitation; do not claim the done predicate holds |
    | Nothing to improve | A pass that finds nothing genuine to improve changes nothing |
    | Empty change-set (diff mode) | No diff after all resolutions and no user-named files. Stop, report pass-through, make no edits |
    | No base ref resolves and committed history exists (diff mode) | Stop and request an explicit base (`deslop against <ref>`). Do not fall back to working-tree-only, which would silently drop committed branch work |
    | Finding requires behavior or control-flow judgment (diff mode) | Flag-only, no edit. Never swallow the failure or pretend the done predicate holds |
    
    Partial-result rule: applied fixes that verify stay; any fix whose verification is unconfirmed or failed is reverted and reported as blocked. In tidy mode, a pass that confirms some candidates dead and leaves others unverified lands only the confirmed deletions; unverified candidates stay untouched. Non-mutation rule (tidy mode): nothing is deleted until the dead-confirmation grep covers code, tests, docs, configs, and error messages. Never swallow an error or pretend the done predicate holds.
    
    ## Output
    
    A compact report naming the mode, changed files, fixes applied (HIGH only in slop mode), findings left for manual inspection, the verifier command and its result, and any rollback action taken. Bloat mode: the artifact rewritten in place plus a one-line summary of what was cut and which non-bloat problems were handed off. Tidy mode: removed/fixed/skipped counts with one-phrase skip reasons. Diff mode: resolved base ref (or working-tree/root-commit note), changed files cleaned, debris classes removed with file/line, flag-only findings left for manual review, the verifier command and its result, and any rollback action taken. If nothing needed doing: `Deslop: nothing to do.`
    
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related