Skip to content

Latest commit

 

History

History
441 lines (322 loc) · 17.5 KB

File metadata and controls

441 lines (322 loc) · 17.5 KB
title Adopt Fallow in an existing repo
description Bring an existing TypeScript or JavaScript codebase to a clean Fallow policy, then keep new changes clean with fallow audit. Drive the cleanup with an AI agent.
keywords
adoption
existing codebase
brownfield
migration
fallow audit
baselines
policy
cleanup
rollout
agent-driven cleanup
icon rocket

This guide is for repositories that already have backlog: unused code, duplicates, complexity hotspots, and existing exceptions.

By the end of this guide you will have:

  • a repo-level Fallow policy encoded in config
  • dead code fixed or intentionally modeled
  • duplication at or below your chosen threshold
  • complex functions refactored or consciously widened with a written justification
  • fallow audit enforcing the same policy on changed files

Repo clean vs PR clean

These are different goals. Both matter. Do them in order.

Full-repo analysis to understand and clean the whole codebase. Use `fallow`, `fallow dead-code`, `fallow dupes`, and `fallow health`. Changed-files gate that enforces the policy on every PR. Use `fallow audit` after the repo is clean.

Start with repo clean. Then turn on PR clean.

Before you start

Run `fallow migrate` first to port your knip config, then follow this guide. Apply this flow per workspace package. Use `fallow --workspace ` and a shared root config.

1. First run: get the whole picture

From your project root:

npx fallow

Then the focused commands you will use during cleanup:

npx fallow dead-code          # Cleanup candidates
npx fallow dupes              # Repeated logic
npx fallow health             # Complexity and refactor targets

If you do not yet have a config file:

fallow init

fallow init auto-detects your project structure (package manager, workspaces, frameworks) and generates a tailored starting config. It also adds .fallow/ to your .gitignore.

2. Decide the project policy before triage

Do not start by suppressing findings one by one. That path turns into an ever-growing list of inline exceptions and no coherent policy.

Not the policy owner? Run section 3 first on a branch, then bring the findings into the policy discussion. Fallow output is a good forcing function for the "what do we actually care about" conversation.

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

A reasonable starting point:

