CI fuer Gitea Actions: Build und Tests auf Linux plus Waechter gegen Rueckfaelle

Die Plattformneutralitaet war nach dem WinForms-Ausbau eine Momentaufnahme: eine
einzige net10.0-windows-Zeile oder ein "using System.Drawing" genuegt, und der
Linux-Build ist kaputt, ohne dass es auf einer Windows-Maschine auffaellt - dort
baut es weiter. Genau das faengt die CI ab, und zwar auf Linux.

.gitea/workflows/ci.yml, zwei Jobs auf ubuntu-latest:

- build-test: restore, build, die 476 Tests (brauchen keine DB, laufen gegen
  EF-InMemory), Linux-Publish. Der Publish ist kein Selbstzweck - dort faellt
  auf, wenn ein Paket doch windows-only ist. Anschliessend wird geprueft, dass
  die Executable, appsettings.json, setup.json und libSkiaSharp.so wirklich im
  Ergebnis liegen.
- guard: keine windows-TFMs, kein UseWindowsForms/UseWPF, keine windows-only
  using-Direktiven, keine versionierten Secret-Dateien, keine anfaelligen Pakete.

Alle Waechter sind in beide Richtungen gegengeprueft: sie schlagen bei
simulierten Regressionen an (windows-TFM, UseWindowsForms, using System.Drawing,
getrackte packager.config.json samt dc_sub_-Token, Newtonsoft 11.0.2) und
schweigen beim Ist-Zustand. Die Namespace-Pruefung trifft bewusst nur echte
using-Direktiven: im Bestand steht an vielen Stellen erklaert, WARUM
System.Drawing nicht verwendet wird, und das darf keinen Fehlalarm ausloesen.

Der Schwachstellen-Check ist Punkt 1 der wiederkehrenden Audit-Checkliste aus
dem Sicherheitskonzept - laeuft ab jetzt bei jedem Push statt quartalsweise von
Hand. Er wuerde zum Beispiel anschlagen, wenn der Newtonsoft-Pin im Core faellt.

Bewusst kein -warnaserror: die 15 vorhandenen Warnungen muessten erst weg, sonst
ist die CI ab dem ersten Tag rot und wird ignoriert.

WICHTIG - die CI laeuft noch nicht: auf der Gitea-Instanz ist kein Actions-Runner
registriert (auf Repo-, Benutzer- und Instanzebene geprueft, ueberall 0).
has_actions ist true, es fehlt nur der Runner. Einrichtung Schritt fuer Schritt
in docs/LEITFADEN-CI.md Abschnitt 3.

