CLI tool
fallow-rs/fallow avatar
fallow-rs/fallow

fallow for TypeScript and JavaScript: static codebase intelligence without a Node runtime

Codebase intelligence for TypeScript and JavaScript. Free static analysis of code and styles: unused code, duplication, circular deps, complexity hotspots, architecture boundaries, design-system drift. Optional paid runtime layer (Fallow Runtime): hot-path review and cold-path deletion evidence from real production traffic.

4,963 stars166 forksRustMIT

At a glance

What is it?
fallow is a Rust binary that reads a JS/TS repository as one dependency graph and reports unused code, circular dependencies, duplication, complexity hotspots, boundary violations and styling drift. The interesting part is what it does not need: no TypeScript compiler, no Node.js runtime, no AI in the analyzer.
Who is it for?
Adopt fallow if your repository is a JS/TS monorepo where nobody wants to delete code because deletion requires proving a negative, and if you want that verdict inside a PR gate. Skip it if your codebase is mostly another language, or if you expected the paid Runtime layer to be part of the free analyzer.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 30, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem fallow solves is proving a negative about dead code

The README states the motivation plainly: most repositories carry code nobody dares to delete, because deleting means proving a negative. That is a real category of engineering work, and it is not what a linter does. A linter checks a file you already decided to keep. fallow reads the repository as one dependency graph, from import edges to styling tokens, and reports what that graph shows.

The audience is narrower than "JavaScript developers". It is teams with a monorepo large enough that nobody holds the whole import structure in their head, plus the people who review their pull requests. The README's own example is `fallow audit` on the vitest monorepo, auditing its last 15 commits, with a warm base-snapshot cache. The gate passed because 163 inherited findings were excluded by design: they are pre-existing, so they do not block the change. That behaviour is the product. A tool that fails a PR on every legacy problem gets switched off in a week.

Two design choices are worth naming. Static analysis needs no TypeScript compiler and no Node.js runtime, which means the analyzer is a single binary rather than a process that boots your toolchain. And the README says there is no AI inside the analyzer. The one semantic feature, `fallow similar-code`, is opt-in and uses a pinned local model with explicit review, so it does not quietly change what the deterministic pass reports.

How the dependency graph becomes findings

The workspace layout in Cargo.toml shows the pipeline split across crates: fallow-extract, fallow-graph, fallow-engine, fallow-config, fallow-output, fallow-security, fallow-process, fallow-types, with fallow-core and fallow-api on top. Extraction comes first. The parsing dependencies are the oxc family (oxc_parser, oxc_ast, oxc_semantic, oxc_resolver, oxc_span), and lightningcss plus cssparser for CSS class and import extraction. So JS/TS and CSS-family stylesheets are parsed into the same graph rather than handled by two unrelated tools.

That is why the reported categories are not independent features bolted together. Circular dependencies and re-export cycles are described as part of `fallow dead-code`, which makes sense once import edges are the substrate. Duplication uses a suffix-array detector covering JS/TS and CSS-family stylesheets, plus Vue, Svelte and Astro component regions. Boundary violations are checked against presets: bulletproof, layered, hexagonal and feature-sliced. Styling drift covers CSS and CSS-in-JS, which is the same extraction path as the CSS parsing dependencies.

Two things sit outside the deterministic core. `fallow security` is opt-in and ranks candidates by reachability from entry points. The `--type-aware` pass adds checker-backed TypeScript evidence for exact symbol usage, cross-file private type leaks, targeted tests and public-signature coupling. The README calls that pass optional and slower, and `fallow recommend` points to it without enabling it. Read that as a deliberate split: the fast path is syntactic, and type-aware precision is a second pass you pay for in time.

Install fallow and run a first audit

The quick start offers a zero-install path. Running the bare command executes the full pipeline, which the README describes as dead code plus duplication plus health, and it needs no configuration on the first run because more than 100 built-in framework plugins detect entry points and framework-consumed exports.

bash
npx fallow

