what_to_wear/LEDGER.md
Nora 2996400098 feat(4.2): LLM-Phraser mit Prompt-Haertung & Ausgabe-Validierung
Story 4.2 (TDD, reiner Kern, Suite gruen 157/157):
- logic/phraser.py: build_prompt (nur strukturierte Empfehlung, JSON-escaped
  Untrusted-Stuecknamen + Ignorier-Instruktion, nie Fotos/Koordinaten/Entity-IDs),
  validate (a-e: kein Markup/Link/Code/Steuerzeichen, <=700, jeder Stueckname+Luecke,
  >=20; Verstoss->None=Regeltext), Injection-Katalog (AD-16/FR-6.6).
- luna-pro-Security-Review: 4/5 Findings uebernommen, 1 verworfen (Evidenz im Ledger).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 15:24:42 +00:00

338 lines
28 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Fortschritts-Ledger — What to Wear (WTW)
> Methode: **AI-Dev-Method** (BMAD + adversariale Reviewer-Linsen + KI-Zweitkritiker qwen/Cloud).
> Quelle der Methode: `/root/.claude/method/DEV-METHOD.md` (v1.1) bzw. Repo `Kenearos/ai-dev-method`.
> Dieses Ledger überlebt Kontext-Kompaktierung: erledigte Stories, offene Punkte, Gate-Ergebnisse
> (inkl. je Kritiker-Aufruf geschickter Dateien + Modell), Finding-Urteile.
**Stand:** 2026-07-13 · **Phase:** 5 LAUFEND — Story-Loop; **EPIC 1+2 KOMPLETT** (Stories 1.12.5, 130 Tests grün): 15-Minuten-Wow-Pfad steht (Kleiderschrank→Karte→Service→Push/TTS). Umgebung `.venv` Py3.13 + HA 2025.3.4. · Nächster Epic: 3 (Anpassung & Robustheit), Story 3.1
## Benutzer-Entscheidungen E-1…E-4 (2026-07-11)
- **E-1 Lizenz: Apache-2.0** (bewusst statt MIT-Vorschlag).
- **E-2 Min-HA: ≥ 2025.3** (Subentries; Architekturphase verifiziert gegen reale APIs).
- **E-3 Repo-Modell: Forgejo = Entwicklungs-Quelle, öffentliches GitHub = gepflegter
Push-Mirror + Vertriebskanal** (Releases/Issues auf GitHub — HACS-Pflicht). Kostet
Release-Disziplin; vor Release einrichten.
- **E-4 Foto-Feld: v1 gestrichen → v1.1** (LagerLens-Connector).
---
## Umgebung (für spätere Sessions wichtig)
- Bash läuft als **`openclaw`** (uid 1000), **kein passwortloses sudo**. `/root/Labor` gehört `root`;
Projektordner `/root/Labor/what-to-wear` wurde einmalig per sudo an `openclaw` übereignet.
- **qwen-Kritiker** nur über Tailscale erreichbar, wenn der Windows-Desktop (100.64.0.2) an ist
(am 2026-07-11 erreichbar). Fällt er aus → Cloud-Eskalation `openai/gpt-5.6-luna-pro` via OpenRouter
(`QWEN_MAXTOK=16000 QWEN_REASONING=medium` Pflicht). Tool: `/root/.claude/tools/qwen.mjs`.
- Repo: **lokal** (git `master`), noch kein Remote. Ziel-Remote später zu entscheiden (Forgejo vs. neues GitHub).
## Phase 0 — Intent
**Produkt:** Quelloffene **Home-Assistant-Integration** (`what_to_wear`, HACS), die abends sagt, welche
**konkreten Kleidungsstücke aus dem Kleiderschrank** man für morgen rauslegen soll — deterministisch aus
der Wetterprognose, optional sprachlich schön formuliert, ausgespielt über Dashboard/Push/Ansage.
**Vertriebsprodukt** (läuft in jeder Kunden-HA, nicht bei uns).
## Design-Entscheidungen (Phase 0/Brainstorm, vom Benutzer freigegeben)
1. Form: Open-Source **HACS-Custom-Integration** `what_to_wear`, standalone in jeder Kunden-HA. Kein Vendor-Cloud.
2. Monetarisierung: **kostenlos/Open-Source** (Reichweite/Reputation, späterer Upsell via LagerLens).
3. Kleiderschrank: **Inventar echter Stücke** (nicht nur Kategorien), v1 nativ über **HA-Sub-Entries**.
4. LagerLens: **Vision C** — Standalone-Kern + **optionaler** LagerLens-Connector (v1.1), nicht Pflicht.
5. Logik: **Hybrid** — Regeln bestimmen *was*, optionaler **LLM** (Bring-your-own-Key, default AUS) den *Ton*.
6. Wetterquelle v1: **HA-Wetter-Entität** via `weather.get_forecasts`; Open-Meteo als Zweit-Provider v1.1.
7. Ausgabe: **Sensor + Service/Event**, read-only **Lovelace-Karte**, **2 Blueprints** (Push, TTS) — alle 3 Kanäle.
8. Sprache: **de + en**.
9. Name: **What to Wear (WTW)**, Domain `what_to_wear`.
## Kritiker-Konsultationen
### K1 — Wetterquelle (Design-Entscheidung), 2026-07-11
- **Modell:** `openai/gpt-5.6-luna-pro` (Cloud-Eskalation; vom Benutzer ausdrücklich erlaubt, da nuancierte
HA-Architekturfrage). **Geschickte Datei:** `scratchpad/wetterquelle-briefing.md`. Laufzeit 43 s, 10 Findings.
- **Mein Urteil:** alle 10 nach eigener Prüfung **berechtigt** (kein Fehlalarm; starkes Modell).
- **Kern-Findings → übernommen ins Design:**
- [KRITISCH] HA hat nicht zwingend eine Wetter-Entität → Config-Flow-Auswahl + klarer Fehlerzustand.
- [KRITISCH] `weather.get_forecasts` liefert kein einheitlich reiches Schema (`return_response=True`,
HA-Mindestversion; UV/gefühlt/Böen/Regen-Wkt. oft fehlend) → bewusste Normalisierung, „fehlend ≠ 0".
- [HOCH] Kein stiller Feld-Fallback zwischen Providern → Provider stabil pro Konfiguration.
- [HOCH] Open-Meteo nicht pauschal „kostenlos/kommerziell frei" + Koordinaten an Dritte → Opt-in +
Attribution + Coordinator/Timeout/Backoff (deshalb v1.1, nicht v1-Default).
- [HOCH] „Morgen" = lokales Kalenderdatum in `config.time_zone` (DST 23/25 h), nie `now+24h`.
- [HOCH] Einheiten mehr als °C/°F → festes SI-Internmodell, Werte nie aus Feldname/Größenordnung raten.
- [MITTEL] Provider-Interface = ok; automatisch-fehlertoleranter Open-Meteo-Zweig = Over-Engineering für v1.
- **Konsequenz:** v1 = nur HA-Wetter-Entität hinter kleinem `WeatherProvider`-Interface; Open-Meteo v1.1.
### K2 — Adversariale PRD-Validierung (4 Linsen, Multi-Agent-Workflow), 2026-07-11
- **Aufbau:** 4 parallele Reviewer-Linsen (rubrik / adversarial / edge-cases / sicherheit-recht,
Claude-Subagenten) auf PRD Rev. 1 + Addendum + Spec; semantisches Dedup; je Finding ein
Skeptiker-Agent mit Widerlegungsauftrag und Dokument-Evidenz. 36 Agenten, Detail-Reviews in
`docs/reviews/prd/review-*.md`.
- **Ergebnis:** 45 Roh-Findings → 31 nach Dedup → **28 CONFIRMED / 3 REFUTED / 0 PLAUSIBLE**.
- **Urteil:** Alle 28 bestätigten Findings in PRD Rev. 2 + Addendum Rev. 2 eingearbeitet
(Kernpunkte: Outfit-Komposition/Schichtsumme; Requirement→Stück-Mapping inkl. Streichung
`profilsohle`; Zieldatum-/Umschaltzeitpunkt-Logik statt Mitternachts-Kipp; Min-HA ≥ 2025.3
statt 2024.12 [faktisch falsch]; Karten-/Blueprint-Auslieferung HACS-konform; Degradations-
tabelle mit met.no-Realität; Prioritäten je Requirement; Offset-Semantik; kanonische
Entity-ID `sensor.what_to_wear`; Foto → v1.1 [E-4]; FR-6.5/6.6 Datenschutz + Injection;
Metriken messbar gemacht inkl. S-4 Retention; NFR-9 Lizenz / NFR-10 Migration;
single_config_entry; PRD als führend ggü. Spec deklariert).
- **3 Verwerfungen (mit Evidenz, wie von den Skeptikern belegt):**
1. „Wetter-Entität gelöscht/umbenannt undefiniert" — REFUTED: FR-3.4 + INV-1 decken den Fall
(Entität liefert keine Prognose → erklärender Fehlzustand).
2. „Markenrecht Home Assistant nicht adressiert" — REFUTED: NFR-5/S-3 schließen den
brands-Eintrag mit eigenem Icon ein; brands-Review erzwingt das.
3. „Blueprint-Sicherheitsleitplanken fehlen" — REFUTED: FR-7.4 (nur nutzergewählte Ziele) +
NFR-1/INV-5 legen die Leitplanken bereits fest.
### K3 — Architektur-API-Verifikation (4 Recherche-Agenten, 2026-07-11)
- **Auftrag E-2/NFR-4:** alle real genutzten HA-APIs gegen Core-Tag 2025.3.0 verifiziert (Subentries/
Config, weather.get_forecasts, Lovelace/HACS/Blueprints, Services/i18n/LLM/Testing) + Nachverifikation
(Subentry-Update-Listener, Coordinator-Shutdown/Lock). **Ergebnis: min-HA 2025.3 HALTBAR** (Subentry-
Reconfigure ab 2025.3 in nativer UI). Kernbefunde: get_forecasts liefert ANZEIGE-Einheiten; met.no nie
apparent_temperature (§3a-Fallback = Normalfall); kein strings.json für Custom-Integrationen;
SupportsResponse.OPTIONAL; API-Fallen 2025.3/2025.4/2025.8/2025.12/2026.2 dokumentiert.
Belege: `.memlog.md` (Einträge 1439) im Architektur-Workspace.
### K4 — Adversariale Architektur-Validierung (6 Linsen, 2026-07-11)
- **Aufbau:** 5 Reviewer-Linsen (Rubrik / Web-Verifikation / Inkompatibilitäts-Angriff / Edge-Cases /
Sicherheit-Recht) + Input-Abgleich (122 Punkte), parallel als Claude-Subagenten auf Spine Rev. 1.
Detail-Reviews: `_bmad-output/planning-artifacts/architecture/architecture-what-to-wear-2026-07-11/reviews/`.
- **Ergebnis:** Web-Verify 0 Sachfehler; Inkompat 17 Löcher (5 KRITISCH), Edge-Cases 16 Findings
(1 KRITISCH), Rubrik 10, Sicherheit 7, Abgleich 7 FEHLT/12 teilweise. **Alle Findings selbst geprüft:
sämtlich berechtigt** → Spine Rev. 2: neue AD-19…AD-24 (kanonischer Payload, Textmodell,
Requirement-Registry, Item-Schema, Options-Schema, Parsing-Besitz) + Schärfungen AD-1…AD-18.
### Gate 2 — Architektur (2026-07-11)
- **Modell:** `openai/gpt-5.6-luna-pro` via OpenRouter — **Ersatz für qwen** (qwen offline: Desktop
pingt, Port 8088 zu; gleiche Lage wie Gate 1, dortige Benutzer-Freigabe »weiter mit luna-pro«).
Laufzeit 69 s. **Geschickte Datei:** `ARCHITECTURE-SPINE.md` (Rev. 2) — ein Aufruf.
- **NICHT-melden-Liste (wörtlich):** »E-1 Lizenz Apache-2.0; E-2 minimale HA-Version 2025.3;
E-3 Forgejo-Entwicklungsquelle + öffentlicher GitHub-Push-Mirror als Vertriebskanal; E-4 kein
Foto-Feld in v1; PRD-Non-Goals v1 (Open-Meteo, LagerLens-Connector, CRUD-Karte,
Mehrpersonen-Profile, Rotation, Export/Import, Aktivitätskontext, Wäschestatus, Telemetrie);
die im Dokument als [PRÄZISIERUNG] markierten, im Ledger dokumentierten PRD-Abweichungen.«
- **Ergebnis:** 9 Findings (3 KRITISCH / 5 HOCH / 1 MITTEL). Urteile (Evidenz im Memlog E42):
1. Zeitbudget 10+25>30 s ✔ übernommen (LLM-Timeout 18 s + Budget-Arithmetik in AD-6)
2. Service-Frische-Race ✔ **teilweise** — Serialisierung in den Coordinator verlegt (asyncio.Lock
um `_async_update_data`); Verifikation zeigte: `async_refresh` startet eigenen Lauf, Risiko war
Überschreiben durch parallelen Alt-Lauf, nicht Warten
3. `coordinator.data is None` beim Erstfehler ✔ übernommen (Fehler-Contract in AD-7)
4. Event-/Response-Limits ✔ übernommen (strukturelle Begrenztheit + to_event-Assert, AD-19)
5. Semantische LLM-Manipulation ✖ **verworfen** als Architekturänderung (Evidenz: FR-6.6 final
definiert die objektiven Kriterien; semantische Validierung nicht objektiv entscheidbar) —
als dokumentiertes Rest-Risiko in AD-16 + README aufgenommen
6. LLM-Response-Body ungedeckelt ✔ übernommen (64-KB-Cap, AD-6)
7. AD-10↔AD-12-Widerspruch ✖ **verworfen** (Evidenz: AD-10 verbietet dict-ITEM-Zugriff
`hass.data["lovelace"]["…"]`, AD-12 nutzt Attribut-Zugriff `.mode`/`.resources`);
Stabilitätsrisiko-Teil ✔ teilweise (Ressourcen-Test im latest-CI-Job, AD-18)
8. Private Subentry-Helper ✖ **verworfen** als Blocker (Evidenz: offizielles Dev-Doku-Muster +
Kompat-Kaskade AD-9 + CI-Matrix fängt Drift); Migrations-Robustheit ✔ übernommen (AD-8)
9. Naive datetimes ✔ übernommen (verwerfen statt Zeitzone raten, AD-24)
- **Status: Gate 2 BESTANDEN** (nach Einarbeitung; Spine `status: final`).
### Phase 4 — Epics & Stories + Party-Mode (2026-07-13)
- **Artefakt:** `_bmad-output/planning-artifacts/epics.md` — 5 Epics, 23 Stories, alle mit
Given/When/Then + FR-/AD-Referenzen. Reihenfolge vorwärts-abhängigkeitsfrei; »15-Min-Wow«
(S-2/JTBD-3) landet Ende Epic 2.
- **Party-Mode (Pflicht, 4 Linsen parallel: Architekt/Dev/PM/UX):** Draft (5 Epics/19 Stories) →
reorganisiert (5 Epics/23 Stories). Alle Findings selbst geprüft, sämtlich berechtigt:
- **PM [KRITISCH]:** Golden-Path über 3 Epics verstreut → Epic 2 = »15-Minuten-Wow«
(Kleiderschrank+Karte+Service+Push zusammengezogen).
- **Architekt [HOCH]:** Story 1.6/1.7 zu groß → gesplittet (1.6 ha_entity+Provider-Port /
1.7 Coordinator / 1.8 Config-Flow+Lebenszyklus / 1.9 Sensor+Stale); `changed`/Store-Owner →
Coordinator (1.7); AD-23-Options-Schema einmalig in 1.1/const.py; CI-grün von Mirror entkoppelt.
- **Dev [HOCH]:** RawForecast/NormForecast ins Modell (1.1); WeatherProvider-Protocol-AC (1.6);
`__init__`-Unload-Lebenszyklus (1.8); NFR-7-Timing-Assert (1.7).
- **UX [KRITISCH]:** `stale`-Feld nutzersichtbar (Payload 1.5 + Sensor-Owner 1.9 + Karten-Render 2.3);
Erklärbarkeit im Volltext (1.5); Doku-Links in Config-Flow-Fehlern (1.8); Ton-Indikator auf Karte (2.3).
- LLM-Phraser (rein) von LLM-Client (Adapter) getrennt (4.2/4.3).
- **Readiness-Check (adversarial):** **READY-MIT-AUFLAGEN** → alle 34 FRs / 10 NFRs / 24 ADs in
≥ 1 Story-AC; alle 10 Party-Mode-Fixes bestätigt. 3 Auflagen eingearbeitet: (1) 1.8/1.9-
Plattform-Naht (1.8 testet mit `PLATFORMS=[]`, 1.9 schaltet Forwarding scharf → keine
Forward-Dependency); (2) NFR-1 expliziter Netz-Abstinenz-Assert im 5.3-E2E; (3) 1.4-Matcher
emittiert sprachneutrale Grund-Keys, 1.5 lokalisiert (AD-13). **epics.md `status: final`.**
- **Test-Umgebung provisioniert:** `.venv` (Python 3.13.11) + `pytest-homeassistant-custom-component
==0.13.225` (HA **2025.3.4** = Min-Ziel) + pytest 8.3.4. aiohttp==3.11.13 ist yanked → expliziter
Pin nötig (für CI-Requirements merken). Bereit für den Story-Loop.
### Dokumentierte PRD-/Addendum-Präzisierungen (Architekturphase, FR-4.1-konform)
1. FR-7.1 »LLM-Text in State/Karte« → State trägt max 255 Zeichen ⇒ `llm_text` nur in
Event + Service-Response; Karte v1 = Kurztext + Strukturdaten (AD-20).
2. Addendum §5 »Morgenfenster vollständig abdecken« → ≥ 1 hourly-Eintrag im Fenster (AD-17).
3. Addendum §5 Tagesaggregate »aus daily« → bei fehlendem daily deterministisch aus hourly (AD-17).
4. Addendum §8 »englische Namen via Translations« → `logic/texts.py` (hassfest-Schema, AD-13).
5. FR-2.1-UI-Kategorien → englisches Enum + Selector-Translations (AD-22).
6. Statusmenge: effektiv leerer Schrank ist kein Fehlerstatus, sondern Empfehlungsinhalt (AD-2).
### Gate 1 — PRD (2026-07-11)
- **Modell:** `openai/gpt-5.6-luna-pro` via OpenRouter — **Ersatz für qwen** (qwen offline:
Desktop pingt, Port 8088 zu; Benutzer-Freigabe »weiter mit luna-pro«). Laufzeit 58 s.
- **Geschickte Dateien:** `_bmad-output/planning-artifacts/prd.md` (Rev. 2) +
`_bmad-output/planning-artifacts/prd-addendum.md` (Rev. 2) — ein Aufruf.
- **NICHT-melden-Liste (wörtlich, wie an den Kritiker gesendet):** »kostenlos/Open-Source ohne
Telemetrie; Kleiderschrank als Inventar echter Stücke; LLM-Ton optional per
Bring-your-own-Key (default aus); Wetterquelle v1 ausschließlich HA-Wetter-Entität;
LagerLens-Connector, Open-Meteo, CRUD-Karte, Mehrpersonen-Profile, Foto-Feld, Rotation,
Export/Import erst v1.1; Zielplattform Home Assistant; Sprachen de+en.«
- **Ergebnis:** 12 Findings (3 KRITISCH / 5 HOCH / 4 MITTEL). **Eigenes Urteil: alle 12
übernommen** (2 davon teilweise, s. u.). Fixes in PRD Rev. 3 + Addendum Rev. 3:
1. Umschaltzeitpunkt-Recompute exakt (FR-7.5) ✔ übernommen
2. Forecast-Zeitzuordnung/TZ + prognose_stand (Addendum §5, FR-7.1) ✔ **teilweise** — der
Teil »frisch berechnet, aber Provider-Forecast veraltet« ist über prognose_stand-Attribut
sichtbar gemacht, ein hartes Alters-Limit der Provider-Prognose bewusst nicht definiert
(Evidenz: HA liefert kein verlässliches Prognose-Erzeugungsdatum über get_forecasts;
Erkennbarkeit statt Blockade).
3. FR-6.6 objektive Abnahmekriterien + Injection-Testkatalog ✔ übernommen
4. Service-Response statt Event-Race (FR-7.2) ✔ übernommen
5. Test-Abruf: Timeout + 4. temporärer Fehlerfall (FR-1.2) ✔ übernommen
6. Subentry-Annahme ✔ **teilweise** — CI-Test gegen Minimalversion ergänzt (NFR-4);
Migration/Löschen bereits durch NFR-10/FR-2.4 abgedeckt (Evidenz: Zitate ebd.).
7. Zusammengesetzte Wärmebedarf-Lücke + Konfliktvorrang (Addendum §4.6) ✔ übernommen
8. Lovelace-Registrierung präzisiert: idempotent, ?v=, YAML-Erkennung, Deregistrierung ✔
9. Options-Struktur + Offset-Scope (nur °C-Schwellen) (FR-1.4/4.2) ✔ übernommen
10. Attribut-/Event-Größengrenzen (FR-7.1) ✔ übernommen
11. Sprachbindung dynamisch vs. registriert (FR-1.3) ✔ übernommen
12. Beispiel-Set normativ (Addendum §8 + Abnahmetest) ✔ übernommen
- **Status: Gate 1 BESTANDEN** (nach Einarbeitung; PRD `final` nach Benutzerfreigabe E-1…E-4).
## Gates (Status)
| Gate | Gegenstand | Modell | Datei(en) | Status |
|---|---|---|---|---|
| — | Wetterquelle (Design) | luna-pro | wetterquelle-briefing.md | ✅ verifiziert |
| — | PRD adversarial (4 Linsen) | Claude-Workflow | prd.md, prd-addendum.md, Spec | ✅ 28/31 übernommen |
| Gate 1 | PRD Rev. 2→3 | **luna-pro (qwen-Ersatz)** | prd.md + prd-addendum.md | ✅ bestanden, 12/12 übernommen |
| Gate 2 | Architektur Rev. 2 | **luna-pro (qwen-Ersatz)** | ARCHITECTURE-SPINE.md | ✅ bestanden, 9 Findings (6 übernommen, 3 verworfen mit Evidenz) |
| Gate 3 | Code (Security) | qwen/luna | — | offen |
**Architektur-Artefakt:** `_bmad-output/planning-artifacts/architecture/architecture-what-to-wear-2026-07-11/`
(`ARCHITECTURE-SPINE.md` final, 24 ADs; `.memlog.md` 43 Einträge; `reviews/` 6 Linsen). Paradigma:
Pipes-and-Filters-Kern (`logic/`, hass-frei) in Ports-and-Adapters-Schale.
## Stories (Phase 5 — Story-Loop; TDD, luna-pro-Review je Story, ein Commit je Story)
> qwen offline → Story-Reviews mit luna-pro (dokumentierter Ersatz). Umgebung: `.venv` Py3.13 + HA 2025.3.4.
- **1.1 Skeleton/Model/const/Guard** ✅ — 19 Tests grün. Dateien: `logic/model.py`, `const.py`,
`__init__.py`, `logic/__init__.py`, `manifest.json`, `hacs.json`, `tests/{test_layering,
test_const_schema, logic/test_model}.py`. **luna-pro-Review:** 6 Findings, 4 übernommen
(default_options()-Deepcopy F3; condition eigenes Feld F4; gaps_count/alert abgeleitet statt
gespeichert F5; Guard auf AST F6), 2 verworfen mit Evidenz (F1 RawForecast transient/nicht
signiert → keine Tief-Immutabilität nötig; F2 Schema-Validierung ist Story 3.1/3.2-Scope).
- **1.2 Forecast-Normalizer** ✅ — 31 Tests grün. `logic/normalize.py` + `tests/logic/test_normalize.py`.
**luna-pro-Review:** 8 Findings, 6 übernommen (u. a. **echter Bug F2:** UTC-Zeitstempel müssen vor
Morgenfenster-Auswertung nach `ZoneInfo(time_zone)` konvertiert werden; F3 Plausibilität pro Element
vor Aggregation; F4 naive datetimes verwerfen; F5 `_num`-Typguard; F6 kein bool als Messwert;
F8 condition-Fallback per Schwere-Max), 1 teils (F1 lightning-rainy→gewitter behalten, rules-korrekt +
dokumentiert), 1 verworfen mit Evidenz (F7 gemischte Per-Eintrag-Einheiten treten bei HA nicht auf).
- **1.3 Regel-Engine** ✅ — 47 Tests grün. `logic/rules.py` + `tests/logic/test_rules.py`. Doku-Fix:
Offset-Beispiel in epics.md korrigiert (Richtung war verkehrt: +1 = Schwellen +2 °C nach oben ⇒ bei
9 °C Wärmegrad 3→4, mehr Wärme). **luna-pro-Review:** 1 Finding **verworfen mit Evidenz** (Schnee +
temp fehlt → warmth:4 ist korrekt: §3a entfernt nur die Temperatur-Band-Regel, Schnee ist »übrige
Regel« mit Wärme-Floor; temp_missing-Note bleibt richtig).
## Offene Punkte / nächste Schritte
- Phase 4: Epics & Stories aus dem Spine + Party-Mode + Readiness-Check.
- Autonomer Durchlauf (Goal-Hook, Benutzer abwesend): qwen offline → Gates mit luna-pro (dokumentiert).
- Bei Phase 7: irreversible/identitätskritische Schritte (GitHub-Mirror-Setup E-3, brands-PR,
HACS-Default-Antrag, Version-Tag/Release) dem Benutzer vorlegen, nicht selbst ausführen.
- **1.4 Matcher** ✅ — 59 Tests grün (inkl. normativer §8-Frost/Regen-Abnahmetest). `logic/matcher.py`
+ `tests/logic/test_matcher.py`. **luna-pro-Review:** 7 Findings, 6 übernommen (F1 §4.6-Prioritäts-
konflikt Wärme↔Attribut; F2 Attribut auch auf Top/Pullover; F4 Wärme-Lücken bei fehlender
Hose/Schuhe/Top; F5 sturdy_shoes bevorzugt jede wasserdichte; F6 Tiebreak-Determinismus; F7
Lücken-Priorität aus Requirement), 1 verworfen mit Evidenz (F3 Schicht-Minimierung würde §4.2-
Reihenfolge + §8-Abnahmetest brechen).
- **1.5 Texte & Payload-Projektionen** ✅ — 72 Tests grün. `logic/texts.py`, `logic/assemble.py`,
Projektionen in `logic/model.py` + `tests/logic/test_texts_payload.py`. Volltext webt Gründe +
Datenlage ein; `to_payload`/state/attributes/event/response mit Shedding-Kaskade; `compute_signature`.
**luna-pro-Review:** 6 Findings, 5 übernommen (F1/F6 finaler Größen-Garant nach Shedding; F3
feels_like_min/max ins Payload; F4 Signatur über Item-Identität statt nur Namen; F5 texts._lang-Subtag),
1 verworfen mit Evidenz (F2 HA nutzt orjson-UTF-8 ohne \uXXXX → ensure_ascii=False misst korrekt + 1-KB-Puffer).
**→ Epic 1 Kern (logic/) komplett; als Nächstes HA-Adapter (1.6 Wetter-Adapter).**
- **1.6 Wetter-Adapter (Port + HA-Entität)** ✅ — 79 Tests grün (phcc). `weather/provider.py`
(Protocol), `weather/ha_entity.py`, `weather/errors.py` + `tests/conftest.py` +
`tests/test_weather_adapter.py`. get_forecasts (blocking+return_response), Feature-Check,
Zeit-Parsing/Verwerfen naiver, Einheiten aus State (AD-17/24). **luna-pro-Review:** 4 Findings,
alle übernommen (F1 Struktur-Guards [graceful-empty bewusst behalten], F2 Eintrags-Mapping-Guard,
F3 aware via utcoffset(), F4 int()-Guard).
- **1.7 Coordinator (einziger Mutator)** ✅ — 97 Tests grün. `coordinator.py`, `logic/pipeline.py`,
`logic/schedule.py` + `tests/{test_coordinator, logic/test_pipeline, logic/test_schedule}.py`.
Lock, Zieldatum/Umschaltzeit (halboffen), asyncio.timeout(10), Fehler-Contract, changed via Store,
last_success_utc, Item-Aufbau aus Subentries, Timing-Assert <5s (AD-2/3/7). **luna-pro-Review:**
6 Findings, 5 übernommen (F2+F3 einheitlicher Fehler-Contract für Abruf- UND no-coverage-Fehler;
F4 changed=stored!=sig; F5 _bool-Coercion; F6 parse_switchover tz strippen), 1 verworfen mit Evidenz
(F1 `datetime.time()` ist naiv, kein TypeError — Reviewer verwechselt mit `.timetz()`).
- **1.8 Config-Flow + Entry-Lebenszyklus** ✅ — 105 Tests grün (phcc). `config_flow.py`, `__init__.py`,
`translations/{en,de}.json`, `requirements-test.txt` + `tests/test_config_flow.py`. Test-Abruf mit
4 übersetzten Fehlern, single_config_entry, Setup/Unload/Listener leak-frei (PLATFORMS=[]), Doku-Links
in Meldungen. **Umgebungs-Fix:** `home-assistant-frontend==20250306.0` nötig (frontend-Dependency);
phcc-`hass_storage`-Fixture gegen Lingering-Timer. **luna-pro-Review:** 3 Findings, 2 übernommen
(F1 breitere Fehlerklassifikation→temporary; F2 Refresh auch bei entfernter Entität), 1 verworfen mit
Evidenz (F3 HA räumt on_unload bei Setup-Fehler selbst; PLATFORMS=[] kann nicht fehlschlagen).
- **1.9 Sensor** ✅ — 108 Tests grün (phcc). **EPIC 1 KOMPLETT.** `sensor.py`, `__init__.py`
(PLATFORMS=[SENSOR]), `translations/*` (entity-Name) + `tests/test_sensor.py`. Stabile ID
sensor.what_to_wear, Stale-Besitz (Einmal-Timer), leerer Schrank=ok, kein llm_text in Attributen
(AD-14/15/2). **luna-pro-Review:** 3 Findings, 2 übernommen (F2 1s-Timer-Margin; F3 _stale_fired
räumt _stale_unsub nicht mehr → kein Handle-Verlust bei Race), 1 verworfen mit Evidenz (F1 to_state()
schneidet bereits auf 255).
## Epic 2 — Der 15-Minuten-Wow
- **2.1 Kleidungsstücke verwalten (Sub-Entries)** ✅ — 114 Tests grün. `config_flow.py`
(ItemSubentryFlow user+reconfigure, Kompat-Accessor), `__init__.py`/`coordinator.py`
(Options-vs-Subentry-Listener via Snapshot), `translations/*` (config_subentries+selector).
**luna-pro-Review:** 4 Findings, 3 übernommen (F1 float-Guard in Validierung; F2 warmth round+clamp;
F3 Eingaben nach Fehler erhalten), 1 verworfen mit Evidenz (F4 Listener-Serialisierung — HA ruft
sequenziell, In-Flight-Refresh harmlos, seltene manuelle Aktion; v1-Vereinfachung).
- **2.2 Beispiel-Kleiderschrank** ✅ — 118 Tests grün (stabil ×5). `logic/texts.py` (example_items,
12 §8-Stücke lokalisiert, keine Handschuhe), `config_flow.py` (create_example-Option, subentries=),
`translations/*`, `tests/test_example_set.py` (inkl. §8-Frost/Regen-Abnahmetest mit echtem Set).
Test-Infra-Fix: conftest entlädt WTW-Entries nach jedem hass-Test (kein Coordinator-Timer-Flake).
**luna-pro-Review:** 2 Findings, beide übernommen (F1 create_example-Default-Fallback; F2 _lang/
pick_language auch Unterstrich-Locales de_DE).
- **2.3 Lovelace-Karte + Auto-Registrierung** ✅ — 122 Tests grün. `frontend.py`, `www/what-to-wear-card.js`,
`__init__.py` (async_setup_card + async_remove_entry) + `tests/test_frontend_card.py`. StaticPath +
Ressource idempotent (Storage-Mode, Attribut-Zugriff), Deregistrierung bei Remove; Karte XSS-sicher
(nur textContent, kein innerHTML), stale+tone-Indikator (AD-12). **luna-pro-Review:** 7 Findings,
6 übernommen (F1 customElements-Guard; F2+F4 asyncio.Lock gegen Registrierungs-Race; F5 Zieldatum+Label;
F6 Platzhalter nutzt this._entity; F7 null-Eintrags-Guards), 1 verworfen mit Evidenz (F3 manifest-
dependency garantiert lovelace-Verfügbarkeit).
- **2.4 Service recommend + Event** ✅ — 127 Tests grün. `services.py`, `services.yaml`,
`translations/*` (services), `__init__.py` + `tests/test_service.py`. SupportsResponse.OPTIONAL,
ungedrosselter async_refresh (Coordinator-Lock serialisiert), Event immer, stale-Stempelung,
Frische-/Parallel-Test (AD-11). **luna-pro-Review:** 4 Findings, 2 übernommen (F1 stale aus
last_success_utc stempeln; F4 eigene Registrierung per Flag), 2 verworfen mit Evidenz (F2 frühe
Returns unerreichbar da Service nur mit Coordinator registriert + data nie None; F3 async_refresh
propagiert keine Exceptions, to_event/response projizieren jeden Status).
- **2.5 Blueprints Push & TTS** ✅ — 130 Tests grün. **EPIC 2 KOMPLETT.**
`blueprints/automation/what_to_wear/{notify_push,announce_tts}.yaml` + `tests/test_blueprints.py`
(deklaratives Artefakt: Struktur-/YAML-Validierung statt rotem Test). min_version 2025.3.0,
device/time/boolean bzw. tts/media_player/trigger-Selektoren, what_to_wear.recommend via
response_variable, changed/gaps_count/alert-Bedingung (FR-7.4/AD-11/20). **luna-pro-Review:**
6 Findings, 5 übernommen (F1F4 Template-Härtung mit .get()+Defaults; F5 continue_on_error am
notify), 1 verworfen mit Evidenz (F6 to_response ist flach, kein Nesting).
## Epic 3 — Anpassung & Robustheit
- **3.1 Options-Flow** ✅ — 135 Tests grün. `config_flow.py` (WhatToWearOptionsFlow, async_get_options_flow,
async_test_entity-Refactor mit switchover-Param), `coordinator.py`/`__init__.py` (data-Snapshot →
Reload auch bei Entity-Wechsel), `translations/*` (options+abort). Schwellen einzeln, Bandgrenzen
strikt monoton, TimeSelector HH:MM:SS, Entity-Wechsel mit Test-Abruf (AD-23/8/10). **luna-pro-Review:**
7 Findings, alle 7 übernommen (F1+F7 atomares async_update_entry+abort → ein Reload; F2 switchover an
Test-Abruf; F3 HH:MM:SS-Normalisierung; F4 try um Konvertierung; F5 alle Schwellen typ-normalisiert;
F6 Options aus current-Kopie → LLM-Block bewahrt).
- **3.2 Schema-Versionierung & Migration** ✅ — 141 Tests grün. `__init__.py` (async_migrate_entry
v1→v1 no-op), `const.py` (sanitized_options: fehlend→Default, typfalsch→Default+Warn, unbekannt→
erhalten, nie Abbruch; Bereichs-/NaN-/HH:MM:SS-Prüfung), `coordinator.py` (robustes _options) +
`tests/test_migration.py`. **luna-pro-Review:** 5 Findings, 4 übernommen (F1 Switchover-Listener
nutzt Sanitizer; F2 Bereichs-/isfinite-Prüfung; F3 HH:MM:SS-Validierung; F4 non-Mapping-Guard),
1 verworfen mit Evidenz (F5 VERSION/MINOR_VERSION sind in config_flow.py gesetzt). **Eigener Fund
beim Verifizieren:** F4-Guard `isinstance(dict)` verwarf `MappingProxyType` (entry.options) → auf
`Mapping` korrigiert (Test fing es).
## Epic 4 — Optionaler LLM-Ton
- **4.1 LLM-Optionen & Datenschutz-Disclosure** ✅ — 148 Tests grün. `config_flow.py` (LLM-Sektion im
Options-Flow: Passwort-Selector, leer=behalten, Deaktivieren=löschen, Provider-Whitelist),
`translations/*` (Disclosure de+en + Labels) + `tests/test_llm_options.py` (AD-5/FR-6.1/6.5).
**luna-pro-Review (Security):** 4 Findings, 3 übernommen (F1 Key .strip() gegen Whitespace-Overwrite;
F2 Deaktivieren löscht Key; F3 Provider-Whitelist serverseitig), 1 verworfen mit Evidenz (F4 Disclosure
liegt in Übersetzungen options.step.init.description, per Test verifiziert).
- **4.2 LLM-Phraser & Injection-Katalog (rein)** ✅ — 157 Tests grün. `logic/phraser.py` +
`tests/logic/test_phraser.py`. build_prompt (nur strukturierte Daten, JSON-escaped Untrusted-Namen,
Ignorier-Instruktion), validate (ae, fail-closed→Regeltext), Injection-Katalog (AD-16/FR-6.6).
**luna-pro-Security-Review:** 5 Findings, 4 übernommen (F1 full_text-summary entfernt; F2 strengere
URL/Scheme/Entity-Sperre; F3 Steuerzeichen+Markdown verboten; F4 leere Namen übersprungen),
1 verworfen mit Evidenz (F5 items+gaps-beide-leer unerreichbar bei status=ok).