> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fensu.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Configuration

> Every configuration key Fensu reads, how it discovers config, and how to override thresholds and contracts.

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:

```text theme={null}
No fensu config found; create fensu.toml or [tool.fensu].
```

A standalone `fensu.toml` holds keys at the top level. In `pyproject.toml`, the
same keys live under `[tool.fensu]`:

<CodeGroup>
  ```toml fensu.toml theme={null}
  roots = ["src/my_package"]
  tests = ["tests"]
  tooling = ["scripts"]
  select = ["FF"]

  [thresholds]
  max_positional_args = 1
  ```

  ```toml pyproject.toml theme={null}
  [tool.fensu]
  roots = ["src/my_package"]
  tests = ["tests"]
  tooling = ["scripts"]
  select = ["FF"]

  [tool.fensu.thresholds]
  max_positional_args = 1
  ```
</CodeGroup>

<Note>
  [`fensu map`](/cli/map) can use configured roots when they are available, but it
  does not require configuration and ignores `[evaluation]` targeting.
</Note>

## 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

| Key                   | Type              | Default                       | Purpose                                                                   |
| --------------------- | ----------------- | ----------------------------- | ------------------------------------------------------------------------- |
| `roots`               | list of strings   | required                      | Product code scopes. No nesting.                                          |
| `tests`               | list of strings   | `["tests"]`                   | Test scopes.                                                              |
| `tooling`             | list of strings   | `[]`                          | Dev tooling scopes.                                                       |
| `select`              | list of selectors | `["FF"]`                      | Blocking rules to enable.                                                 |
| `warn`                | list of selectors | `[]`                          | Advisory rules evaluated by `fensu check --warn`.                         |
| `ignore`              | list of selectors | `[]`                          | Rule codes to disable.                                                    |
| `rule_paths`          | list of strings   | `[]`                          | Directories of custom rule files to load.                                 |
| `rule_modules`        | list of strings   | `[]`                          | Importable module names holding custom rules.                             |
| `thresholds`          | table             | shipped defaults              | Global threshold overrides.                                               |
| `threshold_overrides` | array of tables   | `[]`                          | Path-scoped threshold overrides with reasons.                             |
| `rule_exceptions`     | array of tables   | `[]`                          | Exact rule/file exceptions, optionally narrowed to symbols, with reasons. |
| `evaluation`          | table             | evaluate all discovered files | Direct rule-evaluation include/exclude globs.                             |
| `cache`               | table             | caching on                    | Result-cache settings for `fensu check`.                                  |
| `skills`              | table             | inferred identity             | Generated agent-skill settings.                                           |
| `roles`               | table of tables   | none                          | Per-role threshold overrides.                                             |
| `contracts`           | table             | ten shipped patterns          | Name-glob to behavior contracts.                                          |

## 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:

| Selector | Matches                                                       |
| -------- | ------------------------------------------------------------- |
| `FF`     | Every core rule.                                              |
| `FFR`    | One core family (here: roles).                                |
| `FFR3`   | One hundreds bucket (here: the `FFR3xx` layout rules).        |
| `FFR301` | Exactly one rule.                                             |
| `X`      | Every custom rule.                                            |
| `XDB`    | A custom namespace (every rule whose code starts with `XDB`). |
| `XDB001` | Exactly one custom rule.                                      |

