Przejdź do treści

Inwards: Reguły architektury dla Pythona, na tyle szybkie, że można je uruchamiać po każdej edycji agenta AI.

Deklarujesz warstwy w pyproject.toml. Gdy import wskazuje w złą stronę, inwards check podaje linię i mówi agentowi, jak to naprawić.

Zacznij Przeglądaj reguły

shop/domain/order.py1 dodana linia
3from __future__ import annotations45from dataclasses import dataclass6from shop.infrastructure.in_memory_orders import InMemoryOrderRepository
$ uv run inwards check shop/domain/order.py
shop/domain/order.py:6:50: INW001 Layer "domain" imports "shop.infrastructure.in_memory_orders.InMemoryOrderRepository" from outer layer "infrastructure". Allowed direction: domain <- application <- infrastructure <- presentation <- bootstrap.
  fix: Depend on an abstraction owned by "domain" instead of "shop.infrastructure.in_memory_orders.InMemoryOrderRepository".
    1. Delete `from shop.infrastructure.in_memory_orders import InMemoryOrderRepository`. Do not move the import into a function or behind TYPE_CHECKING; Inwards checks those too.
    2. Declare a typing.Protocol in `shop.domain` (for example `shop.domain.ports`) that describes only what this module needs from `InMemoryOrderRepository`.
    3. Type this module against that Protocol and receive the implementation through a constructor or function parameter.
    4. Make the class in "infrastructure" satisfy the Protocol, and wire it in the outermost layer (the composition root).
  docs: https://sircypkowskyy.github.io/inwards/rules/INW001/

Found 1 violation in 1 file (63.7 ms).
Prawdziwe uruchomienie Inwards 0.4.0 na przykładowym pakiecie ze scaffoldu z jednym dodanym błędnym importem, 2026-09-29. Kod wyjścia 1.

Twoja architektura jako reguły

Warstwy trafiają do pyproject.toml, od najbardziej wewnętrznej. INW001 pilnuje kierunku; każda diagnostyka ma kroki naprawy.

[tool.inwards]
root = "."
layers = [
  { name = "domain", modules = ["shop.domain"] },
  { name = "application", modules = ["shop.application"] },
  { name = "infrastructure", modules = ["shop.infrastructure"] },
  { name = "presentation", modules = ["shop.presentation"] },
  { name = "bootstrap", modules = ["shop.bootstrap"] },
]

Wbudowane w pętlę agenta

Sprawdzenie po każdej edycji, Stop gate nad całą sesją (także edycje przez Bash), config guard na reguły i eskalacja do ciebie po escalate-after próbach.

naruszenie + kroki naprawy czysto Agent edytuje plik Hook uruchamia inwards check Agent pracuje dalej

Łapie to, co robią agenci

Zmyślone moduły, importy za TYPE_CHECKING, importy dynamiczne. Wyciszenie wymaga powodu (INW009); poluzowanie konfiguracji zostaje odrzucone.

$ uv run inwards check shop/domain/dodge.py --format concise
shop/domain/dodge.py:8:33: INW010 "shop.domain.pricing" is not a module of this project: "shop.domain" has no "pricing". fix: Check the name. The closest modules in "shop.domain": `shop.domain.order`.
shop/domain/dodge.py:11:54: INW001 Layer "domain" imports "shop.infrastructure.in_memory_orders.InMemoryOrderRepository" from outer layer "infrastructure". Allowed direction: domain <- application <- infrastructure <- presentation <- bootstrap. fix: Delete `from shop.infrastructure.in_memory_orders import InMemoryOrderRepository`. Do not move the import into a function or behind TYPE_CHECKING; Inwards checks those too.
shop/domain/dodge.py:15:12: INW011 Layer "domain" imports "shop.infrastructure.in_memory_orders" from outer layer "infrastructure" through a dynamic import (importlib.import_module). Allowed direction: domain <- application <- infrastructure <- presentation <- bootstrap. fix: Delete `importlib.import_module("shop.infrastructure.in_memory_orders")`. A dynamic import is still a dependency: building the module name at runtime or moving it to another loader hides it instead of removing it.
Found 3 violations in 1 file (109.4 ms).

Na tyle szybkie, by działać po każdej edycji

Sprawdzenie jednego pliku, które uruchamia hook, mieści się w budżecie 100 ms dla p95, nawet dla pliku z 4482 liniami.

Plik p95, ze 100 ms
saleor, jeden plik 30 ms
Przykładowy pakiet ze scaffoldu 39.5 ms
polar, plik z 4482 liniami 90 ms
inwards check <file> ze startem procesu, jeden wątek, 2026-09-26.

Zimne pełne sprawdzenie, jeden rdzeń: 496 000 linii w 0,4 s, 848 000 linii saleora w 1,8 s. Przeoczenia prescanu: 0 w 6543 plikach.

Szybki start

  1. Zainstaluj albo pobierz plik binarny.

    uv add --dev inwards
    
    pip install inwards
    
  2. Dodaj hooki Claude Code. Nowy projekt? Dodaj --style clean --scaffold.

    uv run inwards init --agent claude
    
    inwards init --agent claude
    
  3. Sprawdź projekt.

    uv run inwards check
    
    inwards check
    

Inni agenci: OpenCode, Aider, AGENTS.md.

Pre-alpha. Najnowsze wydanie: 0.7.0 na PyPI, 2026-10-11. Reguły: INW000–INW021, FAPI001–FAPI003 i FAPI005–FAPI009.