# CLAUDE.md — GerbilManager Leitfaden für die Arbeit an diesem Repo. Bitte vor Änderungen lesen. ## Was ist das? Verwaltungs-App für eine **Rennmaus-Zucht** („Zucht der kleinen Chaoten"): Tiere, Würfe, Stammbäume/Ahnentafeln, Genetik (Farbschläge/Genotypen), Gehege, Kontakte (Züchter/Abnehmer), Abgabeverträge, Statistik und eine öffentliche CMS-Webseite. Single-User (die Züchterin), Daten größtenteils aus bestehenden Quelldateien importiert. ## Architektur / Projekte | Projekt | Zweck | |---|---| | `GerbilManager.AppHost` | **.NET Aspire** Orchestrator — startet PostgreSQL (Container) + WebAPI | | `GerbilManagerWebAPI` | Backend: ASP.NET **Minimal API**, EF Core + Postgres, Endpoints in `Endpoints/`, Entities in `Models/`, DTOs in `Dtos/` | | `GerbilManager.ServiceDefaults` | Aspire-Defaults (Telemetry/Health) | | `GerbilManager.Tests` | Backend-Tests (xUnit) | | `gerbil-manager-web` | Frontend: **React + Vite + TypeScript** (React Router, react-d3-tree für den Stammbaum) | | `tools/import` | **Python**-Import-Pipeline (zero-dep), wandelt Quelldateien → `resolved_import.json` | ## App starten (IMMER über Aspire, nicht nur die WebAPI) ```bash dotnet run --project GerbilManager.AppHost ``` - WebAPI: http://localhost:5179 · OpenAPI: `/openapi/v1.json` - Aspire-Dashboard: https://localhost:17004 (Login-Token steht im AppHost-Log) - EF-Migrationen werden beim Start automatisch angewandt (`db.Database.Migrate()` in `Program.cs`). - Postgres läuft als Container (Name wechselt pro Run, z. B. `postgres-xxxx`); die **Daten liegen in einem persistenten Volume** → überleben Neustarts. DB-Name: `gerbilmanager`. ## Import → DB (3-stufig, WICHTIG) Die DB wird **nicht** direkt von Python beschrieben. Nach jeder Import-Code-Änderung neu generieren UND neu einspielen, sonst zeigt die App den alten Stand. ```bash # 1. Stammbäume + Wurfchronik → animals.json/litters.json (+ Fotos) python tools/import/extract.py # 2. Abgabeverträge (DOCX) → contracts.json python tools/import/extract_contracts.py # 3. Dedup, Eltern-Resolver, Provenance, Vertrags-Anreicherung → resolved_import.json python tools/import/merge_and_resolve.py # 4. In die laufende DB laden (API muss laufen): WISCHT + lädt neu curl -X POST http://localhost:5179/import/ingest-resolved ``` - **`IngestResolvedService` wischt** Gerbils/Litters/Contacts/Photos/Health/Weights/ **SaleContracts** und lädt aus `resolved_import.json` neu. Importierte SaleContracts sind Teil des Payloads → werden bei jedem Ingest neu erzeugt (idempotent, IDs deterministisch). - **Feedback überlebt den Ingest** (wird nicht gewischt) — daher hat `Feedback` nur **lose, nullable Guid-Spalten ohne FK** (GerbilId/LitterId/ContactId). Dieses Muster für alles nutzen, was Re-Ingests überleben soll. - `resolved_import.json` ist **gitignored** (Runtime-Output); `review-report.md` ist getrackt. - Deterministische GUIDs via `generate_guid("...")` → stabile IDs über Re-Ingests. ### Quelldateien (lokal/Netzwerk) - Stammbäume: `C:\Users\gulum\dev\Sttammbäume\*.xlsx` - Wurfchronik: `C:\Users\gulum\dev\Wurfchronik der Kleine Chaoten Teil1.xlsx` - Abgabeverträge: `\\truenas\Datengrab\Rennmäuse\Verträge\*.docx` (~1,4k; kann zeitweise offline sein) ## Build & Test **Frontend** (`cd gerbil-manager-web`): ```bash npx tsc --noEmit # Typecheck (muss sauber sein) npx vitest run # Unit-Tests npx eslint # Lint npx playwright test [spec]# e2e (Projekte: desktop + phone), läuft gegen Mock-API ``` - e2e nutzt einen **Mock-Modus** (`e2e/mock-api.ts` + `e2e/mock-data.ts`), kein echtes Backend. Neue API-Routen/Felder dort spiegeln. `skipUnlessMock()` am Testanfang. **Backend**: ```bash dotnet build dotnet test # GerbilManager.Tests dotnet ef migrations add --project GerbilManagerWebAPI # neue Migration ``` - Zum Bauen muss eine laufende WebAPI ggf. gestoppt werden (Datei-Lock auf `bin`). **Python** (`cd tools/import`, zero-dep, exit 0 = pass): ```bash python test_extract.py test_extract_docx.py test_genotype.py test_merge_resolve.py test_extract_contracts.py ``` ## Konventionen (bitte einhalten) - **Alle deutschen UI-Texte in `gerbil-manager-web/src/strings/de.ts`** — niemals in Komponenten hartkodieren. - **EF-Migration** für jede Schema-Änderung; wird beim Aspire-Start angewandt. - **Provenance/Datenherkunft** liegt als **JSON-Text-Spalte** (`Provenance`) auf Gerbil/Contact/Litter → JSON-Feld-Erweiterungen brauchen **keine** Migration. - **Lose nullable Guid-Spalten ohne FK** für Tabellen, die den Ingest-Wipe überleben. - Commits: nur wenn der Nutzer es verlangt; auf `main` wird gearbeitet (Remote: `http://truenas:13000/Gulum/GerbilManager.git`). Commit-Messages auf Deutsch, Format `feat(scope): …`. - Subagenten committen NICHT — Haupt-Loop verifiziert und committet. ## Domänenwissen (Rennmaus-Zucht) - **Lebensspanne ~6 Jahre** → ein Elternteil kann max. ~6 Jahre älter als sein Kind sein und muss vorher geboren sein (`parent_age_plausible` im Import; verwirft unplausible Eltern-Links). - **Geschwisterverpaarung** (Vollgeschwister als Eltern): erkannt, wenn Vater & Mutter **dieselbe `litterId`** ODER **dasselbe Geburtsdatum** teilen (gleiches DOB = starkes Indiz). UI: Diagramm führt den Vorfahren-Ast zusammen (Verweis-Knoten); Akte zeigt „⚭ Geschwisterverpaarung"-Chip. - **Box-Farbe im Stammbaum-xlsx = Geschlecht**: weiß = weiblich, blau = männlich. - **Genotyp/Farbschlag**: 8-Locus-Notation (siehe `tools/import/genotype.py`, `gerbil-manager-web/src/genetics`). Unbekanntes Allel = `-` (nicht `?`). E-Locus: `ee`=Fuchs, `eef`=Fuchsschimmel, `efef`=Schimmel. Rezessiver Fuchs ist zwingend homozygot → `e-` ist ungültig (wirft), `ee[-]`→`ee`. - **Genetik-Engine = korrektheitskritisch: IMMER vollständig mit Tests absichern.** Jede Änderung an `src/genetics/**` (+ Backend-Mirror + `genotype.py`) braucht pro Use-Case/Ticket einen Regressionstest (`src/genetics/__tests__/genetics.test.ts` und `tools/import/test_genotype.py`), damit nie eine Regression entsteht. `npx vitest run` + `python test_genotype.py` müssen grün sein, bevor „fertig". - **Stammbaum-Charts**: Generationen = Spaltenbänder (Proband links in Spalte B/2, je Generation +3 Spalten); Eltern-Position: Vater oben, Mutter unten. Fotos liegen je nach Datei links/auf der Namenszelle — die Seite wird heuristisch über die geringste Spalten-Fehlausrichtung bestimmt (`extract._attach_photos`). ## Stolperfallen - **Aspire-Containername wechselt** pro Run, das Daten-Volume bleibt → DB-Inhalt überlebt Neustarts (auch alte Schema-Stände → Migrationskonflikte möglich, s. u.). - **Migrations-Historie vs. committete Migration-IDs**: Wird eine Migration angewandt und danach mit neuem Zeitstempel neu generiert, sieht EF sie als „pending" und der Start crasht („column … already exists"). Schema ist dann korrekt — nur die `__EFMigrationsHistory`-IDs müssen an die committeten Dateinamen angeglichen werden. - **PowerShell + psql**: PascalCase-Identifier (`"__EFMigrationsHistory"`) über PowerShell→docker→psql zerschießen das Quoting. Stattdessen Bash-Heredoc: `docker exec -i -e PGPASSWORD=$PW psql -U postgres -d gerbilmanager <<'SQL' … SQL`. Passwort: `docker exec printenv POSTGRES_PASSWORD`. - **Direkte DB-Schreibzugriffe** (psql) werden vom Auto-Mode-Classifier blockiert — vorher beim Nutzer rückversichern. - Bash-Tool ist **Git-Bash (POSIX)**, das andere Tool ist **PowerShell** — je eigene Syntax. ## Wo liegt was (Feature-Landkarte) - **Stammbaum-Viewer**: `gerbil-manager-web/src/pages/StammbaumPage.tsx`, `src/pedigree/*` (build.ts = Baum-Aufbau + Geschwister-Erkennung; types.ts). Desktop passt den ganzen Baum ein; Touch: Long-Press öffnet das Kontextmenü (ID kopieren / Fehler melden). - **Datenherkunft/Nachverfolgung**: `src/components/ProvenanceDialog.tsx`; erzeugt im Import via `build_entity_provenance`/`_build_*_history` (chronologische, datei- attribuierte `history[]`, inkl. „⚠ … verworfen — Grund; … verwendet"). - **Fehler melden (Feedback)**: `src/components/ReportErrorDialog.tsx`, `src/api/feedback.ts`, Backend `Endpoints/FeedbackEndpoints.cs` + `Models/Feedback.cs`. - **Verträge**: `Models/SaleContract.cs`, `Endpoints/ContractEndpoints.cs`, `ContractGenerator.cs`, Frontend `src/pages/Vertraege*`; Import: `extract_contracts.py` + `enrich_from_contracts` in `merge_and_resolve.py`. - **Import-Kernlogik**: `tools/import/merge_and_resolve.py` (Dedup, Eltern-Resolver mit Gender-/Alters-Plausibilität, Litter-/Sibling-Dedup, Provenance, Vertrags-Anreicherung). ## Verifikations-Checkliste vor „fertig" 1. `npx tsc --noEmit`, `npx vitest run`, `npx eslint` sauber. 2. Betroffene `npx playwright test`-Specs grün. 3. `dotnet build` + `dotnet test` grün. 4. Bei Import-Änderungen: `python test_*.py` grün + `resolved_import.json` plausibel (Stichprobe), danach App neu starten + `POST /import/ingest-resolved`. ## Gedächtnis (MemPalace) — IMMER nutzen Dieses Projekt nutzt **MemPalace** als KI-Langzeitgedächtnis (lokal, kein API-Key). Die Anbindung ist im Repo persistiert und lädt automatisch: - `.mcp.json` (committet) registriert den MCP-Server `mempalace-mcp` → **19 MCP-Tools** (`mempalace_search`, `mempalace_kg_query/add/invalidate/timeline`, `mempalace_add_drawer`, `mempalace_diary_*` …). Aktiv ab Claude-Neustart (MCP-Server einmal bestätigen). - `.claude/settings.json` (committet) verdrahtet die Hooks `mempalace hook run --hook {session-start,stop,precompact} --harness claude-code`: **SessionStart** lädt relevanten Kontext automatisch, **Stop/PreCompact** persistieren. - Voraussetzung: `mempalace` ist installiert und auf PATH (`~/.local/bin`). Nach `git clone` ggf. MemPalace installieren; `mempalace.yaml` (Raum-Config) ist bewusst **gitignored** (per-project, lokal). Der Palast selbst liegt lokal unter `~/.mempalace` (nicht im Repo). **Arbeitsweise pro Session:** zu Aufgabenbeginn relevantes Wissen abrufen (`mempalace_search` / `mempalace_kg_query`), bevor du im Code suchst; am Ende neue durable Erkenntnisse ablegen. **Subagenten MÜSSEN MemPalace ebenfalls nutzen** — das im Subagent-Prompt IMMER explizit anweisen: zu Aufgabenbeginn den Palast abfragen (am einfachsten per CLI, kein Key nötig: `mempalace search ""` bzw. `mempalace search "<…>" --wing gerbilmanager`; falls MCP-Tools verfügbar auch `mempalace_search`/`mempalace_kg_query`) — relevante Domänenregeln, frühere Entscheidungen und Stolperfallen holen, BEVOR im Code gesucht wird; durable Erkenntnisse am Ende ablegen (`mempalace add-drawer`/MCP). Gilt für JEDEN gespawnten Agenten. ### Memory-Policy — WANN / WIE / WO eine Memory anlegen **WANN** (anlegen): nur **dauerhaft** nützliches, **nicht-offensichtliches** Wissen, das künftige Sessions braucht — Architektur-/Design-Entscheidungen und ihr *Warum*, Domänenregeln (z. B. 6-Jahre-Lebensspanne, Geschwisterverpaarung), Konventionen, Stolperfallen, bestätigte Nutzer-Präferenzen, wiederkehrende Workflows. **NICHT anlegen**: transienter Task-Status; was Code/Git/CLAUDE.md ohnehin festhalten; Dinge, die nur für das aktuelle Gespräch zählen. **WIE** (anlegen/pflegen): - Fakt mit Gültigkeit → `mempalace_kg_add`. Freitext-Notiz/Erkenntnis → `mempalace_add_drawer` (vorher `mempalace_check_duplicate`). - Ändert/erledigt sich ein Fakt → `mempalace_kg_invalidate` (setzt End-Datum, **nicht löschen** — der Graph ist temporal). So bleibt nichts veraltet stehen. - Repo-Dateien werden über `mempalace mine ` automatisch gefilet; `mempalace sync --apply --wing gerbilmanager` entfernt Drawers gelöschter/verschobener Dateien. **WO** (welcher Speicher): - **MemPalace-Palast** (`~/.mempalace`, lokal): reichhaltiges, durchsuchbares Recall — der Default für Memories. - **CLAUDE.md** (Repo, committet): die **kanonischen** Projektfakten/Konventionen, die geteilt und versioniert sein müssen. Wichtige durable Erkenntnisse hier spiegeln, damit sie im Repo (nicht nur lokal) liegen. Faustregel: durable + projektweit kanonisch → **CLAUDE.md** *und* Palast; sonstiges durable Wissen → Palast; veraltetes → invalidieren, nicht stehen lassen.