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. Use after a feature or route is deleted and the project still compiles, or when `package.json` has accumulated dependencies nobody can account for.

Install with skills.sh (any agent)

npx skills add testland/qa --skill knip-dead-code
View source

knip-dead-code

Knip detects unused files, dependencies, exports, types, enum members, and class members in JS/TS projects per the Knip docs (opens in new window). 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 (opens in new window).

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 (opens in new window).

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 (opens in new window) 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

Related skills

codeclimate-config

Configure Code Climate Quality (now Qlty) for repository-wide quality gates - duplication, complexity, similar-code, exclude_patterns. Covers both legacy `.codeclimate.yml` (Code Climate Velocity / GitHub integration) and the new `.qlty/qlty.toml` per the Qlty platform migration. Use when a repo needs duplication and complexity thresholds enforced on PRs, or when an existing `.codeclimate.yml` must be migrated to Qlty without losing its plugin and exclude settings.

lizard-complexity

Run Lizard against production source to enforce per-function cyclomatic complexity (CCN), NLOC, and parameter-count thresholds - language-agnostic (30+ languages). Scoped to production code via `-x"./tests/*"`; test complexity is reviewed separately. Use when a codebase spans several languages and needs one complexity gate across all of them, or when a function has grown unreviewable and the team wants a numeric threshold in CI.

madge-deps

Run Madge against a JS/TS production source tree to detect circular dependencies, find orphan modules, and visualize the module graph. Scoped to production code via `excludeRegExp` for test files. Use when a build compiles but throws `Cannot read property X of undefined` on a module that is clearly imported, or when a repo needs a CI gate that blocks new import cycles.

sonarqube-maintainability-gate

Run SonarQube/SonarCloud against production code to surface Code Smells, Bugs, and Maintainability ratings - the maintainability lens rather than the security lens. Production-only scope via sonar.exclusions; test code is reviewed separately. Use when a team wants maintainability and technical-debt ratings gating PRs, or when an existing SonarQube project reports numbers nobody has tied to a quality gate.