# Codebase Hygiene

> Two-mode skill: (1) find semantic duplicates — functions doing the same thing under different names, invisible to copy-paste detectors; (2) deepen shallow modules — thin wrappers and pass-through layers that spread complexity. Advisory and read-only; changes route through BUILD with full gates.

- Skill: `romiluz13/codebase-hygiene` (Agent Skill)
- Install (CLI): `npx skillmds@latest add romiluz13/codebase-hygiene`
- Raw SKILL.md: https://api.skillmd.com/api/skills/romiluz13/codebase-hygiene/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: romiluz13 (https://skillmd.com/u/romiluz13)
- Updated: 2026-09-09
- Page: https://skillmd.com/skills/romiluz13/codebase-hygiene

---


# Codebase Hygiene (Duplicate Detection + Module Deepening)

Advisory and read-heavy. Diagnoses and proposes; does not refactor. Any actual change goes through BUILD with full gates.

## Mode: DUPLICATE DETECTION

Semantic duplicates: functions serving the same purpose but implemented independently under different names. Copy-paste detectors catch syntactic duplicates; this finds "same intent, different implementation."

### Method

0. **Scope before you scan** — take the user's named target; else `git log --oneline` for hot spots and weight recently-changed code — deepening pays off in proportion to future change.
1. **Extract catalog** — Grep/Glob for exported functions. Record `name | file:line | signature`.
2. **Categorize by domain** (cheap tier) — validation, formatting, path manipulation, HTTP shaping, date handling. Mechanical bucketing to shrink comparison space.
3. **Drop categories with <3 functions** — can't hide a meaningful duplication pattern.
4. **Detect duplicates per category** (capable tier) — read implementations, decide which share intent. **Never use cheap tier for detection** — it anchors on names and rubber-stamps "these look different."
5. **Emit findings** — group by confidence, highest first. Each finding: what is duplicated, why it matters, the fix, `file:line` evidence. Route through code-reviewer finding contract.

### High-Risk Zones

| Zone | Why it duplicates |
| ------ | ------------------- |
| `utils/`, `helpers/`, `lib/` | Catch-all dumping grounds |
| Validation code | "Is this a valid email/id/url" rewritten per feature |
| Error formatting | Every module invents its own Error → string |
| Path manipulation | Join/normalize/relativize reimplemented |
| String formatting | Truncate, slugify, titlecase, pad re-rolled |
| Date formatting | Parse/format/diff scattered |
| API response shaping | Envelope/pagination/error-body copied per endpoint |

### Consolidation Discipline

Never delete a duplicate until all three hold:

1. **Survivor has tests** — pick the implementation with real coverage. If neither has tests, write the test against the chosen survivor first.
2. **All callers updated** — Grep to enumerate callers. Missing one is a silent break.
3. **Re-run after consolidation** — test suite + build/typecheck pass. Green tests on the survivor license the deletion.

---

## Mode: MODULE DEEPENING

Existing code works; the problem is **shape**, not features. LLM-grown codebases accrete shallow modules — thin wrappers, pass-through layers, near-duplicate helpers. Each looks harmless; together they spread complexity across every caller.

**Deep module:** hides a lot of behavior behind a small interface.
**Shallow module:** interface is almost as complex as its implementation — callers learn a thing without getting much.

### The Deletion Test

For each candidate: **If I deleted this module and inlined its code at every call site, where does the complexity go?**

- Complexity **vanishes** → the module was a pass-through / shallow. It adds indirection without hiding complexity. Deepening candidate (delete it or deepen its interface).
- Complexity **reappears across N call sites** → the module is deep. It earns its existence by hiding complexity that would otherwise be duplicated. Leave it alone.

This verdict agrees with `cc10x:codebase-design` (canonical) and `cc10x:architecture`. Depth is leverage at the interface — not a lines-ratio.

| Smell | Usually |
| ------- | --------- |
| Thin wrapper (`doX(a){ return lib.doX(a) }`) | Shallow |
| Pass-through layer (service maps 1:1 to repo) | Shallow |
| Near-duplicate helper (same intent, different name) | Shallow — consolidate first |
| Config/leverage module (small interface, branching/retry/state inside) | Deep — keep |

### Diagnosis Flow (READ-ONLY)

1. Enumerate exported surface (`name | file:line | signature`)
2. Flag shallow shapes
3. Cross-reference near-duplicates (run duplicate detection mode first — consolidate before deepening)
4. Run deletion test on each survivor
5. Output: candidate list, not a refactor

### Present Candidates Before Proposing Interfaces

Badge each candidate: **Strong** (unambiguous), **Worth-exploring** (real cost/risk), **Speculative** (hunch, low confidence).

For each: before/after sketch (current shallow interface vs proposed deeper one) with deletion test result. Then stop and ask which to pursue. Do not design interfaces for candidates the user hasn't chosen.

### Two-Adapters Rule for Seam Placement

When designing the deeper interface, a **port** (an injected external dependency seam) needs two concrete **adapters** — e.g. a production transport and a test stand-in. An ordinary caller or a test exercising a public interface is NOT an adapter; the two-adapter rule is about ports, not every seam.

For a non-port seam (the common case for module deepening): the test must be able to reach the interface. If only production code crosses the seam and no test can reach it → the seam is a guess. Put the interface where a test can reach it, or don't introduce it. This aligns with `cc10x:codebase-design` — "one adapter means a hypothetical seam" applies to ports; internal seams need reachability, not a second adapter.

### Handoff to BUILD

This skill ends at: chosen candidate + deeper-interface proposal + named seam. It does NOT edit code. The refactor routes through planner → BUILD workflow (builder → reviewer → verifier → doc-sync → memory). The deepening is verified: survivor interface has tests at its seam, every caller is repointed, suite + typecheck pass after merge.

Proposing is read-only; changing is gated.

