The codebase analyzer for JavaScript and TypeScript. Finds unused code, circular dependencies, code duplication, complexity hotspots, architecture boundary violations, and feature flag patterns. 90 framework plugins, zero configuration, sub-second performance.
When to Use
- Finding dead code (unused files, exports, types, enum/class members)
- Finding unused or unlisted dependencies
- Detecting code duplication and clones
- Checking code health and complexity hotspots
- Cleaning up a codebase before a release or refactor
- Auditing a project for structural issues
- Setting up CI checks for dead code or duplication thresholds
- Auto-fixing unused exports and dependencies
- Detecting feature flag patterns (environment gates, SDK calls, config objects)
- Investigating why a specific export or file appears unused
When NOT to Use
- Runtime error analysis or debugging
- Type checking (use
tscfor that) - Linting style or formatting issues (use ESLint, Biome, Prettier)
- Security vulnerability scanning
- Bundle size analysis
- Projects that are not JavaScript or TypeScript
Prerequisites
Fallow must be installed. If not available, install it:
npm install -g fallow # prebuilt binaries (fastest)
# or
npx fallow dead-code # run without installing
# or
cargo install fallow-cli # build from source
Agent Rules
- Always use
--format json --quiet 2>/dev/nullfor machine-readable output. The2>/dev/nulldiscards stderr so progress messages and threshold warnings don't corrupt the JSON on stdout. Never use2>&1 - Always append
|| trueto every fallow command. Exit code 1 means "issues found" (normal), not a runtime error. Without|| true, the Bash tool treats exit 1 as failure and cancels parallel commands. Only exit code 2 is a real error (invalid config, parse failure) - Use
--explainto include a_metaobject in JSON output with metric definitions, ranges, and interpretation hints - Use issue type filters (
--unused-exports,--unused-files, etc.) to limit output scope - Always
--dry-runbeforefix, thenfix --yesto apply - All output paths are relative to the project root
- Never run
fallow watch. It is interactive and never exits
Commands
| Command | Purpose | Key Flags |
|---|---|---|
fallow |
Run all analyses: dead code + duplication + complexity (default) | --only, --skip, --ci, --fail-on-issues, --group-by, --summary, --fail-on-regression, --tolerance, --regression-baseline, --save-regression-baseline, --score, --trend, --save-snapshot |
dead-code |
Dead code analysis (check is an alias) |
--unused-exports, --changed-since, --production, --file, --include-entry-exports, --stale-suppressions, --ci, --group-by, --summary, --fail-on-regression, --tolerance, --regression-baseline, --save-regression-baseline |
dupes |
Code duplication detection | --mode, --threshold, --top, --changed-since, --skip-local, --cross-language, --ignore-imports, --fail-on-regression, --tolerance, --regression-baseline, --save-regression-baseline |
fix |
Auto-remove unused exports/deps | --dry-run, --yes (required in non-TTY) |
init |
Generate config file or pre-commit hook | --toml, --hooks, --branch |
migrate |
Convert knip/jscpd config | --dry-run, --from PATH |
list |
Inspect project structure | --files, --entry-points, --plugins, --boundaries |
health |
Function complexity analysis | --complexity, --max-cyclomatic, --max-cognitive, --top, --sort, --file-scores, --hotspots, --targets, --effort, --score, --min-score, --since, --min-commits, --save-snapshot, --trend, --coverage-gaps, --workspace, --baseline, --save-baseline |
audit |
Combined dead-code + complexity + duplication for changed files | --base, --production, --workspace, --ci, --fail-on-issues, --explain |
flags |
Detect feature flag patterns (env vars, SDK calls, config objects) | --top |
schema |
Dump CLI definition as JSON | |
config |
Show the loaded config path and resolved config (verifies which .fallowrc.json is in effect) |
--path |
Issue Types
| Type | Filter Flag | Description |
|---|---|---|
| Unused files | --unused-files |
Files unreachable from entry points |
| Unused exports | --unused-exports |
Symbols never imported elsewhere |
| Unused types | --unused-types |
Type aliases and interfaces |
| Unused dependencies | --unused-deps |
Packages in dependencies, devDependencies, optionalDependencies, type-only production deps, and test-only production deps |
| Unused enum members | --unused-enum-members |
Enum values never referenced |
| Unused class members | --unused-class-members |
Methods and properties |
| Unresolved imports | --unresolved-imports |
Imports that can't be resolved |
| Unlisted dependencies | --unlisted-deps |
Used packages missing from package.json |
| Duplicate exports | --duplicate-exports |
Same symbol exported from multiple modules |
| Circular dependencies | --circular-deps |
Import cycles in the module graph |
| Boundary violations | --boundary-violations |
Imports crossing architecture zone boundaries. Presets: layered, hexagonal, feature-sliced, bulletproof |
| Stale suppressions | --stale-suppressions |
fallow-ignore comments or @expected-unused JSDoc tags that no longer match any issue |
| Test-only dependencies | — | Production deps only imported from test files (should be devDependencies) |
MCP Tools
When using fallow via MCP (fallow-mcp), the following tools are available:
| Tool | Description |
|---|---|
analyze |
Full dead code analysis. Set boundary_violations: true as a convenience alias for issue_types: ["boundary-violations"]. Set group_by to "owner" or "directory" to partition results |
check_changed |
Incremental analysis of files changed since a git ref |
find_dupes |
Code duplication detection. Set changed_since to scope to changed files since a git ref |
fix_preview |
Dry-run auto-fix preview |
fix_apply |
Apply auto-fixes (destructive) |
check_health |
Complexity metrics, health scores, hotspots, and refactoring targets |
audit |
Combined dead-code + complexity + duplication for changed files, returns verdict |
project_info |
Project metadata. Set entry_points, files, plugins, or boundaries to true to request specific sections |
list_boundaries |
Architecture boundary zones and access rules. Returns {"configured": false} if no boundaries configured |
detect_flags |
Detect feature flag patterns (env vars, SDK calls, config objects). Set top to limit results |
All tools accept root, config, no_cache, and threads params. The MCP server subprocess timeout defaults to 120s, configurable via FALLOW_TIMEOUT_SECS.
All JSON responses include structured actions arrays on every finding (dead code, health, duplication), enabling programmatic fix application or suppression.
References
- CLI Reference: complete command and flag specifications
- Gotchas: common pitfalls, edge cases, and correct usage patterns
- Patterns: workflow recipes for CI, monorepos, migration, and incremental adoption
Common Workflows
Audit a project for all dead code
fallow dead-code --format json --quiet
Parse the JSON output. It contains arrays for each issue type (unused_files, unused_exports, unused_types, unused_dependencies, etc.) plus total_issues and elapsed_ms metadata. Each issue object includes an actions array with structured fix suggestions (action type, auto_fixable flag, description, and optional suppression comment).
Find only unused exports (smaller output)
fallow dead-code --format json --quiet --unused-exports
Check if a PR introduces dead code
fallow dead-code --format json --quiet --changed-since main --fail-on-issues
Exit code 1 if new dead code is introduced. Only analyzes files changed since the main branch.
Find code duplication
fallow dupes --format json --quiet
fallow dupes --format json --quiet --mode semantic
The semantic mode detects renamed variables. Other modes: strict (exact), mild (default, syntax normalized), weak (different literals).
Safe auto-fix cycle
# 1. Preview what will be removed
fallow fix --dry-run --format json --quiet
# 2. Review the output, then apply
fallow fix --yes --format json --quiet
# 3. Verify the fix worked
fallow dead-code --format json --quiet
The --yes flag is required in non-TTY environments (agent subprocesses). Without it, fix exits with code 2.
Discover project structure
fallow list --entry-points --format json --quiet
fallow list --plugins --format json --quiet
Shows detected entry points and active framework plugins (90 built-in: Next.js, Vite, Jest, Storybook, Tailwind, etc.).
Production-only analysis
fallow dead-code --format json --quiet --production
Excludes test/dev files (*.test.*, *.spec.*, *.stories.*) and only analyzes production scripts.
Analyze a single workspace package
fallow dead-code --format json --quiet --workspace my-package
Scopes output to one package while keeping the full cross-workspace graph.
Scope to specific files (lint-staged)
fallow dead-code --format json --quiet --file src/utils.ts --file src/helpers.ts
Only reports issues in the specified files. Project-wide dependency issues are suppressed. Warns on non-existent paths.
Catch typos in entry file exports
fallow dead-code --format json --quiet --include-entry-exports
Reports unused exports in entry files (package.json main/exports, framework pages). By default, exports in entry files are assumed externally consumed. This flag catches typos like meatdata instead of metadata.
Debug why something is flagged
# Trace an export's usage chain
fallow dead-code --format json --quiet --trace src/utils.ts:myFunction
# Trace all edges for a file
fallow dead-code --format json --quiet --trace-file src/utils.ts
# Trace where a dependency is used
fallow dead-code --format json --quiet --trace-dependency lodash
Migrate from knip or jscpd
# Preview migration
fallow migrate --dry-run
# Apply migration (creates .fallowrc.json)
fallow migrate
# Migrate to TOML (creates fallow.toml)
fallow migrate --toml
Auto-detects knip.json, .knip.json, .jscpd.json, and package.json embedded configs.
Initialize a new config
fallow init # creates .fallowrc.json, adds .fallow/ to .gitignore
fallow init --toml # creates fallow.toml, adds .fallow/ to .gitignore
fallow init --hooks # scaffold a pre-commit git hook
fallow init --hooks --branch develop # hook using custom base branch
Exit Codes
| Code | Meaning |
|---|---|
| 0 | Success, no error-severity issues |
| 1 | Error-severity issues found |
| 2 | Runtime error (invalid config, parse failure, or fix without --yes in non-TTY) |
When --format json is active and exit code is 2, errors are emitted as JSON on stdout:
{"error": true, "message": "invalid config: ...", "exit_code": 2}
Configuration
Fallow reads config from project root: .fallowrc.json > fallow.toml > .fallow.toml. Most projects work with zero configuration thanks to 90 auto-detecting framework plugins.
{
"$schema": "https://raw.githubusercontent.com/fallow-rs/fallow/main/schema.json",
"entry": ["src/index.ts"],
"ignorePatterns": ["**/*.generated.ts"],
"ignoreDependencies": ["autoprefixer"],
"publicPackages": ["@myorg/shared-lib"],
"dynamicallyLoaded": ["plugins/**/*.ts"],
"rules": {
"unused-files": "error",
"unused-exports": "warn",
"unused-types": "off"
}
}
Rules: "error" (fail CI), "warn" (report only), "off" (skip detection).
Config fields:
publicPackages: workspace packages that are public libraries; exports from these packages are not flagged as unuseddynamicallyLoaded: glob patterns for files loaded at runtime (plugin dirs, locale files); treated as always-usedusedClassMembers: class method/property names that extend the built-in Angular/React lifecycle allowlist with framework-invoked names (ag-GridagInit/refresh, TypeORMup/down, Web ComponentsconnectedCallback, etc.). Use plugin-levelusedClassMembersin a.fallow/plugins/*.jsoncfile for library-specific allowlists
Inline suppression
// fallow-ignore-next-line
export const keepThis = 1;
// fallow-ignore-next-line unused-export
export const keepThisToo = 2;
// fallow-ignore-file
// fallow-ignore-file unused-export
// Mark as intentionally unused (tracked for staleness)
/** @expected-unused */
export const deprecatedHelper = () => {};
Key Gotchas
fix --yesis required in non-TTY (agent) environments. Without it,fixexits with code 2- Zero config by default. 90 framework plugins auto-detect. Don't create config unless customization is needed
- Syntactic analysis only. No TypeScript compiler, so fully dynamic
import(variable)is not resolved - Function overloads are deduplicated. TypeScript function overload signatures are merged into a single export (not reported as separate unused exports)
- Re-export chains are resolved. Exports through barrel files are tracked, not falsely flagged
--changed-sinceis additive. Only new issues in changed files, not all issues in the project
For the full list with examples, see references/gotchas.md.
Instructions
- Identify the task from the user's request (audit, fix, find dupes, set up CI, migrate, debug)
- Run the appropriate command with
--format json --quiet - Use filter flags to limit output when the user asks about specific issue types
- Always dry-run before fix. Show the user what will change, then apply
- Report results clearly. Summarize issue counts, list specific findings, suggest next steps
- For false positives, suggest inline suppression comments or config rule adjustments
If $ARGUMENTS is provided, use it as the --root path or pass it as the target for the appropriate fallow command.