Skip to main content
Fensu is configured with a fensu.toml file or a [tool.fensu] table in pyproject.toml. Configuration declares the scopes Fensu scans, the rules it runs, and the thresholds and contracts it enforces.

Where config comes from

fensu check, fensu rule, and fensu skills load configuration by searching upward from the current directory. In each directory, Fensu looks for:
  1. fensu.toml
  2. a pyproject.toml containing a [tool.fensu] table
The nearest directory wins, and within a directory fensu.toml takes precedence over pyproject.toml. The first source found is used whole; sources are never merged. If neither exists anywhere up the tree, Fensu reports:
A standalone fensu.toml holds keys at the top level. In pyproject.toml, the same keys live under [tool.fensu]:
fensu map can use configured roots when they are available, but it does not require configuration and ignores [evaluation] targeting.

Validation is strict

Fensu fails loudly on a bad config rather than silently ignoring it, because a silently dropped key is how a typo quietly disables a rule.
  • An unknown top-level key is rejected: Unknown config key(s): ....
  • roots is required and must contain at least one non-empty path.
  • roots entries must not be nested or overlapping.
  • Every configured root path must exist on disk.
  • A path may not belong to two scopes, and two scopes may not claim the same import package name (for example a roots entry and a tooling entry both ending in tools/).
  • Selectors in select, warn, and ignore must be well formed.
  • A resolved rule may not be both blocking and warning, or both warning and ignored.
  • Threshold keys must be known threshold names with non-negative integer values.
  • Role names under roles must be known role names.
  • Each threshold_overrides entry must define exactly paths, thresholds, and a non-empty reason, with repository-relative POSIX glob paths.
  • Each rule_exceptions entry must name one exact rule, one exact Python file, and a non-empty reason. An optional symbols list must contain one or more qualified functions when present.
  • [evaluation] accepts only include and exclude; when present, each is a non-empty list of repository-relative POSIX globs.
  • [skills] accepts only an optional non-empty name.
  • Contract behaviors must be recognized.

Top-level keys

Selection

select, warn, and ignore assign rules to policy tiers. A selector is a rule-code prefix, the same way ruff accepts prefixes and full codes, and any prefix of a valid code works: The core family letters are FFL, FFR, FFS, FFN, FFH, FFT, and FFA; see Rule families. Custom selectors follow custom codes: X plus optional uppercase letters plus optional digits, so namespaces nest by prefix (see Custom rules).

The default

select defaults to ["FF"], which turns on every family. If you want all of Fensu’s opinion, set nothing: the default already does it.

Run only some families

Set select to the families you want. This replaces the default, so anything not listed is off:

Run only specific rules

List individual codes to run a hand-picked set and nothing else:

Turn individual rules off

Keep the broad select and remove the few you do not want with ignore. ignore always wins over select:

The warn tier

warn is a separate, advisory ruleset. Its rules are evaluated only with fensu check --warn, are reported after faults, and never affect the exit code:
A rule cannot resolve into both select and warn, or both warn and ignore. Fensu compares resolved rule codes, so select = ["FF"] with warn = ["FFA"] is rejected as an overlap.

How it composes

  • A rule runs if it is enabled by default and matches a selector in select, or if it is named by its exact code in select.
  • A rule named in ignore never runs, whatever select says.
  • A rule in warn is separate from the blocking set and runs only with --warn.
  • Selecting a family does not pull in that family’s opt-in (default-off) rules. Only naming such a rule by its exact code turns it on (for example select = ["FF", "FFS102"]).

Exact rule exceptions

Use [[rule_exceptions]] for an unavoidable finding in one exact file. Include symbols to narrow the exception to named functions or methods, such as an external callback signature:
Omit symbols only for an ownerless file-level finding:
Omitting symbols does not suppress findings owned by functions in that file. If symbols is present, it must be non-empty; symbols = [] is rejected rather than silently broadening the exception to file scope. The rule, repository-relative Python file, and any qualified symbols are exact: globs, directories, line numbers, and rule prefixes are not accepted. The path and symbols must exist, and fensu check fails if either form becomes stale and suppresses no fault.

