Skip to main content

Architecture linting for Python repositories

Project description

Strata

Keeping Python repos from turning into spaghetti.

Most linters catch bad code inside files. Strata catches architectural drift: code crossing the wrong boundary, living in the wrong module, or growing into the wrong shape.

As a repository grows, code moves, lessons get forgotten, and the mental map decays. Tests preserve behavior and types preserve interfaces. Strata makes the repository's architectural expectations executable.

Strata enforces:

  • which layers may import which;
  • what each module or role file may contain;
  • whether orchestrator functions stay small;
  • whether dataflow and mutation are explicit;
  • whether names such as validate_* mean what they claim.

It ships a coherent default architecture rather than a blank rule framework, then lets projects disable, extend, or replace parts deliberately.

Strata is functional and self-hosting, but remains pre-release.

Installation

pip install stratalint

The distribution name is stratalint; the installed command is strata.

Strata requires Python 3.12+ and includes a compiled analysis core. Prebuilt wheels cover Linux (x86_64, aarch64) and macOS (Intel, Apple silicon); on any other platform, pip builds from source, which requires a Rust toolchain. Native Windows is not yet verified — use WSL, where the Linux wheels work as-is.

Quick Start

Detect the repository layout, choose a starting ruleset, and write a validated configuration:

strata init

For non-interactive setup, use strata init --yes. To configure manually instead, add strata.toml at the repository root:

roots = ["src/my_package"]
tests = ["tests"]
tooling = ["scripts"]

[cache]
enabled = true

Then run:

strata check

All rule families are enabled by default. Product roots and tooling receive structural rules; tests receive test-convention and annotation rules.

Default Structure

Product code uses domain, optional subdomain, then role. Every leaf domain or subdomain owns meaningful behavior through a direct main/ containing at least one entry module. Branch-domain parents do not need their own main/; their leaf subdomains do. Tests mirror the code they cover; tooling uses one ownership level because scripts/ already establishes the outer boundary.

src/my_package/
└── domain/
    └── subdomain/
        ├── main/
        │   └── run.py
        ├── _helpers/
        ├── classes/
        ├── models.py
        ├── types.py
        ├── constants.py
        └── exceptions.py
tests/unit/src/my_package/domain/subdomain/
├── _test_types.py
└── test_run.py
scripts/
├── run_tool.py
└── tool_name/
    ├── main/
    ├── _helpers/
    └── classes/

Do not create an empty or initializer-only main/ to satisfy the layout. If a package contains only passive models, types, constants, exceptions, or classes, move those declarations into the closest domain or subdomain whose main/ behavior owns and uses them. Fixed role files are siblings of _helpers/, never descendants such as _helpers/entry/models.py.