For a pull request gate, use the audit subcommand, which scopes the run to changed files. The README's example output shows an audit scope line naming the number of changed files and the commit range, followed by sections for unused files and circular dependencies, and a summary line with counts for dead code, complexity, duplication and changed files. The gate returns a pass, warn or fail verdict.

bash
npx fallow audit

For scripts and agents, the JSON mode has a contract worth understanding before you parse it. Exit 0 and exit 1 both mean the run succeeded, where 1 means findings were produced. Exit 2 is a real error and is reported as a JSON envelope on stdout, which is why stderr is discarded in the README's example.

bash
npx fallow audit --format json --quiet 2>/dev/null

To pin it as a devDependency instead, the README gives this install. The npm package ships the `fallow`, `fallow-lsp` and `fallow-mcp` launchers plus a version-matched agent skill, so editor and agent integrations resolve the project-local binary instead of whatever happens to be on PATH.

bash
npm install --save-dev fallow

If the first run produces findings, the README's diagnosis is usually one of three things: a missing entry point, a missing framework convention, or generated files you never meant to include. Framework detection is handled by the plugins, so generated code is the common case. Rather than authoring config by hand, `fallow recommend` detects the stack (frameworks, workspace layout, test runner, package manager), prints a proposed config as a safe starting point, and ends with the subjective choices it will not decide for you. It is read-only and always exits 0, so nothing changes until you save the config.

bash
npx fallow recommend

A manual ignore entry looks like this, and patterns are relative to the project root. They add to the built-in ignore defaults for node_modules, dist, coverage and minified bundles rather than replacing them.

json
{
  "$schema": "./node_modules/fallow/schema.json",
  "ignorePatterns": ["**/*.generated.ts"]
}

Config precedence is worth memorising because there is no merging: first match wins per directory, and the order is `.fallowrc.json` (JSONC accepted), then `.fallowrc.jsonc`, then `fallow.toml`, then `.fallow.toml`.

Where fallow is the wrong tool

The default analysis is syntactic. That is the source of its speed and also its main failure mode. If your dead-code question depends on type-level resolution, the plain run may not answer it, and the README positions `--type-aware` as the optional, slower pass that adds that evidence. Teams that enable it should expect a different cost profile than the fast path, and the documentation points to a limitations page describing the boundaries of both analyses.

Generated code is the second trap, and the README treats it as the expected cause of a noisy first run rather than an edge case. A repo that commits large generated TypeScript and does not add ignore patterns will get findings that are technically correct and practically useless. The `ignorePatterns` key exists for exactly this.

The paid layer is the third boundary. Fallow Runtime merges production execution evidence into the same reports, which is how the README describes hot-path review and cold-path deletion evidence from real traffic. If your actual question is "is this code still executed in production", the free static analyzer answers a different question: what the dependency graph shows. Nothing in the README suggests the runtime evidence is available without that layer.

Finally, the tool is scoped to TypeScript and JavaScript. The CSS analysis covers CSS-family stylesheets and CSS-in-JS, and the component-region duplication covers Vue, Svelte and Astro. A repository that is mostly Go, Python or Java has no reason to run it.

fallow compared with knip, and the Docker channel

The obvious comparison is knip, which also hunts unused files, exports and dependencies in JS/TS projects. The difference is in what the analyzer is built on. knip is a Node package that runs inside your JavaScript toolchain. fallow is a Rust binary whose parsing dependencies are oxc and lightningcss, and the README states that static analysis needs neither a TypeScript compiler nor a Node.js runtime. That changes the deployment shape: one binary in a container or a CI image, rather than a Node process with the project's dependencies installed.

The second difference is scope. knip's focus is unused code and dependencies. fallow bundles that with duplication, complexity hotspots and a 0 to 100 health score with a letter grade, architecture boundary presets, and design-system styling drift, all read from the same graph. Whether that bundling is an advantage depends on whether you already have separate tools for duplication and complexity. If you do, you are comparing one integrated report against several, and the integrated one wins only if the graph-level view is what you actually needed.