Evaluation targeting

The [evaluation] table narrows which discovered Python files receive direct rule execution. It is intended for gradual adoption across a large repository:
Both keys are optional, producing four cases: Exclude always wins. Patterns are repository-relative POSIX globs: * stays within one path component and ** crosses directories. Every include pattern must match a discovered Python file; unmatched excludes are allowed. A stale include or a configuration leaving zero targets fails instead of reporting a false green. Evaluation targeting does not shrink discovery or project context. Files not selected for direct evaluation remain available for module and ownership resolution, cross-file and directory queries, cache invalidation, and fensu map. An included importer can therefore still fault for reaching into an excluded file, and structural queries from included files still see excluded siblings. fensu check reports target and discovery counts when filtering is configured.

Choosing the right boundary

These controls solve different problems: Use ignore when a rule is wrong globally, rule_exceptions for isolated file-level or named-symbol findings, and evaluation targeting as the broad migration boundary for whole regions.

Thresholds

The [thresholds] table overrides the shipped numeric defaults globally. Every key must be a known threshold name (see Architecture model for the list and defaults):
This includes the container-layout limits max_helpers_container_modules, max_main_container_modules, and max_role_depth that govern flat-or-grouped containers. The two min_ thresholds use 0 to disable their rule.

Per-role overrides

The [roles.<role>] tables override thresholds for one role only, layered on top of the global values. Role names are the fixed Fensu roles (for example main, helpers, classes, models, types, constants, exceptions, entry).
roles tables tune thresholds per role. They do not define new roles; the role set is fixed. User-defined roles are a planned future addition.

Path-scoped threshold overrides

[[threshold_overrides]] entries raise or lower thresholds for particular paths, with a required reason. Each entry defines exactly three keys:
  • paths is a non-empty list of repository-relative POSIX globs (* for one segment, ** for any depth; no absolute paths, ./.. parts, or character classes). Patterns are matched against the fault’s path.
  • thresholds is a non-empty inline table of known threshold keys with non-negative integer values.
  • reason is required and must be non-empty.
When several entries match a path, the most specific pattern wins; among equally specific patterns, the later entry wins. A matching path override beats the [roles] and [thresholds] values. fensu check reports each applied override, with its pattern and reason, in the run summary.

Contracts

The [contracts] table maps a function-name glob to one of four behaviors enforced by the naming family: Fensu ships these contracts by default:
Add more globs for your project’s conventions:
Contracts use closed behavior categories rather than per-name lists, which keeps the configuration from decaying into an unmanageable list of special cases. If a function matches patterns with different behaviors, evaluation fails with a configuration error naming the function, file, and conflicting patterns. For a behavior outside these four categories, write a custom rule.

Custom rule loading

rule_paths and rule_modules tell Fensu where to find custom rules.
  • rule_paths entries are directories (or files) of .py modules containing @rule-decorated functions. Each file is loaded under an isolated synthetic module name so it cannot pollute your import namespace.
  • rule_modules entries are importable dotted module names, loaded with a normal import. Their Python source must be inside the repository so diagnostics, test coverage, and cache identity have a stable owner.
Loaded custom rules merge with the core rules into one catalogue, so they appear in fensu check, fensu rule, and fensu skills once configured. See Custom rules for the authoring API.

Cache

The [cache] table ([tool.fensu.cache] in pyproject.toml) controls the persistent result cache used by fensu check. It takes two boolean keys:
The --cache and --no-cache flags on fensu check override these keys for one run. See Custom rules for what require_cacheable demands of your rule files.

Skills

The [skills] table sets the generated project identity used in paths such as .agents/skills/fensu-acme-api/SKILL.md:
When omitted, Fensu uses the nearest [project].name from pyproject.toml, then the configuration directory name. See fensu skills for normalization, installation roots, and freshness checks.

The self-hosted example

Fensu checks itself with this configuration: