Reguły¶
Każda diagnostyka Inwards linkuje do strony swojej reguły w tej sekcji: linia docs: w wyjściu tekstowym, pole docs w wyjściu JSON, helpUri w SARIF i link przy kodzie w edytorze. Każda strona mówi, co reguła zgłasza, dlaczego ma to znaczenie, gdy kod pisze agent AI, pokazuje przykład zgłoszony i poprawiony, opisuje, jak naprawić diagnostykę i jak skonfigurować regułę, oraz czego reguła jeszcze nie wyłapuje.
inwards rule INW001 wypisuje stronę reguły w terminalu, bez sieci, z pliku binarnego, którego używasz, a inwards rules pokazuje, które reguły włącza twoja konfiguracja, z jakim poziomem i dlaczego (konfiguracja).
Przeszukaj reguły, przefiltruj je według kategorii, statusu albo poprawki automatycznej, posortuj według kodu, nazwy, kategorii albo statusu i przeglądaj je stronami. Pasek adresu zapamiętuje widok, więc link otwiera tę samą listę. Kategoria obejmuje swoje podkategorie: imports pokazuje też reguły z imports › layers.
Reguły dzielą się na sześć kategorii, opisanych niżej w tej kolejności: importy, układ, role, integralność, diagramy i FastAPI. Każda kategoria ma też własną stronę, a nawigacja pokazuje pod nią jej reguły: importy, układ, role, integralność, diagramy i FastAPI. Reguła jest albo domyślnie włączona, albo opt-in. Presety inwards init --style zostawiają każdą domyślnie włączoną regułę, tylko django i fastapi wyłączają INW002, i włączają niektóre reguły opt-in jako ostrzeżenia: INW014, INW015 i INW020 w clean i hexagonal, INW012, INW013, INW016 i reguły FAPI w fastapi.
Importy¶
Co moduł może importować: warstwy wskazują do środka, konteksty importują się nawzajem tylko tak, jak to zadeklarowano, i tylko przez swoje moduły publiczne, bez cykli, bez biblioteki, której zabrania konfiguracja warstwy, bez własnego modułu, który nie istnieje, bez importu dynamicznego, który ukrywa któreś z tych naruszeń, i bez sięgania do modułu prywatnego spoza jego pakietu. Każda reguła z tej kategorii jest domyślnie włączona poza INW021, która jest opt-in.
| Kod | Nazwa | Co zgłasza | Włączona | Poziom | Włącza ją preset | Wyciszenie w linii |
|---|---|---|---|---|---|---|
| INW001 | layer-dependency |
Import z warstwy wewnętrznej do zewnętrznej | domyślnie | błąd | wszystkie | tak |
| INW002 | context-independence |
Import z jednego kontekstu ograniczonego do innego, którego nie deklaruje jego depends-on |
domyślnie | błąd | wszystkie oprócz django, fastapi | tak |
| INW003 | public-api-only |
Import niepublicznego modułu kontekstu spoza tego kontekstu | domyślnie | błąd | wszystkie | tak |
| INW004 | import-cycles |
Moduły albo konteksty ograniczone, które importują się nawzajem w cyklu (przy sprawdzaniu całego projektu) | domyślnie | błąd | wszystkie | nie |
| INW005 | pure-domain |
Import biblioteki, na który konfiguracja warstwy nie pozwala, np. SQLAlchemy w domenie | domyślnie | błąd | wszystkie | tak |
| INW010 | unknown-first-party |
Import własnego modułu, który nie istnieje | domyślnie | błąd | wszystkie | tak |
| INW011 | dynamic-import |
Import dynamiczny, który sięga do warstwy zewnętrznej albo którego celu Inwards nie umie odczytać | domyślnie | błąd | wszystkie | tak |
| INW021 | private-module |
Import modułu, którego nazwa zaczyna się od _, spoza pakietu, który go zawiera |
opt-in | błąd | vertical-slices | tak |
Układ¶
Gdzie leży kod: każdy własny moduł w jakiejś warstwie i każdy pakiet w kształcie, który nadaje mu konfiguracja. Domyślnie włączone.
| Kod | Nazwa | Co zgłasza | Włączona | Poziom | Włącza ją preset | Wyciszenie w linii |
|---|---|---|---|---|---|---|
| INW006 | unassigned-module |
Własny kod poza wszystkimi warstwami i prefiksy warstw, do których nie pasuje żaden moduł | domyślnie | błąd, część diagnostyk to ostrzeżenia | wszystkie | tak |
| INW007 | package-shape |
Element pakietu, na który jego kształt nie pozwala, albo nazwa poza pakietami, do których należy | domyślnie | błąd, część diagnostyk to ostrzeżenia | wszystkie | nie |
| INW008 | missing-member |
Brakuje elementu, którego wymaga kształt pakietu | domyślnie | błąd | wszystkie | nie |
Role¶
Co robi kod w modułach danej roli: endpointy przekazują pracę dalej, w async def nie ma blokujących wywołań, porty są abstrakcyjne, adaptery deklarują swój port, jest jeden korzeń kompozycji, nazwy ORM trzymają konwencję. Te reguły oceniają kod według wyborów zespołu, więc są opt-in; szablon może włączyć regułę dla roli, a niektóre presety też je włączają.
| Kod | Nazwa | Co zgłasza | Włączona | Poziom | Włącza ją preset | Wyciszenie w linii |
|---|---|---|---|---|---|---|
| INW012 | thin-endpoint |
Endpoint HTTP, który sam wykonuje pracę: za dużo instrukcji, gałęzi albo pętli, własne wywołania bazy danych albo HTTP, albo brak wywołania warstwy, którą wskazuje delegate-to |
opt-in | ostrzeżenie | fastapi | tak |
| INW013 | async-blocking |
Synchroniczne wywołanie bazy danych, cache'u albo chmury wewnątrz async def: synchroniczny Session SQLAlchemy, redis.Redis, boto3 albo blokujący sterownik |
opt-in | błąd | fastapi | tak |
| INW014 | ports-abstract |
Klasa w module portu, która nie jest ABC ani Protocol, albo metoda portu, której ciało wykonuje pracę | opt-in | błąd | clean, hexagonal | tak |
| INW015 | construct-only-in |
Moduł spoza korzenia kompozycji, który w czasie działania importuje adapter wyjściowy albo inną chronioną rolę lub buduje jedną z jej klas | opt-in | błąd | clean, hexagonal | tak |
| INW016 | orm-naming |
Nazwa tabeli, która nie jest w lower_case_snake i w liczbie pojedynczej, albo kolumna daty i czasu bez _at lub _date; uruchomienie dla całego projektu wymaga też konwencji nazw w MetaData |
opt-in | błąd | fastapi | tak |
| INW020 | adapter-conformance |
Publiczna klasa w module adaptera bez linii _: Port = Adapter(...) pod if TYPE_CHECKING:, taka linia wykonywana przy imporcie albo linia, której adnotacja nie nazywa żadnego portu |
opt-in | błąd | clean, hexagonal | tak |
Integralność¶
Reguły, które pilnują uczciwości samego sprawdzenia: plik, którego zadeklarowane kodowanie może ukryć import, i wyciszenie w linii, które niczego nie ukrywa. Domyślnie włączone; INW000 nie da się wyłączyć.
| Kod | Nazwa | Co zgłasza | Włączona | Poziom | Włącza ją preset | Wyciszenie w linii |
|---|---|---|---|---|---|---|
| INW000 | unsupported-encoding |
Zadeklarowane kodowanie źródła, w którym komentarz może być prawdziwym importem | domyślnie | błąd | wszystkie | nie |
| INW009 | suppression-comment |
Wyciszenie w linii, które niczego nie ukrywa albo nie pasuje do żadnej diagnostyki | domyślnie | błąd, nieużywane to ostrzeżenia | wszystkie | nie |
Diagramy¶
Diagramy architektury w dokumentacji, sprawdzane względem [tool.inwards] (ADR-045). Opt-in i jako ostrzeżenia, żeby rozjazd dokumentacji nie blokował agenta.
| Kod | Nazwa | Co zgłasza | Włączona | Poziom | Włącza ją preset | Wyciszenie w linii |
|---|---|---|---|---|---|---|
| INW017 | diagram-unknown-name |
Węzeł w oznaczonym diagramie architektury w Mermaid albo PlantUML, który nie nazywa żadnej zadeklarowanej warstwy ani kontekstu, albo którego etykieta w cudzysłowie (moduł aliasu w PlantUML) nie pasuje do żadnego modułu | opt-in | ostrzeżenie | żaden | nie |
| INW018 | diagram-forbidden-edge |
Ciągła strzałka w oznaczonym diagramie architektury w Mermaid albo PlantUML, która rysuje import zabroniony przez konfigurację: na zewnątrz albo między warstwami sąsiednimi, między kontekstami bez depends-on albo do modułu, który nie jest public |
opt-in | ostrzeżenie | żaden | nie |
| INW019 | diagram-missing-dependency |
Import w kodzie między dwiema warstwami albo kontekstami, które rysuje oznaczony diagram architektury w Mermaid albo PlantUML, bez strzałki (dla warstw: bez ścieżki strzałek), która by go rysowała (sprawdzenie całego projektu) | opt-in | ostrzeżenie | żaden | nie |
Reguły FastAPI¶
Rodzina FAPI sprawdza aplikacje FastAPI między plikami: który router aplikacja dołącza, jakie kody błędów deklaruje schemat OpenAPI (ADR-037). Każda reguła FAPI jest opt-in i żadna nie zgłasza tego, co już zgłaszają reguły FAST Ruffa. W nowym projekcie inwards init --style fastapi włącza je wszystkie jako ostrzeżenia.
| Kod | Nazwa | Co zgłasza | Włączona | Poziom | Włącza ją preset | Wyciszenie w linii |
|---|---|---|---|---|---|---|
| FAPI001 | endpoint-metadata |
Operacja ścieżki bez metadanych OpenAPI, których wymaga projekt (podsumowanie, model odpowiedzi, kod statusu) | opt-in | błąd | fastapi | tak |
| FAPI002 | undocumented-error-response |
Operacja ścieżki, która może zwrócić kod błędu (rzucony bezpośrednio, w funkcji pomocniczej albo zależności, albo przez handler wyjątków aplikacji) niezadeklarowany w responses= |
opt-in | błąd | fastapi | tak |
| FAPI003 | router-wiring |
APIRouter z trasami, którego nie dołącza żadna aplikacja, routery dołączające się nawzajem w cyklu albo include_router nad trasami dołączanego routera |
opt-in | błąd | fastapi | tak |
| FAPI005 | route-shadowing |
Operacja ścieżki, na którą wcześniejsza z tą samą metodą już odpowiada: /users/{id} nad /users/me albo ta sama metoda i ścieżka dwa razy |
opt-in | błąd | fastapi | tak |
| FAPI006 | lifespan-events |
Przestarzały handler on_event albo on_startup= (ostrzeżenie) oraz taki obok lifespan=, przez który FastAPI go pomija (błąd) |
opt-in | błąd | fastapi | tak |
| FAPI007 | yield-dependency-swallows |
Zależność z yield, której klauzula except wokół niego nie rzuca wyjątku ponownie, więc błąd endpointu jest ukryty |
opt-in | błąd | fastapi | tak |
| FAPI008 | duplicate-operation-id |
Dwie operacje ścieżki jednej aplikacji z tym samym literalnym operation_id |
opt-in | błąd | fastapi | tak |
| FAPI009 | depends-called |
Depends(get_db()): zależność jest wywoływana przy imporcie zamiast zostać przekazana |
opt-in | błąd | fastapi | tak |
Jeden kod jest zarezerwowany i niezarejestrowany (nieznany kod nadal jest błędem konfiguracji):
| Kod | Nazwa | Zgłoszenie | Status |
|---|---|---|---|
| FAPI004 | unhandled-exception |
#185 | nieużywany: przebieg na korpusie w spike'u wypadł na nie (precyzja 3% dla zadeklarowanych klas wyjątków, 4 prawdziwe trafienia w 25 aplikacjach dla zgłaszanych) |
Katalog reguł w rozdziale 3 wymienia każdą regułę razem z resztą projektu.
Konfiguracja reguł¶
Tabela [tool.inwards.rules] w pyproject.toml określa, które reguły zgłaszają i jak głośno (ADR-027):
[tool.inwards.rules]
ignore = ["INW007", "INW008"] # these rules never report
severity = { INW005 = "warning" } # reported, but doesn't fail a check or block the agent
# extend-select = [...] # turn opt-in rules on, next to the defaults
# select = ["INW001", "INW011"] # or: only these rules report (default: every rule that is on by default)
Kody są dokładne, nie są prefiksami, a nieznany kod to błąd konfiguracji (kod wyjścia 2). ignore wygrywa z select i extend-select. Reguła ustawiona na "warning" pojawia się w każdym formacie, ale nie zmienia kodu wyjścia, nie blokuje hooków i nie trafia do baseline'u. INW000 nie da się wyłączyć ani obniżyć. Tabela jest częścią [tool.inwards], więc config guard nie pozwala agentowi jej edytować.
Reguły opt-in i opcje reguł¶
Reguła, która w kolumnie Włączona ma „opt-in”, nie zgłasza niczego, dopóki jej nie włączysz: wpisz jej kod do extend-select, co zostawia pozostałe reguły bez zmian, albo do select. Reguły INW są domyślnie włączone, poza INW012, INW013, INW014, INW015, INW016, INW017, INW018, INW019, INW020 i INW021; opt-in są reguły, które oceniają kod według progów wybranych przez zespół, takie jak INW012, reguły, które oceniają zawartość modułów danej roli, takie jak INW013, INW015, INW016 i INW020, reguły, które sprawdzają dokumentację względem konfiguracji i kodu, INW017, INW018 i INW019, konwencja, której trzymają się tylko niektóre projekty, INW021, oraz rodziny reguł dla frameworków, takie jak FastAPI. SARIF wymienia regułę opt-in z defaultConfiguration.enabled ustawionym na false.
Opcje reguły trafiają do tabeli nazwanej jak reguła, [tool.inwards.rules.<rule-name>]. Niektóre reguły mają też własne opcje, opisane na ich stronach (INW012, INW013, INW014, INW015, INW016, INW020, INW021, FAPI001, FAPI002). Każda reguła przyjmuje modules, listę prefiksów modułów albo selektorów zapisanych jak w modules warstwy (shop.domain obejmuje ten pakiet i wszystko pod nim, shop.*.api używa symboli wieloznacznych): reguła zgłasza wtedy tylko w modułach, które pasują. Nieznany klucz albo zły typ to błąd konfiguracji, który podaje nazwę klucza.
[tool.inwards.rules.pure-domain]
modules = ["shop.domain"] # INW005 reports only in shop.domain and below
Reguły treści opt-in, INW012 do INW016, INW020 i reguły FAPI, oraz INW021 pomijają moduły testowe: moduł, którego ostatni segment nazwy to test_* albo *_test, albo który ma segment tests, test lub conftest, na przykład app/tests/test_orders.py, tests/conftest.py czy app/orders_test.py. Test celowo buduje jednorazową aplikację albo importuje adapter, więc zgłoszenie w nim rzadko warto poprawiać (ADR-037). check-tests = true w tabeli reguły przywraca je dla tej reguły. Pozostałe reguły sprawdzają moduły testowe jak każde inne, a ignore najwyższego poziomu nie zawęża reguł treści.
[tool.inwards.rules.async-blocking]
check-tests = true # INW013 also reports in tests/ and test_*.py
Szablon może włączyć regułę opt-in dla jednej ze swoich ról, na przykład router = { async-blocking = true }; rozwija się to do extend-select i tabeli opcji reguły, z modułami roli w modules.
Tabela opcji nie włącza reguły. Tabela dla reguły wyłączonej (opt-in i niewybranej albo wymienionej w ignore) nic nie robi, więc inwards check zgłasza ostrzeżenie przy tej tabeli w pyproject.toml, z kodem reguły. Dzięki temu zespół może przygotować opcje reguły, zanim ją włączy.
Żeby na stałe zaakceptować jedną diagnostykę, dodaj wyciszenie w linii, na którą wskazuje, z kodem reguły i powodem (ADR-028):
from shop.infrastructure.legacy import LegacyClient # inwards: ignore[INW001] reason="old billing adapter, removed in #210"
W ten sposób można wyciszać reguły, które wskazują na linię kodu Pythona: INW001, INW002, INW003, INW005, INW006, INW010, INW011, INW012, INW013, INW014, INW015, INW016, INW020, INW021 oraz reguły FAPI. Wyciszenie bez powodu, z nieznanym kodem albo z kodem, którego nie da się wyciszyć, niczego nie ukrywa i jest zgłaszane jako INW009. Hooki Claude Code pomijają wyciszenie, które agent dodał w trakcie sesji, chyba że ustawiono agent-suppressions = "allow".
Format strony¶
Każda strona reguły zaczyna się od front matter w formacie OKF: type: rule, title, description, resource (plik źródłowy reguły), tags (kategoria, najpierw nadrzędna), timestamp (kiedy reguła trafiła do kodu), status oraz pola Inwards code, name, severity, suppressible, autofix i related_issues. Każda strona ma też sekcje Co robi, Dlaczego to źle, Przykład (zgłoszony i poprawiony blok kodu) i Bezpieczeństwo poprawki oraz wiersz w tabeli na tej stronie i na stronie swojej kategorii (rules/<category>/), a także wpis w grupie swojej kategorii w nawigacji, w docs/zensical.toml i docs/zensical.pl.toml. uv run scripts/check-rule-pages.py sprawdza to wszystko w obu językach, także w CI, i zapisuje plik rules.json, z którego czyta lista powyżej; po zmianie strony reguły albo wiersza tutaj uruchom go z --write. Test sprawdza, że każda zarejestrowana reguła ma stronę, a każda strona wskazuje zarejestrowaną regułę. Przykłady zgłoszone i poprawione też działają jako testy, sprawdzane względem wyjścia pokazanego na stronie.