# Ruff-Konfiguration für das Novaberg-Projekt # # Ablageort: Repo-Wurzel novaberg/. Es gibt keine pyproject.toml — der Server # wird nicht als Paket installiert, sondern als Quellbaum in den Container # gemountet. Eine pyproject.toml ohne [project]-Abschnitt nur als Träger für # den Linter wäre irreführend, deshalb die eigenständige ruff.toml. # Basis des Server-Images: novaberg/server/Dockerfile Zeile 1 — FROM python:3.12-slim. # Bewusst NICHT die Python-Version des Hosts: der Code läuft im Container. target-version = "py312" # Messung über novaberg/server/**/*.py (54.074 Zeilen in 244 Dateien): # > 88 Zeichen: 1235 Zeilen (2,28 %) # > 100 Zeichen: 378 Zeilen (0,70 %) # > 120 Zeichen: 79 Zeilen (0,15 %) # Gewählt: 100. Der Schritt 88 -> 100 räumt 857 Treffer ab (69 % des Rauschens), # der Schritt 100 -> 120 nur weitere 299. Die Kurve knickt bei 100 ab. 120 hätte # die absolut geringste Trefferzahl, ist aber keine vertretbare Grenze mehr: # dort bleibt Fließtext-Code unbeanstandet, den im Review niemand überblickt. line-length = 100 # Quellwurzel fuer die Importsortierung. Ohne diese Zeile sucht Ruff nach # novaberg/ und novaberg/src/ — beides falsch: der Quellbaum ist server/, und er # wird als solcher in den Container gemountet (docker inspect: server -> /app). # Die Folge war messbar und stand in jeder Datei: `from config import ...` und # `from memory.kzg import ...` galten als FREMDE Importe und wurden zwischen # `redis` und `langgraph` einsortiert. Erst das Einschalten der harten Wand fuer # I001 machte das sichtbar — eine Wand haette sonst die falsche Ordnung # festgeschrieben. # # Gemessen am 25.08.2026: 142 Dateien sortieren mit dieser Zeile anders als ohne. src = ["server"] [lint] select = [ "D", # pydocstyle — Docstrings "ANN", # flake8-annotations — Typannotationen "F", # Pyflakes — echte Fehler "E", # pycodestyle Error — Stil "W", # pycodestyle Warning — Stil "N", # pep8-naming — Namensformen "I", # isort — Importsortierung "S", # flake8-bandit — Sicherheit "B", # flake8-bugbear — häufige Fallen "ERA", # eradicate — auskommentierter Code "T20", # flake8-print — print statt Logger "BLE", # flake8-blind-except — blankes except "TRY", # tryceratops — Ausnahmebehandlung "LOG", # flake8-logging — Logger-Fehlgebrauch "C90", # mccabe — zyklomatische Komplexität # "PLR09" — die Pylint-Zählregeln, und zwar NUR der 09xx-Block, nicht "PLR". # Gemessen am 30.07.2026 mit den Grenzen dieser Datei: "PLR" liefert 299 # Treffer, der 09xx-Block davon 193. Die Differenz von 106 ist zu 104 Teilen # PLR2004 (magic-value-comparison) — also eine Meldung pro # Schwellenvergleich. Die Differenz ist von den Grenzen unabhängig, die # beiden Summen sind es nicht: sie verschieben sich mit max-branches. Bei # einem System, dessen Kern aus kalibrierten Schwellen besteht, meldete die # Regel die Arbeitsweise des Systems als Fehler. Der engere Präfix hält sie # draußen; zusätzlich steht PLR2004 unten im ignore-Block, damit die # Entscheidung als Entscheidung lesbar ist und nicht wie eine Auslassung. # # Der 09xx-Block umfasst acht Regeln; fünf davon greifen: PLR0911, PLR0912, # PLR0913, PLR0915, PLR0917. Drei greifen ohne "preview = true" nicht — # PLR0904 (too-many-public-methods), PLR0914 (too-many-locals), # PLR0916 (too-many-boolean-expressions). PLR0911 ist zusätzlich unten # ausgeschlossen, greift also faktisch nicht; wirksam sind vier. # C901 kommt nicht von hier, sondern aus der Familie "C90" eine Zeile höher. "PLR09", ] # D415 nachgezogen. Die Konvention pep257 unten schaltet die Regel ab; sie tritt # hier an die Stelle von D400 (Begruendung im ignore-Block). Ohne diese Zeile # bliebe die Zusammenfassung ohne Satzende voellig ungeprueft — select = ["D"] # allein genuegt nicht, weil die Konvention nach der Auswahl greift. extend-select = ["D415"] ignore = [ # G004 "Logging statement uses f-string" # Abgeschaltet, weil f-Strings im Logging in diesem Projekt VORGESCHRIEBEN # sind. Die Regel widerspricht dem Standard des Projekts, nicht umgekehrt. # Der Eintrag steht hier defensiv: die Familie "G" ist oben nicht selektiert, # aber falls sie später hinzukommt, bleibt G004 stumm. "G004", # D203 "1 blank line required before class docstring" # Von den beiden gegenläufigen Formvarianten weicht D203. Sein Gegenstück # D211 ("No blank lines allowed before class docstring") fordert das genaue # Gegenteil; wären beide aktiv, wäre jede Klassen-Docstring unreparierbar, # weil jede Behebung die andere Regel auslöst. Gewählt wurde D211, weil der # Bestand und jeder moderne Formatter die Docstring direkt unter "class X:" # setzen — D203 zu behalten hieße, den gesamten Bestand umzuformatieren, um # eine Leerzeile einzufügen, die niemand haben will. # Nachgemessen mit --show-settings: D203 ist inaktiv, D211 ist aktiv. # Hinweis: die Konvention pep257 schaltet D203 ohnehin ab. Der Eintrag bleibt # trotzdem stehen — er dokumentiert die Entscheidung an der Stelle, an der # man sie sucht, und hält sie fest, falls die Konvention je wechselt. "D203", # PLR0911 "Too many return statements" # Abgeschaltet, weil die Regel die im Projekt vorgeschriebene Bauart # bestraft. Jede geprüfte Vorbedingung erzeugt einen eigenen Rückkehrpunkt — # eine Funktion mit fünf Vorbedingungen hat sechs. Das ist die Folge der # EVA-Disziplin und kein Verstoß gegen die Funktionsanatomie. # Widerspricht eine Werkzeugregel dem Standard des Projekts, gewinnt der # Standard des Projekts. # # Gemessen am 30.07.2026 mit der Standardgrenze 6: 14 Treffer. Entscheidend # ist nicht die Zahl, sondern ihre Zusammensetzung — 5 der 14 sind reine # Wächterketten: # validieren_gegen_db() agents/direktiven/crud.py 9 Rückgaben # validieren_gegen_db() agents/charakter_identitaet/crud.py 8 # _vorturn_laden() graph/nodes/gespraechsvektor.py 8 # _timeline_aufloesen() agents/kzg/magnete.py 7 # _modus_plausibilitaet() ei/berechnung.py 8 # _vorturn_laden() ist der reine Fall: sechs von acht Rückgaben sind # "return None, ''" hinter je einer verletzten Vorbedingung. Beide # validieren_gegen_db() geben pro verletzter Vorbedingung ein # ValidationResult(ok=False, grund=...) zurück — genau die Form, die # "fail loud, fail logged" verlangt. # # Eine andere Grenze würde das nicht reparieren: Die Rückgabezahl eines # Validierers ist die Zahl seiner Vorbedingungen. Die folgt aus dem # Datenmodell, nicht aus der Codequalität — kommt ein Pflichtfeld hinzu, # wandert die Funktion auf jede Grenze zu, die man setzt. Die Regel erzieht # zu verschachtelten Bedingungsketten, in denen die verletzte Vorbedingung # nicht mehr an der Stelle steht, an der sie geprüft wird. # # Was damit ungeprüft bleibt, offen benannt: _stage_detail_bauen() in # services/event_consumer.py hat 18 Rückgaben und ist keine Wächterkette, # sondern eine als if-Kette geschriebene Dispatch-Tabelle. Der eine Fall, # den die Regel zu Recht melden würde. Dafür ist der Review zuständig. "PLR0911", # PLR2004 "Magic value used in comparison" # Abgeschaltet, weil die Schwellenvergleiche dieses Systems kalibrierte, # dokumentierte Parameter sind und keine Magic Values. "arousal >= 0.7" ist # ein hergeleiteter Messwert mit Herkunft, kein vergessenes Literal. # # Gemessen am 30.07.2026: 104 Treffer — mehr, als jede einzelne aktivierte # Zählregel liefert (die größte ist C901 mit 52). # # Der Eintrag steht defensiv: PLR2004 liegt außerhalb des oben selektierten # 09xx-Blocks und greift derzeit gar nicht. Er steht trotzdem hier, weil ein # nicht selektierter Präfix wie eine Auslassung aussieht und ein begründeter # Eintrag eine Entscheidung ist. Wird "PLR" je breiter selektiert, bleibt die # Entscheidung wirksam. "PLR2004", # D400 "First line should end with a period" # Abgeschaltet zugunsten von D415, das unten per extend-select nachgezogen # wird. Beide pruefen dieselbe Sache mit einem Zeichen Unterschied: D400 # verlangt den Punkt, D415 laesst Punkt, Fragezeichen oder Ausrufezeichen # gelten. # # Gemessen am 25.08.2026 ueber server/: D400 14 Treffer, D415 **null**. Alle # vierzehn sind Docstrings, die als FRAGE formuliert sind und auf "?" enden: # """Was fuer ein Gespraech ist das?""" ei/farbton.py # """Wie warm oder kalt ist die Stimmung?""" ei/farbton.py # """Ergebnis des Vektor-Parsings: Was wurde erkannt?""" utils/zeitparser.py # Das ist eine durchgehaltene Form dieses Bestands und kein Versehen — ein # Punkt hinter einem Fragezeichen waere schlechteres Deutsch, nicht besseres. # Widerspricht eine Werkzeugregel dem Standard des Projekts, gewinnt der # Standard des Projekts. # # Der Zweck der Regel geht dabei NICHT verloren: D415 faengt weiterhin jede # Zusammenfassung ohne Satzende. Die Null bei D415 ist ein Befund, keine # Abschaltung — nachgemessen mit einem konstruierten Verstoss. "D400", ] [lint.per-file-ignores] # Ausnahmen je Datei. Keine davon ist pauschal: Jede nennt eine einzelne Regel # und den Grund, warum sie an dieser Stelle etwas anderes misst als gemeint. # Der Bestand des Testverzeichnisses hat am 25.08.2026 die Gesamtzahl dominiert # — 1032 der 2305 Treffer lagen dort, und 571 davon waren die drei Regeln unten. # # Was hier ausdruecklich NICHT steht, obwohl es dort ebenfalls trifft: # D205 (45), D101 (10), D104 (5), D107 (2) — Formregeln an echten Docstrings, # die auch in einem Zeugen gelten. Und ANN201 stand mit 304 Treffern hier zur # Debatte und ist stattdessen BEHOBEN worden: 303 der 304 waren maschinell mit # `-> None` zu schliessen, und eine richtige Annotation ist besser als eine # begruendete Ausnahme. "server/tests/**" = [ # D102 "Missing docstring in public method" — 560 Treffer, der groesste # Einzelposten des gesamten Bestands. # # Bei einer Testmethode IST der Name die Beschreibung: # `test_der_dispatcher_reicht_beim_nutzer_turn_die_aeusserung`. Eine # erzwungene Docstring darueber wiederholt entweder den Namen oder sagt # nichts. Der Bestand setzt sie dort, wo sie etwas hinzufuegt — etwa # "Dieselbe Stelle, andere Herkunft, anderes Ergebnis." —, und genau diese # Freiwilligkeit macht sie lesenswert. # # Die Regel bleibt fuer den Produktivcode scharf: dort 82 Treffer, und die # sind eine durchsetzbare Groesse. "D102", # S101 "Use of assert detected" — in einem Zeugen ist die Zusicherung der # Zweck. Die Regel zielt auf Produktivcode, wo ein `assert` bei -O # verschwindet; ein Test laeuft nie mit -O. "S101", # S608 "Possible SQL injection vector" — die Treffer sind Abfragen, die # eine Testvorrichtung baut, mit Literalen aus derselben Datei. Es gibt # keine Eingabe von aussen, gegen die die Regel schuetzen koennte. "S608", ] # Die Zeugen des Clients (seit 30.08.2026) tragen dieselbe Begruendung wie die # des Servers — bei einer Testmethode IST der Name die Beschreibung. Sie liegen # in einem eigenen Baum, weil sie auf dem Host laufen und nicht im Behaelter: # `client/` traegt GTK-Abhaengigkeiten und ist nicht ins Server-Abbild gemountet. # Der kanonische Linter-Lauf geht ueber `server/`; dieser Block wirkt, sobald # jemand `client/` mitprueft. "client/tests/**" = ["D102", "S101", "S608"] # korpus_laeufer.py ist kein Zeuge, sondern ein Werkzeug im Testverzeichnis: Es # faehrt einen Korpus und schreibt sein Ergebnis auf die Standardausgabe. Fuer # ein Werkzeug mit Konsolenausgabe ist `print` die Schnittstelle und nicht der # Fehler; ein Logger schriebe woandershin als der Aufrufer liest. "server/tests/korpus_laeufer.py" = ["T201"] [lint.pydocstyle] # Konvention pep257. Wirkt sich aus: die Konvention schaltet von der oben mit # "D" selektierten Familie eine Reihe von Regeln wieder ab, nachgemessen mit # --show-settings — darunter D203 sowie BEIDE Varianten des zweiten Paarkonflikts, # D212 (Zusammenfassung in Zeile 1) und D213 (Zusammenfassung in Zeile 2). # Zur Position der Zusammenfassung in mehrzeiligen Docstrings erzwingt pep257 # also gar nichts; wer eine der beiden Formen verbindlich will, muss sie hier # per extend-select nachziehen. Ebenfalls abgeschaltet sind die Abschnitts- # Regeln D405–D417 (numpy-/google-Stil), die pep257 nicht kennt. # Aktiv bleiben unter anderem: D1xx (fehlende Docstrings), D200, D205, D206–D208, # D211, D301, D402, D414, D418, D419. convention = "pep257" [lint.mccabe] # Standardwert, sichtbar gemacht — keine gewählte Grenze. # # Gemessen am 30.07.2026 über 664 Funktionen mit Komplexität >= 2: # max-complexity = 10 -> 52 Treffer (gesetzt) # max-complexity = 12 -> 39 # max-complexity = 14 -> 29 # max-complexity = 15 -> 24 # max-complexity = 16 -> 23 # max-complexity = 18 -> 19 # max-complexity = 20 -> 11 # max-complexity = 25 -> 4 # # Hergeleitet wurde, dass hier nichts herzuleiten ist: Die Verteilung hat keine # Klippe. Der Schritt von 15 auf 16 verschiebt einen einzigen Treffer. Ohne # natürlichen Bruch im Bestand ist kein abgeleiteter Wert besser begründet als # der Literaturwert, und ein höherer Wert senkte nur die Trefferzahl, ohne sie # zu begründen. Deshalb bleibt die 10 stehen — ausdrücklich als Standardwert # und nicht als Fund. # # 52 Treffer sind 7,8 % der gemessenen Funktionen; 92 % erfüllen die Grenze # bereits, die Regel ist für neuen Code also durchsetzbar. Die Spitze: # _build_system_prompt() graph/nodes/responder.py 47 # _stage_detail_bauen() services/event_consumer.py 35 # _text_normalisieren() utils/zeitparser.py 31 # plan() graph/nodes/planner.py 28 # _call2_nachbearbeiten() agents/promotion/agent.py 25 max-complexity = 10 [lint.pylint] # Drei Werte gesetzt, einer bewusst nicht. Gemessen am 30.07.2026. # # max-args und max-statements sind Standardwerte, sichtbar gemacht — in ihren # Verteilungen gibt es keine Klippe, die einen abgeleiteten Wert tragen würde. # max-branches ist eine GEWÄHLTE Grenze: der Standardwert wäre dort neben C901 # wirkungslos gewesen, die Begründung steht bei dem Wert. # max-returns ist nicht gesetzt; die Begründung steht am Ende des Abschnitts. # 47 Treffer beim Standardwert 5. Verteilung: 4->83, 5->47, 6->33, 7->17, # 8->13, 10->4. # # max-positional-args ist bewusst nicht gesetzt: ohne eigene Angabe folgt es # automatisch max-args. Die zugehörige Regel PLR0917 # (too-many-positional-arguments) liefert 44 Treffer und ist erst in Ruff 0.16.0 # stabil geworden — gegen eine ältere Ruff-Version wäre dieselbe Konfiguration # hier still, ohne anders auszusehen. Deshalb gehört die Werkzeugversion in den # Messbericht, nicht nur in die Erinnerung. max-args = 5 # 37 Treffer beim Standardwert 50. Verteilung: 50->37, 60->27, 70->19, 80->13, # 100->2. # # KEINE ABBILDUNG DER LÄNGENREGEL: Die Regel zählt ANWEISUNGEN, nicht Zeilen. # Die Funktionslängen-Regel des Projektstandards spricht von Zeilen. Beides fällt # auseinander — eine Anweisung kann sich über mehrere Zeilen ziehen, und # Leerzeilen, Kommentare und Docstrings zählt die Regel gar nicht mit. Der Wert # ist eine Näherung an die Längenregel und keine Umsetzung davon. Wer die # Längenregel maschinell prüfen will, braucht eine eigene Prüfung; dieser Wert # ersetzt sie nicht und darf nicht als ihre Durchsetzung gelesen werden. max-statements = 50 # GEWÄHLTE GRENZE, kein Standardwert. Der Standardwert 12 wäre hier wirkungslos: # gemessen am 30.07.2026 sind alle 35 Treffer bei 12 auch von C901 gemeldet, die # Regel sagt bei diesem Wert nichts Eigenes. # # Ursache ist die Arithmetik der beiden Zählungen. Nachgemessen an isolierten # Testfällen gilt: C901 = Verzweigungen + 1 - Anzahl der else-Arme. McCabe zählt # ab 1 und ignoriert else, weil ein else keinen neuen unabhängigen Pfad erzeugt; # PLR0912 zählt es mit. Verzweigungen > 12 bedeutet damit Komplexität >= 13 # minus else-Zahl — und solange die unter 3 liegt, hat C901 mit Grenze 10 längst # angeschlagen. # # Eigenständigkeit gegen C901 bei Grenze 10, gemessen: # max-branches = 12 -> 35 Treffer, davon 0 nur von PLR0912 # max-branches = 11 -> 44 Treffer, davon 2 nur von PLR0912 # max-branches = 10 -> 51 Treffer, davon 7 nur von PLR0912 (gesetzt) # max-branches = 9 -> 61 Treffer, davon 14 nur von PLR0912 # max-branches = 8 -> 79 Treffer, davon 30 nur von PLR0912 # # Gewählt ist 10, weil die Regel dort eigenständig wird und dabei genau die # Blindstelle von C901 abdeckt: Weil C901 jedes else abzieht, unterschätzt es # systematisch die als if/elif/else-Kette geschriebene Dispatch-Tabelle. Die # sieben eigenständigen Treffer sind per Konstruktion die else-reichen # Funktionen. Das ist zugleich das Muster, das der Ausschluss von PLR0911 # unbeaufsichtigt gelassen hätte — _stage_detail_bauen() hat 36 Verzweigungen # bei Komplexität 35, die Verzweigungszahl ist dort die höhere. # # Blindstelle, die beide Regeln teilen und die dieser Wert nicht behebt: # Boolesche Operatoren zählen als ein Verzweigungspunkt, nicht als mehrere, und # Bedingungen in Comprehensions zählen bei keiner der beiden Regeln mit. max-branches = 10 # max-returns ist ABSICHTLICH NICHT GESETZT — die Abwesenheit ist die # Entscheidung. Die zugehörige Regel PLR0911 steht oben im ignore-Block, dort # steht auch die Begründung. Ein Wert an dieser Stelle wäre # wirkungslos und würde vortäuschen, die Grenze sei verhandelt worden. Sie ist # nicht verhandelt, sie ist abgelehnt.