{
  "$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 you do not list inherit their defaults. Health thresholds default to maxCyclomatic: 20 and maxCognitive: 15; override in a health block only when your repo's realistic baseline differs.

Use `warn` during rollout when you need visibility without blocking CI. Promote rules to `error` as the backlog shrinks. See the [rules reference](/configuration/rules) for 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 otherwise distort the next step.

High confidence, low debate. These are real bugs waiting to happen and should be cleared first.
```bash
npx fallow dead-code --unresolved-imports
npx fallow dead-code --unlisted-deps
```
Unreachable files are usually safe cleanup. Use `fallow list --entry-points` to sanity-check reachability before deleting anything you are unsure about.
```bash
npx fallow dead-code --unused-files
npx fallow list --entry-points
```
Remove dead packages early. This reduces noise in later steps and speeds up installs.
```bash
npx fallow dead-code --unused-deps
```
Usually the biggest category. Fix real dead code. Model intentional API surface with visibility tags or config instead of suppressing it. Hand this step to an agent using the [prompt in section 8](#8-clean-up-with-an-agent): the findings are mechanical but the decisions (delete vs. mark `@public` vs. add to `entry`) benefit from code-aware judgement.
```bash
npx fallow dead-code --unused-exports --unused-types
```

Add `--unused-enum-members` and `--unused-class-members` when you want to focus on those categories explicitly. See the [`fallow dead-code` reference](/cli/dead-code) for every filter flag.
Merge repeated logic into shared helpers. Only ignore generated or template-heavy files when the duplication is genuinely intentional.
```bash
npx fallow dupes
npx fallow dupes --mode semantic   # also catch renamed-variable clones
```
`fallow health` output includes Hotspots and Targets sections that rank the worst offenders. Use those to prioritize. Do not bike-shed every file above the mean.
```bash
npx fallow health
```

4. Match the reason to the right mechanism

Do not default to inline suppression. Pick the mechanism that matches the reason.

External or public API

Exports consumed outside the repo (published libraries, SDKs, public packages).

Prefer, in order:

  • @public, @internal, @beta, @alpha JSDoc visibility tags on the export
  • publicPackages as the coarse switch for "this whole package is external API"
  • ignoreExports only when file-level or export-level exceptions cannot be expressed any other way

publicPackages and visibility tags are complementary, not alternatives: publicPackages marks the package as externally consumed; @public/@internal tags distinguish public API from internal helpers that happen to be exported for cross-file use.

Framework or runtime-discovered entry points

Files your framework or runtime invokes but no other module imports directly (workers, CLI scripts, route files, plugin modules).

Prefer, in order:

  • built-in plugin detection (Next.js, Vite, NestJS, Remotion, and dozens more are handled out of the box)
  • entry for project-specific entry globs
  • dynamicallyLoaded for code pulled in through reflection, manifests, or dynamic imports
  • fallow list --entry-points to inspect what Fallow already considers reachable

Generated or vendored files

Files produced by a build step, code generator, or vendored third-party bundle.

Prefer:

  • ignorePatterns to exclude them from analysis entirely
  • health.ignore when the noise is health-only and dead-code analysis should still run
  • duplication configuration for clone-heavy generated code

Runtime-provided or intentionally retained dependencies

Packages installed for runtime usage only (no static import), CLI tools, or peer dependencies.

Prefer:

One-off intentional unused export

An export you want to keep around deliberately (compatibility shims, future API).

Prefer:

  • /** @expected-unused */ on the export
`@expected-unused` is self-cleaning. Unlike inline `fallow-ignore` comments, Fallow tracks the tag for staleness: the moment the export becomes imported, the tag is reported as a stale suppression so you can remove it. Use it liberally for shims and future API surface.

One-off false positive

A specific site where Fallow is wrong and no config rule captures the reason cleanly.

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 genuinely has a different standard for a whole category.

Prefer:

  • rules, health, duplication thresholds, or overrides blocks

Do this only when it reflects a real team policy, not to hide a few ugly hotspots.

See [suppression](/configuration/suppression) for the full decision tree and examples, and [configuration overview](/configuration/overview) for every key.

5. Keep exceptions narrow and reviewable

Narrow exceptions are an asset. Broad exceptions are a debt.

Good:

  • a single @public export on a library entry point
  • one ignoreDependencies entry for a runtime-provided package
  • one entry pattern for worker scripts
  • one file-level generated-code ignore with a comment explaining why

Bad:

  • broad ignorePatterns that silently exclude half the repo
  • repeated inline suppressions that could be one config rule
  • raising global health.maxCyclomatic to hide a handful of hotspots (acceptable only when the new threshold reflects a thought-through project standard with written justification)

If you cannot explain an exception in one sentence, it is probably the wrong mechanism.

6. Define done

Work in two stages. Most teams ship fallow audit at the end of stage 1 and finish stage 2 over the following weeks.

Stage 1: good enough to ship fallow audit

  • the chosen policy is encoded in config, not accumulated in scattered suppressions
  • unresolved imports and unlisted dependencies are cleared
  • blatant dead code (unused files, unused dependencies) is removed
  • fallow audit is wired into CI and passes for new changes against the default branch

Stage 2: ideal state

  • no functions above your chosen health thresholds, either by refactoring or by consciously widening the threshold with written justification
  • duplication at or below the chosen threshold
  • stale suppressions gone or consciously accepted

7. Turn on PR enforcement

Once stage 1 is done, add the PR gate:

npx fallow audit

fallow audit runs dead code, duplication, and complexity analysis scoped to changed files, then returns a pass, warn, or fail verdict. See the fallow audit reference for flags and CI recipes.

Pick one rollout strategy

Best for smaller teams. CI never blocks; fixes land under social pressure.
```jsonc
{
  "rules": {
    "unused-exports": "warn",
    "unused-files": "warn",
    "unused-dependencies": "warn"
  }
}
```

Risk: warn-only gates become warning-forever gates. Set a calendar reminder to promote rules to `error` after the first clean month.
Best for larger orgs. CI blocks on new issues; pre-existing debt on touched files is baselined.
```bash
# 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.json
```

Store committed baselines outside `.fallow/` (which `fallow init` adds to `.gitignore` for machine-local cache). `fallow-baselines/` is the recommended default. Commit the baseline files so every PR compares against the same snapshot, and regenerate on a schedule (quarterly, or per release) rather than per merge, otherwise teams silently absorb new debt every time CI runs.
Best when Claude Code is the main pusher. Add a project-level `PreToolUse` hook that intercepts `git commit` and `git push`, runs `fallow audit --format json --quiet --explain`, and blocks only on `verdict: "fail"`. Claude receives the raw audit JSON on stderr, fixes the findings, and retries the command.
Pair this with Option A or B, not instead of them. This is a local reinforcement layer. Keep CI on `fallow audit` so human pushes and non-Claude workflows are still covered.

Runtime errors (no base ref yet, first commit in an empty repo, config errors) fail open so new repos do not get stuck.

See [Claude Code hooks](/integrations/claude-hooks) for the full recipe, or generate the files with:

```bash
fallow hooks install --target agent
```

You can also configure 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"
  }
}
Baselines are a debt ledger, not a steady state. If the baseline file only grows, the policy is not being enforced.

8. Clean up with an agent

Fallow finds the problems. An AI agent (Claude Code, Cursor, Codex, Windsurf, any shell-capable coding assistant) is the right tool to fix them: edits are mechanical but decisions ("delete this export" vs. "mark it @public" vs. "add it to entry") benefit from code-aware judgement.

Three levels of integration, pick whichever your agent supports:

Install `fallow-skills` for Claude Code, Cursor, Windsurf, or any Agent Skills compatible agent. Once installed, the skill works offline; the agent does not need to fetch docs URLs during use. Structured tool calling with JSON output and `_meta` explanations. Works alongside skills. Any agent that can run a shell command can drive Fallow. Use the copy-paste prompt below. Paste this prompt into your agent. It is self-contained: the agent does not need to fetch this page to follow it.
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` (preferred over inline comments because it self-cleans)
   - 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

Next steps

Enforce the policy on changed files in CI. Every config key, with examples. Visibility tags, inline comments, and when to use each. Install `fallow-skills` for agent-driven adoption.