INW006 unassigned-module¶
Co robi¶
Inwards sprawdza tylko kod, który należy do warstwy. INW006 pokazuje resztę:
| Diagnostyka | Gdzie | Poziom |
|---|---|---|
Plik w warstwie importuje własny kod, który nie należy do żadnej warstwy, statycznie albo dynamicznie. Dotyczy to też pakietu nad warstwami: from shop import x uruchamia shop/__init__.py, którego nie ma żadna warstwa |
import | błąd |
Pakiet poza wszystkimi warstwami i poza ignore |
jego __init__.py albo pierwszy plik |
ostrzeżenie |
| Prefiks albo selektor warstwy, do którego nie pasuje żaden moduł | pyproject.toml |
ostrzeżenie |
| Warstwa, do której żadnego wpisu nie pasuje żaden moduł | pyproject.toml |
błąd |
Zagnieżdżony projekt pod root: katalog z własnym pyproject.toml, którego kod leży w folderze src/, na przykład członek workspace'u uv |
pyproject.toml, przy root |
ostrzeżenie |
Dowiązanie symboliczne w warstwie (do katalogu albo do pliku .py lub .pyi), którego rzeczywisty cel leży poza root, w innej warstwie albo nad warstwami. Python importuje kod za nim pod nazwą dowiązania, ale żadna reguła nie czyta celu spoza root, a INW001 bierze kod innej warstwy za kod warstwy dowiązania. Walker podąża za dowiązaniami, dopóki zostają w root, więc liczy się też łańcuch przez kod poza wszystkimi warstwami, a także dowiązanie nad warstwami wychodzące poza root tam, gdzie może pasować warstwa (shop/payments przy shop.*.domain) (#83, #84) |
dowiązanie | błąd |
| W trakcie sesji agenta: prefiks, który na starcie sesji pasował do modułów, a teraz nie pasuje do żadnego, wycinek selektora, który miał moduły, a teraz nie ma żadnego, kod warstwy przeniesiony poza wszystkie warstwy albo dowiązanie takie jak wyżej, które jest nowe albo od startu sesji wskazuje gdzie indziej | Stop gate | błąd |
Przy selektorach pakiet zawiera warstwę tylko wtedy, gdy projekt ma pod nim moduł, do którego selektor pasuje: przy shop.*.domain plik shop/util.py nadal dostaje ostrzeżenie, podobnie jak shop/orders/core/ przy shop.**.domain. Selektor, do którego nie pasuje żaden moduł, jest martwy niezależnie od pierwszeństwa, a jego diagnostyka wskazuje tekst selektora.
Dowiązanie w obrębie własnej warstwy jest w porządku, podobnie jak dowiązanie do kodu poza wszystkimi warstwami: ten kod jest sprawdzany pod nazwą dowiązania, jako kod warstwy, a pod własną nazwą dostaje ostrzeżenie o pakiecie. Na przykład przy warstwach z INW001 polecenie ln -s ../infrastructure shop/domain/infra_alias daje:
shop/domain/infra_alias:1:1: INW006 shop/domain/infra_alias is a symlink to "shop.infrastructure", in layer "infrastructure": an import of "shop.domain.infra_alias" (layer "domain") loads that code, and no layer rule sees the dependency.
W sesji agenta nowy pakiet najwyższego poziomu może zamienić stary import w import kodu własnego projektu: requests/__init__.py dodany pod import requests w warstwie. Stop gate sprawdza każdy plik, który zapisuje nową nazwę jako nazwę najwyższego poziomu, względem sprawdzenia ze startu sesji, które tego pakietu nie widzi, więc błąd INW006 blokuje, choć plik z importem się nie zmienił (#86).
Wpisy ignore w [tool.inwards] pasują do całych segmentów nazwy w dowolnym miejscu, więc ignore = ["migrations"] obejmuje shop.orders.migrations; wpis zaczynający się od / pasuje tylko do nazw najwyższego poziomu, więc przy /tests katalog shop/tests/ nadal dostaje ostrzeżenie (ignore). Nieznane klucze i prefiks wymieniony w dwóch warstwach to błędy konfiguracji (kod wyjścia 2).
Dlaczego to źle¶
Gdy import do warstwy zewnętrznej jest zablokowany, agent może utworzyć nowy pakiet, którego nie ma żadna warstwa, shop/persistence/ albo shop/common/, i zaimportować go zamiast tamtej. Nic nie sprawdza, co importuje nowy pakiet, więc może swobodnie sięgać do bazy danych. Przeniesienie warstwy (git mv shop/domain shop/core) opróżnia jej prefiks i wyłącza całą warstwę. INW006 zamyka obie drogi, a martwy prefiks w ręcznie pisanej konfiguracji (literówka, pakiet o zmienionej nazwie) dostaje tę samą uwagę.
Przykład¶
Z warstwami z INW001 agent umieszcza typ wartości w nowym pakiecie shop.common i importuje go z domeny:
from dataclasses import dataclass
@dataclass(frozen=True)
class Money:
cents: int
from shop.common.money import Money
def total(prices: list[Money]) -> Money:
return Money(sum(p.cents for p in prices))
shop/common/__init__.py:1:1: INW006 (warning) "shop.common" belongs to no layer, so no layer rule checks its imports.
fix: Put the code under a layer's package, or ask the user to assign "shop.common".
1. If this code is part of the application, move it under the package of the layer it belongs to.
2. Otherwise ask the user to add "shop.common" to a layer, or to `ignore` in [tool.inwards] if it is tooling. Don't edit [tool.inwards] yourself.
docs: https://sircypkowskyy.github.io/inwards/rules/INW006/
shop/domain/order.py:1:31: INW006 Layer "domain" imports "shop.common.money.Money", which belongs to no layer, so no layer rule checks what "shop.common" imports.
fix: Move the code into a layer, or ask the user which layer "shop.common" belongs to.
1. If the code belongs to "domain" or an inner layer, move it under that layer's package and import it from there.
2. Otherwise ask the user to add "shop.common" to a layer in [tool.inwards]. Don't edit [tool.inwards] yourself.
docs: https://sircypkowskyy.github.io/inwards/rules/INW006/
Po poprawce: Money to wartość domenowa, więc trafia do domeny.
from shop.domain.money import Money
def total(prices: list[Money]) -> Money:
return Money(sum(p.cents for p in prices))
Prefiks, do którego nie pasuje żaden moduł, daje ostrzeżenie w pyproject.toml:
[tool.inwards]
layers = [
{ name = "domain", modules = ["shop.domain"] },
{ name = "application", modules = ["shop.application"] },
{ name = "infrastructure", modules = ["shop.infrastructure", "shop.adapters"] },
]
pyproject.toml:5:64: INW006 (warning) "shop.adapters" (layer "infrastructure") matches no module.
fix: Ask the user to fix or remove "shop.adapters" in [tool.inwards].
1. Check the prefix for a typo: a prefix that matches nothing leaves the code it was meant for unchecked.
2. Don't edit [tool.inwards] yourself; tell the user.
docs: https://sircypkowskyy.github.io/inwards/rules/INW006/
Jak naprawić¶
- Import kodu bez warstwy: jeśli kod należy do warstwy importującej albo bardziej wewnętrznej, przenieś go do pakietu tej warstwy i importuj stamtąd. W przeciwnym razie użytkownik decyduje, do której warstwy należy, i dopisuje jego prefiks w
[tool.inwards]. - Pakiet poza wszystkimi warstwami: przenieś go do warstwy albo poproś użytkownika, żeby przypisał go do warstwy lub dopisał do
ignore, jeśli to narzędzia (testy, skrypty, migracje). - Martwy prefiks: popraw literówkę albo usuń prefiks. Agent mówi o tym użytkownikowi; nie edytuje konfiguracji.
- Warstwa albo wycinek przeniesione w trakcie sesji: przenieś je z powrotem. Zmiana nazwy albo usunięcie całego wycinka wymaga użytkownika, tak jak zmiana nazwy pakietu warstwy.
- Dowiązanie symboliczne w warstwie: usuń dowiązanie i importuj kod pod jego prawdziwą nazwą, żeby INW001 sprawdził tę zależność. Kod spoza
roottrafia podroot, staje się zainstalowanym pakietem albo dostaje własne[tool.inwards]. - Zagnieżdżony projekt: daj każdemu członkowi workspace'u własne
[tool.inwards].inwards checkw katalogu głównym workspace'u sprawdzi wtedy każdego członka jego własną konfiguracją i pominie pliki członka w konfiguracji katalogu głównego (Monorepo i workspace'y uv).
Bezpieczeństwo poprawki¶
Brak automatycznej poprawki (autofix: false). To, gdzie należy kod, jest decyzją projektową, a każda zmiana konfiguracji należy do użytkownika.
Konfiguracja¶
Poziom: błąd, z ostrzeżeniami z tabeli powyżej. [tool.inwards.rules] może wyłączyć regułę albo ustawić poziom każdej jej diagnostyki: przy severity = { INW006 = "error" } ostrzeżenie o pakiecie też staje się błędem (Konfiguracja reguł). Sprawdzenie układu w Stop gate (prefiks opróżniony, kod warstwy przeniesiony albo dowiązanie symboliczne utworzone w warstwie od startu sesji) pomija tę tabelę, więc nawet ignore = ["INW006"] nie pozwala agentowi przenieść warstwy (ADR-027). Diagnostykę importu można wyciszyć w jej linii przez # inwards: ignore[INW006] reason="...".
W hookach Claude Code edycja z samymi ostrzeżeniami INW006 zostaje, a ostrzeżenia trafiają do agenta jako kontekst.
Znane ograniczenia¶
- Importy wewnątrz nieprzypisanego pakietu nie są sprawdzane względem warstw, dopóki użytkownik go nie przypisze; INW006 tylko go pokazuje. Gdy zadeklarowano konteksty, jego importy nadal sprawdzają INW002 i INW003.
inwards checkze ścieżkami jako argumentami iinwards serverw trakcie pisania sprawdzają pliki pojedynczo i nie zgłaszają martwych prefiksów, zagnieżdżonych projektów ani dowiązań symbolicznych w warstwach. Robi to tylko sprawdzenie całego projektu (jest nim przebieg całego projektu w serwerze, na starcie i po zapisie), a dowiązanie utworzone w trakcie sesji zgłasza też Stop gate. Mówi o tyminwards check --help(#86): sprawdzenie kilku plików nie powie, czy prefiks nie pasuje do niczego gdzie indziej.- Zgłaszane jest każde dowiązanie w warstwie wychodzące poza
root, także do katalogu z plikami danych: Inwards nie zagląda za dowiązanie, którego nie może sprawdzić. Trzymaj taki katalog poza pakietem warstwy albo zaakceptuj diagnostykę w baseline. - Zagnieżdżony projekt to ostrzeżenie, nie błąd. Jego pakiet dostaje nazwę wyprowadzoną ze ścieżki (
src/packages/core/src/coretopackages.core.src.coreprzyroot = "src"), a inni członkowie importują go jakocore, co nie pasuje do żadnego modułu. Taki import jest brany za import biblioteki zewnętrznej i przechodzi, więc naruszenie warstw między członkami nie jest zgłaszane. To ostrzeżenie to pokazuje (#201). Uruchomiony bez--config,inwards checkzostawia członka z własnym[tool.inwards]jego konfiguracji, więc ostrzeżenie dotyczy tylko członków bez niej. - Moduł skompilowany albo bez źródeł (
shop/persistence.cpython-313-x86_64-linux-gnu.so,.pyd,.pycalbo.pyxCythona) to kod własny projektu, więc jego import z warstwy jest błędem, gdy moduł leży poza wszystkimi warstwami (#86). Inwards nie przeczyta, co taki moduł importuje: nie dostaje on ostrzeżenia o pakiecie, a prefiks warstwy pasujący tylko do takich plików liczy się jako martwy. - Moduł warstwy przemianowany, zmieniony i przeniesiony do nowego modułu najwyższego poziomu, którego nic nie importuje, przechodzi przez Stop gate tylko z ostrzeżeniem o pakiecie. Bramka łapie przeniesienie do pakietu, który zawiera warstwy, oraz przeniesienie gdzie indziej z zachowaniem nazwy pliku albo zawartości. Wykrywanie zmiany nazwy po podobieństwie pominięto celowo (#86):
git diff -Muruchamia mechanizm diff gita, który czyta atrybuty i konfigurację zapisywalne przez agenta, na repozytorium agenta, a ocena podobieństwa względem zawartości ze startu sesji czytałaby każdy zniknięty i każdy nowy plik przy każdym Stop i nadal byłaby zgadywaniem z progiem do strojenia. Dopóki nic nie importuje przeniesionego kodu, nie wniesie on zależności do warstwy, a pierwszy jego import z warstwy to błąd INW006. - Import, który INW010 zgłasza jako brakujący moduł, nie dostaje dodatkowo INW006.