ruff.toml
Rama master · réplica de lectura del repositorio original.
# 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.