yigitkonur/skills-by-yigitkonur

run-ts-cleanup

Use if cleaning up a TypeScript codebase — dead code, unused deps, AI slop, weak types.

Zobacz źródło
Oryginalny dokument Skill

Treść z repozytorium z zachowaniem nagłówków, przykładów, kodu, tabel, linków i obrazów.

Run TS Cleanup

A TypeScript codebase accumulates. Features get deprecated but their files stay. A refactor lands half-finished and both abstractions survive. An LLM writes cn for the fourth time in a fourth directory. Dependencies outlive the code that imported them. Types get exported "just in case" and never consumed. This skill sweeps all of it into a clean end state:

  • Every dead symbol, file, export, type, and dependency is found by a real engine, not by guesswork — and each finding is triaged before deletion, never deleted on the engine's word alone.
  • Every deletion lands in a reversible wave behind a passing verification gate, so any single step reverts with one git revert.
  • The root cause is addressed, not just the symptom: duplicate helpers get consolidated, circular barrels get untangled, single-use wrappers get inlined — so the same bloat does not regrow.
  • The codebase ends more navigable than it started, for the next agent and the next human, without a single behavioural change.

This is a finishing tool. Run it on a green main branch, not mid-feature.

When To Use

Trigger on phrases and repo states like:

  • "clean up this TypeScript project", "my codebase is a mess", "remove the dead code"
  • "find unused dependencies", "prune unused exports", "delete orphaned files"
  • "there's too much AI slop in here", "tighten up the types", "untangle these imports"
  • "set up knip", "run knip", "why is this bundle so big"
  • Repo state: after a feature deprecation, a framework migration, or a long LLM-driven build-out.

Do NOT use this skill for:

  • Pure formatting or style-only changes — run the project's own formatter; this skill deletes code.
  • Branch, worktree, and repo-artifact cleanup — use run-repo-cleanup.
  • Reviewing a diff or PR — use run-review.
  • A dirty working tree — commit or stash first; Phase 0 refuses to start otherwise.
  • A red baseline — fix the failing build or tests first. Cleanup on a broken repo cannot tell your breakage from its own.

Modes

Pick the mode explicitly at the start, from the user's ask. Announce which one you picked. Escalating mid-run is fine; silently doing more than was asked is not.

ModeRunsFires on
SweepPhase 0-1, then lint autofix and Wave 1 only"tidy this up quickly", "drop the unused deps"
StandardPhases 0-3, then Waves 1-5"clean up the dead code"the default
DeepStandard, plus Phase 4 root-cause work, type hardening, and structural refactor"the codebase is a mess", "make this maintainable"

The Engines

No single tool finds everything. Each engine sees one scope and is blind to the others.

EngineFindsScope
KnipUnused files, exports, types, dependenciesModule graph
Biome / Oxlint / ESLint / UltraciteUnused locals, dead imports, correctness lintFile AST
`tsc`Type errors, declaration-emit breakageProgram
`type-coverage`any-creep, as a numberProgram
`madge`Circular dependenciesImport graph
anti-slop (Oxlint plugin)LLM-generated antipatternsFile AST

Knip and the linters are complements, not alternatives: Knip finds a file nothing imports, a linter finds an import nothing uses. Read references/engines/engine-matrix.md for the full matrix and the auto-detection protocol.

Pinned Defaults

Decided once; override per-project only if the repo says otherwise (AGENTS.md / CLAUDE.md / CONTRIBUTING.md win over this skill).

KeyDefaultWhy
the gatetsc --noEmittestbuild, resolved for the detected package managerThe single verification barrier. Defined once in references/remediation/waves.md; every "Run the gate" in this skill means exactly that.
branchchore/ts-cleanupNever work on main.
config fileknip.jsonc (fallback knip.json)JSONC carries comments explaining why each entry exists.
package managerDetected from packageManager field, else lockfileNever hardcode pnpm. See waves.md §1.2.
wave orderDeps → Files → Barrels → Exports → TypesReverse order leaves dangling imports and ghost references.
commit unitOne commit per waveEach wave reverts independently.
suppressionTargeted keys onlyignoreDependencies, ignoreBinaries, ignoreExportsUsedInFile, explicit entry.

Non-Negotiable Safety Rails

  1. Start green. Verify git status --porcelain is empty, the typecheck passes, and the test suite passes before touching anything. A cleanup that begins on a red baseline cannot distinguish its own damage from pre-existing damage.
  2. Suppress false positives with targeted keys. Reach for ignoreDependencies, ignoreBinaries, ignoreExportsUsedInFile, or an explicit entry — each one silences exactly the finding it names. A broad top-level ignore severs graph edges instead, so files the ignored code imports start reporting as dead and get deleted.
  3. Triage every finding before deleting it. Apply the Three-Question Verification Test to each one. An engine reports what it cannot see a reference to; framework entry points, dynamic imports, and published API surfaces are all invisible to it.
  4. Delete in wave order. Dependencies, then files, then barrels, then exports, then types. Each wave's output is the next wave's input.
  5. Run the gate after every wave, before every commit. A wave that fails the gate is reverted or fixed, never committed.
  6. Prove declaration-emit safety before un-exporting. tsc --noEmit builds the implementation graph and never the declaration graph, so it passes on code that tsc -b will reject. See references/types/declaration-emit.md.
  7. Fix the cause, then the symptom. Consolidate the four copies of cn before deleting three of them; migrate the call sites before pruning the abstraction.

