Skip to main content
Every Fensu 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. Fensu ships seven core families and reserves the X namespace for custom rules.

Rule codes

A core rule code is FF + a family letter + exactly three digits, for example FFL001 or FFS131.
  • FF marks a Fensu 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 FFS0xx are size caps, FFS1xx are dataflow), and the rule’s slug carries the precise meaning.
Because codes are structured, any prefix is a valid selector: FF matches every core rule, FFS a family, FFS1 a hundreds bucket, FFS120 one rule. 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 (X + optional uppercase letters + digits, like XDB001) and are rejected at load if they start with FF. See Custom rules for how teams carve X sub-namespaces.

The families

Which families apply depends on the file’s scope: product roots and tooling get the structural families (FFL, FFR, FFS, FFN, FFH, FFA), while the tests scope gets FFT and FFA.

FFL: Layers

Layers rules govern which modules and packages may import which, so boundaries stay intact. This is the family most directly aimed at keeping a codebase from tangling. For FFL101 and FFL102, ownership is role-boundary-relative: a module’s owner is everything before its first role segment. Imports within one owner are free (main/ to its own _helpers/, between modules of one _helpers/ tree including its buckets, into the owner’s classes/). Imports across owners must enter through the owner’s public surface: its role files or role packages (models, types, constants, exceptions, classes) or its main/ entry modules. Another owner’s _helpers/ internals always fault. If an importable path has no role segment, Fensu infers its containing package as the owner (or the package itself for an __init__.py); a direct non-role module is internal, not a public surface.

FFR: Roles

The largest family, and the clearest separation from tools that only look at the import graph: FFR 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: FFR spans role-file content (FFR0xx), misplaced declarations (FFR1xx), naming (FFR2xx), package layout (FFR3xx), surface shape (FFR4xx), class and helper shape (FFR5xx), file size (FFR6xx), and tooling layout (FFR7xx).

FFS: Shape

Shape rules keep functions small and their dataflow explicit. Several of them target the way generated code tends to sprawl. FFS102 (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 FFS110.

FFN: Naming

Naming rules enforce that a name means what it claims. This is the “dishonest functions” family. Each diagnostic offers two safe directions: honor the name or preserve the behavior and rename it. For example, an eager iter_items() -> list[Item] can return an iterator or become collect_items. The shipped patterns and behaviors are configured through contracts, where projects can add their own name globs without changing these rule codes.

FFH: Hygiene

Hygiene rules target clarity and smells that hide meaning. FFH007 and FFH008 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. FFH009 was previously numbered FFR206; import-time discipline is hygiene, not module taxonomy.

FFT: 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: FFT groups layout and mirroring (FFT0xx), module hygiene (FFT1xx), test-role files (FFT2xx), test file and function naming (FFT3xx), and dataclass-backed parametrization (FFT4xx). FFT104 applies to every test function, not only parametrized tests, and to top-level functions, async functions, and classes in helpers.py and _test_helpers.py. It detects if, match, conditional expressions, and filtered simple comprehensions. It does not currently treat while or try as conditionals. Use parametrization when setup and assertions stay branch-free. Otherwise split the behavior into separate tests or helper functions.

FFA: 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. FFA runs over both the product and test scopes. All five are on by default. FFA103 exempts direct scalar literal bindings whose type is already explicit in the value, including strings, bytes, booleans, numeric literals, signed numeric literals, and f-strings:
Collections, constructor and function-call results, comprehensions, attribute reads, and other inferred values still require an annotation on first binding:
See Philosophy for why explicit annotations remain the default for those less obvious values.

Inspecting rules

Look up any rule’s full metadata, including its message and remediation, with fensu rule:
Generate agent-facing guidance from your active rules with fensu skills.