npx skills add ...
npx skills add factory-ai/factory-plugins --skill fix-knip-unused-exports
npx skills add factory-ai/factory-plugins --skill fix-knip-unused-exports
Fix knip "Unused exports" violations. Handles all violation categories: test-only exports (extract to new file), dead barrel re-exports (remove from index.ts), and internally-only-used exports (un-export). Use when `npm run knip` reports unused exports.
Fix knip "Unused exports" violations. There are several categories of violation, each with a different fix strategy.
npm run knip reports "Unused exports"Output looks like:
For each flagged export, grep the entire repository (not just the package):
Determine which category it falls into:
| Category | Callers | Fix |
|---|---|---|
| Test-only export | Used in same file + test files only | Extract to new file |
| Dead barrel re-export | Re-exported from index.ts, but production code imports via relative paths or other subpaths instead | Remove the re-export from the barrel |
| Internally-only-used export | Used only within the same file, not by tests or other files | Remove the export keyword |
| Dead code | No callers anywhere | Delete the export |
| Production consumer exists | Used by non-test code in another file | Not a knip issue -- investigate further |
Important: When grepping, exclude test files to identify production consumers:
When a function is exported solely for test access but is also used internally in the same file.
Before writing code, answer these questions:
a) What moves to the new file?
b) Are any helpers shared with functions staying behind?
c) Will the new file have exactly one exported function?
filename-match-export lint rule, the file MUST be named after that export: myFunction.tsd) Does a test file with a matching name exist?
bar.ts stays and bar.test.ts exists, the test must still import something from ./bar (if your project enforces a test-imports-source rule)bar.ts is deleted (everything moved out), that rule typically only applies when the matching source file existse) Any circular dependency risk?
f) Does it export a constant?
constants-file-organization lint rule, exported constants must live in a file named constants.tsBUDGET[effort] with getBudget(effort)) to avoid needing a separate constants.tsCreate the new file in the same directory:
Update the original file to import from the new file:
Update test files to import from the new file:
After extracting, run npm run knip again. If function A was extracted to a new file alongside function B that A calls, but B is also only consumed by tests externally, knip will flag B too. You need to extract B to its own file so that A's file creates a genuine production import of B.
Example: suppose throwMappedError was first extracted alongside mapResponseFailure into error-mappers.ts. If throwMappedError is only called internally within that file (by mapResponseFailure), it will still be flagged. Fix: extract it to throwMappedError.ts, making the import from error-mappers.ts a genuine production consumer.
When a barrel index.ts re-exports something, but no production code imports it through the barrel. This happens when:
import { x } from './source') instead of the barrel@scope/pkg/feature/handlers) instead of the barrelGrep excluding test files. If the only hits are:
index.ts itselfThen the barrel re-export is unused. Simply remove it from index.ts.
If a test in another package imports the symbol through the barrel (e.g., import { x } from '@scope/pkg/feature'), you need to provide an alternative import path after removing the barrel re-export:
Add a subpath export in the source package's package.json:
Update the test to import from the new subpath:
This pattern follows typical subpath-export conventions used in monorepos.
When an export is only used within the same file and not imported by anything else (not even tests), just remove the export keyword:
This is common for Zod schemas that are only used as building blocks for other schemas in the same file.
Run ALL of these checks on the affected packages:
If cross-package imports exist, also verify the consuming package.
Many TypeScript monorepos layer additional custom lint rules on top of knip. Adapt the fixes below to whichever of these your project uses.
filename-match-export (or similar)If a file has exactly ONE exported function (not a React component), the filename must match the function name.
export function loadConfig in loadConfig.ts -- passesexport function loadConfig in helpers.ts -- failshelpers.ts -- rule does not apply (multiple exports)test-imports-source (or similar)If foo.test.ts and foo.ts both exist, the test must import from ./foo.
import { x } from './foo' satisfy the rule'.' or './index' if index.ts re-exports from foo.tsfoo.ts is deleted, the rule does not applyconstants-file-organization (or similar)Exported constants must be defined in a file named constants.ts.
BUDGET[effort] becomes getBudget(effort))constants.ts file**/*.test.*, **/*.spec.*)ignoreIssues in knip.json suppresses warnings ON the listed file, but does NOT make the source export "used"export { x } from './source') from an index.ts with ignoreIssues do NOT count as usage of the source exportincludeEntryExports: true (if set) means exports from entry point files are checked too, so entry-point-style files (migrations, scripts) may need explicit ignoreIssuesWhen removing barrel re-exports that cross-package tests relied on, add subpath exports to package.json:
ignoreIssues in knip.json unless they are genuine entry point scripts (migrations, CLIs)export keyword if tests need it -- the tests would breakconstants.ts files if your project enforces a constants-file-organization lint rule// bar.ts (original file, updated)
import { myFunction } from './myFunction';
function otherFunction() {
const result = myFunction(); // Now imports from new file
}// bar.test.ts (updated)
import { myFunction } from './myFunction';
// If bar.ts still exists, you may need to also import something from './bar'
// to satisfy any test-imports-source rule