Skip to main content
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:
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]:
roots = ["src/my_package"]
tests = ["tests"]
tooling = ["scripts"]
select = ["SF"]

[thresholds]
max_positional_args = 1
[tool.strata]
roots = ["src/my_package"]
tests = ["tests"]
tooling = ["scripts"]
select = ["SF"]

[tool.strata.thresholds]
max_positional_args = 1
strata map does not read configuration. It resolves import roots on its own so it can run on any repository.

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

KeyTypeDefaultPurpose
rootslist of stringsrequiredProduct code scopes. No nesting.
testslist of strings["tests"]Test scopes.
toolinglist of strings[]Dev tooling scopes.
selectlist of selectors["SF"]Rules to enable.
ignorelist of selectors[]Rule codes to disable.
rule_pathslist of strings[]Directories of custom rule files to load.
rule_moduleslist of strings[]Importable module names holding custom rules.
thresholdstableshipped defaultsGlobal threshold overrides.
rolestable of tablesnonePer-role threshold overrides.
contractstablevalidate_*, 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:
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:
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:
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 for the list and defaults):
[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).
[thresholds]
max_statements = 40

[roles.entry]
max_statements = 30
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.

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:
[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:
[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.

Custom rule loading

rule_paths and rule_modules tell Strata where to find custom rules.
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, and strata skills once configured. See Custom rules for the authoring API.

The self-hosted example

Strata checks itself with this configuration:
roots = ["src/strata"]
tests = ["tests"]
tooling = []
select = ["SF"]

[thresholds]
max_positional_args = 1