# AGENTS.md — Arbeits-Konventionen für Rippy ## Grundregeln **1. Plan vor Code.** Vor jeder Etappe: Klare Akzeptanzkriterien schreiben, dann bauen. **2. Kleine Schritte.** Max. eine Feature pro Commit. Commit-Nachrichten beschreiben WAS und WARUM. **3. Beweisen statt behaupten.** Testen, was gebaut wurde. Keine "es sollte funktionieren"-Commits. **4. Deutsch-Nicht-Entwickler.** Der Commander liest alles — Variablennamen auf Englisch, aber Kommentare und Docs auf Deutsch. ## ⛔ HARTE Regeln (mechanisch geprüft — seit 22.07.2026) **A. Die CI-Ampel muss GRÜN sein, bevor irgendetwas „fertig" heißt.** `.gitea/workflows/ci.yml` läuft bei jedem Push auf dem Gitea-Runner (NICHT löschen, NICHT abschwächen). Keine Tests = rot = nicht fertig. Der Commander liest die Ampel, nicht den Code. **B. Abweichung vom KONZEPT = STOPP + fragen.** Anderes Werkzeug, andere Bibliothek, gestrichenes Muss-Feature → erst den Commander fragen, NIE still ersetzen. (Vorgefallen: Muss-Feature „MakeMKV lossless" wurde still durch lossy HandBrake ersetzt.) **C. Single Source of Truth ist `main` — es gibt nur diesen einen Branch** (seit 24.07.2026; der `stable`-Zwischenbranch ist abgeschafft). Die CI-Ampel läuft bei jedem Push und PRÜFT nur (Ruff/pytest/Vite-Build) — sie befördert nichts mehr. Deployt wird direkt aus `main`: auf der VM `git pull` bzw. `./deploy.sh` (verifiziere vorher, dass die Ampel für den Commit GRÜN ist — rot heißt: nicht deployen). Nie freihändig per SSH auf der VM bauen. (Vorgefallen: Doppel-Anlage `Rippy` + `rippy` auf der VM durch Freihand-Deploys.) **D. Externe Schnittstellen NIE aus dem Kopf.** Vor Nutzung fremder CLI-Flags oder Bibliotheks-APIs: `--help`/Doku prüfen und die Fundstelle im Commit nennen. (Vorgefallen: erfundene Celery-Methode `self.send_task`, erfundene abcde-Flags.) ## Workflow 1. **Read:** KONZEPT.md + ROADMAP.md lesen. Verstehen, welche Etappe dran ist. 2. **Plan:** Was genau soll diese Sitzung bauen? Eine Zeile. 3. **Build:** Code schreiben, testen, committen. 4. **Verify:** `docker compose up` — funktioniert das Ganze? 5. **Savepoint:** SAVEPOINT.md aktualisieren. Nächster Chat beginnt nicht von Null. ## Modelle je Aufgabe - **Planung:** Heavy-Modell (Reasoning) für Architektur-Entscheidungen. - **Bauen:** schnelles Coding-Modell. - **Review:** Heavy nochmal für Code-Review vor Merge. ## Docker-Praxis - Alles läuft in Containern: `docker compose up -d` - Keine System-Pakete auf dem Host — alles im Container. - Dockerfile immer multi-stage, kleinste Images. ## Was NICHT gebaut wird - Kein Code direkt im Host-OS. - Kein Proxmox-LXC-Nesting-Workaround — das Projekt lebt bewusst in normalem Docker. - Kein ARM-Fork — Rippy ist ein Eigenbau von Grund auf. ## Aktueller Stand (26.07.2026) - ✅ **E2E bewiesen (v3.1):** BD-50 komplett durch die Kette (43 GB → 4,8 GB) - ✅ **Etappe 13 (v3.2):** Universal-Komfort-Runde — Media-Server-Integration, echte Benachrichtigungen, SMB-Klartext-Fehler, UHD-Arbeitsverzeichnis, MakeMKV-Key via UI, Job-Detail-Popup, Toast-Feedback, Ampel-Blocker behoben - ✅ **Etappe 17 (v3.10):** 4K-UHD-Disc-Schlüssel — persistentes MakeMKV-Datenverzeichnis + `KEYDB.cfg` im UI - ✅ **Etappe 18 (v3.11):** 4K-UHD gelöst — `makemkvcon` holt Schlüssel unter Linux nie, unter Windows schon; Schlüsselspeicher übernehmbar. Akira-UHD geht auf der VM auf (`TCOUNT:5`, bewiesen) - ✅ **Etappe 19 (v3.14):** Durchsicht Frontend/Backend — vier Placebos weg (Fortschritt log, Auswurf tat nichts, „Alle Tracks" konnte nichts, Encoder wurden behauptet statt gemessen), Zombie-Erkennung gebaut, Pfad-Prüfung gehärtet, und der Platten-Schutz aus `c065967` als **unwirksam** entlarvt - ✅ **Etappe 20 (v3.15):** Aufräum-Runde — `/capabilities` 1,010 s → 0,003 s (Ping im Hintergrund), Kompression je Disc-Typ abwählbar (4K verlustfrei), Wizard empfiehlt nach gemessener CPU, vier tote Routen entfernt - ✅ **Etappe 21 (v3.17):** Der Blocker externes Encoden ist zu — `RIPPY_PATH_MAP` leitet Rippy aus seinen eigenen Mounts ab, der Installer holt es selbst; Presets kommen vom Worker statt aus dem Quelltext; Dashboard ohne Placebos, mit Restzeit. Dazu vier Bestandsfehler, alle live gemessen (u. a. „Neu komprimieren" ging nie, und `os.path.isdir` hing im Kernel) - ✅ **Etappe 22 (v3.18):** Auswurf wirkt endlich (MakeMKV verriegelt die Tür — erst entriegeln, dann prüfen statt glauben), externer Worker meldet sein Log nach Rippy, zeigt den laufenden Job und nimmt mehrere Aufträge an. Dazu **die Mount-Ursache**: Die CIFS-Verbindung lebt in der Netz-Namespace des api-Containers und stirbt mit ihm — eine Wache heilt das jetzt selbst - ✅ **Etappe 23 (v3.19):** Sprachwahl vor dem Rip (Ton + Untertitel, Automatik einstellbar), Auswurf wirkt wirklich (MakeMKV verriegelt die Tür), externer Worker mit Verwaltungsfenster/Deinstaller/Slots, Schlüssel-Automatik für 4K, Mount-Wiederanbindung 202 s → 8 s, Weitergabe an einem frischen Klon geprüft - 📝 **Details immer in SAVEPOINT.md** — diese Sektion nennt nur die Etappe ## Was diese Sitzungen wiederholt gekostet hat **Nicht aus einem Zustandswert auf einen Mechanismus schließen.** Vorgefallen: aus „kein Schlüssel da" → „Server abgeschaltet" (falsch), aus Status `transcoding` → „Celery hat neu zugestellt" (falsch), aus `progress=99` → „Altwert aus dem Absturz" (falsch — ein Bug), aus gleichem `st_dev` → „`os.rename` funktioniert" (falsch — der Kernel vergleicht den Mount). Jedes Mal hätte eine Messung von unter einer Minute gereicht. **Und die Umkehrung gilt genauso:** gleiches `st_dev` heißt NICHT gleicher Mount. Wo eine Eigenschaft ausprobierbar ist, probiere sie aus, statt sie vorherzusagen. **Ein Hintergrund-Prozess, der still scheitert, ist schlimmer als einer, der laut scheitert** (26.07.2026). Ein `except Exception: pass` in einer Vorrats-Schleife hat eine Stunde gekostet: Der Vorrat blieb leer, die Funktion lief direkt aufgerufen einwandfrei, und der Grund stand nirgends. Gefunden erst über die Thread-Zustände (`/proc//task/*/stat`, Zustand `D` = im Kernel blockiert). Jede Hintergrund-Schleife MELDET ihren Fehler, und wer einen Vorrat anlegt, macht sein Alter abfragbar (`GET /health/vorraete`) — sonst ist am Endpunkt selbst nichts zu sehen. **Ein Rückgabewert ist kein Beweis, wo die Wirkung prüfbar ist** (26.07.2026, zweimal am selben Abend). `CDROMEJECT` quittiert Erfolg auf einem verriegelten Laufwerk und wirft nichts aus; `mount` quittiert Erfolg auf einer Verbindung, die Sekunden später stirbt. Beide Fehler waren monatelang unsichtbar, weil der Code dem Rückgabewert glaubte. Nach einer Aktion den ZUSTAND fragen — und wenn er flattert, zweimal mit Abstand. **Bei „zu langsam" die DAUER je Schritt messbar machen, nicht die plausibelste Ursache beheben** (26.07.2026). Eine Mount-Wiederanbindung brauchte 150 s; die erste, sehr plausible Erklärung war falsch, und die Änderung machte es langsamer (202 s). Erst Zeitstempel im Log zeigten die Stelle: ein `os.makedirs` in Zeile eins, das drei Minuten im Kernel hing. Danach 8 s. Wer eine Dauer nicht aufschlüsselt, optimiert die falsche Stelle. **Netz-Pfade nie ungebremst anfassen.** `os.path.isdir`/`open` auf einem toten CIFS-Mount blockieren im Kernel und lassen sich aus Python NICHT abbrechen. Ein Kind-Prozess lässt sich abbrechen: `timeout N ls -d ` (Muster in `mounts.ist_erreichbar` und `rohdaten.verzeichnis_da`). Und „konnte nicht nachsehen" ist etwas anderes als „ist nicht da" — beides zu vermischen erzeugt falsche Aussagen im UI.