Adopt Fallow in an existing repo
Clean up the backlog in an existing TypeScript or JavaScript codebase under one team policy, then use fallow audit to keep new changes clean. An AI agent can do the cleanup work.
Use this guide when your repository already has a backlog: unused code, duplicates, complexity hotspots, and existing exceptions. You clean the backlog once, then fallow keeps new changes clean.
At the end, you have:
- a repo-level fallow policy in config
- dead code fixed, or modeled as intentional
- duplication at or below your chosen threshold
- complex functions refactored, or thresholds widened on purpose with a written justification
fallow auditthat enforces the same policy on changed files
Repo clean vs PR clean
These are two different goals. You need both, in this order.
Repo clean (this guide)
Analyze the full repo to understand and clean the whole codebase.
Use fallow, fallow dead-code, fallow dupes, and fallow health.
Enforce the policy on the changed files of every PR.
Use fallow audit after the repo is clean.
Before you start
Run fallow migrate first to port your knip config, then follow this guide.
Apply this guide to one workspace package at a time. Use fallow --workspace <name> and a shared root config.
1. First run: get the whole picture
From your project root, run:
npx fallow
The quick start shows the focused commands for the cleanup (dead-code, dupes, health). It also shows how fallow init writes a starting config.
2. Decide the project policy before triage
Do not start by suppressing findings one by one. That gives you a list of inline exceptions that only grows, and no clear policy.
If you do not own the policy, run section 3 on a branch first. Then bring the findings to the policy discussion. The fallow output makes the team decide what it actually cares about.
First, decide as a team:
- what counts as a real entry point (workers, scripts, route files, dynamically loaded modules)
- which files are generated or out of scope
- which packages are public APIs that ship exports outside the repo
- which dependencies are runtime-provided or intentionally retained
- which health thresholds you actually want to enforce
- whether duplication should warn or fail
This config is a good start:
{
"$schema": "https://raw.githubusercontent.com/fallow-rs/fallow/main/schema.json",
"ignorePatterns": ["**/*.generated.ts", "**/*.d.ts"],
"rules": {
"unresolved-imports": "error",
"unlisted-dependencies": "error",
"unused-exports": "warn"
}
}
Rules that you do not list keep their defaults. The health thresholds default to maxCyclomatic: 20 and maxCognitive: 15. Override them in a health block only when the realistic baseline of your repo is different.
During rollout, use warn to see findings without blocking CI. Change rules to error as the backlog gets smaller. The rules reference lists every issue type and severity.
3. Work findings in the right order
Use this order for most existing repos. Each step removes noise that would make the next step harder to read.
Fix unresolved imports and unlisted dependencies
These findings are high-confidence and rarely need discussion. They are real bugs that have not failed yet, so fix them first.
npx fallow dead-code --unresolved-imports npx fallow dead-code --unlisted-depsDelete unused files
Unreachable files are usually safe to delete. If you are not sure about a file, run
fallow list --entry-pointsto check reachability before you delete it.npx fallow dead-code --unused-files npx fallow list --entry-pointsRemove unused dependencies
Remove unused packages early. This gives less noise in later steps and faster installs.
npx fallow dead-code --unused-depsClean up unused exports, types, enum members, and class members
This category is usually the biggest. Fix real dead code. For an intentional public API, use visibility tags or config, and do not suppress it. To give this step to an agent, use the prompt in section 8.
npx fallow dead-code --unused-exports --unused-typesTo focus on enum members and class members, add
--unused-enum-membersand--unused-class-members. In a TypeScript project, add--type-awarewhen aliases, re-exports, class contracts, packages, or exact consumers affect the decision. This flag starts an optional semantic pass that is slower. See Type-aware TypeScript analysis and thefallow dead-codereference.Consolidate duplication
Merge repeated logic into shared helpers. Ignore generated or template-heavy files only when the duplication is intentional.
npx fallow dupes npx fallow dupes --mode semantic # also catch renamed-variable clonesSplit health hotspots
The
fallow healthoutput has Hotspots and Targets sections that rank the worst files. Start with those. Do not debate every file above the mean.npx fallow health
4. Match the reason to the right mechanism
Inline suppression is not the default. For each finding you keep, pick the mechanism that matches the reason.
External or public API
These are exports that code outside the repo uses (published libraries, SDKs, public packages).
Prefer, in order:
@public,@internal,@beta,@alphaJSDoc visibility tags on the exportpublicPackagesto mark a whole package as external APIignoreExportsonly when no other mechanism can express a file-level or export-level exception
Use publicPackages and visibility tags together. publicPackages marks the package as used from outside the repo. The @public and @internal tags separate the public API from internal helpers that you export only for use in other files.
Framework or runtime-discovered entry points
These are files that your framework or runtime calls, but that no other module imports directly (workers, CLI scripts, route files, plugin modules).
Prefer, in order:
- built-in plugin detection, which handles Next.js, Vite, NestJS, Remotion, and dozens more with no config
entryfor project-specific entry globsdynamicallyLoadedfor code pulled in through reflection, manifests, or dynamic importsfallow list --entry-pointsto see what fallow already considers reachable
Generated or vendored files
These are files from a build step, a code generator, or a vendored third-party bundle.
Prefer:
ignorePatternsto exclude them from analysis entirelyhealth.ignorewhen the noise is only in health findings and dead-code analysis must still run- duplication configuration for clone-heavy generated code
Runtime-provided or intentionally retained dependencies
These are packages that only the runtime uses (no static import), CLI tools, or peer dependencies.
Prefer:
ignoreDependencieswith the exact package names
One-off intentional unused export
This is an export that you keep on purpose (compatibility shims, future API).
Prefer:
/** @expected-unused */on the export
fallow tells you when an @expected-unused tag is no longer needed. When a module imports the export, fallow reports the tag as a stale suppression, and you can remove it. Fallow reports stale fallow-ignore comments in the same way. Use the tag freely for shims and future API.
One-off false positive
This is one location where fallow is wrong, and no config rule expresses the reason clearly.
Prefer:
// fallow-ignore-next-line <issue>on the line above// fallow-ignore-file <issue>only when the whole file is truly exceptional
Repo-wide policy change
The project has a different standard for a whole category.
Prefer:
rules,health, duplication thresholds, oroverridesblocks
Do this only for a real team policy. Do not do it to hide a few bad hotspots.
Suppression has the full decision tree and examples. The configuration overview lists every key.
5. Keep exceptions narrow and reviewable
A narrow exception documents one decision. A broad exception hides findings you have not reviewed.
Good exceptions:
- a single
@publicexport on a library entry point - one
ignoreDependenciesentry for a runtime-provided package - one
entrypattern for worker scripts - one file-level generated-code ignore with a comment explaining why
Bad exceptions:
- broad
ignorePatternsthat silently exclude half the repo - repeated inline suppressions that could be one config rule
- a higher global
health.maxCyclomaticto hide a few hotspots (acceptable only when the new threshold is a considered project standard with a written justification)
If you cannot explain an exception in one sentence, you probably use the wrong mechanism.
6. Define done
Work in two stages. Most teams turn on fallow audit at the end of stage 1, and finish stage 2 in the weeks after.
Stage 1: good enough to ship fallow audit
- the config holds the chosen policy, and scattered suppressions do not
- no unresolved imports or unlisted dependencies are left
- obvious dead code (unused files, unused dependencies) is removed
fallow auditruns in CI and passes for new changes against the default branch
Stage 2: ideal state
- no functions above your chosen health thresholds, because you refactored them or widened the threshold on purpose with a written justification
- duplication at or below the chosen threshold
- stale suppressions removed, or accepted on purpose
7. Turn on PR enforcement
When stage 1 is done, add the PR gate:
npx fallow audit
fallow audit runs dead code, duplication, complexity, and styling analysis on the changed files only. It then returns a pass, warn, or fail verdict. The fallow audit reference has the flags and CI recipes.
Pick one rollout strategy
This option is best for smaller teams. CI never blocks. The team fixes findings because everyone sees the warnings.
{
"rules": {
"unused-exports": "warn",
"unused-files": "warn",
"unused-dependencies": "warn"
}
}The risk: a gate that only warns can stay that way forever. Set a calendar reminder to change rules to error after the first clean month.
This option is best for larger organizations. CI blocks on new issues. Existing debt in the files that a PR touches is in the baseline, so it does not block.
# Save baselines once on the default branch
fallow dead-code --save-baseline fallow-baselines/dead-code.json
fallow health --save-baseline fallow-baselines/health.json
fallow dupes --save-baseline fallow-baselines/dupes.json
# Audit only new issues on PRs
fallow audit \
--dead-code-baseline fallow-baselines/dead-code.json \
--health-baseline fallow-baselines/health.json \
--dupes-baseline fallow-baselines/dupes.jsonKeep committed baselines outside .fallow/. fallow init adds .fallow/ to .gitignore because it holds the local cache. The recommended default is fallow-baselines/.
Commit the baseline files, so that every PR compares against the same snapshot. Regenerate them on a schedule (quarterly, or per release), not per merge. If you regenerate them per merge, new debt goes into the baseline each time CI runs, and nobody sees it.
This option is best when Claude Code makes most of the pushes. Add a project-level PreToolUse hook that does these steps:
- It intercepts
git commitandgit push. - It runs
fallow audit --format json --quiet --explain --gate-marker agent. - It blocks only on
verdict: "fail".
Claude gets the raw audit JSON on stderr, fixes the findings, and runs the command again.
Use this option together with Option A or B, not as a replacement. It is an extra local check. Keep fallow audit in CI, so that it also checks pushes from people and from workflows without Claude.
On runtime errors (no base ref yet, the first commit in an empty repo, config errors), the hook lets the command through. New repos do not get stuck.
Claude Code hooks has the full recipe. You can also generate the files:
fallow hooks install --target agentYou can also set the baselines in .fallowrc.json and run fallow audit with no flags:
{
"audit": {
"deadCodeBaseline": "fallow-baselines/dead-code.json",
"healthBaseline": "fallow-baselines/health.json",
"dupesBaseline": "fallow-baselines/dupes.json"
}
}
A baseline records debt that you still must pay. If the baseline file only grows, nobody enforces the policy.
8. Clean up with an agent
Fallow finds the problems, and an AI agent can fix them. Use Claude Code, Cursor, Codex, Windsurf, or any coding assistant that can run shell commands. The edits are mechanical. The decisions ("delete this export", "mark it @public", or "add it to entry") need judgement that reads the code.
Pick the integration level that your agent supports:
Install fallow-skills for Claude Code, Cursor, Windsurf, or any agent that supports Agent Skills. After you install the skill, it works offline. The agent does not need to fetch docs URLs.
Give the agent structured tool calls with JSON output and _meta explanations. MCP works together with skills.
Plain shell
Any agent that can run a shell command can use fallow. Use the copy-paste prompt below.
Copy-paste adoption prompt (for agents without skills or MCP)
Paste this prompt into your agent. The prompt is complete, so the agent does not need to fetch this page.
Adopt Fallow in this repository.
Goal:
- use full-repo analysis first (`fallow`, `fallow dead-code`, `fallow dupes`, `fallow health`), not `fallow audit`
- fix real dead code, duplication, and complexity issues in code
- model intentional exceptions with the narrowest correct mechanism
- end with no functions above the repo's chosen health thresholds, or a consciously widened threshold with written justification
- then set up `fallow audit` as a PR gate
Process:
1. Run `npx fallow`, then `npx fallow dead-code`, `npx fallow dupes`, and `npx fallow health`. Use `--format json` if you want structured output.
2. If no config exists, run `fallow init` and create a minimal repo policy.
3. Fix high-confidence issues first:
- unresolved imports
- unlisted dependencies
- unused files (sanity-check with `fallow list --entry-points`)
- unused dependencies
4. For each remaining finding, choose one path:
- fix it in code (preferred)
- model it in config
- add a narrow inline exception only if it is truly one-off
5. Match reasons to mechanisms:
- external API: `@public` / `@internal` / `@beta` / `@alpha`, or `publicPackages`
- runtime or framework entry point: `entry`, `dynamicallyLoaded`, plugin-aware config
- generated code: `ignorePatterns`, `health.ignore`
- intentionally retained dependency: `ignoreDependencies`
- intentional unused export: `@expected-unused` (fallow reports it as stale when the export becomes used)
- one-off false positive: `fallow-ignore-next-line`
- repo-wide policy: `rules`, `health`, duplication settings, `overrides`
6. Prefer config-level modeling over repeated suppression.
7. Keep every exception narrow and explain why it exists in a commit message.
8. Re-run Fallow after each batch until the repo is clean under the chosen policy.
9. Only after repo cleanup, run `npx fallow audit` and wire it into CI.
At the end, report:
- code changes
- config changes
- exceptions added and why
- anything left
- the final commands and outputs that show the repo is clean