The Six Phases

        dead code · unused deps · AI slop · weak types · tangled imports
                              │
   Phase 0 — Baseline: clean tree, green typecheck, green tests, branch cut
                              │
   Phase 1 — Engine setup: detect the stack, configure each engine, resolve the manager
                              │
   Phase 2 — Survey: run every engine, batch findings, score risk
                              │
   Phase 3 — Triage: every finding gets a verdict — dead, false-positive, or deferred
                              │
   Phase 4 — Root cause: consolidate duplicates, untangle cycles, inline wrappers  [Deep]
                              │
   Phase 5 — Waves: delete in order, gate after each, one commit per wave
                              │
     zero dead code · green gate · one revertable commit per wave · a report you trust

Each phase gates the next. If you are tempted to skip one, re-survey instead.


Phase 0 — Baseline

Think first: "Is this repo green right now, and can I prove it?"

  1. Refuse to proceed on a dirty tree:
bash
   test -z "$(git status --porcelain)" || { echo "Dirty tree — commit or stash first."; exit 1; }
  1. Cut the working branch: git checkout -b chore/ts-cleanup
  2. Resolve the package manager and write the gate script once — references/remediation/waves.md §1.2-1.4 gives the detection snippet.
  3. Run the gate. Record that it passed. This is the baseline every later gate is compared against.
  4. Capture the starting metrics for the final report: finding counts per engine, type-coverage percentage, cycle count.

Gate → Phase 1 when: the tree is clean, the branch exists, the gate passes, and baseline metrics are recorded.


Phase 1 — Engine Setup

Think first: "Which engines does this repo already have, and which does it need?"

  1. Detect what is installed — Biome, Oxlint, ESLint, Prettier, Ultracite, Knip, madge, type-coverage:
bash
   python3 scripts/audit-ts-health.py --target .

With no --check-* flag it runs all four audits. Passing any --check-* flag narrows it to just those.

  1. Generate or review the Knip config:
bash
   python3 scripts/init-knip-config.py --dry-run     # inspect first
   python3 scripts/init-knip-config.py --format jsonc

It detects frameworks and monorepo layout, and stamps a $schema matching the installed Knip major version.

  1. Resolve configuration hints before trusting any finding. Knip reports config hints ahead of issues; a hint left unresolved produces a cascade of false findings downstream. Tune entry points until the hints are gone — references/engines/knip-configuration.md has the plugin catalog, monorepo patterns, and compiler settings.
  2. Confirm zero broad top-level ignore patterns in the config.

Gate → Phase 2 when: every engine the mode needs is runnable, config hints are resolved, and no broad ignores remain.


Phase 2 — Survey

Think first: "What does every engine see, and how much of it is worth acting on?"

  1. Run the dead-code engine and batch its output:
bash
   python3 scripts/batch-findings.py --run --output markdown --out-file cleanup-plan.md

Batches 1-6 come from the dead-code engine, each with a risk score and a gate rendered for the detected manager.

  1. Run the remaining engines the mode calls for — lint, type-coverage, madge. Their findings form batches 7-9. Commands in references/engines/lint-engines.md and references/engines/analysis-engines.md.
  2. Read the batch definitions and risk model in references/detection/finding-classification.md — nine batches, each naming its producing engine and the wave it feeds. Several collapse: batches 4 and 5 both feed Wave 4, 3 and 9 feed Wave 3, 6 and 8 feed Wave 5.

Gate → Phase 3 when: cleanup-plan.md exists, every finding sits in a batch, and every batch carries a risk score.


Phase 3 — Triage

Think first: "Which of these findings would break production if I deleted it?"

Apply the Three-Question Verification Test to every finding:

  1. Framework lifecycle? Does a convention invoke this without importing it — a Next.js page.tsx / route.ts, a Remix loader, an Astro content config, a Playwright fixture?
  2. Dynamic invocation? Is it reached through a template-literal import(), a string-keyed registry, or runtime reflection?
  3. Public contract? Is it exported to consumers outside this repo's static graph, via exports or typesVersions?

Verify with the codebase, not from memory:

bash
rg -g '!node_modules' -g '!dist' 'symbolOrPackageName'

Codify each confirmed false positive back into config as a targeted key — never as a broad ignore. Framework entry catalogs, dynamic-import patterns, and lint/type false positives are in references/detection/false-positive-triage.md.

