{ "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "FallowConfig", "description": "User-facing configuration loaded from `.fallowrc.json`, `.fallowrc.jsonc`, `fallow.toml`, or `.fallow.toml`.\n\n# Examples\n\n```\nuse fallow_config::FallowConfig;\n\n// Default config has sensible defaults\nlet config = FallowConfig::default();\nassert!(config.entry.is_empty());\nassert!(!config.production);\n\n// Deserialize from JSON\nlet config: FallowConfig = serde_json::from_str(r#\"{\n \"entry\": [\"src/main.ts\"],\n \"production\": true\n}\"#).unwrap();\nassert_eq!(config.entry, vec![\"src/main.ts\"]);\nassert!(config.production);\n```", "type": "object", "properties": { "$schema": { "description": "JSON Schema reference (ignored during deserialization).", "type": [ "string", "null" ], "writeOnly": true }, "extends": { "description": "Base config files to extend from.\n\nSupports three resolution strategies:\n- **Relative paths**: `\"./base.json\"` — resolved relative to the config file.\n- **npm packages**: `\"npm:@co/config\"` — resolved by walking up `node_modules/`.\n Package resolution checks `package.json` `exports`/`main` first, then falls back\n to standard config file names. Subpaths are supported (e.g., `npm:@co/config/strict.json`).\n- **HTTPS URLs**: `\"https://example.com/fallow-base.json\"` — fetched remotely.\n Only HTTPS is supported (no plain HTTP). URL-sourced configs may extend other\n URLs or `npm:` packages, but not relative paths. Only JSON/JSONC format is\n supported for remote configs. Timeout is configurable via\n `FALLOW_EXTENDS_TIMEOUT_SECS` (default: 5s).\n\nBase configs are loaded first, then this config's values override them.\nLater entries in the array override earlier ones.\n\n**Note:** `npm:` resolution uses `node_modules/` directory walk-up and is\nincompatible with Yarn Plug'n'Play (PnP), which has no `node_modules/`.\nURL extends fetch on every run (no caching). For reliable CI, prefer `npm:`\nfor private or critical configs.", "type": "array", "items": { "type": "string" }, "writeOnly": true }, "entry": { "description": "Additional entry point glob patterns.", "type": "array", "items": { "type": "string" }, "default": [] }, "ignorePatterns": { "description": "Glob patterns to ignore from analysis.", "type": "array", "items": { "type": "string" }, "default": [] }, "framework": { "description": "Custom framework definitions (inline plugin definitions).", "type": "array", "items": { "$ref": "#/$defs/ExternalPluginDef" }, "default": [] }, "workspaces": { "description": "Workspace overrides.", "anyOf": [ { "$ref": "#/$defs/WorkspaceConfig" }, { "type": "null" } ], "default": null }, "ignoreDependencies": { "description": "Dependencies to ignore (always considered used and always considered available).\n\nListed dependencies are excluded from both unused dependency and unlisted\ndependency detection. Useful for runtime-provided packages like `bun:sqlite`\nor implicitly available dependencies.", "type": "array", "items": { "type": "string" }, "default": [] }, "ignoreExports": { "description": "Export ignore rules.", "type": "array", "items": { "$ref": "#/$defs/IgnoreExportRule" }, "default": [] }, "ignoreCatalogReferences": { "description": "Rules for suppressing `unresolved-catalog-reference` findings.\n\nEach rule matches by package name, optionally scoped to a specific\ncatalog and/or consumer `package.json` glob. Useful for staged catalog\nmigrations where the catalog edit lands separately from the consumer\nedit, and for library-internal placeholder packages whose target\ncatalog isn't ready yet.", "type": "array", "items": { "$ref": "#/$defs/IgnoreCatalogReferenceRule" } }, "ignoreDependencyOverrides": { "description": "Rules for suppressing `unused-dependency-override` and\n`misconfigured-dependency-override` findings.\n\nEach rule matches by override target package, optionally scoped to the\ndeclaring source file (`pnpm-workspace.yaml` or `package.json`). Useful\nfor overrides targeting purely-transitive packages (CVE-fix pattern)\nwhere the conservative static algorithm would otherwise cry wolf.", "type": "array", "items": { "$ref": "#/$defs/IgnoreDependencyOverrideRule" } }, "ignoreExportsUsedInFile": { "description": "Suppress unused-export findings when the exported symbol is referenced\ninside the file that declares it. This mirrors Knip's\n`ignoreExportsUsedInFile` option while still reporting exports that have\nno references at all.", "$ref": "#/$defs/IgnoreExportsUsedInFileConfig", "default": false }, "usedClassMembers": { "description": "Class member method/property rules that should never be flagged as\nunused. Supports plain member names for global suppression and scoped\nobjects with `extends` / `implements` constraints for framework-invoked\nmethods that should only be suppressed on matching classes.", "type": "array", "items": { "$ref": "#/$defs/UsedClassMemberRule" }, "default": [] }, "duplicates": { "description": "Duplication detection settings.", "$ref": "#/$defs/DuplicatesConfig", "default": { "enabled": true, "mode": "mild", "minTokens": 50, "minLines": 5, "minOccurrences": 2, "threshold": 0.0, "ignore": [], "ignoreDefaults": true, "skipLocal": false, "crossLanguage": false, "ignoreImports": false, "normalization": {}, "minCorpusSizeForShingleFilter": 1024, "minCorpusSizeForTokenCache": 5000 } }, "health": { "description": "Complexity health metrics settings.", "$ref": "#/$defs/HealthConfig", "default": { "maxCyclomatic": 20, "maxCognitive": 15, "maxCrap": 30.0, "ignore": [], "ownership": { "botPatterns": [ "*\\[bot\\]*", "dependabot*", "renovate*", "github-actions*", "svc-*", "*-service-account*" ], "emailMode": "handle" }, "suggestInlineSuppression": true } }, "rules": { "description": "Per-issue-type severity rules.", "$ref": "#/$defs/RulesConfig", "default": { "unused-files": "error", "unused-exports": "error", "unused-types": "error", "private-type-leaks": "off", "unused-dependencies": "error", "unused-dev-dependencies": "warn", "unused-optional-dependencies": "warn", "unused-enum-members": "error", "unused-class-members": "error", "unresolved-imports": "error", "unlisted-dependencies": "error", "duplicate-exports": "error", "type-only-dependencies": "warn", "test-only-dependencies": "warn", "circular-dependencies": "error", "boundary-violation": "error", "coverage-gaps": "off", "feature-flags": "off", "stale-suppressions": "warn", "unused-catalog-entries": "warn", "empty-catalog-groups": "warn", "unresolved-catalog-references": "error", "unused-dependency-overrides": "warn", "misconfigured-dependency-overrides": "error" } }, "boundaries": { "description": "Architecture boundary enforcement configuration.", "$ref": "#/$defs/BoundaryConfig", "default": { "zones": [], "rules": [] } }, "flags": { "description": "Feature flag detection configuration.", "$ref": "#/$defs/FlagsConfig", "default": { "configObjectHeuristics": false } }, "resolve": { "description": "Module resolver configuration (custom conditions, etc.).", "$ref": "#/$defs/ResolveConfig", "default": {} }, "production": { "description": "Production mode: exclude test/dev files, only start/build scripts.\n\nAccepts the legacy boolean form (`true` applies to all analyses) or a\nper-analysis object (`{ \"deadCode\": false, \"health\": true, \"dupes\": false }`).", "$ref": "#/$defs/ProductionConfig", "default": false }, "plugins": { "description": "Paths to external plugin files or directories containing plugin files.\n\nSupports TOML, JSON, and JSONC formats.\n\nIn addition to these explicit paths, fallow automatically discovers:\n- `*.toml`, `*.json`, `*.jsonc` files in `.fallow/plugins/`\n- `fallow-plugin-*.{toml,json,jsonc}` files in the project root", "type": "array", "items": { "type": "string" }, "default": [] }, "dynamicallyLoaded": { "description": "Glob patterns for files that are dynamically loaded at runtime\n(plugin directories, locale files, etc.). These files are treated as\nalways-used and will never be flagged as unused.", "type": "array", "items": { "type": "string" }, "default": [] }, "overrides": { "description": "Per-file rule overrides matching oxlint's overrides pattern.", "type": "array", "items": { "$ref": "#/$defs/ConfigOverride" }, "default": [] }, "codeowners": { "description": "Path to a CODEOWNERS file for `--group-by owner`.\n\nWhen unset, fallow auto-probes `CODEOWNERS`, `.github/CODEOWNERS`,\n`.gitlab/CODEOWNERS`, and `docs/CODEOWNERS`. Set this to use a\nnon-standard location.", "type": [ "string", "null" ] }, "publicPackages": { "description": "Workspace package name patterns that are public libraries.\nExported API surface from these packages is not flagged as unused.", "type": "array", "items": { "type": "string" }, "default": [] }, "regression": { "description": "Regression detection baseline embedded in config.\nStores issue counts from a known-good state for CI regression checks.\nPopulated by `--save-regression-baseline` (no path), read by `--fail-on-regression`.", "anyOf": [ { "$ref": "#/$defs/RegressionConfig" }, { "type": "null" } ] }, "audit": { "description": "Audit command baseline paths (one per analysis: dead-code, health, dupes).\n\n`fallow audit` runs three analyses and each has its own baseline format.\nPaths in this section are resolved relative to the project root. CLI flags\n(`--dead-code-baseline`, `--health-baseline`, `--dupes-baseline`) override\nthese values when provided.", "$ref": "#/$defs/AuditConfig" }, "sealed": { "description": "Mark this config as sealed: `extends` paths must be file-relative and\nresolve within this config's own directory. `npm:` and `https:` extends\nare rejected. Useful for library publishers and monorepo sub-packages\nthat want to guarantee their config is self-contained and not subject\nto ancestor configs being injected via `extends`.\n\nDiscovery is unaffected (first-match-wins already stops the directory\nwalk at the nearest config). This only constrains `extends`.", "type": "boolean", "default": false }, "includeEntryExports": { "description": "Report unused exports in entry files instead of auto-marking them as\nused. Catches typos in framework exports (e.g. `meatdata` instead of\n`metadata`). The CLI flag `--include-entry-exports` (global) overrides\nthis when set; otherwise the config value is used.", "type": "boolean", "default": false } }, "additionalProperties": false, "$defs": { "ExternalPluginDef": { "description": "A declarative plugin definition loaded from a standalone file or inline config.\n\nExternal plugins provide the same static pattern capabilities as built-in\nplugins (entry points, always-used files, used exports, tooling dependencies),\nbut are defined in standalone files or inline in the fallow config rather than\ncompiled Rust code.\n\nThey cannot do AST-based config parsing (`resolve_config()`), but cover the\nvast majority of framework integration use cases.\n\nSupports JSONC, JSON, and TOML formats. All use camelCase field names.\n\n```json\n{\n \"$schema\": \"https://raw.githubusercontent.com/fallow-rs/fallow/main/plugin-schema.json\",\n \"name\": \"my-framework\",\n \"enablers\": [\"my-framework\", \"@my-framework/core\"],\n \"entryPoints\": [\"src/routes/**/*.{ts,tsx}\"],\n \"configPatterns\": [\"my-framework.config.{ts,js}\"],\n \"alwaysUsed\": [\"src/setup.ts\"],\n \"toolingDependencies\": [\"my-framework-cli\"],\n \"usedExports\": [\n { \"pattern\": \"src/routes/**/*.{ts,tsx}\", \"exports\": [\"default\", \"loader\", \"action\"] }\n ]\n}\n```", "type": "object", "properties": { "name": { "description": "Unique name for this plugin.", "type": "string" }, "detection": { "description": "Rich detection logic (dependency checks, file existence, boolean combinators).\nTakes priority over `enablers` when set.", "anyOf": [ { "$ref": "#/$defs/PluginDetection" }, { "type": "null" } ], "default": null }, "enablers": { "description": "Package names that activate this plugin when found in package.json.\nSupports exact matches and prefix patterns (ending with `/`).\nOnly used when `detection` is not set.", "type": "array", "items": { "type": "string" }, "default": [] }, "entryPoints": { "description": "Glob patterns for entry point files.", "type": "array", "items": { "type": "string" }, "default": [] }, "entryPointRole": { "description": "Coverage role for `entryPoints`.\n\nDefaults to `support`. Set to `runtime` for application entry points\nor `test` for test framework entry points.", "$ref": "#/$defs/EntryPointRole", "default": "support" }, "configPatterns": { "description": "Glob patterns for config files (marked as always-used when active).", "type": "array", "items": { "type": "string" }, "default": [] }, "alwaysUsed": { "description": "Files that are always considered \"used\" when this plugin is active.", "type": "array", "items": { "type": "string" }, "default": [] }, "toolingDependencies": { "description": "Dependencies that are tooling (used via CLI/config, not source imports).\nThese should not be flagged as unused devDependencies.", "type": "array", "items": { "type": "string" }, "default": [] }, "usedExports": { "description": "Exports that are always considered used for matching file patterns.", "type": "array", "items": { "$ref": "#/$defs/ExternalUsedExport" }, "default": [] }, "usedClassMembers": { "description": "Class member method/property rules the framework invokes at runtime.\nSupports plain member names for global suppression and scoped objects\nwith `extends` / `implements` constraints when the method name is too\ncommon to suppress across the whole workspace.", "type": "array", "items": { "$ref": "#/$defs/UsedClassMemberRule" }, "default": [] } }, "required": [ "name" ] }, "PluginDetection": { "description": "How to detect if a plugin should be activated.\n\nWhen set on an `ExternalPluginDef`, this takes priority over `enablers`.\nSupports dependency checks, file existence checks, and boolean combinators.", "oneOf": [ { "description": "Plugin detected if this package is in dependencies.", "type": "object", "properties": { "package": { "type": "string" }, "type": { "type": "string", "const": "dependency" } }, "required": [ "type", "package" ] }, { "description": "Plugin detected if this file pattern matches.", "type": "object", "properties": { "pattern": { "type": "string" }, "type": { "type": "string", "const": "fileExists" } }, "required": [ "type", "pattern" ] }, { "description": "All conditions must be true.", "type": "object", "properties": { "conditions": { "type": "array", "items": { "$ref": "#/$defs/PluginDetection" } }, "type": { "type": "string", "const": "all" } }, "required": [ "type", "conditions" ] }, { "description": "Any condition must be true.", "type": "object", "properties": { "conditions": { "type": "array", "items": { "$ref": "#/$defs/PluginDetection" } }, "type": { "type": "string", "const": "any" } }, "required": [ "type", "conditions" ] } ] }, "EntryPointRole": { "description": "How a plugin's discovered entry points contribute to coverage reachability.", "oneOf": [ { "description": "Runtime/application roots that should count toward runtime reachability.", "type": "string", "const": "runtime" }, { "description": "Test roots that should count toward test reachability.", "type": "string", "const": "test" }, { "description": "Support/setup/config roots that should keep files alive but not count as runtime/test.", "type": "string", "const": "support" } ] }, "ExternalUsedExport": { "description": "Exports considered used for files matching a pattern.", "type": "object", "properties": { "pattern": { "description": "Glob pattern for files.", "type": "string" }, "exports": { "description": "Export names always considered used.", "type": "array", "items": { "type": "string" } } }, "required": [ "pattern", "exports" ] }, "UsedClassMemberRule": { "description": "A `usedClassMembers` entry from config or an external plugin.\n\nSupports either a plain member name or glob pattern (`\"agInit\"`,\n`\"enter*\"`) or a scoped rule that only applies when a class matches\nspecific `extends` / `implements` heritage clauses.", "anyOf": [ { "description": "Globally suppress this class member name or glob pattern for all classes.", "type": "string" }, { "description": "Suppress these class member names only for matching classes.", "$ref": "#/$defs/ScopedUsedClassMemberRule" } ] }, "ScopedUsedClassMemberRule": { "description": "A heritage-constrained `usedClassMembers` rule.", "type": "object", "properties": { "extends": { "description": "Only apply when the class extends this parent class name.", "type": [ "string", "null" ] }, "implements": { "description": "Only apply when the class implements this interface name.", "type": [ "string", "null" ] }, "members": { "description": "Member names or glob patterns that should be treated as framework-used.", "type": "array", "items": { "type": "string" } } }, "additionalProperties": false, "required": [ "members" ] }, "WorkspaceConfig": { "description": "Workspace configuration for monorepo support.", "type": "object", "properties": { "patterns": { "description": "Additional workspace patterns (beyond what's in root package.json).", "type": "array", "items": { "type": "string" }, "default": [] } } }, "IgnoreExportRule": { "description": "Rule for ignoring specific exports.", "type": "object", "properties": { "file": { "description": "Glob pattern for files.", "type": "string" }, "exports": { "description": "Export names to ignore (`*` for all).", "type": "array", "items": { "type": "string" } } }, "required": [ "file", "exports" ] }, "IgnoreCatalogReferenceRule": { "description": "Rule for suppressing an `unresolved-catalog-reference` finding.\n\nA finding is suppressed when ALL provided fields match the finding:\n- `package` matches the consumed package name exactly (case-sensitive).\n- `catalog`, if set, matches the referenced catalog name (`\"default\"` for\n bare `catalog:` references; named catalogs use their declared key). When\n omitted, any catalog matches.\n- `consumer`, if set, is a glob matched against the consumer `package.json`\n path relative to the project root. When omitted, any consumer matches.\n\nTypical use cases:\n- Staged migrations: catalog entry is being added in a separate PR\n- Library-internal placeholder packages whose target catalog isn't ready yet", "type": "object", "properties": { "package": { "description": "Package name being referenced via the catalog protocol (exact match).", "type": "string" }, "catalog": { "description": "Catalog name to scope the suppression to. `None` matches any catalog.", "type": [ "string", "null" ] }, "consumer": { "description": "Glob (root-relative) for the consumer `package.json`. `None` matches any consumer.", "type": [ "string", "null" ] } }, "additionalProperties": false, "required": [ "package" ] }, "IgnoreDependencyOverrideRule": { "description": "Rule for suppressing an `unused-dependency-override` or\n`misconfigured-dependency-override` finding.\n\nA finding is suppressed when ALL provided fields match the finding:\n- `package` matches the override's target package name exactly\n (case-sensitive). For parent-chain overrides (`react>react-dom`), the\n target is the rightmost segment (`react-dom`).\n- `source`, if set, scopes the suppression to overrides declared in that\n source file. Accepts `\"pnpm-workspace.yaml\"` or `\"package.json\"`.\n When omitted, both sources match.\n\nTypical use cases:\n- Library-internal CI tooling overrides we cannot drop yet\n- Overrides targeting purely-transitive packages (CVE-fix pattern)", "type": "object", "properties": { "package": { "description": "Override target package name (exact match; case-sensitive).", "type": "string" }, "source": { "description": "Source file scope: `\"pnpm-workspace.yaml\"` or `\"package.json\"`.\n`None` matches both sources.", "type": [ "string", "null" ] } }, "additionalProperties": false, "required": [ "package" ] }, "IgnoreExportsUsedInFileConfig": { "description": "Controls whether exports referenced only inside their defining file are\nreported as unused exports.", "anyOf": [ { "description": "`true` suppresses both value and type exports that are referenced in\ntheir defining file. `false` preserves the default cross-file behavior.", "type": "boolean" }, { "description": "Knip-compatible fine-grained form. Fallow groups type aliases and\ninterfaces under `unused_types`, so either field enables type-export\nsuppression for same-file references.", "$ref": "#/$defs/IgnoreExportsUsedInFileByKind" } ] }, "IgnoreExportsUsedInFileByKind": { "description": "Knip-compatible `ignoreExportsUsedInFile` object form.", "type": "object", "properties": { "type": { "description": "Suppress same-file references for exported type aliases.", "type": "boolean", "default": false }, "interface": { "description": "Suppress same-file references for exported interfaces.", "type": "boolean", "default": false } } }, "DuplicatesConfig": { "description": "Configuration for code duplication detection.", "type": "object", "properties": { "enabled": { "description": "Whether duplication detection is enabled.", "type": "boolean", "default": true }, "mode": { "description": "Detection mode: strict, mild, weak, or semantic.", "$ref": "#/$defs/DetectionMode", "default": "mild" }, "minTokens": { "description": "Minimum number of tokens for a clone.", "type": "integer", "format": "uint", "minimum": 0, "default": 50 }, "minLines": { "description": "Minimum number of lines for a clone.", "type": "integer", "format": "uint", "minimum": 0, "default": 5 }, "minOccurrences": { "description": "Minimum number of occurrences (instances of the same clone) before a\ngroup is reported. Defaults to 2 (every duplicated pair is reported).\nRaise this to focus on widespread copy-paste worth refactoring and skip\ncontext-sensitive pairs.", "type": "integer", "format": "uint", "minimum": 2, "default": 2 }, "threshold": { "description": "Maximum allowed duplication percentage (0 = no limit).", "type": "number", "format": "double", "default": 0.0 }, "ignore": { "description": "Additional ignore patterns for duplication analysis.", "type": "array", "items": { "type": "string" }, "default": [] }, "ignoreDefaults": { "description": "Merge built-in generated-framework ignore patterns with `ignore`.\n\nSet to `false` to use only the user-provided `ignore` list.", "type": "boolean", "default": true }, "skipLocal": { "description": "Only report cross-directory duplicates.", "type": "boolean", "default": false }, "crossLanguage": { "description": "Enable cross-language clone detection by stripping type annotations.\n\nWhen enabled, TypeScript type annotations (parameter types, return types,\ngenerics, interfaces, type aliases) are stripped from the token stream,\nallowing detection of clones between `.ts` and `.js` files.", "type": "boolean", "default": false }, "ignoreImports": { "description": "Exclude ES `import` declarations from clone detection.\n\nWhen enabled, all `import` statements (value imports, type imports, and\nside-effect imports) are stripped from the token stream before clone\ndetection. This reduces noise from sorted import blocks that naturally\nlook similar across files. Only affects ES `import` declarations;\nCommonJS `require()` calls are not filtered.", "type": "boolean", "default": false }, "normalization": { "description": "Fine-grained normalization overrides on top of the detection mode.", "$ref": "#/$defs/NormalizationConfig", "default": {} }, "minCorpusSizeForShingleFilter": { "description": "Minimum tokenized file count before focused duplicate analysis prefilters\nunchanged files with k-token shingles.", "type": "integer", "format": "uint", "minimum": 0, "default": 1024 }, "minCorpusSizeForTokenCache": { "description": "Minimum source file count before the persistent duplication token cache\nactivates. Below this threshold the cache load/save overhead exceeds the\ntokenize savings, so the cache stays disabled even when not running with\n`--no-cache`.", "type": "integer", "format": "uint", "minimum": 0, "default": 5000 } } }, "DetectionMode": { "description": "Detection mode controlling how aggressively tokens are normalized.\n\nSince fallow uses AST-based tokenization (not lexer-based), whitespace and\ncomments are inherently absent from the token stream. The `Strict` and `Mild`\nmodes are currently equivalent. `Weak` mode additionally blinds string\nliterals. `Semantic` mode blinds all identifiers and literal values for\nType-2 (renamed variable) clone detection.", "oneOf": [ { "description": "All tokens preserved including identifier names and literal values (Type-1 only).", "type": "string", "const": "strict" }, { "description": "Default mode -- equivalent to strict for AST-based tokenization.", "type": "string", "const": "mild" }, { "description": "Blind string literal values (structure-preserving).", "type": "string", "const": "weak" }, { "description": "Blind all identifiers and literal values for structural (Type-2) detection.", "type": "string", "const": "semantic" } ] }, "NormalizationConfig": { "description": "Fine-grained normalization overrides.\n\nEach option, when set to `Some(true)`, forces that normalization regardless of\nthe detection mode. When set to `Some(false)`, it forces preservation. When\n`None`, the detection mode's default behavior applies.", "type": "object", "properties": { "ignoreIdentifiers": { "description": "Blind all identifiers (variable names, function names, etc.) to the same hash.\nDefault in `semantic` mode.", "type": [ "boolean", "null" ] }, "ignoreStringValues": { "description": "Blind string literal values to the same hash.\nDefault in `weak` and `semantic` modes.", "type": [ "boolean", "null" ] }, "ignoreNumericValues": { "description": "Blind numeric literal values to the same hash.\nDefault in `semantic` mode.", "type": [ "boolean", "null" ] } } }, "HealthConfig": { "description": "Configuration for complexity health metrics (`fallow health`).", "type": "object", "properties": { "maxCyclomatic": { "description": "Maximum allowed cyclomatic complexity per function (default: 20).\nFunctions exceeding this threshold are reported.", "type": "integer", "format": "uint16", "minimum": 0, "maximum": 65535, "default": 20 }, "maxCognitive": { "description": "Maximum allowed cognitive complexity per function (default: 15).\nFunctions exceeding this threshold are reported.", "type": "integer", "format": "uint16", "minimum": 0, "maximum": 65535, "default": 15 }, "maxCrap": { "description": "Maximum allowed CRAP (Change Risk Anti-Patterns) score per function\n(default: 30.0). CRAP combines cyclomatic complexity with test\ncoverage: high complexity plus low coverage produces a high CRAP\nscore. Functions meeting or exceeding this threshold are reported.\nUse `--coverage` with Istanbul data for accurate per-function CRAP;\notherwise fallow estimates coverage from the module graph.", "type": "number", "format": "double", "default": 30.0 }, "ignore": { "description": "Glob patterns to exclude from complexity analysis.", "type": "array", "items": { "type": "string" }, "default": [] }, "ownership": { "description": "Ownership analysis configuration. Controls bot filtering and email\nprivacy mode for `--ownership` output.", "$ref": "#/$defs/OwnershipConfig", "default": { "botPatterns": [ "*\\[bot\\]*", "dependabot*", "renovate*", "github-actions*", "svc-*", "*-service-account*" ], "emailMode": "handle" } }, "suggestInlineSuppression": { "description": "Whether health JSON output emits `suppress-line` action hints\nalongside complexity findings (default: `true`). Set to `false` to\nopt out across the project: useful for teams that manage suppressions\nexclusively through `// fallow-ignore-*` comments authored by hand or\nthrough the `fallow.suppress` LSP code action, but who do not want\nCI-driven `suppress-line` action hints in their JSON output.\n`--baseline` activates auto-omission regardless of this setting,\nsince baseline files are a separate suppression mechanism.", "type": "boolean", "default": true } } }, "OwnershipConfig": { "description": "Configuration for ownership analysis (`fallow health --hotspots --ownership`).", "type": "object", "properties": { "botPatterns": { "description": "Glob patterns (matched against the author email local-part) that\nidentify bot or service-account commits to exclude from ownership\nsignals. Overrides the defaults entirely when set.", "type": "array", "items": { "type": "string" }, "default": [ "*\\[bot\\]*", "dependabot*", "renovate*", "github-actions*", "svc-*", "*-service-account*" ] }, "emailMode": { "description": "Privacy mode for emitted author emails. Defaults to `handle`.\nOverride on the CLI via `--ownership-emails=raw|handle|hash`.", "$ref": "#/$defs/EmailMode", "default": "handle" } } }, "EmailMode": { "description": "Privacy mode for author emails emitted in ownership output.\n\nDefaults to `handle` (local-part only, no domain) so SARIF and JSON\nartifacts do not leak raw email addresses into CI pipelines.", "oneOf": [ { "description": "Show the raw email address as it appears in git history.\nUse for public repositories where history is already exposed.", "type": "string", "const": "raw" }, { "description": "Show the local-part only (before the `@`). Mailmap-resolved where possible.\nDefault. Balances readability and privacy.", "type": "string", "const": "handle" }, { "description": "Show a stable `xxh3:<16hex>` pseudonym derived from the raw email.\nNon-cryptographic; suitable to keep raw emails out of CI artifacts\n(SARIF, code-scanning uploads) but not as a security primitive --\na known list of org emails can be brute-forced into a rainbow table.\nUse in regulated environments where even local-parts are sensitive.", "type": "string", "const": "hash" } ] }, "RulesConfig": { "description": "Per-issue-type severity configuration.\n\nControls which issue types cause CI failure, are reported as warnings,\nor are suppressed entirely. Most fields default to `Severity::Error`.\n\nRule names use kebab-case in config files (e.g., `\"unused-files\": \"error\"`).", "type": "object", "properties": { "unused-files": { "$ref": "#/$defs/Severity", "default": "error" }, "unused-exports": { "$ref": "#/$defs/Severity", "default": "error" }, "unused-types": { "$ref": "#/$defs/Severity", "default": "error" }, "private-type-leaks": { "$ref": "#/$defs/Severity", "default": "off" }, "unused-dependencies": { "$ref": "#/$defs/Severity", "default": "error" }, "unused-dev-dependencies": { "$ref": "#/$defs/Severity", "default": "warn" }, "unused-optional-dependencies": { "$ref": "#/$defs/Severity", "default": "warn" }, "unused-enum-members": { "$ref": "#/$defs/Severity", "default": "error" }, "unused-class-members": { "$ref": "#/$defs/Severity", "default": "error" }, "unresolved-imports": { "$ref": "#/$defs/Severity", "default": "error" }, "unlisted-dependencies": { "$ref": "#/$defs/Severity", "default": "error" }, "duplicate-exports": { "$ref": "#/$defs/Severity", "default": "error" }, "type-only-dependencies": { "$ref": "#/$defs/Severity", "default": "warn" }, "test-only-dependencies": { "$ref": "#/$defs/Severity", "default": "warn" }, "circular-dependencies": { "$ref": "#/$defs/Severity", "default": "error" }, "boundary-violation": { "$ref": "#/$defs/Severity", "default": "error" }, "coverage-gaps": { "$ref": "#/$defs/Severity", "default": "error" }, "feature-flags": { "$ref": "#/$defs/Severity", "default": "off" }, "stale-suppressions": { "$ref": "#/$defs/Severity", "default": "warn" }, "unused-catalog-entries": { "$ref": "#/$defs/Severity", "default": "warn" }, "empty-catalog-groups": { "$ref": "#/$defs/Severity", "default": "warn" }, "unresolved-catalog-references": { "$ref": "#/$defs/Severity", "default": "error" }, "unused-dependency-overrides": { "$ref": "#/$defs/Severity", "default": "warn" }, "misconfigured-dependency-overrides": { "$ref": "#/$defs/Severity", "default": "error" } } }, "Severity": { "description": "Severity level for rules.\n\nControls whether an issue type causes CI failure (`error`), is reported\nwithout failing (`warn`), or is suppressed entirely (`off`).", "oneOf": [ { "description": "Report and fail CI (non-zero exit code).", "type": "string", "const": "error" }, { "description": "Report but don't fail CI.", "type": "string", "const": "warn" }, { "description": "Don't detect or report.", "type": "string", "const": "off" } ] }, "BoundaryConfig": { "description": "Architecture boundary configuration.\n\nDefines zones (directory groupings) and rules (which zones may import from which).\nOptionally uses a built-in preset as a starting point.\n\n# Examples\n\n```\nuse fallow_config::BoundaryConfig;\n\nlet json = r#\"{\n \"zones\": [\n { \"name\": \"ui\", \"patterns\": [\"src/components/**\"] },\n { \"name\": \"db\", \"patterns\": [\"src/db/**\"] }\n ],\n \"rules\": [\n { \"from\": \"ui\", \"allow\": [\"db\"] }\n ]\n}\"#;\nlet config: BoundaryConfig = serde_json::from_str(json).unwrap();\nassert_eq!(config.zones.len(), 2);\nassert_eq!(config.rules.len(), 1);\n```\n\nUsing a preset:\n\n```\nuse fallow_config::BoundaryConfig;\n\nlet json = r#\"{ \"preset\": \"layered\" }\"#;\nlet mut config: BoundaryConfig = serde_json::from_str(json).unwrap();\nconfig.expand(\"src\");\nassert_eq!(config.zones.len(), 4);\nassert_eq!(config.rules.len(), 4);\n```", "type": "object", "properties": { "preset": { "description": "Built-in architecture preset. When set, expands into default zones and rules.\nUser-defined zones and rules merge on top: zones with the same name replace\nthe preset zone; rules with the same `from` replace the preset rule.\nPreset patterns use `{rootDir}/{zone}/**` where rootDir is auto-detected\nfrom tsconfig.json (falls back to `src`).\nNote: preset patterns are flat (`src//**`). For monorepos with\nper-package source directories, define zones explicitly instead.", "anyOf": [ { "$ref": "#/$defs/BoundaryPreset" }, { "type": "null" } ] }, "zones": { "description": "Named zones mapping directory patterns to architectural layers.", "type": "array", "items": { "$ref": "#/$defs/BoundaryZone" }, "default": [] }, "rules": { "description": "Import rules between zones. A zone with a rule entry can only import\nfrom the listed zones (plus itself). A zone without a rule entry is unrestricted.", "type": "array", "items": { "$ref": "#/$defs/BoundaryRule" }, "default": [] } } }, "BoundaryPreset": { "description": "Built-in architecture presets.\n\nEach preset expands into a set of zones and import rules for a common\narchitecture pattern. User-defined zones and rules merge on top of the\npreset defaults (zones with the same name replace the preset zone;\nrules with the same `from` replace the preset rule).\n\n# Examples\n\n```\nuse fallow_config::BoundaryPreset;\n\nlet preset: BoundaryPreset = serde_json::from_str(r#\"\"layered\"\"#).unwrap();\nassert!(matches!(preset, BoundaryPreset::Layered));\n```", "oneOf": [ { "description": "Classic layered architecture: presentation → application → domain ← infrastructure.\nInfrastructure may also import from application (common in DI frameworks).", "type": "string", "const": "layered" }, { "description": "Hexagonal / ports-and-adapters: adapters → ports → domain.", "type": "string", "const": "hexagonal" }, { "description": "Feature-Sliced Design: app > pages > widgets > features > entities > shared.\nEach layer may only import from layers below it.", "type": "string", "const": "feature-sliced" }, { "description": "Bulletproof React: app → features → shared + server.\nFeature modules are isolated from each other via `autoDiscover`: every\nimmediate child of `src/features/` becomes its own `features/` zone,\nand cross-feature imports are reported as boundary violations.\nTop-level files in `src/features/` are classified by the logical\n`features` parent zone, so barrels can re-export child features while\nnon-barrel top-level files still obey the `features` boundary rule.", "type": "string", "const": "bulletproof" } ] }, "BoundaryZone": { "description": "A named zone grouping files by directory pattern.", "type": "object", "properties": { "name": { "description": "Zone identifier referenced in rules (e.g., `\"ui\"`, `\"database\"`, `\"shared\"`).", "type": "string" }, "patterns": { "description": "Glob patterns (relative to project root) that define zone membership.\nA file belongs to the first zone whose pattern matches.", "type": "array", "items": { "type": "string" } }, "autoDiscover": { "description": "Directories whose immediate child directories should become separate\nzones under this logical group.\n\nFor example, `{ \"name\": \"features\", \"autoDiscover\": [\"src/features\"] }`\ncreates zones such as `features/auth` and `features/billing`, each with\na pattern for its own subtree. Rules that reference `features` expand to\nevery discovered child zone. If `patterns` is also set, the parent zone\nremains as a fallback after discovered child zones.", "type": "array", "items": { "type": "string" } }, "root": { "description": "Optional subtree scope for monorepo per-package boundaries.\n\nWhen set, the zone's `patterns` are matched against paths *relative*\nto this directory rather than the project root. At classification\ntime, fallow checks that a candidate path starts with `root` and\nstrips that prefix before glob-matching the patterns against the\nremainder. Files outside the subtree never match the zone.\n\nUseful for monorepos where each package has the same internal\ndirectory layout: instead of writing `packages/app/src/**` and\n`packages/core/src/**` (which collide on shared zone names), set\n`root: \"packages/app/\"` and `patterns: [\"src/**\"]` per package.\n\nTrailing slash and leading `./` are normalized; backslashes are\nconverted to forward slashes. Patterns must NOT redundantly include\nthe root prefix: `root: \"packages/app/\"` with\n`patterns: [\"packages/app/src/**\"]` is rejected with\n`FALLOW-BOUNDARY-ROOT-REDUNDANT-PREFIX` because patterns are\nresolved relative to the root.", "type": [ "string", "null" ] } }, "required": [ "name" ] }, "BoundaryRule": { "description": "An import rule between zones.", "type": "object", "properties": { "from": { "description": "The zone this rule applies to (the importing side).", "type": "string" }, "allow": { "description": "Zones that `from` is allowed to import from. Self-imports are always allowed.\nAn empty list means the zone may not import from any other zone.", "type": "array", "items": { "type": "string" }, "default": [] }, "allowTypeOnly": { "description": "Zones that `from` may type-only-import from even when not listed in\n`allow`. Mirrors the `allow` shape: a list of target zone names. A\ntype-only import declaration (`import type {...}`, `import type * as ns`,\nor a per-specifier inline `type` qualifier on every named specifier) to a\nlisted zone is not reported as a boundary violation. Mixed-specifier\nimports (`import { type Foo, Bar }`) that carry at least one value\nsymbol still fire because the runtime dependency on `Bar` is real.\nType-only re-exports (`export type { Foo } from \"...\"`) participate\nin the same allowance because they surface as edges flagged\n`is_type_only: true` and, like type-only imports, are erased at\ncompile time.", "type": "array", "items": { "type": "string" } } }, "required": [ "from" ] }, "FlagsConfig": { "description": "Feature flag detection configuration.\n\nControls which patterns fallow uses to detect feature flags in source code.\nConfigured via the `flags` section in `.fallowrc.json`, `.fallowrc.jsonc`, `fallow.toml`, or `.fallow.toml`.\n\n# Examples\n\n```json\n{\n \"flags\": {\n \"sdkPatterns\": [\n { \"function\": \"useFlag\", \"nameArg\": 0, \"provider\": \"LaunchDarkly\" }\n ],\n \"envPrefixes\": [\"FEATURE_\", \"NEXT_PUBLIC_ENABLE_\"],\n \"configObjectHeuristics\": false\n }\n}\n```", "type": "object", "properties": { "sdkPatterns": { "description": "Additional SDK call patterns to detect as feature flags.\nThese are merged with the built-in patterns (LaunchDarkly, Statsig, Unleash, GrowthBook).", "type": "array", "items": { "$ref": "#/$defs/SdkPattern" } }, "envPrefixes": { "description": "Environment variable prefixes that indicate feature flags.\nMerged with built-in prefixes. Only `process.env.*` accesses matching\nthese prefixes are reported as feature flags.", "type": "array", "items": { "type": "string" } }, "configObjectHeuristics": { "description": "Enable config object heuristic detection.\nWhen true, property accesses on objects whose name contains \"feature\",\n\"flag\", or \"toggle\" are reported as low-confidence feature flags.\nDefault: false (opt-in due to higher false positive rate).", "type": "boolean", "default": false } } }, "SdkPattern": { "description": "A custom SDK call pattern for feature flag detection.\n\nDescribes a function call that evaluates a feature flag, e.g.,\n`useFlag('new-checkout')` or `client.getFeatureValue('parser', false)`.", "type": "object", "properties": { "function": { "description": "Function name to match (e.g., `\"useFlag\"`, `\"variation\"`).", "type": "string" }, "nameArg": { "description": "Zero-based index of the argument containing the flag name.", "type": "integer", "format": "uint", "minimum": 0, "default": 0 }, "provider": { "description": "Optional SDK/provider label shown in output (e.g., `\"LaunchDarkly\"`).", "type": [ "string", "null" ] } }, "required": [ "function" ] }, "ResolveConfig": { "description": "Module resolver configuration.\n\nControls how fallow resolves import specifiers against package.json\n`exports` / `imports` fields and tsconfig paths. Configured via the\n`resolve` section in `.fallowrc.json`, `.fallowrc.jsonc`, `fallow.toml`, or `.fallow.toml`.\n\n# Examples\n\n```json\n{\n \"resolve\": {\n \"conditions\": [\"development\", \"worker\"]\n }\n}\n```", "type": "object", "properties": { "conditions": { "description": "Additional export/import condition names to honor during module\nresolution. Merged with fallow's built-in conditions (`development`,\n`import`, `require`, `default`, `types`, `node`; plus `react-native`\nand `browser` when the React Native or Expo plugin is active).\n\nUser conditions are matched with higher priority than the baseline,\nso a package.json `exports` entry like:\n\n```json\n{ \"./api\": { \"worker\": \"./src/api.worker.ts\", \"import\": \"./dist/api.js\" } }\n```\n\nresolves to the `worker` branch when `\"worker\"` is listed here.\n\nSee \nfor the set of community-defined conditions.", "type": "array", "items": { "type": "string" } } } }, "ProductionConfig": { "description": "Production-mode defaults.", "anyOf": [ { "description": "Legacy/global form: `production = true` or `\"production\": true`.", "type": "boolean" }, { "description": "Per-analysis form.", "$ref": "#/$defs/PerAnalysisProductionConfig" } ] }, "PerAnalysisProductionConfig": { "description": "Per-analysis production-mode defaults.", "type": "object", "properties": { "deadCode": { "description": "Production mode for dead-code analysis.", "type": "boolean", "default": false }, "health": { "description": "Production mode for health analysis.", "type": "boolean", "default": false }, "dupes": { "description": "Production mode for duplication analysis.", "type": "boolean", "default": false } }, "additionalProperties": false }, "ConfigOverride": { "description": "Per-file override entry.", "type": "object", "properties": { "files": { "description": "Glob patterns to match files against (relative to config file location).", "type": "array", "items": { "type": "string" } }, "rules": { "description": "Partial rules — only specified fields override the base rules.", "$ref": "#/$defs/PartialRulesConfig", "default": {} } }, "required": [ "files" ] }, "PartialRulesConfig": { "description": "Partial per-issue-type severity for overrides. All fields optional.", "type": "object", "properties": { "unused-files": { "anyOf": [ { "$ref": "#/$defs/Severity" }, { "type": "null" } ] }, "unused-exports": { "anyOf": [ { "$ref": "#/$defs/Severity" }, { "type": "null" } ] }, "unused-types": { "anyOf": [ { "$ref": "#/$defs/Severity" }, { "type": "null" } ] }, "private-type-leaks": { "anyOf": [ { "$ref": "#/$defs/Severity" }, { "type": "null" } ] }, "unused-dependencies": { "anyOf": [ { "$ref": "#/$defs/Severity" }, { "type": "null" } ] }, "unused-dev-dependencies": { "anyOf": [ { "$ref": "#/$defs/Severity" }, { "type": "null" } ] }, "unused-optional-dependencies": { "anyOf": [ { "$ref": "#/$defs/Severity" }, { "type": "null" } ] }, "unused-enum-members": { "anyOf": [ { "$ref": "#/$defs/Severity" }, { "type": "null" } ] }, "unused-class-members": { "anyOf": [ { "$ref": "#/$defs/Severity" }, { "type": "null" } ] }, "unresolved-imports": { "anyOf": [ { "$ref": "#/$defs/Severity" }, { "type": "null" } ] }, "unlisted-dependencies": { "anyOf": [ { "$ref": "#/$defs/Severity" }, { "type": "null" } ] }, "duplicate-exports": { "anyOf": [ { "$ref": "#/$defs/Severity" }, { "type": "null" } ] }, "type-only-dependencies": { "anyOf": [ { "$ref": "#/$defs/Severity" }, { "type": "null" } ] }, "test-only-dependencies": { "anyOf": [ { "$ref": "#/$defs/Severity" }, { "type": "null" } ] }, "circular-dependencies": { "anyOf": [ { "$ref": "#/$defs/Severity" }, { "type": "null" } ] }, "boundary-violation": { "anyOf": [ { "$ref": "#/$defs/Severity" }, { "type": "null" } ] }, "coverage-gaps": { "anyOf": [ { "$ref": "#/$defs/Severity" }, { "type": "null" } ] }, "feature-flags": { "anyOf": [ { "$ref": "#/$defs/Severity" }, { "type": "null" } ] }, "stale-suppressions": { "anyOf": [ { "$ref": "#/$defs/Severity" }, { "type": "null" } ] }, "unused-catalog-entries": { "anyOf": [ { "$ref": "#/$defs/Severity" }, { "type": "null" } ] }, "empty-catalog-groups": { "anyOf": [ { "$ref": "#/$defs/Severity" }, { "type": "null" } ] }, "unresolved-catalog-references": { "anyOf": [ { "$ref": "#/$defs/Severity" }, { "type": "null" } ] }, "unused-dependency-overrides": { "anyOf": [ { "$ref": "#/$defs/Severity" }, { "type": "null" } ] }, "misconfigured-dependency-overrides": { "anyOf": [ { "$ref": "#/$defs/Severity" }, { "type": "null" } ] } } }, "RegressionConfig": { "description": "Regression baseline counts, embedded in the config file.\n\nWhen `--fail-on-regression` is used without `--regression-baseline `,\nfallow reads the baseline from this config section.\nWhen `--save-regression-baseline` is used without a path argument,\nfallow writes the baseline into the config file.", "type": "object", "properties": { "baseline": { "description": "Dead code issue counts baseline.", "anyOf": [ { "$ref": "#/$defs/RegressionBaseline" }, { "type": "null" } ] } } }, "RegressionBaseline": { "description": "Per-type issue counts for regression comparison.", "type": "object", "properties": { "totalIssues": { "type": "integer", "format": "uint", "minimum": 0, "default": 0 }, "unusedFiles": { "type": "integer", "format": "uint", "minimum": 0, "default": 0 }, "unusedExports": { "type": "integer", "format": "uint", "minimum": 0, "default": 0 }, "unusedTypes": { "type": "integer", "format": "uint", "minimum": 0, "default": 0 }, "unusedDependencies": { "type": "integer", "format": "uint", "minimum": 0, "default": 0 }, "unusedDevDependencies": { "type": "integer", "format": "uint", "minimum": 0, "default": 0 }, "unusedOptionalDependencies": { "type": "integer", "format": "uint", "minimum": 0, "default": 0 }, "unusedEnumMembers": { "type": "integer", "format": "uint", "minimum": 0, "default": 0 }, "unusedClassMembers": { "type": "integer", "format": "uint", "minimum": 0, "default": 0 }, "unresolvedImports": { "type": "integer", "format": "uint", "minimum": 0, "default": 0 }, "unlistedDependencies": { "type": "integer", "format": "uint", "minimum": 0, "default": 0 }, "duplicateExports": { "type": "integer", "format": "uint", "minimum": 0, "default": 0 }, "circularDependencies": { "type": "integer", "format": "uint", "minimum": 0, "default": 0 }, "typeOnlyDependencies": { "type": "integer", "format": "uint", "minimum": 0, "default": 0 }, "testOnlyDependencies": { "type": "integer", "format": "uint", "minimum": 0, "default": 0 }, "boundaryViolations": { "type": "integer", "format": "uint", "minimum": 0, "default": 0 } } }, "AuditConfig": { "description": "Per-analysis baseline paths for the `audit` command.\n\nEach field points to a baseline file produced by the corresponding\nsubcommand (`fallow dead-code --save-baseline`, `fallow health --save-baseline`,\n`fallow dupes --save-baseline`). `audit` passes each baseline through to its\nunderlying analysis; baseline-matched issues are excluded from the verdict.", "type": "object", "properties": { "gate": { "description": "Which findings should make `fallow audit` fail.", "$ref": "#/$defs/AuditGate" }, "deadCodeBaseline": { "description": "Path to the dead-code baseline (produced by `fallow dead-code --save-baseline`).", "type": [ "string", "null" ] }, "healthBaseline": { "description": "Path to the health baseline (produced by `fallow health --save-baseline`).", "type": [ "string", "null" ] }, "dupesBaseline": { "description": "Path to the duplication baseline (produced by `fallow dupes --save-baseline`).", "type": [ "string", "null" ] } } }, "AuditGate": { "description": "Gating mode for `fallow audit`.", "oneOf": [ { "description": "Only findings introduced by the current changeset affect the verdict.", "type": "string", "const": "new-only" }, { "description": "All findings in changed files affect the verdict.", "type": "string", "const": "all" } ] } } }