| name | code-dedup |
| description | Searches for duplicate code, duplicate tests, and dead code in the Deslop Rust workspace, then safely merges or removes them. Use when the user says "deduplicate", "find duplicates", "remove dead code", "DRY up", or "code dedup". Requires test coverage — refuses to touch untested code. |
Code Dedup
Carefully search for duplicate code, duplicate tests, and dead code across the Rust workspace (crates/deslop-core, crates/deslop). Merge duplicates and delete dead code — but only when test coverage proves the change is safe.
Prerequisites — hard gate
Before touching ANY code:
- The Deslop MCP must be reachable and returning correct data. This skill is
driven by the live MCP (
top-offenders, cluster-by-id, find-similar). If
the MCP is unavailable, errors, or returns stale/wrong data, STOP — do not
dedup blind, and file a GitHub issue with gh issue create per
CLAUDE.md Rule zero.
- Do NOT run
make test — or any test — as part of this skill. Deduping is a
pure refactor. Tests run exactly once, at the very end, through the ci-prep
skill (Step 6) — never before or between dedup edits.
- Rust is statically typed — the compiler and ci-prep catch breakage.
Steps
Copy this checklist and track progress:
Dedup Progress:
- [ ] Step 1: MCP reachable, duplicate surface inventoried
- [ ] Step 2: Dead code scan complete
- [ ] Step 3: Duplicate code scan complete (via Deslop MCP)
- [ ] Step 4: Duplicate test scan complete
- [ ] Step 5: Changes applied (no tests run)
- [ ] Step 6: ci-prep skill run AFTER dedup — green
Step 1 — Inventory the duplicate surface
- Query the Deslop MCP
top-offenders (worst-first) and note the clusters you
intend to merge. This — not a test run — is where dedup starts.
- Identify which files have E2E coverage in crates/deslop/tests;
prefer deduping covered files. ci-prep enforces the coverage floor at the end.
- Do NOT run
make test here. Do not run it anywhere except via ci-prep in Step 6.
Step 2 — Scan for dead code
- Run
make lint — cargo clippy already denies dead_code, unused_imports, unused_variables, unused_mut, unused_assignments, unused_results per Cargo.toml workspace lints. Treat every warning as dead-code evidence.
- For each candidate,
grep the entire workspace (including tests, fixtures, docs/) for references. Only mark as dead if truly zero references.
- List all dead code found with file paths and line numbers. Do NOT delete yet.
Step 3 — Scan for duplicate code
- Look for functions/methods with identical or near-identical logic across crates/deslop-core/src and crates/deslop/src.
- Look for copy-pasted blocks (same structure, maybe different identifiers — Type-2 clones).
- Check across pipeline stages (discover → parse → normalize → fingerprint → cluster → LSH → embed → fuse → rank → render). Duplicates often hide between adjacent stages.
- Dogfood the live Deslop MCP —
top-offenders, then cluster-by-id for each cluster you'll merge. This is the first-class duplicate signal. Do not shell out to the CLI or run tests for this.
- For each duplicate pair: note both locations, what they do, and how they differ (if at all). Do NOT merge yet.
Step 4 — Scan for duplicate tests
- Look for E2E tests that assert the same rendered-report behaviour against the same fixture.
- Look for duplicated fixture directories or helper functions across crates/deslop/tests.
- If an integration-level E2E fully covers what a narrower E2E also covers, mark the narrower one redundant. Per CLAUDE.md: coarse E2E tests only — never delete a failing test, never remove assertions.
- List all duplicate tests found. Do NOT delete yet.
Step 5 — Apply changes (one at a time)
Make one dedup change at a time so a mistake is easy to isolate. Do NOT run
make test between changes — the compiler catches type breakage and ci-prep
(Step 6) is the single validation gate.
5a. Remove dead code
- Delete dead code identified in Step 2.
5b. Merge duplicate code
- Extract shared logic into crates/deslop-core (the library
owns all non-trivial logic — the binary is <50 LOC of glue per CLAUDE.md).
Shared test scaffolding belongs in each test crate's
tests/common/mod.rs.
- Update call sites to use the shared version.
5c. Remove duplicate tests
- Delete the redundant test (keep the more thorough one). Never remove an
assertion per CLAUDE.md.
Step 6 — Validate with ci-prep (AFTER all dedup)
Run the ci-prep skill now — and only now. It runs lint, the full test suite,
and the coverage gate exactly as CI does. This is the first and only time tests
run in this workflow.
- Invoke the ci-prep skill. If it reports failures, fix them and re-run it
until green.
- If coverage rose, ratchet coverage-thresholds.json
up in the same PR (−1% rounding buffer per [COVERAGE-THRESHOLDS-JSON]).
- Report: what was removed, what was merged, and the ci-prep result.
Rules
- No test coverage = do not touch. If a file has no E2E coverage, leave it alone entirely.
- Coverage is enforced by ci-prep, at the end — not mid-dedup. Never run
make test while deduping. If ci-prep shows coverage dropped, the "duplicate" was covering something — restore it.
- One change at a time. Make one dedup change, then the next. Never batch, so a mistake is easy to isolate when ci-prep runs.
- When in doubt, leave it. False dedup is worse than duplication. Only merge when you are 100% sure behaviour is identical.
- Preserve public API surface. Do not change public function signatures, crate-level exports, or CLI flags without a spec update in docs/specs/SPEC.md.
- Three similar lines is fine. Only dedup when the shared logic is substantial (>10 lines) or there are 3+ copies.
- No linter suppressions.
#[allow(clippy::...)] is ⛔️ ILLEGAL per CLAUDE.md. Fix the underlying code.
- No regex on source. Tree-sitter only — the tool's own codebase must obey its own rules.