.gitattributes neu: erzwingt LF fuer .yml/.sh/.service. Entwickelt wird mit
autocrlf=true auf Windows, ausgefuehrt auf Linux - ein Shell-Skript mit CRLF
scheitert dort mit irrefuehrenden Meldungen. Bisher gab es keine .gitattributes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Richard
2026-08-22 11:25:01 +02:00
co-authored by Claude Opus 5
parent 1fd3671b42
commit 5507db3e32
5 changed files with 321 additions and 4 deletions
+22
View File
@@ -0,0 +1,22 @@
# Zeilenenden
#
# Entwickelt wird auf Windows (core.autocrlf=true), ausgeführt wird auf Linux. Für die
# meisten Dateien ist das folgenlos - für die unten aufgeführten nicht: ein Shell-Skript
# oder eine systemd-Unit mit CRLF scheitert auf Linux mit irreführenden Meldungen
# ("command not found" für einen Befehl, der sichtbar dasteht - das \r gehört noch dazu).
# Deshalb wird für diese Dateien LF erzwungen, unabhängig von der lokalen Git-Konfiguration.
# Standard: Git entscheidet, im Repo immer LF
* text=auto
# Muss im Arbeitsverzeichnis LF bleiben - wird auf Linux ausgeführt
*.yml text eol=lf
*.yaml text eol=lf
*.sh text eol=lf
*.service text eol=lf
# Binärdateien nicht anfassen
*.png binary
*.ico binary
*.nupkg binary
*.pdf binary
+159
View File
@@ -0,0 +1,159 @@
name: CI
# Wozu diese CI da ist:
# PolyTrader läuft künftig auf einem Linux-Server. Der plattformneutrale Zustand ist am
# 22.08.2026 mühsam hergestellt worden (WinForms-Ausbau) und driftet ohne Wächter wieder weg —
# eine einzige `net10.0-windows`-Zeile oder ein `using System.Drawing` genügt, und der
# Linux-Build ist still kaputt. Genau das fängt der "guard"-Job ab, und zwar auf Linux,
# nicht auf einer Windows-Entwicklermaschine, auf der es zufällig weiter baut.
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:
env:
DOTNET_NOLOGO: "true"
DOTNET_CLI_TELEMETRY_OPTOUT: "true"
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: "true"
jobs:
build-test:
name: Build & Tests (Linux)
runs-on: ubuntu-latest
steps:
- name: Quellcode holen
uses: actions/checkout@v4
- name: .NET SDK einrichten
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.x'
- name: Pakete wiederherstellen
# Der lokale Feed lib/nuget (Deploymentcenter.Client) ist in NuGet.Config relativ
# eingebunden und liegt im Repo - hier ist also nichts zusätzlich einzurichten.
run: dotnet restore PolyTraderSharp.sln
- name: Bauen
run: dotnet build PolyTraderSharp.sln --configuration Release --no-restore
- name: Tests
run: dotnet test tests/PolyTrader.Tests/PolyTrader.Tests.csproj --configuration Release --no-build --verbosity normal
- name: Linux-Publish (Nachweis der Lauffähigkeit)
# Kein Selbstzweck: hier fällt auf, wenn ein Paket doch windows-only ist.
run: dotnet publish src/PolyTrader.App.Avalonia/PolyTrader.App.Avalonia.csproj --configuration Release --runtime linux-x64 --self-contained false --output ./artifacts/linux-x64
- name: Publish-Ergebnis prüfen
run: |
set -e
cd ./artifacts/linux-x64
for f in PolyTrader.App.Avalonia PolyTrader.App.Avalonia.dll appsettings.json setup.json; do
if [ ! -f "$f" ]; then
echo "FEHLER: '$f' fehlt im Linux-Publish."
exit 1
fi
done
# Die nativen Skia-/HarfBuzz-Bibliotheken müssen die Linux-Fassung sein.
if ! ls libSkiaSharp.so >/dev/null 2>&1; then
echo "FEHLER: libSkiaSharp.so fehlt - Avalonia wäre auf Linux nicht lauffähig."
exit 1
fi
echo "Linux-Publish vollständig ($(ls | wc -l) Dateien)."
guard:
name: Plattformneutralität & Hygiene
runs-on: ubuntu-latest
steps:
- name: Quellcode holen
uses: actions/checkout@v4
- name: .NET SDK einrichten
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.x'
- name: Keine windows-spezifischen Zielframeworks
run: |
set -e
treffer=$(grep -rn -E "<TargetFrameworks?>[^<]*-windows" --include="*.csproj" . || true)
if [ -n "$treffer" ]; then
echo "FEHLER: windows-spezifisches Zielframework gefunden:"
echo "$treffer"
echo ""
echo "PolyTrader ist seit dem 22.08.2026 plattformneutral. Siehe docs/PROJEKTSTAND.md."
exit 1
fi
echo "OK - alle Projekte sind plattformneutral."
- name: Kein WinForms/WPF
run: |
set -e
treffer=$(grep -rn -E "<(UseWindowsForms|UseWPF)>\s*true" --include="*.csproj" . || true)
if [ -n "$treffer" ]; then
echo "FEHLER: WinForms/WPF wurde wieder eingeschaltet:"
echo "$treffer"
exit 1
fi
echo "OK - kein WinForms/WPF."
- name: Keine windows-only Namespaces im Code
run: |
set -e
# Nur echte using-Direktiven, keine Kommentare - die erklären im Bestand
# bewusst, WARUM etwas nicht verwendet wird.
treffer=$(grep -rn -E "^\s*using\s+System\.(Windows\.Forms|Drawing)\s*;" --include="*.cs" src/ tests/ || true)
if [ -n "$treffer" ]; then
echo "FEHLER: windows-only Namespace eingebunden:"
echo "$treffer"
echo ""
echo "System.Drawing.Common ist seit .NET 7 Windows-only und wirft auf Linux."
exit 1
fi
echo "OK - keine windows-only Namespaces."
- name: Keine Secret-Dateien versioniert
run: |
set -e
# Diese Dateien enthalten echte Zugangsdaten und gehören nie ins Repo.
# packager.config.json trägt FTP-Passwort und updateservice:publish-Token.
verboten="deploy/packager.config.json appsettings.Local.json master.key openrouter.key .gitea-token server_settings.xml"
fehler=0
for f in $verboten; do
if git ls-files --error-unmatch "$f" >/dev/null 2>&1; then
echo "FEHLER: '$f' ist versioniert - enthält Zugangsdaten."
fehler=1
fi
done
# Zusätzlich: Deploymentcenter-Tokens im Klartext irgendwo in versionierten Dateien.
# Die Plandokumente kürzen Tokens bewusst mit "…" ab, echte sind 40+ Zeichen.
if git grep -nE "dc_(sub|master)_[0-9a-f]{40,}" -- . >/dev/null 2>&1; then
echo "FEHLER: Deploymentcenter-Token im Klartext gefunden:"
git grep -nE "dc_(sub|master)_[0-9a-f]{40,}" -- .
fehler=1
fi
if [ "$fehler" -ne 0 ]; then
echo ""
echo "Diese Dateien gehören in .gitignore, nicht ins Repo."
exit 1
fi
echo "OK - keine Secret-Dateien versioniert."
- name: Pakete auf bekannte Schwachstellen prüfen
# Punkt 1 der wiederkehrenden Audit-Checkliste aus docs/sicherheit/SICHERHEITSKONZEPT.md.
# Läuft ab jetzt bei jedem Push statt nur quartalsweise von Hand.
run: |
set -e
dotnet restore PolyTraderSharp.sln
ausgabe=$(dotnet list PolyTraderSharp.sln package --vulnerable --include-transitive)
echo "$ausgabe"
if echo "$ausgabe" | grep -qE "^\s+>\s"; then
echo ""
echo "FEHLER: anfällige Pakete gefunden (siehe oben)."
exit 1
fi
echo "OK - keine anfälligen Pakete."
+133
View File
@@ -0,0 +1,133 @@
# Leitfaden: Continuous Integration
**Stand: 22.08.2026** · Workflow: [`.gitea/workflows/ci.yml`](../.gitea/workflows/ci.yml)
> **Status: Der Workflow liegt, der Runner fehlt noch.** Gitea 1.26.2 hat Actions aktiviert
> (`has_actions: true`), aber auf der Instanz ist **kein einziger Runner registriert** — geprüft
> auf Repo-, Benutzer- und Instanzebene. Bis Abschnitt 3 erledigt ist, passiert bei einem Push
> nichts. Der Workflow ist dann sofort lauffähig, ohne weitere Änderung.
---
## 1. Warum überhaupt
PolyTrader soll auf einem Linux-Server laufen. Der plattformneutrale Zustand wurde am
22.08.2026 mit dem WinForms-Ausbau hergestellt — und er **driftet ohne Wächter wieder weg**.
Eine einzige `net10.0-windows`-Zeile oder ein `using System.Drawing` genügt, und der
Linux-Build ist kaputt, ohne dass es auf einer Windows-Entwicklermaschine auffällt: dort baut
es weiter.
Genau deshalb läuft die CI **auf Linux**. Sie ist kein Selbstzweck, sondern die einzige
Instanz, die den mühsam hergestellten Zustand verteidigt.
## 2. Was geprüft wird
Zwei Jobs, beide auf `ubuntu-latest`:
### `build-test` — Build & Tests
| Schritt | Zweck |
|---|---|
| `dotnet restore` | Der lokale Feed `lib/nuget` (Deploymentcenter-SDK) ist relativ eingebunden und liegt im Repo — nichts einzurichten |
| `dotnet build -c Release` | Baut alle 7 Projekte |
| `dotnet test` | Die 476 Tests. Brauchen **keine Datenbank** — sie laufen gegen EF-InMemory |
| `dotnet publish -r linux-x64` | Kein Selbstzweck: hier fällt auf, wenn ein Paket doch windows-only ist |
| Publish-Prüfung | Erwartet `PolyTrader.App.Avalonia`, `appsettings.json`, `setup.json` und `libSkiaSharp.so` — ohne die native Skia-Bibliothek wäre Avalonia auf Linux nicht lauffähig |
### `guard` — Plattformneutralität & Hygiene
| Prüfung | Schlägt fehl bei |
|---|---|
| Zielframeworks | irgendeinem `<TargetFramework>…-windows` in einer `.csproj` |
| WinForms/WPF | `<UseWindowsForms>true` oder `<UseWPF>true` |
| Namespaces | echten `using System.Windows.Forms;` / `using System.Drawing;`-Direktiven in `src/` oder `tests/` |
| Secret-Dateien | `deploy/packager.config.json`, `appsettings.Local.json`, `master.key`, `openrouter.key`, `.gitea-token` oder `server_settings.xml` **versioniert**; oder einem Deploymentcenter-Token im Klartext |
| Schwachstellen | `dotnet list package --vulnerable --include-transitive` findet etwas |
**Zur Namespace-Prüfung:** Sie trifft bewusst nur echte `using`-Direktiven, keine Kommentare.
Im Bestand steht an vielen Stellen erklärt, *warum* `System.Drawing` nicht verwendet wird —
das darf keinen Fehlalarm auslösen. Beide Richtungen sind gegengeprüft.
**Zum Schwachstellen-Check:** Das ist Punkt 1 der wiederkehrenden Audit-Checkliste aus
[`sicherheit/SICHERHEITSKONZEPT.md`](./sicherheit/SICHERHEITSKONZEPT.md) — läuft ab jetzt bei
jedem Push statt quartalsweise von Hand. Er würde zum Beispiel anschlagen, wenn der
`Newtonsoft.Json`-Pin im Core entfernt wird: Nethereum 6.1.0 löst dann transitiv auf 11.0.2
auf (GHSA-5crp-9r3c-p9vr, Schweregrad hoch).
> Die Warnungen des Builds (aktuell 15) lassen die CI **nicht** fehlschlagen. `-warnaserror`
> wäre hier verfrüht: die vorhandenen Warnungen müssten erst abgearbeitet werden, sonst ist
> die CI ab dem ersten Tag rot und wird ignoriert.
## 3. Runner einrichten (einmalig)
Ohne Runner führt Gitea den Workflow nicht aus. Der Runner ist ein eigenes Programm
(`act_runner`), das sich beim Gitea-Server meldet und Jobs abholt. Er läuft sinnvollerweise
**auf dem Gitea-Host** (`192.168.178.10`) oder jeder anderen Maschine im selben Netz, die
Docker hat.
### 3.1 Registrierungstoken holen
In der Weboberfläche: **Repo → Einstellungen → Actions → Runner → „Runner erstellen"**.
Dort steht ein Token der Form `…`. Alternativ instanzweit unter
**Website-Verwaltung → Actions → Runner**, wenn der Runner mehreren Repos dienen soll —
für den Anfang genügt der Repo-Runner.
### 3.2 Runner per Docker starten
```bash
docker run -d --restart always \
--name gitea-runner \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /opt/gitea-runner:/data \
-e GITEA_INSTANCE_URL=http://192.168.178.10:8418 \
-e GITEA_RUNNER_REGISTRATION_TOKEN=<TOKEN_AUS_3.1> \
-e GITEA_RUNNER_NAME=polytrader-runner \
-e GITEA_RUNNER_LABELS=ubuntu-latest:docker://catthehacker/ubuntu:act-latest \
gitea/act_runner:latest
```
**Das `GITEA_RUNNER_LABELS`-Feld ist der kritische Teil.** Es bildet `runs-on: ubuntu-latest`
aus dem Workflow auf ein Container-Image ab. Fehlt das Label, bleibt der Job auf
„warten auf Runner" stehen, ohne Fehlermeldung. Das Image `catthehacker/ubuntu:act-latest`
bringt Node mit, das `actions/checkout` und `actions/setup-dotnet` benötigen.
Der Mount von `docker.sock` ist nötig, weil der Runner die Job-Container selbst startet.
### 3.3 Prüfen
```bash
curl -s -H "Authorization: token $(cat ~/.gitea-token)" http://192.168.178.10:8418/api/v1/repos/Richard/PolyTraderSharp/actions/runners
```
Steht dort `"total_count": 0`, hat sich der Runner nicht registriert — dann in die Logs sehen:
`docker logs gitea-runner`.
Danach den Workflow von Hand anstoßen: **Repo → Actions → CI → „Run workflow"**
(`workflow_dispatch` ist im Workflow vorgesehen). Der erste Lauf dauert länger, weil das
.NET-SDK heruntergeladen wird.
## 4. Wenn kein Internetzugang besteht
`actions/setup-dotnet` lädt das SDK von Microsoft. Ist der Runner offline, gibt es zwei Wege:
1. **Container-Image mit SDK** statt `setup-dotnet` — im Workflow je Job ergänzen:
```yaml
container:
image: mcr.microsoft.com/dotnet/sdk:10.0
```
Dann muss das `setup-dotnet`-Steps-Paar entfallen. **Achtung:** Dieses Image bringt kein
Node mit; `actions/checkout` braucht es. Entweder ein eigenes Image bauen
(`dotnet/sdk:10.0` + `nodejs`) oder auschecken per `git clone` statt per Action.
2. **SDK im Runner-Image vorinstallieren** und `setup-dotnet` weglassen.
Weg 1 mit eigenem Image ist der sauberere, sobald das Netz wirklich zu ist.
## 5. Was die CI (noch) nicht tut
- **Kein Deployment.** Die Auslieferung läuft über den Deploymentcenter-Packager
(`deploy/packager.config.json`, siehe Schnitt D-4). Das bewusst nicht automatisiert, solange
die Live-Abnahme aussteht.
- **Keine Integrationstests gegen eine echte MySQL.** Die Testsuite läuft gegen EF-InMemory.
Ein MySQL-Service-Container wäre der nächste sinnvolle Ausbauschritt, wenn die
Repository-Schicht einmal gegen echtes SQL geprüft werden soll.
- **Kein `-warnaserror`.** Siehe Kasten in Abschnitt 2.
+5 -3
View File
@@ -114,7 +114,7 @@ etwas anderes:
| Punkt | Details | | Punkt | Details |
|---|---| |---|---|
| **Keine CI** | Weder `.github/workflows` noch `.gitea/workflows` — es existiert keinerlei Automatisierung. Build und Tests laufen ausschließlich von Hand. **Der größte strukturelle Mangel** | | **CI: Workflow da, Runner fehlt** | [`.gitea/workflows/ci.yml`](../.gitea/workflows/ci.yml) ist angelegt (Build + 476 Tests + Linux-Publish auf Linux, dazu Wächter gegen windows-spezifische Rückfälle, versionierte Secrets und anfällige Pakete). **Auf der Gitea-Instanz ist aber kein Runner registriert** — geprüft auf Repo-, Benutzer- und Instanzebene. Bis der Runner steht, läuft der Workflow nicht. Einrichtung: [LEITFADEN-CI.md](./LEITFADEN-CI.md) §3 |
| **15 Build-Warnungen** | Alle im Avalonia-Projekt: 13 × `CS8618` (Felder in Fenster-Konstruktoren), 1 × `CS8848` (Vorrang bei `switch`), 1 × `CS8602` (möglicher Nullverweis in `PdfExporter.cs:40`). Die letzten beiden sind einen Blick wert | | **15 Build-Warnungen** | Alle im Avalonia-Projekt: 13 × `CS8618` (Felder in Fenster-Konstruktoren), 1 × `CS8848` (Vorrang bei `switch`), 1 × `CS8602` (möglicher Nullverweis in `PdfExporter.cs:40`). Die letzten beiden sind einen Blick wert |
| **TerminalLogger** | Stempelt mit `DateTime.Now` statt der konfigurierten `AppTimeZone`. Auf einem UTC-Linuxserver passen Logdatei-Grenzen nicht zur angezeigten Uhrzeit. Umstellung auf `Microsoft.Extensions.Logging` steht ohnehin aus | | **TerminalLogger** | Stempelt mit `DateTime.Now` statt der konfigurierten `AppTimeZone`. Auf einem UTC-Linuxserver passen Logdatei-Grenzen nicht zur angezeigten Uhrzeit. Umstellung auf `Microsoft.Extensions.Logging` steht ohnehin aus |
| **God-Methoden** | `PollLiveAccountsAsync`, `ProcessAccountOrderAsync`; duplizierte Closed-Trade-Erzeugung (Phase 7) | | **God-Methoden** | `PollLiveAccountsAsync`, `ProcessAccountOrderAsync`; duplizierte Closed-Trade-Erzeugung (Phase 7) |
@@ -173,6 +173,7 @@ gekennzeichnet, `ANALYSE-Linux-Portierung.md` steht auf Revision 6.
| `PROJEKTSTAND.md` | **Dieses Dokument** — Einstieg und Gesamtüberblick | | `PROJEKTSTAND.md` | **Dieses Dokument** — Einstieg und Gesamtüberblick |
| `ANALYSE-Linux-Portierung.md` | Rahmen der Linux-Fähigkeit, Fahrplan P1P11 / L1L5 | | `ANALYSE-Linux-Portierung.md` | Rahmen der Linux-Fähigkeit, Fahrplan P1P11 / L1L5 |
| `LEITFADEN-Avalonia-Portierung.md` | Arbeitsregeln für die Oberfläche. **Vor jeder UI-Arbeit lesen** | | `LEITFADEN-Avalonia-Portierung.md` | Arbeitsregeln für die Oberfläche. **Vor jeder UI-Arbeit lesen** |
| `LEITFADEN-CI.md` | Was die CI prüft und wie der Gitea-Runner eingerichtet wird |
| `UI-SPEZIFIKATION-WinForms.md` | Beschreibung der alten Oberfläche — Vergleichsvorlage für A5 | | `UI-SPEZIFIKATION-WinForms.md` | Beschreibung der alten Oberfläche — Vergleichsvorlage für A5 |
| `umsetzungsplaene/` | Detailpläne je Vorhaben | | `umsetzungsplaene/` | Detailpläne je Vorhaben |
| `konzepte/` | Vorhaben ohne Umsetzungsplan | | `konzepte/` | Vorhaben ohne Umsetzungsplan |
@@ -188,8 +189,9 @@ gekennzeichnet, `ANALYSE-Linux-Portierung.md` steht auf Revision 6.
1. **A5 durchklicken** — die Oberfläche mit echten Daten abnehmen. Blockiert nichts 1. **A5 durchklicken** — die Oberfläche mit echten Daten abnehmen. Blockiert nichts
technisch, ist aber die letzte offene Zusage der UI-Portierung. technisch, ist aber die letzte offene Zusage der UI-Portierung.
2. **CI aufsetzen** — der größte strukturelle Mangel. Ein Workflow, der `dotnet build` und 2. **CI scharf schalten** — der Workflow liegt, es fehlt nur der Runner (etwa 10 Minuten,
`dotnet test` auf Linux fährt, hätte den plattformneutralen Zustand von allein gehalten. siehe [LEITFADEN-CI.md](./LEITFADEN-CI.md) §3). Erst dann hält der plattformneutrale
Zustand von allein; bis dahin ist er nur eine Momentaufnahme.
3. **Deploymentcenter live abnehmen** — danach können die Altdienste abgeschaltet werden, 3. **Deploymentcenter live abnehmen** — danach können die Altdienste abgeschaltet werden,
womit zwei Sicherheits-Auflagen von selbst entfallen. womit zwei Sicherheits-Auflagen von selbst entfallen.
4. **Erst dann neue Module.** MarketMaking und BundleArbitrage sind fertig geplant und 4. **Erst dann neue Module.** MarketMaking und BundleArbitrage sind fertig geplant und
+2 -1
View File
@@ -12,7 +12,8 @@ durch die Detailpläne zu lesen.
Daneben liegen im Wurzelverzeichnis dieses Ordners drei querschnittliche Dokumente: Daneben liegen im Wurzelverzeichnis dieses Ordners drei querschnittliche Dokumente:
`ANALYSE-Linux-Portierung.md` (Rahmen und Fahrplan der Plattformneutralität), `ANALYSE-Linux-Portierung.md` (Rahmen und Fahrplan der Plattformneutralität),
`LEITFADEN-Avalonia-Portierung.md` (**Arbeitsregeln — vor jeder UI-Arbeit lesen**) und `LEITFADEN-Avalonia-Portierung.md` (**Arbeitsregeln — vor jeder UI-Arbeit lesen**),
`LEITFADEN-CI.md` (was die CI prüft und wie der Gitea-Runner eingerichtet wird) und
`UI-SPEZIFIKATION-WinForms.md` (Beschreibung der abgelösten Oberfläche, Vergleichsvorlage `UI-SPEZIFIKATION-WinForms.md` (Beschreibung der abgelösten Oberfläche, Vergleichsvorlage
für die Abnahme A5). für die Abnahme A5).