# Adopt Fallow in an existing repo Source: https://docs.fallow.tools/adoption 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. 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: ```bash theme={null} npx fallow ``` The [quick start](/quickstart) covers the focused commands you will use during cleanup (`dead-code`, `dupes`, `health`) and `fallow init` for generating a starting config. ## 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: ```jsonc theme={null} { "$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 theme={null} 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 theme={null} 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 theme={null} 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). ```bash theme={null} 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. In a TypeScript project, add `--type-aware` when aliases, re-exports, class contracts, packages, or exact consumers affect the decision. This starts an optional, slower semantic pass. See [Type-aware TypeScript analysis](/analysis/type-aware) and the [`fallow dead-code` reference](/cli/dead-code). Merge repeated logic into shared helpers. Only ignore generated or template-heavy files when the duplication is genuinely intentional. ```bash theme={null} 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 theme={null} 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`](/configuration/overview#publicpackages) as the coarse switch for "this whole package is external API" * [`ignoreExports`](/configuration/overview#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](/frameworks/built-in) are handled out of the box) * [`entry`](/configuration/overview#entry) for project-specific entry globs * [`dynamicallyLoaded`](/configuration/overview#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`](/configuration/overview#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: * [`ignoreDependencies`](/configuration/overview#ignoredependencies) with the exact package names ### 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 ` on the line above * `// fallow-ignore-file ` 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: ```bash theme={null} 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](/cli/audit) for flags and CI recipes. ### Pick one rollout strategy Best for smaller teams. CI never blocks; fixes land under social pressure. ```jsonc theme={null} { "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 theme={null} # 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 theme={null} fallow hooks install --target agent ``` You can also configure baselines in `.fallowrc.json` and run `fallow audit` with no flags: ```jsonc theme={null} { "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. ```text theme={null} 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. # Auto-fix Source: https://docs.fallow.tools/analysis/auto-fix Automatically remove unused exports and dependencies from your TypeScript and JavaScript codebase. Supports dry-run preview and non-interactive agent workflows. `fallow fix` removes unused exports and unused dependencies from your codebase. In agent workflows, `fallow fix --yes --format json` cleans up dead code non-interactively after code generation. Always commit your changes before running `fallow fix`. Git lets you undo if needed. ## Recommended workflow Run a dry run to see exactly what would be removed without making any changes. ```bash theme={null} fallow fix --dry-run ``` ```bash title="$ fallow fix --dry-run" theme={null} Would remove export from src/components/Card/index.ts:1 `CardFooter` Would remove export from src/providers/trpc-provider/index.tsx:12 `TRPCProvider` Would remove export from src/server/jobs/queue.ts:61 `enqueueJobDelayed` Would remove export from src/server/jobs/queue.ts:206 `sweepStuckProcessingJobs` Would remove export from src/server/jobs/queue.ts:276 `getDeadLetterJobs` Would remove `@trpc/react-query` from dependencies 6 fixes available (5 exports, 1 dependency). Run without --dry-run to apply. ``` Check that every proposed removal is safe. Pay attention to exports consumed by external packages or dynamic imports that fallow can't detect statically. ```bash theme={null} fallow fix --dry-run --format json ``` After reviewing the preview, apply the changes. In interactive terminals, fallow asks for confirmation before writing. ```bash theme={null} fallow fix ``` In CI or non-TTY environments, use `--yes` to skip confirmation: ```bash theme={null} fallow fix --yes ``` Agents typically use `fallow fix --yes --format json` to apply fixes non-interactively. The JSON output confirms exactly what was changed, so the agent can verify the result. Run your build and tests to confirm nothing broke. ```bash theme={null} npm run build && npm test ``` ## What gets fixed * **Unused exports**: the `export` keyword is removed, keeping the declaration. For exported enums where the enum itself is never referenced outside its body in the file, the entire `enum` block is deleted. * **Unused dependencies**: removed from `package.json` when they are not imported by another workspace * **Unused enum members**: removed from the enum declaration. When every member of an exported enum is unused, the whole declaration is deleted in one pass instead of leaving behind an empty `export enum X {}` shell. Importers in other files now reference a name that no longer exists, so run your TypeScript build to find and clean them up. * **Unused pnpm catalog entries**: removed from `pnpm-workspace.yaml` by line-aware deletion. Object-form entries are removed as one block. By default, fallow also removes a contiguous YAML comment block immediately above the entry when it clearly belongs to that entry; configure this with [`fix.catalog.deletePrecedingComments`](/configuration/overview) (`"auto"`, `"always"`, or `"never"`). Other comments and stylistic choices in the file are preserved. When the last entry of a catalog group is removed, the header is rewritten to `catalog: {}` (or `: {}`) instead of leaving bare `catalog:`, because pnpm rejects null-valued catalogs with `Cannot convert undefined or null to object` at install time. After a successful catalog edit fallow reminds you to run `pnpm install` so `pnpm-lock.yaml` stays in sync. Auto-fix does not delete entire files. Class methods are eligible only when type-aware analysis supplies complete evidence that they are unused and the declaration still matches the analyzed source. Other class members require manual review. ## Incomplete analysis If source files could not be read or parsed completely, or discovery skipped source files, affected findings carry `reachability_caveats`. Read `workspace_diagnostics` for the files and reasons. The finding remains visible, but its removal is withheld from `fallow fix`, editor quick fixes, MCP fixes, and one-click review suggestions. Caveats can apply to unused files, exports, types, dependencies, enum members, and class members. An unseen import can point outside the affected file, so a caveat may apply across the project. Resolve the diagnostic and rerun the analysis before applying the proposed removal. A type-aware result does not override incomplete source evidence. ## `auto_fixable` is per-finding, not per action type Every action in a finding's `actions[]` array carries an `auto_fixable` bool. The value is evaluated **per finding**, not per action type: the same action type may appear with `auto_fixable: true` on one finding and `auto_fixable: false` on another, depending on per-instance guards in the `fallow fix` applier. Agents that filter on `auto_fixable: true` to decide what is safe to apply blindly must branch on the bool of each individual action, not on the action `type` alone. Per-instance flips today: * **`remove-catalog-entry`** (unused-catalog-entries): `true` only when the finding's `hardcoded_consumers` array is empty. When a workspace package still pins a hardcoded version of the same package, `fallow fix` skips the entry to avoid breaking `pnpm install`, and the action is emitted with `auto_fixable: false`. * **`remove-dependency` vs `move-dependency`** (dependency findings): when the finding's `used_in_workspaces` array is non-empty, the primary action flips to `move-dependency` with `auto_fixable: false` because `fallow fix` will not remove a dependency that another workspace imports. On findings without cross-workspace consumers the action stays `remove-dependency` with `auto_fixable: true`. * **`add-to-config` for `ignoreExports`** (duplicate-exports): `true` when `fallow fix` can safely apply the action, which today means EITHER a fallow config file already exists OR no config exists and the working directory is NOT inside a monorepo subpackage. In the second case the applier creates `.fallowrc.json` using the same scaffolding `fallow init` emits and layers the new rules on top. `false` inside a monorepo subpackage with no workspace-root config, because the applier refuses to fragment per-package configs across the monorepo and points at the workspace root instead. Pass `--no-create-config` to `fallow fix` from pre-commit hooks, CI bots, and `fallow watch` to opt out of the create-fallback; the action then surfaces with `auto_fixable: false` and is skipped at apply time. * **`update-catalog-reference`** (unresolved-catalog-references): always `false` today. The catalog-switching applier is not yet wired; the field is non-singleton so future enablement does not require a schema change. All `suppress-line` and `suppress-file` actions are uniformly `auto_fixable: false`. ## JSON output For scripting, CI, and agent workflows: ```bash theme={null} fallow fix --dry-run --format json ``` ```json theme={null} { "dry_run": true, "fixes": [ { "type": "remove_export", "path": "src/greet.ts", "line": 2, "name": "unusedGreeting" } ], "total_fixed": 0, "skipped": 0, "skipped_content_changed": 0, "skipped_mixed_line_endings": 0, "skipped_low_confidence_exports": 0, "skipped_low_confidence_dependencies": 0, "skipped_low_confidence_members": 0 } ``` `fixes` lists the proposed actions. During a dry run, `total_fixed` stays at `0` because no files were changed. Paths are relative to the project root. ## See also Full reference for the `fallow fix` command and its flags. Understand what fallow detects before auto-fixing. # Architecture boundaries Source: https://docs.fallow.tools/analysis/boundaries Enforce architecture rules with directory-based import boundaries. Define zones for UI, data, and shared code. Fallow catches violations at Rust speed. `fallow dead-code` enforces architecture boundaries by checking that imports between directories follow your rules. Define zones and declare which zones may import from which. ```bash theme={null} fallow dead-code --boundary-violations ``` Boundary violations are included in `fallow dead-code` output by default. Use `--boundary-violations` to show only boundary issues. ## Quick start with presets The fastest way to add boundaries is with a built-in preset. Fallow ships four presets for common architecture patterns: ```jsonc .fallowrc.json theme={null} { "boundaries": { "preset": "bulletproof" } } ``` ```toml fallow.toml theme={null} [boundaries] preset = "bulletproof" ``` Run `fallow list --boundaries` to see the expanded zones and rules: ```bash title="$ fallow list --boundaries" theme={null} Boundaries: 5 zones, 5 rules Zones: app 3 files src/app/** features/auth 7 files src/features/auth/** features/billing 5 files src/features/billing/** shared 8 files src/components/**, src/hooks/**, src/lib/**, ... server 4 files src/server/** Rules: app → features/auth, features/billing, shared, server features/auth → shared, server features/billing → shared, server server → shared shared (isolated, no imports allowed) ``` ## Presets A widely used React/Next.js pattern. Feature modules are isolated from each other; shared utilities and server infrastructure form the base layers. **4 logical zones:** `app`, `features`, `shared`, `server`. The `features` zone auto-discovers immediate child directories so sibling features are isolated as `features/` zones. Top-level files inside `src/features/` (barrels, shared types) fall back to the parent `features` zone; the parent rule automatically allows discovered children, so barrels can re-export features without false positives while non-barrel top-level files still obey the `features` rule. ``` app → every feature, shared, server features/ → shared, server server → shared shared (isolated) ``` The `shared` zone covers: `components`, `hooks`, `lib`, `utils`, `utilities`, `providers`, `shared`, `types`, `styles`, `i18n`. This preset matches the architecture from [Bulletproof React](https://github.com/alan2207/bulletproof-react) and is the most common pattern in modern React and Next.js projects. Classic N-tier architecture with four layers. Infrastructure may import from both domain and application (common for dependency injection). **4 zones:** `presentation`, `application`, `domain`, `infrastructure` ``` presentation → application application → domain domain (isolated) infrastructure → domain, application ``` Ports and adapters pattern. The domain has zero outward dependencies; adapters implement port interfaces. **3 zones:** `adapters`, `ports`, `domain` ``` adapters → ports ports → domain domain (isolated) ``` [Feature-Sliced Design](https://fsd.how/) with strict downward-only imports. Each layer may only import from layers below it. **6 zones:** `app`, `pages`, `widgets`, `features`, `entities`, `shared` ``` app → pages, widgets, features, entities, shared pages → widgets, features, entities, shared widgets → features, entities, shared features → entities, shared entities → shared shared (isolated) ``` ### Source root detection Preset zone patterns use `{rootDir}/{zone}/**`. Fallow auto-detects the source root from `tsconfig.json`: ```jsonc title="tsconfig.json" theme={null} { "compilerOptions": { "rootDir": "./lib" // zones become lib/app/**, lib/features/**, etc. } } ``` If no `rootDir` is found, fallow falls back to `src`. ## Custom zones and rules For full control, define zones and rules directly: ```jsonc .fallowrc.json theme={null} { "boundaries": { "zones": [ { "name": "ui", "patterns": ["src/components/**", "src/pages/**"] }, { "name": "data", "patterns": ["src/db/**", "src/api/**"] }, { "name": "shared", "patterns": ["src/lib/**", "src/utils/**"] } ], "rules": [ { "from": "ui", "allow": ["shared"] }, { "from": "data", "allow": ["shared"] }, { "from": "shared", "allow": [] } ] } } ``` ```toml fallow.toml theme={null} [[boundaries.zones]] name = "ui" patterns = ["src/components/**", "src/pages/**"] [[boundaries.zones]] name = "data" patterns = ["src/db/**", "src/api/**"] [[boundaries.zones]] name = "shared" patterns = ["src/lib/**", "src/utils/**"] [[boundaries.rules]] from = "ui" allow = ["shared"] [[boundaries.rules]] from = "data" allow = ["shared"] [[boundaries.rules]] from = "shared" allow = [] ``` ### How zones work * A file belongs to the **first zone** whose pattern matches (first-match wins) * Files that don't match any zone are **unrestricted**: they can import from and be imported by any zone * **Self-imports** are always allowed (files in the same zone can freely import each other) * A zone with **no rule entry** is unrestricted: it can import from any zone * A zone with a rule and an **empty `allow` list** is isolated: it cannot import from other zones ### Auto-discovered feature zones Use `autoDiscover` when one logical zone should create one concrete zone per child directory. This is useful for feature-module architectures where `src/features/auth` and `src/features/billing` should be isolated from each other without writing a zone and rule for every feature. ```jsonc theme={null} { "boundaries": { "zones": [ { "name": "app", "patterns": ["src/app/**"] }, { "name": "features", "patterns": ["src/features/**"], "autoDiscover": ["src/features"] }, { "name": "shared", "patterns": ["src/shared/**"] } ], "rules": [ { "from": "app", "allow": ["features", "shared"] }, { "from": "features", "allow": ["shared"] } ] } } ``` If `src/features/auth` and `src/features/billing` exist, fallow expands this to `features/auth` and `features/billing`. Rules that reference the logical `features` parent apply to every discovered feature. Explicit child rules, such as `from: "features/auth"`, override generated parent rules. When the zone also has `patterns`, top-level files inside the auto-discover directory fall back to the parent zone, whose rule automatically allows its discovered children. See [Auto-discovered zones](/configuration/boundaries#auto-discovered-zones) for the full semantics. ### Subtree scope (`root`) Monorepos with per-package boundaries usually have the same internal directory layout under each package (`packages/app/src/`, `packages/core/src/`, ...). Writing flat patterns from the project root forces zone definitions to scale with the cross product of (zones, packages): ```jsonc theme={null} // Without `root`: one zone per (layer, package) pair. { "name": "ui-app", "patterns": ["packages/app/src/**"] }, { "name": "domain-core", "patterns": ["packages/core/src/**"] } ``` Set `root` on a zone to scope its patterns to a subtree. At classification time, fallow checks that the file's path starts with the `root` prefix and strips that prefix before matching the patterns against the remainder. Files outside the subtree never match the zone. ```jsonc .fallowrc.json theme={null} { "boundaries": { "zones": [ { "name": "ui", "patterns": ["src/**"], "root": "packages/app/" }, { "name": "domain", "patterns": ["src/**"], "root": "packages/core/" } ], "rules": [ { "from": "ui", "allow": [] } ] } } ``` ```toml fallow.toml theme={null} [[boundaries.zones]] name = "ui" patterns = ["src/**"] root = "packages/app/" [[boundaries.zones]] name = "domain" patterns = ["src/**"] root = "packages/core/" [[boundaries.rules]] from = "ui" allow = [] ``` In this example, `packages/app/src/login.tsx` classifies as `ui` and `packages/core/src/order.ts` classifies as `domain`. Adding `packages/billing/` later only requires another zone entry with the same `patterns: ["src/**"]` and a different `root`. Trailing slashes and a leading `./` are normalized for you, so `"packages/app"`, `"packages/app/"`, and `"./packages/app/"` are equivalent. Backslashes are converted to forward slashes. Patterns must NOT redundantly include the root prefix. `root: "packages/app/"` paired with `patterns: ["packages/app/src/**"]` is rejected at config-resolve time with `FALLOW-BOUNDARY-ROOT-REDUNDANT-PREFIX`, because patterns are already resolved relative to the root. Drop the root prefix from the pattern (`patterns: ["src/**"]`). ### Overriding preset zones Start from a preset and customize specific zones or rules. Zones with the same name replace the preset zone; rules with the same `from` replace the preset rule. See [Merging presets with custom config](/configuration/boundaries#merging-presets-with-custom-config) for examples. ## Suppressing violations Suppress individual findings with inline comments: ```typescript theme={null} // fallow-ignore-next-line boundary-violation import { db } from '../data/client'; ``` Or suppress all boundary violations in a file: ```typescript theme={null} // fallow-ignore-file boundary-violation ``` To suppress boundary checking entirely, set the rule severity to `off`: ```jsonc theme={null} { "rules": { "boundary-violation": "off" } } ``` The `boundary-violation` token and rule cover the whole boundary family: import-direction violations, [coverage violations](/configuration/boundaries#boundary-coverage), and [forbidden-call violations](/configuration/boundaries#forbidden-calls). The rule-id-shaped `boundary-call-violation` and `boundary-call-violations` tokens are accepted as aliases; any of the three suppresses every boundary finding on that line or file. When introducing boundaries to an existing codebase, start with `"warn"` severity. Fix violations incrementally, then switch to `"error"` once the codebase is clean. ## Output formats Boundary violations appear in all output formats: ```bash title="$ fallow dead-code --boundary-violations" theme={null} Boundary violations (2) src/features/auth/login.ts:3 → src/features/billing/api.ts (features → features) src/components/Button.tsx:1 → src/server/db/client.ts (shared → server) ``` ```json theme={null} { "boundary_violations": [ { "from_path": "src/features/auth/login.ts", "to_path": "src/features/billing/api.ts", "from_zone": "features", "to_zone": "features", "import_specifier": "../billing/api", "line": 3, "col": 0 } ] } ``` The full JSON output includes a `schema_version`, `version`, `elapsed_ms`, and all issue type arrays. Only `boundary_violations` is shown here. ``` boundary-violation:src/features/auth/login.ts:3:src/features/auth/login.ts -> src/features/billing/api.ts (features -> features) ``` ## See also CLI reference for the dead code command, including all issue type filters. Set boundary violations to error, warn, or off. Suppress individual findings in source code. # CSS, SCSS, and Tailwind analysis Source: https://docs.fallow.tools/analysis/css-analysis How fallow tracks CSS imports, SCSS partials, Tailwind dependencies, and CSS Module class names using AST-based extraction. Zero false positives on @use, @forward, and partial imports. Fallow tracks CSS and SCSS files using dedicated extraction. `@use`, `@forward`, SCSS partials (`_prefix` files), and Tailwind directives are all resolved accurately. No manual configuration needed. ## SCSS import resolution Fallow understands the full SCSS module system. Imports create edges in the module graph, and re-exports propagate through the dependency chain. | Directive | How fallow handles it | | :----------------- | :----------------------------------------------------------- | | `@use 'path'` | Resolved as a module import, creating a graph edge | | `@forward 'path'` | Treated as a re-export in the module graph | | `@import 'path'` | Legacy import, tracked as a side-effect edge | | `@use 'sass:math'` | Recognized as a Sass built-in module, not flagged as missing | SCSS partial resolution follows the standard convention. When fallow encounters `@use 'components/button'`, it resolves through these candidates in order: 1. `components/_button.scss` (partial convention) 2. `components/button/_index.scss` (directory index) 3. `components/button/index.scss` (plain index) ```scss styles/theme.scss theme={null} @use 'sass:color'; @use 'variables'; @use 'mixins/responsive'; @forward 'tokens' show $primary, $secondary; ``` Fallow sees this file as: * Importing the `sass:color` built-in (correctly ignored, not a project dependency) * Importing `./variables`, resolved to `_variables.scss` via partial convention * Importing `./mixins/responsive`, resolved to `mixins/_responsive.scss` * Re-exporting `$primary` and `$secondary` from `./tokens` SCSS include paths (configured via frameworks like Angular) are also supported. When a bare specifier like `@use 'variables'` cannot be resolved locally, fallow searches configured include directories with the same partial and index conventions. ## CSS Module class tracking Files with `.module.css`, `.module.scss`, `.module.sass`, or `.module.less` extensions receive special treatment. Fallow extracts every class name from selectors and exposes it as a named export. Repeated selectors for the same class remain one export rather than producing duplicate findings. ```css Button.module.css theme={null} .root { display: flex; align-items: center; } .primary { background: var(--color-primary); } .disabled { opacity: 0.5; pointer-events: none; } ``` This file exports three named symbols: `root`, `primary`, and `disabled`. When a component imports a CSS module, fallow tracks which classes are actually accessed. Default imports may use any local alias, and passing or otherwise consuming the whole imported object conservatively credits every class: ```tsx Button.tsx theme={null} import styles from './Button.module.css'; export const Button = ({ variant }: Props) => ( ); ``` Here, `styles.root` and `styles.primary` are marked as used. The `disabled` class is never referenced, so fallow reports it as an unused export. ```bash title="$ fallow dead-code --unused-exports" theme={null} ● Unused exports (1) src/Button.module.css :3 disabled Exported symbols with zero references: https://docs.fallow.tools/explanations/dead-code#unused-exports ✗ 1 issue (0.02s) ``` Start with `fallow dead-code --unused-exports` to see which CSS module class names are unused across your project. ## Tailwind CSS integration When fallow encounters `@apply` or `@tailwind` directives in any CSS or SCSS file, it creates a synthetic dependency on the `tailwindcss` package. This prevents false "unused dependency" reports. ```css globals.css theme={null} @tailwind base; @tailwind components; @tailwind utilities; .btn { @apply px-4 py-2 rounded-md font-medium; } ``` Fallow detects both `@tailwind` and `@apply` directives and marks `tailwindcss` as a used dependency. ### `@plugin` directive (Tailwind v4) Tailwind v4 moves plugin registration into CSS via the `@plugin` directive. Fallow extracts each `@plugin` target as a default import, so package plugins are credited as used dependencies and relative plugin files have their default export marked used. ```css app.css theme={null} @import "tailwindcss"; @plugin "@tailwindcss/typography"; @plugin "daisyui" { themes: light --default; } @plugin "./tailwind-local-plugin.js"; ``` Fallow marks `@tailwindcss/typography` and `daisyui` as used dependencies, and treats `./tailwind-local-plugin.js` as a reachable file whose `default` export is consumed. The directive is also recognized inside Vue and Svelte `