Gate → Phase 4 when: every finding in cleanup-plan.md carries an explicit verdict of dead, false-positive, or deferred. Zero blanks. Count them and state the number.


Phase 4 — Root Cause

Think first: "Why did this bloat appear, and what stops it coming back?"

Deep mode only. Sweep and Standard skip to Phase 5.

Deleting the symptom leaves the generator running. Work out which origin produced each cluster, then fix that:

What you foundWhere to go
Four copies of cn, duplicate formatDate, useless try/catch, phantom null checks, boolean theaterreferences/detection/code-slop-catalog.md — origins and per-pattern remediation
Circular imports, bloated barrels, single-use wrappers, scattered helpersreferences/remediation/structural-refactor.md — plan-first structural work
Brain methods, deep nesting, long parameter lists, speculative generalityreferences/remediation/complexity-thresholds.md — numeric thresholds to prioritise by
any sprawl, missing strict flags, weak inferencereferences/types/strict-migration.md — the ratchet

Consolidation is a code change like any other: run the gate and commit it before starting Wave 1.

Gate → Phase 5 when: duplicates are consolidated behind one canonical implementation, call sites are migrated, cycles are broken, and the gate passes.


Phase 5 — Waves

Think first: "What is the smallest deletion I can make, prove, and commit right now?"

Five waves, in order. After each: run the gate, then commit. A wave that fails the gate gets fixed or reverted — never committed. Full protocol, per-wave scope, and rollback in references/remediation/waves.md.

WaveDeletesAdditional checkCommit
1Unused dependencies, then refresh the lockfilechore(deps): prune unused dependencies
2Orphaned and unreferenced files (git rm)chore: remove unreferenced files
3Dead barrel re-exports; repoint consumers at source modulesrefactor: prune dead barrel re-exports
4Test-only leaks and in-file-only exports (strip export)Declaration emit, then lint autofixrefactor: internalize private exports
5Unused types, interfaces, enum membersDeclaration emitrefactor: prune dead types

Wave 4 needs the linter bridge. Stripping an export leaves the importing files holding specifiers that now reference nothing. Run the detected engine's autofix immediately after — recipes in references/engines/lint-engines.md, timing rationale in waves.md §7. Verify declaration emit before stripping anything reachable from a public signature: references/types/declaration-emit.md.

Post-flight

  1. Re-run every engine. Confirm zero remaining findings, or an explicit deferred list.
  2. Diff the metrics against the Phase 0 baseline: findings removed, type-coverage delta, cycle count delta, dependency count delta.
  3. Remove scratch artifacts: cleanup-plan.md, .cleanup-gate.sh, any baseline JSON.
  4. Report what was deleted, what was deferred and why, and the metric deltas.

Gate → done when: every wave is committed, the gate passes on the final commit, engines report clean, and the report is delivered.


Common Mistakes

MistakeConsequenceInstead
Broad top-level "ignore": ["src/legacy/**"]Knip stops traversing those files, so their imports report as dead and get deletedUse ignoreDependencies / ignoreBinaries / ignoreExportsUsedInFile / explicit entry
Acting on findings before resolving config hintsEvery downstream finding is suspect; you triage noiseClear hints in Phase 1, then survey
One big cleanup commitNo bisect, no partial revert, unreviewableOne commit per wave
Deleting exports before pruning dependenciesDangling imports and confusing compiler cascadesWave order: deps → files → barrels → exports → types
Trusting tsc --noEmit before un-exportingIt never builds the declaration graph; tsc -b then fails on TS4023Verify declaration emit explicitly
Hardcoding pnpm in the gateThe gate silently no-ops on npm/yarn/bun reposResolve the manager from packageManager or the lockfile
Deleting duplicate helpers without migrating callersEach copy had drifted; consumers depended on the differencesConsolidate, migrate, gate, then delete
Skipping Phase 0 because the repo "looks fine"Pre-existing failures get attributed to the cleanupProve green first

Scripts

Python 3 stdlib only — no dependencies, no network beyond the engines they invoke.

ScriptPhasePurposeMutates?
scripts/audit-ts-health.py1, 4Detects linters and formatters, audits tsconfig.json flags, finds import cycles, flags slop markers.No
scripts/init-knip-config.py1Scans frameworks and monorepo layout; emits knip.jsonc/knip.json with a version-matched $schema.Yes — --dry-run to preview, --force to overwrite
scripts/batch-findings.py2Runs or parses the dead-code engine; batches findings, scores risk, renders gates for the detected manager.Only with --out-file

Run any script with --help for its full flag surface.

References

Bottom Line

Prove green → configure the engines → survey every scope → give every finding a verdict → fix the cause → delete in five gated waves → report the deltas.

z tego samego repozytorium

Więcej Skills

Wszystkie Skills