The core family letters are `FFL`, `FFR`, `FFS`, `FFN`, `FFH`, `FFT`, and `FFA`;
see [Rule families](/concepts/rule-families). Custom selectors follow custom
codes: `X` plus optional uppercase letters plus optional digits, so namespaces
nest by prefix (see [Custom rules](/concepts/custom-rules#codes-and-namespaces)).

### 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:

```toml theme={null}
select = ["FFL", "FFR", "FFS", "FFN"]   # architecture only: no hygiene, tests, or annotations
```

### Run only specific rules

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

```toml theme={null}
select = ["FFL001", "FFL101", "FFS120"]
```

### Turn individual rules off

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

```toml theme={null}
select = ["FF"]          # everything...
ignore = ["FFH007", "FFA103"]   # ...except these two
```

### The warn tier

`warn` is a separate, advisory ruleset. Its rules are evaluated only with
[`fensu check --warn`](/cli/check#warnings), are reported after faults, and never
affect the exit code:

```toml theme={null}
select = ["FFL", "FFR", "FFS", "FFN", "FFH", "FFT"]
warn = ["FFA"]
```

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:

```toml theme={null}
[[rule_exceptions]]
rule = "FFS120"
path = "src/my_package/integrations/_helpers/callbacks.py"
symbols = ["ProgressCollector.update"]
reason = "The external API invokes this callback positionally."
```

Omit `symbols` only for an ownerless file-level finding:

```toml theme={null}
[[rule_exceptions]]
rule = "FFR307"
path = "src/my_package/integrations/compatibility.py"
reason = "This file remains a direct module while the external integration is supported."
```

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:

```toml theme={null}
[evaluation]
include = [
  "src/my_package/compiler/**",
  "tests/unit/src/my_package/compiler/**",
]
exclude = [
  "src/my_package/compiler/legacy/**",
  "**/generated/**",
]
```

Both keys are optional, producing four cases:

| Configuration           | Direct evaluation targets                      |
| ----------------------- | ---------------------------------------------- |
| Neither key             | Every discovered Python file.                  |
| `exclude` only          | Every discovered file except excluded matches. |
| `include` only          | Only included matches.                         |
| `include` and `exclude` | Included matches minus excluded matches.       |

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`](/cli/check#output) reports target and discovery counts
when filtering is configured.

### Choosing the right boundary

These controls solve different problems:

| Control                     | Effect                                                                                   |
| --------------------------- | ---------------------------------------------------------------------------------------- |
| `roots`, `tests`, `tooling` | Define the complete discovered project universe and each file's scope.                   |
| `warn` selectors            | Report matching rules as nonblocking advice with `fensu check --warn`.                   |
| `ignore` selectors          | Disable matching rules globally.                                                         |
| `rule_exceptions`           | Suppress an exact file-level or rule/file/symbol finding and fail when it becomes stale. |
| `evaluation.include`        | Make a discovered subset the direct evaluation target.                                   |
| `evaluation.exclude`        | Run no direct rules for matching discovered files.                                       |

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](/concepts/architecture-model#default-thresholds)
for the list and defaults):

```toml theme={null}
[thresholds]
max_statements = 50
max_positional_args = 1
max_file_lines = 1500
max_helpers_container_modules = 12
min_shared_domain_prefix_packages = 3
min_custom_rule_test_cases = 2
```

This includes the container-layout limits `max_helpers_container_modules`,
`max_main_container_modules`, and `max_role_depth` that govern
[flat-or-grouped containers](/concepts/architecture-model#containers-flat-or-grouped).
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`).

```toml theme={null}
[thresholds]
max_statements = 40

[roles.entry]
max_statements = 30
```

<Note>
  `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.
</Note>

### Path-scoped threshold overrides

`[[threshold_overrides]]` entries raise or lower thresholds for particular paths,
with a required reason. Each entry defines exactly three keys:

```toml theme={null}
[[threshold_overrides]]
paths = ["src/my_package/reporting/**"]
thresholds = { max_file_lines = 3000 }
reason = "Generated report tables exceed the default line budget."
```

* `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:

| Behavior           | Rule     | Requirement                                                          |
| ------------------ | -------- | -------------------------------------------------------------------- |
| `no-return`        | `FFN001` | Do not return a meaningful value.                                    |
| `returns-bool`     | `FFN002` | Declare `bool`, `TypeGuard`, or `TypeIs`.                            |
| `returns-value`    | `FFN003` | Do not explicitly declare a no-value result such as `None`.          |
| `returns-iterator` | `FFN004` | Contain an owned `yield` or declare an iterator or generator result. |

Fensu ships these contracts by default:

```toml theme={null}
[contracts]
"validate_*" = "no-return"
"enforce_*" = "no-return"
"is_*" = "returns-bool"
"has_*" = "returns-bool"
"can_*" = "returns-bool"
"supports_*" = "returns-bool"
"get_*" = "returns-value"
"to_*" = "returns-value"
"as_*" = "returns-value"
"iter_*" = "returns-iterator"
```

Add more globs for your project's conventions:

```toml theme={null}
[contracts]
"write_*" = "no-return"
"yield_*" = "returns-iterator"
```

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](/concepts/custom-rules).

## Custom rule loading

`rule_paths` and `rule_modules` tell Fensu where to find custom rules.

```toml theme={null}
tooling = ["scripts"]
rule_paths = ["scripts/fensu_policy/rules"]
rule_modules = ["company_architecture.rules"]
select = ["FF", "XAC001"]
```

* `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`](/cli/rule), and [`fensu skills`](/cli/skills) once
configured. See [Custom rules](/concepts/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`](/cli/check#caching). It takes two
boolean keys:

| Key                 | Type    | Default | Purpose                                                                                      |
| ------------------- | ------- | ------- | -------------------------------------------------------------------------------------------- |
| `enabled`           | boolean | `true`  | Persist and reuse per-file results between runs.                                             |
| `require_cacheable` | boolean | `false` | Verify evaluated custom rules use only tracked inputs so their results can be cached safely. |

```toml theme={null}
[cache]
enabled = true
require_cacheable = true
```

The `--cache` and `--no-cache` flags on `fensu check` override these keys for one
run. See [Custom rules](/concepts/custom-rules#keeping-custom-rules-cacheable) 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`:

```toml theme={null}
[skills]
name = "acme-api"
```

When omitted, Fensu uses the nearest `[project].name` from `pyproject.toml`, then
the configuration directory name. See [`fensu skills`](/cli/skills) for
normalization, installation roots, and freshness checks.

## The self-hosted example

Fensu checks itself with this configuration:

```toml theme={null}
roots = ["src/fensu"]
tests = ["tests"]
tooling = ["scripts"]
select = ["FF"]

[thresholds]
max_positional_args = 1
```
