Testland
Browse all skills & agents

knip-dead-code

Run Knip against a JS/TS project to detect unused files, unused dependencies, unused exports, and unused class/enum members. Scoped to production code; tests are entry-point-aware via Knip's framework plugins.

knip-dead-code

Knip detects unused files, dependencies, exports, types, enum members, and class members in JS/TS projects per the Knip docs. Requires Node ≥ 20.19.0 (or Bun).

When to use

  • After a feature deletion when "everything still compiles" but you suspect dead code.
  • Periodic cleanup to remove unused npm dependencies (security + bundle-size win).
  • Pre-merge gate to prevent new dead exports from accumulating.

Step 1 - Install

# Recommended: setup wizard (auto-detects framework)
npm init @knip/config

# Manual
npm install -D knip typescript @types/node

Add package.json script:

{
  "scripts": {
    "knip": "knip"
  }
}

Per the Knip docs.

Step 2 - Configure entry points

knip.json:

{
  "$schema": "https://unpkg.com/knip@5/schema.json",
  "entry": [
    "src/index.ts",
    "src/cli.ts",
    "scripts/**/*.ts"
  ],
  "project": ["src/**/*.{ts,tsx}"],
  "ignore": ["src/**/*.generated.ts"],
  "ignoreDependencies": [
    "@types/node"
  ],
  "ignoreExportsUsedInFile": true
}

Or knip.config.ts for typed config.

Entry vs project: entry = files Knip starts walking the import graph from. project = the universe of files it considers reachable. Anything in project not reached from entry is unused.

Step 3 - First run

npx knip

Exit code: 0 if clean, non-zero if unused items found. Shows:

Unused files (3)
Unused dependencies (2)
Unused exports (12)
Unused exported types (4)
Unused exported enum members (1)

Step 4 - Use framework plugins

For framework-aware scanning, enable plugins (auto-detected by npm init @knip/config):

{
  "next": {
    "entry": ["next.config.{js,ts,cjs,mjs}", "app/**/page.{js,ts,tsx}"]
  },
  "playwright": {
    "config": "playwright.config.ts",
    "entry": ["e2e/**/*.spec.ts"]
  }
}

Plugin list (Next.js, Remix, Astro, Vite, Storybook, Playwright, Jest, etc.) - see the Knip plugins page.

Step 5 - Limit output during cleanup

# Show only top 5 of each issue type — useful when first adopting
npx knip --max-show-issues 5

Per the Knip docs for overwhelming output.

Step 6 - CI gate

# GitHub Actions
- name: Knip dead-code check
  run: npx knip

Fails build on any new dead code. For brownfield rollout, baseline existing dead code first:

# Snapshot current findings; CI only fails on additions
npx knip --no-exit-code > knip-baseline.txt

Step 7 - Per-issue triage

Reported asTrue positive?Action
Unused fileUsually yesDelete
Unused dependencyUsually yesnpm uninstall
Unused exportSometimes (public API)Mark with JSDoc @public + tags: ["public"] config
Unused enum memberUsually yesDelete
Unused class memberSometimes (public API)Same as unused export

Anti-patterns

Anti-patternWhy it failsFix
Run without entry pointsEverything looks orphanedAlways set entry (Step 2)
Treat "unused export" as "delete now"Public library APIs flag as unusedUse tags: ["public"] for library packages
Skip framework pluginsNext.js page files look orphaned (Knip doesn't know they're routes)Enable plugin (Step 4)
Block PRs from day 1 in brownfieldHundreds of pre-existing unused; team disables toolBaseline first (Step 6 alt path)
Auto-fix mode on first run--fix deletes legitimate unused public exportsManual review first; --fix after baseline clean

Limitations

  • Dynamic imports with computed paths (import(\./${name}`)) cannot be statically tracked; Knip flags dependencies as unused even though they load at runtime. Use ignoreDependencies` for these cases.
  • Side-effect imports (e.g., import "./polyfill") are tracked, but reflection-heavy code (DI containers that instantiate via string names) requires manual ignore entries.

References

  • Knip docs - install, entry/project config, framework plugins
  • Knip plugins page - full plugin list (Next.js, Remix, Astro, Storybook, Vite, etc.)