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:
fensu.toml- a
pyproject.tomlcontaining a[tool.fensu]table
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:
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.Named analyzer targets
The flat form above remains the concise configuration for one Python project. Use explicit targets for multiple projects or a TypeScript/Svelte analyzer:analyzer and roots. root defaults to . and relocates
that target’s paths relative to the configuration directory. Explicit targets
cannot be mixed with legacy flat keys. fensu check evaluates every configured
target unless --target NAME selects one.
The registered analyzers are python, typescript, and svelte. Web-only keys
include test_layout, ui_kit, shadcn, openapi, and generated; the normal
policy, threshold, exception, evaluation, cache, and scope keys live inside each
target too. The activated rule pack, rather than a separate framework setting,
controls SvelteKit policy. See
TypeScript and SvelteKit for detection,
setup, and current boundaries.
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): .... - A flat config requires
roots; an explicit config requires one or more named targets, each withanalyzerandroots. - Explicit
targetscannot be mixed with legacy top-level policy keys. rootsentries 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
rootsentry and atoolingentry both ending intools/). - Selectors in
select,warn, andignoremust be well formed. - Native
rule_packsnames must be registered and unique. - 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
rolesmust be known role names. - Each
threshold_overridesentry must define exactlypaths,thresholds, and a non-emptyreason, with repository-relative POSIX glob paths. - Each
rule_exceptionsentry must name one exact rule, one exact source file for its analyzer, and a non-emptyreason. An optionalsymbolslist must contain one or more qualified functions or methods when present. - Each
rule_ignoresentry must define non-emptyrulesselectors, non-empty repository-relative POSIX globpaths, and a non-emptyreason. [evaluation]accepts onlyincludeandexclude; when present, each is a non-empty list of repository-relative POSIX globs.[skills]accepts only an optional non-emptyname.- 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 Python core family letters are
FFL, FFR, FFS, FFN, FFH, FFT, and FFA;
see Rule families. FP is reserved for activated
native rule packs. Custom selectors follow custom codes:
X plus optional uppercase letters plus optional digits, so namespaces nest by
prefix (see Custom rules). Web
analyzers use the activated FPTS or FPSK catalogue described in
TypeScript and SvelteKit.
The default
select defaults to ["FF"] for Python and ["FP"] for TypeScript or Svelte.
For web targets, FP selects every activated pack. A web pack must be named in
rule_packs before its selectors become available; generated SvelteKit targets
activate sveltekit and select FPSK explicitly.
Run only some families
Setselect 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 broadselect 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:
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 inselect. - A rule named in
ignorenever runs, whateverselectsays. - A rule in
warnis 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"]). - Activating a native pack makes its catalogue available but does not select it.
Select the pack namespace separately, for example
rule_packs = ["dagster"]withselect = ["FPDG"].
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:
symbols only for an ownerless file-level finding:
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 analyzer source 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.
Path-scoped rule ignores
Use[[rule_ignores]] when selected rules should remain active globally but their
findings are accepted under explicit path patterns:
--warn findings.
Unlike exact rule_exceptions, path-scoped ignores are not stale-checked. Exact
exceptions are applied and validated first, so a broad ignore cannot conceal a
stale exact waiver.
Evaluation targeting
The[evaluation] table narrows which discovered source files receive direct rule
execution. It is intended for gradual adoption across a large repository:
Exclude always wins. Patterns are repository-relative POSIX globs:
* stays
within one path component and ** crosses directories. Every include pattern
must match a discovered source 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 exact
findings, rule_ignores for an accepted rule/path intersection, and evaluation
targeting when matching files should receive no direct rule execution.
Test scopes
Every test lives under a scope directory directly beneath a test root, andtest_scopes lists the names allowed there. The default is:
tests/uint/ is a typo rather than a new scope,
and FFT002 reports it instead of leaving that tree unchecked. Mirroring is
unchanged for a configured scope, so tests/real/src/my_package/config/main/ mirrors
src/my_package/config/main/ the same way tests/unit/ does.
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):
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:
pathsis 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.thresholdsis a non-empty inline table of known threshold keys with non-negative integer values.reasonis required and must be non-empty.
[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:
Native rule packs
rule_packs activates Fensu-shipped Rust policies by registered name. Fensu
currently ships dagster, typescript, and sveltekit packs:
FPSK.
Custom rule loading
rule_paths and rule_modules tell Fensu where to find custom rules.
rule_pathsentries are directories (or files) of.pymodules containing@rule-decorated functions. Each file is loaded under an isolated synthetic module name so it cannot pollute your import namespace.rule_modulesentries 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.
fensu check, fensu rule, and
fensu skills once configured. See
Custom rules for the authoring API.
Rule options
Rules may declare typed options with defaults or required values. Repositories write only overrides, keyed by exact rule code:Cache
The[cache] table ([tool.fensu.cache] in pyproject.toml) controls the
persistent result cache used by fensu check. It takes two
boolean keys:
--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:
[project].name from pyproject.toml, then
the configuration directory name. See fensu skills for
normalization, installation roots, and freshness checks.

