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

# Rule Families

> The seven core rule families Strata ships, what each enforces, and how rule codes are structured.

Every Strata rule belongs to a **family**. A family groups rules by subject, gives
you a single selector to turn the whole group on or off, and carries a letter in
each rule's code. Strata ships seven core families and reserves the `X` namespace
for [custom rules](/concepts/custom-rules).

## Rule codes

A core rule code is `SF` + a family letter + a number, for example `SFL001` or
`SFS131`.

* `SF` marks a Strata fault and is collision-free with other linters' prefixes.
* The third letter is the family.
* The number is a stable address within the family. The hundreds digit groups a
  sub-theme (for example `SFS0xx` are size caps, `SFS1xx` are dataflow), and the
  rule's **slug** carries the precise meaning.

Codes are permanent. A removed or renamed rule's code is retired forever and never
reused, and existing rules are never renumbered. Custom rules must use the `X`
namespace and are rejected at load if they start with `SF`.

## The families

| Selector | Family      | Subject                                                             |
| -------- | ----------- | ------------------------------------------------------------------- |
| `SFL`    | Layers      | Imports, boundaries, dependency direction.                          |
| `SFR`    | Roles       | Module taxonomy: what a file may contain and where it lives.        |
| `SFS`    | Shape       | Function size caps and dataflow discipline.                         |
| `SFN`    | Naming      | Name-to-behavior contracts.                                         |
| `SFX`    | Hygiene     | Clarity and smell: docstrings, comments, raises, decision literals. |
| `SFT`    | Tests       | Test-suite layout, naming, and parametrization conventions.         |
| `SFA`    | Annotations | Type-annotation completeness.                                       |

Which families apply depends on the file's [scope](/concepts/architecture-model#scopes):
product roots and tooling get the structural families (SFL, SFR, SFS, SFN, SFX,
SFA), while the tests scope gets SFT and SFA.

## SFL: Layers

The headline family, and the anti-spaghetti core. Layers rules govern which
modules and packages may import which, so boundaries stay intact.

| Code     | Slug                                      | Checks                                                                       |
| -------- | ----------------------------------------- | ---------------------------------------------------------------------------- |
| `SFL001` | absolute-imports-only                     | Relative imports hide package boundaries.                                    |
| `SFL002` | no-star-imports                           | Star imports hide names from boundary analysis.                              |
| `SFL101` | no-sibling-package-internals              | Do not reach into a sibling's internals; import its published surface.       |
| `SFL102` | no-cross-package-internals                | Import only via a package's public surface.                                  |
| `SFL103` | no-internal-public-surface-imports        | Internal code imports concrete owning modules, not the bare package surface. |
| `SFL110` | no-cross-file-use-of-helper-private-class | A `_`-prefixed class in `helpers/` is file-only.                             |
| `SFL301` | no-runtime-imports-from-tooling           | Runtime code must not import from the tooling scope.                         |

## SFR: Roles

The largest family, and the clearest separation from tools that only look at the
import graph: SFR looks *inside* modules. It enforces what each role file may
contain, where declarations may live, package layout, entry-module shape, and file
size.

Representative rules:

| Code     | Slug                             | Checks                                                                                                                                                                                                 |
| -------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `SFR001` | models-only-models               | `models.py` holds only model declarations.                                                                                                                                                             |
| `SFR101` | model-declaration-outside-models | A model declared outside `models.py`.                                                                                                                                                                  |
| `SFR204` | banned-generic-package-name      | No `shared`, `common`, `util`, `misc`, and similar in domain positions.                                                                                                                                |
| `SFR301` | helpers-package-layout           | `helpers/` stays flat until a subfolder is warranted.                                                                                                                                                  |
| `SFR401` | entry-module-shape               | An entry file defines one public function and at most two private ones (a limit on functions in the file, not on what they may call). See [entry modules](/concepts/architecture-model#entry-modules). |
| `SFR402` | init-module-empty                | `__init__.py` is docstring-only.                                                                                                                                                                       |
| `SFR501` | classes-one-class-per-module     | One class per module in `classes/`.                                                                                                                                                                    |
| `SFR601` | source-file-line-count           | A file must not exceed the line-count threshold.                                                                                                                                                       |
| `SFR701` | tooling-entrypoint-shape         | A direct tooling script is a thin command adapter.                                                                                                                                                     |
| `SFR704` | rules-role-content               | A `rules/` module holds only imports and `@rule` functions.                                                                                                                                            |

SFR spans role-file content (`SFR0xx`), misplaced declarations (`SFR1xx`), naming
(`SFR2xx`), package layout (`SFR3xx`), surface shape (`SFR4xx`), class and helper
shape (`SFR5xx`), file size (`SFR6xx`), and tooling layout (`SFR7xx`).

## SFS: Shape

Shape rules keep functions small and their dataflow explicit. This is where the
LLM-sprawl countermeasures live.

| Code     | Slug                                | Checks                                                          |
| -------- | ----------------------------------- | --------------------------------------------------------------- |
| `SFS001` | too-many-statements                 | Statement cap on `main/` orchestrator functions.                |
| `SFS002` | too-many-distinct-calls             | Distinct-callee cap on `main/` functions.                       |
| `SFS003` | too-many-locals                     | Assigned-local cap on `main/` functions.                        |
| `SFS010` | max-arguments                       | Global argument-count cap on any function.                      |
| `SFS011` | max-statements-global               | Loose global statement floor on any function.                   |
| `SFS101` | meaningful-project-result-discarded | A meaningful returned value must be used, not silently dropped. |
| `SFS110` | default-mutation-return             | A function that mutates a parameter must return it.             |
| `SFS120` | keyword-only-arguments              | Parameters past the positional threshold must be keyword-only.  |
| `SFS130` | no-outer-state-mutation             | No rebinding or mutation of module globals or closure captures. |
| `SFS131` | no-complex-comprehensions           | One generator per comprehension, no nested comprehension.       |
| `SFS201` | mutable-result-model                | `models.py` dataclasses must be `frozen=True`.                  |

`SFS102` (parameter-mutation-in-phase-helpers) is the one core rule that is **opt-in**:
it ships disabled and runs only when named explicitly by code. It is the stricter
input-immutability stance for teams adopting the full phase model, layered on top
of the default `SFS110`.

## SFN: Naming

Naming rules enforce that a name means what it claims. This is the "dishonest
functions" family.

| Code     | Slug                      | Checks                                                                     |
| -------- | ------------------------- | -------------------------------------------------------------------------- |
| `SFN001` | validator-must-not-return | A `validate_*` or `enforce_*` function must not return a meaningful value. |

`SFN001` checks exactly one thing: a validator raises or passes, so returning a
value means it is really a query and should be named `is_*` or `get_*`. Extend the
name-to-behavior contracts to more prefixes with the
[`contracts`](/concepts/configuration#contracts) table.

## SFX: Hygiene

Hygiene rules target clarity and smells that hide meaning.

| Code     | Slug                                 | Checks                                                                   |
| -------- | ------------------------------------ | ------------------------------------------------------------------------ |
| `SFX001` | single-line-docstrings               | Docstrings are a single line.                                            |
| `SFX002` | no-standalone-comments               | No load-bearing standalone comments.                                     |
| `SFX003` | no-raw-builtin-raise                 | Raise structured errors, not bare built-ins.                             |
| `SFX004` | no-assert-in-runtime                 | No `assert` in runtime code.                                             |
| `SFX005` | no-swallowed-exception-probe         | No `except` that silently swallows.                                      |
| `SFX006` | no-complex-comprehensions-in-tooling | The comprehension readability limit, in tooling.                         |
| `SFX007` | no-unnamed-string-decisions          | String literals must not directly control comparisons.                   |
| `SFX008` | no-magic-numeric-comparisons         | Numeric literals other than `-1`, `0`, `1` must not control comparisons. |

`SFX007` and `SFX008` push decisions to compare against named concepts (enum
members or named constants) instead of unexplained literals. They report the
literal expression and do not infer runtime values.

## SFT: Tests

Test rules make a test suite readable and consistent. They apply only in the tests
scope. The conventions include a mirrored layout, given-when-then naming, and
dataclass-backed parametrization.

Representative rules:

| Code     | Slug                   | Checks                                                             |
| -------- | ---------------------- | ------------------------------------------------------------------ |
| `SFT006` | test-file-name         | Test files start with `test_`.                                     |
| `SFT007` | test-function-name     | `test_given_<state>_when_<action>_then_<expectation>`.             |
| `SFT008` | dataclass-parametrize  | Tests use `@pytest.mark.parametrize` with a dataclass-backed case. |
| `SFT025` | description-lambda-ids | Parametrize ids come from `lambda case: case.description`.         |
| `SFT026` | local-test-types-file  | Each test module has a sibling `_test_types.py`.                   |
| `SFT028` | test-layout            | Test directories mirror the source tree.                           |
| `SFT036` | no-if-in-tests         | No conditional control flow inside a test function.                |

SFT groups layout and mirroring (`SFT0xx`), module hygiene (`SFT1xx`), test-role
files (`SFT2xx`), test file and function shape (`SFT3xx`), and dataclass-backed
parametrization (`SFT4xx`).

## SFA: Annotations

Annotation rules require types to be explicit, so they are in the text the reader
(and a type checker, and an LLM) can see. SFA runs over both the product and test
scopes.

| Code     | Slug                       | Checks                                          |
| -------- | -------------------------- | ----------------------------------------------- |
| `SFA001` | parameter-annotation       | Every function parameter is annotated.          |
| `SFA002` | return-annotation          | Every function has a return annotation.         |
| `SFA101` | module-variable-annotation | Module-level variables are annotated.           |
| `SFA102` | class-attribute-annotation | Class attributes are annotated.                 |
| `SFA103` | local-variable-annotation  | Local variables are annotated on first binding. |

All five are on by default, including annotate-every-local. See
[Philosophy](/philosophy#compensating-for-llm-blind-spots) for why this stricter
stance is deliberate.

## Inspecting rules

Look up any rule's full metadata, including its message and remediation, with
[`strata rule`](/cli/rule):

```bash theme={null}
strata rule SFS131
```

Generate agent-facing guidance from your active rules with
[`strata skills`](/cli/skills).