For container use, the repository ships a Dockerfile that downloads a release asset pinned by sha256 and verifies it with sha256sum before chmod. It is a two-stage build: a debian:bookworm-slim stage fetches the binary, and a node:26-bookworm-slim runtime stage installs corepack and copies the binary in, with `fallow` as the entrypoint. There is a comment in the Dockerfile stating that the sha256 pins are bound to the FALLOW_VERSION argument and must be bumped together, that the maintainer release flow refreshes all three after publication via a script, and that no CI job does it because the docker-lockstep job was removed in v3.7.1. If you build from that Dockerfile, treat the pins as something a maintainer updates by hand. A Compose example lives at `examples/docker/compose.yaml`, and other channels (pnpm, yarn, `cargo install fallow-cli`) are covered in the installation guide.

Maintenance, releases and what the MIT licence covers

The last push to the default branch was on 2026-08-28, and the three most recent releases landed within four days of each other: v3.18.0 on 2026-08-25, v3.19.0 on 2026-08-27, and v3.20.0 on 2026-08-28. The release notes describe v3.20.0 as adding Yarn PnP resolution, monorepo tsconfig scope and review schema 8; v3.19.0 as one-pass agent install, MCP resources and similar-code discovery; v3.18.0 as more accurate graphs, consistent diagnostics and leaner editor packages. The Cargo workspace version is 3.25.0 while the README's example output is labelled fallow 3.5.0, so the repository runs ahead of the published example. That is normal, but it means the sample output is not a description of current formatting.

Upgrade cost has one sharp edge: the JSON output is a typed contract, and v3.20.0 bumped the review schema to 8. Anything parsing that schema needs to track it. Runs are deterministic, with stable fingerprints, so re-running to verify an edit is safe, and the same input produces the same output. That property is what makes a cached base snapshot meaningful in the audit example.

The licence is MIT, declared in both the README badge and the Cargo workspace package metadata. MIT covers the analyzer. The optional Fallow Runtime layer is described as a paid product, and the README does not state its licence terms. If your organisation treats licence review as a gate, the static tool and the runtime layer are separate questions, and only the first is answered by the repository. This is not legal advice; read the terms that ship with the runtime layer.

Editorial conclusion

Adopt fallow if your repository is a JS/TS monorepo where nobody wants to delete code because deletion requires proving a negative, and if you want that verdict inside a PR gate. Skip it if your codebase is mostly another language, or if you expected the paid Runtime layer to be part of the free analyzer. Before rolling it out, run npx fallow recommend and read the proposed config, then check the limitations page for the boundary between the default syntactic pass and --type-aware.

Frequently asked questions

What is fallow?

fallow is codebase intelligence for TypeScript and JavaScript: one binary that finds unused code, circular dependencies, duplication, complexity hotspots, boundary violations and design-system styling drift. The README describes it as a static analyzer with deterministic findings and typed output contracts, with no AI inside the analyzer.

How do I use fallow?

Run `npx fallow` for the full pipeline (dead code, duplication and health), or `npx fallow audit` to gate only what a pull request changed. The README also documents `npx fallow recommend`, which detects the stack and prints a proposed config without changing anything.

Does fallow need Node.js or the TypeScript compiler installed?

The README states that static analysis needs no TypeScript compiler and no Node.js runtime. The npm package ships the fallow, fallow-lsp and fallow-mcp launchers, and a Dockerfile builds a runtime image from node:26-bookworm-slim with the binary copied in.

What do fallow's exit codes mean in JSON mode?

For agents and scripts, exit 0 and exit 1 both mean the run succeeded, where 1 indicates findings. Exit 2 is a real error and is reported as a JSON envelope on stdout, which is why the README's example discards stderr.

Which config files does fallow read, and in what order?

Config precedence is first match wins per directory with no merging: `.fallowrc.json` (JSONC accepted), then `.fallowrc.jsonc`, then `fallow.toml`, then `.fallow.toml`. `ignorePatterns` entries are relative to the project root and add to the built-in defaults for node_modules, dist, coverage and minified bundles.

Official sources

  1. Official documentation
  2. Official README
  3. Project repository
  4. Release notes
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/fallow-rs-fallow.svg)](https://hysenlabs.com/projects/fallow-rs-fallow)