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

# Configuration

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

Strata is configured with a `strata.toml` file or a `[tool.strata]` table in
`pyproject.toml`. Configuration declares the scopes Strata scans, the rules it
runs, and the thresholds and contracts it enforces.

## Where config comes from

`strata check`, `strata rule`, and `strata skills` load configuration by searching
upward from the current directory. In each directory, Strata looks for:

1. `strata.toml`
2. a `pyproject.toml` containing a `[tool.strata]` table

The **nearest directory** wins, and within a directory `strata.toml` takes
precedence over `pyproject.toml`. The first source found is used whole; sources are
never merged. If neither exists anywhere up the tree, Strata reports:

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

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

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

  [thresholds]
  max_positional_args = 1
  ```

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

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

<Note>
  [`strata map`](/cli/map) does not read configuration. It resolves import roots on
  its own so it can run on any repository.
</Note>

## Validation is strict

Strata 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.
* Selectors in `select` and `ignore` must be well formed.
* Threshold keys must be known threshold names with non-negative integer values.
* Role names under `roles` must be known role names.
* 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 | `["SF"]`                  | Rules to enable.                              |
| `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.                   |
| `roles`        | table of tables   | none                      | Per-role threshold overrides.                 |
| `contracts`    | table             | `validate_*`, `enforce_*` | Name-glob to behavior contracts.              |

## Selection

`select` and `ignore` control which rules run. Two kinds of values go in each list,
the same way ruff accepts prefixes and full codes:

* A **family selector** matches a whole family: `SF` (all families), or one of
  `SFL`, `SFR`, `SFS`, `SFN`, `SFX`, `SFT`, `SFA`, and `X` for custom rules.
* A **rule code** matches one rule: `SFS120`, `XAC001`.

### The default

`select` defaults to `["SF"]`, which turns on every family. If you want all of
Strata'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 = ["SFL", "SFR", "SFS", "SFN"]   # 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 = ["SFL001", "SFL101", "SFS120"]
```

### 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 = ["SF"]          # everything...
ignore = ["SFX007", "SFA103"]   # ...except these two
```

### 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.
* 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 = ["SF", "SFS102"]`).

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

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

## Contracts

The `[contracts]` table maps a function-name glob to a behavior that the naming
family enforces. Today the one supported behavior is `no-return`, meaning a
function matching the glob must not return a meaningful value.

Strata ships these contracts by default:

```toml theme={null}
[contracts]
"validate_*" = "no-return"
"enforce_*" = "no-return"
```

Add more globs for your project's conventions, for example a writer or callback
that should not return a value:

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

Contracts use closed behavior categories rather than per-name lists, which keeps
the configuration from decaying into an unmanageable list of special cases. For a
behavior other than `no-return`, write a [custom rule](/concepts/custom-rules).

## Custom rule loading

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

```toml theme={null}
tooling = ["scripts"]
rule_paths = ["scripts/strata_rules/rules"]
rule_modules = ["company_architecture.rules"]
select = ["SF", "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.

Loaded custom rules merge with the core rules into one catalogue, so they appear in
`strata check`, [`strata rule`](/cli/rule), and [`strata skills`](/cli/skills) once
configured. See [Custom rules](/concepts/custom-rules) for the authoring API.

## The self-hosted example

Strata checks itself with this configuration:

```toml theme={null}
roots = ["src/strata"]
tests = ["tests"]
tooling = []
select = ["SF"]

[thresholds]
max_positional_args = 1
```
