X namespace
for custom rules.
Rule codes
A core rule code isSF + a family letter + a number, for example SFL001 or
SFS131.
SFmarks 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
SFS0xxare size caps,SFS1xxare dataflow), and the rule’s slug carries the precise meaning.
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. |
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. |
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. |
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 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. |
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. |
Inspecting rules
Look up any rule’s full metadata, including its message and remediation, withstrata rule:
strata skills.
