Dead-code detection: explicit exports + knip (not unimported)
SkillDev toolsDead-code / unused-dependency detection is migrating from the legacy `unimported` tool to `knip`, which requires each package to expose an explicit, minimal `package.json#exports` (no `./*` wildcard). Read this when adding a new package or migrating an existing one.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Dead-code detection: explicit exports + knip (not unimported) skill
What this skill tells your AI
The instructions your AI receives, as published by ledgerhq/ledger-live in .agents/skills/knip-migration/SKILL.md and read by ahel’s review.
The repo is migrating dead-code / unused-dependency detection from the legacy unimported
tool to knip, one package at a time. The root knip.json holds only
the shared rules — a package needs no workspaces entry there, since knip derives its entry files
from package.json (main, bin, exports) and its own defaults
(entry files).
Why this isn't just "swap the tool"
knip treats everything listed in a package's package.json#exports as an entry point.
Today every package exports a ./* wildcard mapped to ./src/*.ts (via the @ledgerhq/source
custom condition declared in tsconfig.base.json):
"./*": {
"@ledgerhq/source": "./src/*.ts",
"import": "./lib-es/*.js",
"require": "./lib/*.js",
"default": "./lib/*.js"
}
Because of this wildcard, knip considers every top-level src/*.ts file "used" and cannot
detect unused ("zombie") top-level source files — the exact thing unimported caught (it
ignored exports and diffed against a curated entry list). So real knip parity for a package
requires replacing the ./* wildcard with explicit, minimal subpath exports that enumerate
the package's true public API.
The few libs already pointed at knip (e.g.
libs/env,libs/promise) still keep the./*wildcard, so they are only partially migrated — do not copy them as the template.
New package = born migrated
A new package has no consumers, so it should start in the target state — no .unimportedrc.json:
- Explicit
exportsonly. Enumerate the real public API; do not add a./*wildcard. Mirror the.root entry per subpath, keeping the conditions:"exports": { ".": { "@ledgerhq/source": "./src/index.ts", "import": "./lib-es/index.js", "require": "./lib/index.js", "default": "./lib/index.js" }, "./logic": { "@ledgerhq/source": "./src/logic.ts", "import": "./lib-es/logic.js", "require": "./lib/logic.js", "default": "./lib/logic.js" }, "./lib-es/*": "./lib-es/*.js", "./lib/*": "./lib/*.js", "./package.json": "./package.json" } - Use knip, not unimported — add a script that runs knip scoped to the workspace:
pnpm knip --directory <relative-hop-to-root> -W <workspace-path>(--directoryis the hop back to the repo root —../..forlibs/<x>,../../..forlibs/coin-modules/<x>;-Wis the workspace path from root).
Migrating an existing package off unimported
- Audit the real consumers of the package's deep imports.
- Replace the
./*wildcard export with explicit subpath exports (the legit public API). - Refactor consumers that relied on now-removed arbitrary entry points.
- Switch the dead-code script
unimported→ knip, delete.unimportedrc.json, and verify a deliberately-unused top-levelsrcfile is now reported.
Start with leaf / low-dependency packages; ledger-live-common (largest surface) comes last.
Notes
unimportedstays until a package reaches knip parity — don't bulk-delete it.- Unifying the script / nx-target name (
unimported→knip-check) is tracked separately, so matching the surrounding package's existing script name is fine.
Reviewing
For a new package, flag either of these and point here:
- a
.unimportedrc.jsonor a script running the bareunimportedbinary ("unimported": "unimported") — it must run knip instead; or - a
./*wildcard inpackage.json#exports— new packages must enumerate explicit exports so knip can detect zombie files.
The workspaces block in knip.json is a temporary workaround
Everything must work without touching knip.json. The workspaces entries that remain are
band-aids for packages that don't declare their surface properly yet — deep imports behind a ./*
wildcard, runtime entries invisible to package.json (Electron preloads, web workers), or
dependencies knip can't resolve. Each one hides a package that hasn't finished the migration above.
So: don't grow this file. Fixing the package's exports is the real fix, and it lets the
corresponding workspaces entry be deleted. If you truly cannot avoid an entry, keep it to the
smallest possible delta and remember a configured entry replaces knip's default patterns
rather than extending them.
Signals
- GitHub stars
- 618
- Forks
- 490
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
knip-migration- Source
- github.com/ledgerhq/ledger-live