Direct scripts/*.py files are thin command adapters. Supporting logic belongs under scripts/<tool>/<role>/.

Core Commands

strata init
strata check
strata rule SFS131
strata map run_plan --depth 3

strata init detects and validates an onboarding configuration, strata check enforces the configured architecture, strata rule explains one rule and its remediation, and strata map renders a conservative downstream call tree. Mapping follows project functions and class methods when imports, annotations, constructors, or return types prove the receiver. Calls through protocols and untyped parameters remain visible as unresolved dispatch seams rather than guessed implementations. Mapping does not require Strata configuration or rule adoption.

strata check stores disposable evaluation results in a repository-local SQLite database under .strata/cache/ and reuses them only after validating source, configuration, rule, implementation, and project-query inputs. Caching is enabled by default; set cache.enabled = false in configuration or pass --no-cache for an explicit uncached check. --cache overrides a disabled project preference for one invocation. Deleting .strata/cache/ is always safe; ignore that directory rather than the complete .strata/ namespace, which is reserved for other Strata-owned state.

Enforce It, Then See It

Because Strata enforces the structure, it can also render it. strata map produces a deterministic downstream call tree with clickable path:line locations, class-qualified method names, and explicit protocol seams while marking unresolved dynamic calls, depth limits, and cycles.

$ strata map run_map --depth 4

run_map(...)  src/strata/cli/main/map.py:21
├── _parser(...)  src/strata/cli/main/map.py:53
├── resolve_mapping_project(...)  src/strata/mapping/main/resolve_project.py:11
│   └── resolve_mapping_project(...)  src/strata/mapping/_helpers/project.py:15
│       ├── _find_project_root(...)  src/strata/mapping/_helpers/project.py:73
│       ├── _explicit_source(...)  src/strata/mapping/_helpers/project.py:65
│       ├── _optional_config_source(...)  src/strata/mapping/_helpers/project.py:38
│       │   └── find_config_source(...)  src/strata/config/main/find_config.py:12  (depth limit)
│       └── _configured_project(...)  src/strata/mapping/_helpers/project.py:45
│           ├── load_config(...)  src/strata/config/main/load_config.py:15  (depth limit)
│           └── _configured_source(...)  src/strata/mapping/_helpers/project.py:57
└── build_call_map(...)  src/strata/mapping/main/build.py:12
    ├── provider(...)  src/strata/mapping/main/build.py:24  (unresolved parameter call)
    └── render_tree(...)  src/strata/mapping/_helpers/render.py:19
        ├── _child_lines(...)  src/strata/mapping/_helpers/render.py:41
        │   └── _child_lines(...)  src/strata/mapping/_helpers/render.py:41  (cycle)
        └── _label(...)  src/strata/mapping/_helpers/render.py:88

The map is useful precisely because it is not guessing. strata check enforces layers, roles, and public surfaces first, and strata map then renders the structure the code is required to expose.

Philosophy

Strata is strict by default wherever it can make an honest deterministic claim. Following the rules should remove repeated architectural decisions from everyday work. Deliberate differences belong in selection, configuration, or custom rules, where they remain visible, rather than in scattered inline suppressions.

Unavoidable external calling conventions can use exact symbol-scoped exceptions:

[[rule_exceptions]]
rule = "SFS120"
path = "src/my_package/integrations/_helpers/callbacks.py"
symbols = ["ProgressCollector.update"]
reason = "The external API invokes this callback positionally."

Exceptions accept one exact rule code, repository-relative Python file, and one or more qualified symbols. Globs, directories, line numbers, path-only entries, and inline suppression comments are not supported. strata check rejects stale exceptions that no longer suppress a fault.

Agent Skills

Generate repository-aware guidance from the active ruleset:

strata skills
strata skills --global

The generated skill includes Strata usage, rule-supported architecture examples, navigation and work-handoff guidance, and every enabled core and custom rule. Existing user-authored skill files are preserved unless --force is supplied.

Custom Rules

Custom checks use X... codes and the same RuleContext as core rules. Once configured, they participate in strata check, strata rule, and generated agent skills. Rules can use ctx.facts, ctx.project, ctx.text, ctx.syntax, and ctx.relations; these are the same backend-neutral analysis zones used by Strata's built-in rules. Project and filesystem reads made through ctx.project are tracked for cache invalidation. Raw ast.Module access remains available for checks that need unrestricted Python syntax traversal. Semantic fact contracts and author-facing models are public Python APIs, while their production extraction has one native Rust owner; raw AST, syntax, and relation artifacts remain lazy CPython capabilities. Keep project-owned checks in the canonical scripts/strata/rules/ tooling role and load them explicitly:

tooling = ["scripts"]
rule_paths = ["scripts/strata/rules"]

See the custom-rule guide for the complete API and configuration.

Documentation

The quickstart, architecture model, configuration reference, adoption guide, and CLI reference live in the Strata documentation repository.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

stratalint-0.20.0.tar.gz (426.9 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

stratalint-0.20.0-cp312-abi3-win_amd64.whl (3.7 MB view details)

Uploaded CPython 3.12+Windows x86-64

stratalint-0.20.0-cp312-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (4.1 MB view details)

Uploaded CPython 3.12+manylinux: glibc 2.17+ x86-64

stratalint-0.20.0-cp312-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (4.1 MB view details)

Uploaded CPython 3.12+manylinux: glibc 2.17+ ARM64

stratalint-0.20.0-cp312-abi3-macosx_11_0_arm64.whl (3.8 MB view details)

Uploaded CPython 3.12+macOS 11.0+ ARM64

stratalint-0.20.0-cp312-abi3-macosx_10_12_x86_64.whl (3.9 MB view details)

Uploaded CPython 3.12+macOS 10.12+ x86-64

File details

Details for the file stratalint-0.20.0.tar.gz.

File metadata

  • Download URL: stratalint-0.20.0.tar.gz
  • Upload date:
  • Size: 426.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for stratalint-0.20.0.tar.gz
Algorithm Hash digest
SHA256 bef345e69b3a2fd99b92b7936bde0157b3d01e8ebe9daa77993bbff7e881a30a
MD5 0a1eb601a9fefd5c54f75e40c6675fc1
BLAKE2b-256 0598ea8e002c9adc01b440685ce28eaef3f20a74b421e812c16225a7f0165249

See more details on using hashes here.

Provenance

The following attestation bundles were made for stratalint-0.20.0.tar.gz:

Publisher: publish.yml on chio-labs/strata

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file stratalint-0.20.0-cp312-abi3-win_amd64.whl.

File metadata

  • Download URL: stratalint-0.20.0-cp312-abi3-win_amd64.whl
  • Upload date:
  • Size: 3.7 MB
  • Tags: CPython 3.12+, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for stratalint-0.20.0-cp312-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 27f4e587163b14b46ac6f30e23528597d776058e95d7301e9e64b375b4d3e71e
MD5 801a35bc30f71cedc9716c698882a92a
BLAKE2b-256 e1d2dbafce391b4f353d7606757aa66af194f62058e10d3a7df3f227c8d3962a

See more details on using hashes here.

Provenance

The following attestation bundles were made for stratalint-0.20.0-cp312-abi3-win_amd64.whl:

Publisher: publish.yml on chio-labs/strata

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file stratalint-0.20.0-cp312-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for stratalint-0.20.0-cp312-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 543463e1990e1e3a9ffabd742cb727ddeeb95508d711b9228a82d6525c54ab72
MD5 d8ddbb73dc419e9b5b8c0e6c3bfdcf28
BLAKE2b-256 24782664e76a9a21b2a71cdfac483d08cddcfdf178c910d122f1aa58c4216d4f

See more details on using hashes here.

Provenance

The following attestation bundles were made for stratalint-0.20.0-cp312-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: publish.yml on chio-labs/strata

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file stratalint-0.20.0-cp312-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for stratalint-0.20.0-cp312-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 98ae014748ccfc461221b3e9af4a1dad4435c94a18589a499aeb9fbe16172ea6
MD5 5009ce6bc9fd27faf08e113fff03df16
BLAKE2b-256 c9b5ed835bda5df974ec1ed2061bec2117ecdf672c4ba38276b7ffb01f2e5355

See more details on using hashes here.

Provenance

The following attestation bundles were made for stratalint-0.20.0-cp312-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:

Publisher: publish.yml on chio-labs/strata

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file stratalint-0.20.0-cp312-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for stratalint-0.20.0-cp312-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 4c1e94a956254c887fe66355e8a7a2d75b642b7988728a3f192ec56b8e1ee7a8
MD5 a40fe56d1c4843745df77adbc09adeb1
BLAKE2b-256 1f04a07f17cc5d612014c9bd1aa47ac4da5ea6ba290975f880bba631a22d624b

See more details on using hashes here.

Provenance

The following attestation bundles were made for stratalint-0.20.0-cp312-abi3-macosx_11_0_arm64.whl:

Publisher: publish.yml on chio-labs/strata

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file stratalint-0.20.0-cp312-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for stratalint-0.20.0-cp312-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 e48cc320ed7a3e8f0aa43da3993eac5103945d9c8c49b6e62a953dfcb7e13dd8
MD5 a0198fc79ef4e5b40b1ae0a3732964cd
BLAKE2b-256 553bb979baaddb4802b01bf3b578a21ebc4e505ecb5e0d868c26961f341a90de

See more details on using hashes here.

Provenance

The following attestation bundles were made for stratalint-0.20.0-cp312-abi3-macosx_10_12_x86_64.whl:

Publisher: publish.yml on chio-labs/strata

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page