SsuperslateDocs

Unused code

Find and remove dead files, dependencies, and exports with knip after a change lands.

Unused code

Knip reports files, dependencies, and exports that nothing reaches. It resolves the real import graph per workspace, so it finds the leftovers that a generated feature, a half-finished refactor, or a removed subsystem leaves behind — the kind of code lint rules and the type checker cannot see, because every dead file is internally consistent.

Run it after a feature is built and before the change is declared complete.

Commands

pnpm knip       # apps/*, packages/*, and e2e
pnpm knip:fix   # apply the safe removals, then re-verify

knip.json at the repository root configures every workspace: its entry points, the files each workspace analyzes, and the dependencies that are referenced by runtime string rather than by import.

pnpm knip:fix removes unused dependencies from manifests and unused export keywords from source files. It does not delete files. Nothing about it is a substitute for verification: run vp check, vp run -r typecheck, and vp run -r test afterwards, and read the diff before committing it.

What fails, and what only reports

ReportedSeverityWhy
Unused fileserrorA file nothing imports is waste in every deployment artifact.
Unused dependencies and dev dependencieserrorUnused packages still carry install time, size, and CVE risk.
Unlisted, unresolved, and missing binarieserrorAn import with no declared owner breaks a fresh clean install.
Unused exports, exported types, namespace members, and duplicate exportswarningThe foundation deliberately ships surface no page calls yet.

CI runs pnpm knip after vp check, so the error tier is a merge gate and the warning tier is review information. Warnings are still findings: an export the change itself introduced and never used is dead code, and it should leave with the same commit.

Declared entry points

Knip treats these as reachable even when nothing imports them, because they are product surface a buyer consumes rather than code this repository calls:

PathReason
apps/web/src/components/ui/**The shipped primitive library documented in Web UI.
**/*.test.ts, scripts/**Test suites and maintenance scripts are roots, not imported modules.
packages/* package entry pointsWorkspace libraries are consumed through their manifest entry.

Add a primitive to apps/web/src/components/ui and knip stays quiet — record it in Web UI instead, so the inventory stays the review surface for that directory.

Handling a finding

Work in this order:

  1. Wire it up. A helper, hook, or component that a feature was supposed to use is a missing call, not a knip problem. Fix the feature.
  2. Delete it. Nothing plans to call it: remove the file, the export, and the dependency it kept alive. Prove the deletion with vp check, typecheck, tests, and a build.
  3. Declare it. It is genuinely reachable outside the import graph — a runtime string reference, a provider plugin, a buyer-facing primitive. Add an entry, ignoreDependencies, or project pattern to the owning knip.json in the same change, and say in the commit why the code cannot be reached statically.

Do not silence a finding by widening ignore patterns because the report is inconvenient. A directory-wide exemption removes that directory from every future report, which is exactly how the next generated feature hides its own leftovers.

Known false-positive sources

  • Runtime string references. pino-pretty is named as a transport target string, never imported; it is declared in apps/server's ignoreDependencies.
  • Toolchain aliases. The root vite entry is the Vite+ core alias that vp resolves, so it is declared at the root workspace.
  • Generated content. apps/docs/.source is produced by fumadocs-mdx at build time and is covered by .gitignore, so knip never sees it as source.

On this page