Projektwissen: Git-Vorgaben, Ticketzugriff, Tests und Einstieg
Erster Stand des Repositories AG_QGIS/Projektwissen (bisher Arbeitstitel "Workspace", ADR 0006). Der lokale Ordner heisst weiter AG_QGIS, weil darin auch die Plugin-Klone liegen. Git verbindlich (ADR 0007), aus dem bisherigen Wiki-Konzept entwickelt: - docs/leitfaden/05_git_arbeitsweise.md mit Kanal-Kette, Branch-Namen und Gitea-Branch-Schutz. - Der Einstiegspunkt eines Fixes richtet sich nach dem Kanal, in dem der Fehler sitzt; ein Fix an testing oder stable ist erst mit der Rueckfuehrung nach unten abgeschlossen. Das fehlte bisher. - Der stabile Kanal heisst stable, nicht main: der Release-Workflow in Repository:hidden/workflows fuehrt git checkout "$CHANNEL" aus, der Branchname muss also dem Kanalnamen entsprechen. Er existiert in keinem Plugin-Repo und ist vom Maintainer anzulegen. - Der Mensch drueckt ab: .claude/settings.json sperrt push, merge, tag. - scripts/hallo.py und /hallo holen den Stand aller Klone, per Fast-Forward wo gefahrlos; nie mergen, stashen oder verwerfen. Ticketzugriff: - scripts/gitea.py liest Issues, Kommentare und PRs ueber die oeffentliche API, ohne Token, nur GET. Schreibendes nur als Entwurf in entwuerfe/. - Skill sn-ticket. Tests (ADR 0009): - docs/leitfaden/06_tests.md: Testebenen, Testpflicht, und wie eine Fachperson beurteilt, ob Tests fachlich sinnvoll sind. - scripts/testkatalog.py erzeugt docs/tests/testkatalog.md per ast-Parsing, ohne Import und ohne QGIS. Befund: nur 100 von 332 Tests haben einen Docstring, sn_basis keinen einzigen von 87. - Vorlage und erste Testkonzept-Seite mit Mermaid. Dabei gefunden und gegen origin geprueft: _ermittle_gebietstyp_aus_fbschl hat keinen Test, obwohl PR #44/#45 genau diese Funktion tolerant gemacht hat. - docs/konzept/07_qgis_tests_plan.md: Stufenplan fuer Tests gegen echtes QGIS. Dabei aufgefallen: QGIS 3.40 ist seit Januar 2026 End of Life, qgis_min steht aber noch darauf. Werkzeuge (ADR 0008): - 04_werkzeuge_einrichten.md nur noch Claude Code als CLI und in VS Code. - .vscode/ entfernt, brauchbare Einstellungen als persoenliche Vorlage. Ausserdem: README als Einstieg neu aufgebaut, Mermaid-Diagramm in schnittstellen.md repariert (unmaskierte Klammern, rendert seither nicht). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,203 @@
|
||||
# Arbeitsweise für Entwickler: So arbeiten wir mit KI-Agenten an den LNO-Plugins
|
||||
|
||||
Für Kolleginnen und Kollegen, die neu einsteigen. Fachwissen wird vorausgesetzt, Software-Erfahrung nicht.
|
||||
Wir arbeiten mit Claude Code (ADR 0008); die Dateien bleiben werkzeugneutral. Die Kurzfassung für den Agenten
|
||||
selbst ist `AGENTS.md`, dieser Leitfaden erklärt das Warum und das Wie. Git im Detail:
|
||||
`05_git_arbeitsweise.md`. Tests im Detail: `06_tests.md`.
|
||||
|
||||
## 1. In zehn Minuten startklar
|
||||
|
||||
**Voraussetzungen:** Git, Python ≥ 3.11 (ist in OSGeo4W enthalten: `python-qgis.bat` oder das OSGeo-Python), Zugang
|
||||
zu Gitea (`entwicklung.flurneuordnung-sachsen.de`), Claude Code (CLI oder VS-Code-Erweiterung).
|
||||
Für QGIS-Tests: QGIS 3.44 LTR oder 4 mit einem eigenen Profil (z. B. `dev`), in dessen Plugin-Ordner die Pakete
|
||||
`sn_basis`, `sn_verfahrensgebiet`, `sn_plan41` liegen (Verknüpfungen auf die Klone).
|
||||
|
||||
```bash
|
||||
git clone https://entwicklung.flurneuordnung-sachsen.de/AG_QGIS/Projektwissen.git AG_QGIS
|
||||
cd AG_QGIS
|
||||
python scripts/setup_workspace.py # klont die Plugin-Repos, spiegelt Skills
|
||||
python scripts/run_tests.py # muss laufen: 3 Suiten, Zusammenfassung
|
||||
```
|
||||
|
||||
> **Solange `AG_QGIS/Projektwissen` auf Gitea noch nicht angelegt ist** (Phase 0 im Konzept), schlägt der Clone mit
|
||||
> „Repository not found“ fehl, obwohl die Anmeldung gelingt. Übergangslösung: den Ordner `AG_QGIS` aus dem
|
||||
> Projektverzeichnis auf dem Netzlaufwerk (`Projekte\QGIS Plan 41\AG_QGIS`) komplett kopieren, darin
|
||||
> `python scripts/setup_workspace.py` ausführen (erneuert den Skill-Spiegel und prüft die Klone) und weiter wie oben.
|
||||
|
||||
Dann das Werkzeug **im Ordner `AG_QGIS/` starten**:
|
||||
|
||||
| Form | Start | Prüfen |
|
||||
|---|---|---|
|
||||
| Claude Code CLI | `claude` im Ordner `AG_QGIS` | `/context` zeigt `CLAUDE.md` und `AGENTS.md` unter „Memory files“; `/hallo` ist im Menü |
|
||||
| Claude Code in VS Code | Ordner `AG_QGIS` öffnen, Panel starten | dasselbe; Diffs erscheinen im Editor |
|
||||
|
||||
Einrichtung Schritt für Schritt: `04_werkzeuge_einrichten.md`.
|
||||
|
||||
Erste Frage an den Agenten zum Warmwerden: „Fasse `docs/status/STATUS.md` zusammen und nenne die drei wichtigsten
|
||||
Regeln aus `AGENTS.md`.“ Antwortet er korrekt, ist die Einrichtung fertig.
|
||||
|
||||
## 2. Der Arbeitszyklus
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["1 Issue auf Gitea (Vorlage docs/vorlagen/issue.md)"] --> B["2 Branch von unstable: feature/<nr>-<kurzname>"]
|
||||
B --> C["3 Auftrag an den Agenten (Abschnitt 3)"]
|
||||
C --> D["4 Umsetzung in kleinen Schritten, Zwischenstände committen"]
|
||||
D --> E["5 python scripts/run_tests.py <paket> --baseline"]
|
||||
E -->|rot| D
|
||||
E -->|grün| F["6 Bei UI/QGIS-Verhalten: Test in QGIS, Ergebnis notieren"]
|
||||
F --> G["7 changelog.txt oben ergänzen"]
|
||||
G --> H["8 PR auf unstable (Vorlage docs/vorlagen/pull_request.md) mit Übergabenotiz"]
|
||||
H --> I["9 Review durch eine Kollegin/einen Kollegen"]
|
||||
I -->|Änderungswünsche| D
|
||||
I -->|Merge| J["10 STATUS.md und Wissensbasis nachziehen"]
|
||||
```
|
||||
|
||||
Jeder Schritt ist klein. Ein PR sollte an einem Tag entstehen und in einer Viertelstunde reviewbar sein. Wenn das
|
||||
nicht geht, ist das Issue zu groß: aufteilen.
|
||||
|
||||
**Branch-Namen:** `feature/<issue-nr>-<kurzname>`, `bugfix/<issue-nr>-<kurzname>`, `doku/<kurzname>`.
|
||||
**Commit-Nachrichten:** deutsch, Präsens, erste Zeile ≤ 72 Zeichen, Issue-Nummer am Ende (`Shape-Import: FBSchl.-Variante akzeptieren (#41)`).
|
||||
|
||||
## 3. Wie man einem Agenten einen Auftrag gibt
|
||||
|
||||
Der Agent liest `AGENTS.md` automatisch. Was er **nicht** weiß: was genau gewollt ist, wo es endet, und was fachlich
|
||||
richtig ist. Ein guter Auftrag hat vier Teile:
|
||||
|
||||
1. **Ziel und Issue:** „Bearbeite Issue Verfahrensgebiet #41: …“ oder das Ziel in zwei Sätzen.
|
||||
2. **Kontext-Zeiger statt Erklärungen:** „Betroffen ist `functions/verfahrensgebiet_shape_komplett.py`, Ablauf in
|
||||
`docs/aus_shape_laden_technisch.md`. Lies zuerst `docs/architektur/schnittstellen.md` Abschnitt 6.“
|
||||
3. **Grenzen:** „Keine Änderung am ALKIS-Ablauf. Keine neuen Abhängigkeiten. Kein Refactoring.“
|
||||
4. **Abnahme:** „Fertig ist es, wenn `run_tests.py sn_verfahrensgebiet --baseline` keine neuen roten Tests zeigt, ein
|
||||
Test für den Fall `FBSchl.` existiert und die Übergabenotiz geschrieben ist.“
|
||||
|
||||
Beispiel eines guten Auftrags (das Issue wurde am 02.09.2026 tatsächlich so in PR #45 gelöst; hier als Muster):
|
||||
|
||||
> Nutze den Skill `sn-aufgabe-bearbeiten` für Issue Verfahrensgebiet #41. Der Shape-Import soll zusätzlich das Feld
|
||||
> `FBSchl.` (mit Punkt) akzeptieren, wie es manche DAVID-Exporte liefern. Betroffen: `_ermittle_gebietstyp_aus_fbschl`
|
||||
> in `functions/verfahrensgebiet_shape_komplett.py`. Nicht ändern: die Bedeutung der Schlüssel `901/10110F` und
|
||||
> `901/10120F`. Schreibe einen Test im Mock-Modus für beide Feldnamen. Am Ende: Übergabenotiz nach Vorlage.
|
||||
|
||||
Beispiel eines schlechten Auftrags: „Schau dir das Shape-Laden an und mach es besser.“ Das erzeugt lange Erkundung,
|
||||
unklare Änderungen und einen PR, den niemand reviewen kann.
|
||||
|
||||
**Während der Arbeit:** Rückfragen des Agenten beantworten, fachlich prüfen, was er behauptet, und ihn bei
|
||||
Abschweifungen zurückholen („Nur Issue #41“). Wenn er anfängt, Tests zu ändern statt Code, nach der Begründung fragen.
|
||||
|
||||
**Was der Agent nicht darf:** mergen, taggen/releasen, Baseline ändern, Verträge (Abschnitt `schnittstellen.md`)
|
||||
ändern ohne Issue, Zugangsdaten anfassen.
|
||||
|
||||
## 4. Die Regeln und warum es sie gibt
|
||||
|
||||
| Regel (aus `AGENTS.md`) | Warum |
|
||||
|---|---|
|
||||
| Qt/QGIS nur über Wrapper | Ein Code für Qt5 und Qt6; Tests ohne QGIS; ein Ort für Kompatibilitätsfixe |
|
||||
| Projektvariablen nur über `get_variable`/`set_variable`, Schlüssel ohne `sn_` | Einheitliches Präfix; Mock-Speicher in Tests; Doppelpräfixe vermeiden |
|
||||
| Fachlogik ins Fachplugin, `sn_basis` importiert keine Fachplugins | Basis muss allein laden können; sonst Startfehler wie Issue Basis #57 |
|
||||
| `grep` vor Änderungen an `sn_basis` | Drei Fachplugins in drei Repos hängen daran |
|
||||
| Mock-Tests vor jedem Commit, keine neuen roten Tests | Tests sind der einzige schnelle Nachweis für Agentenarbeit |
|
||||
| Kein Refactoring nebenbei | Umbauten ohne Plan zerstören Verträge, die nur im Code stehen; eigene Phase |
|
||||
| Deutsch in Doku, Commits, Issues | Team und Fachbegriffe sind deutsch; Agenten folgen der Sprache der Dateien |
|
||||
| Keine Zugangsdaten, keine echten Verfahrensdaten | Öffentliche Repos, Datenschutz |
|
||||
| Übergabenotiz | Die nächste Person oder der nächste Agent soll nicht von vorn beginnen |
|
||||
|
||||
## 5. Tests
|
||||
|
||||
**Mock-Modus (immer):** `python scripts/run_tests.py <paket> --baseline`. Läuft ohne QGIS in Sekunden, verlinkt die
|
||||
Repos unter ihren Paketnamen in ein temporäres Verzeichnis und fasst zusammen. `--baseline` vergleicht mit
|
||||
`docs/status/testbaseline.json` und meldet **NEU ROT** (nicht erlaubt) und **neu grün** (bitte Baseline-Update im PR
|
||||
vorschlagen). `--verbose` zeigt die volle Ausgabe eines Laufs, wenn man einen Fehler verstehen will.
|
||||
|
||||
**QGIS-Tests (bei UI, Layern, WFS, Layouts):** Windows, OSGeo4W-Shell, im Plugin-Repo `tests\test_qgis.bat`
|
||||
(Pfad zu `python-qgis.bat` anpassen) oder QGIS starten und den Ablauf manuell durchspielen. Ergebnis im PR unter
|
||||
„Nachweis“ eintragen: was geprüft wurde, mit welchen Daten, was herauskam.
|
||||
|
||||
**Neue Tests:** `unittest`, Datei `tests/test_<modul>.py`, Mock-Modus, ohne Netzwerk. Muster: vorhandene Tests des
|
||||
Pakets. Ein Test pro Verhalten, deutsche Methodennamen sind üblich (`test_zip_fehler_gibt_none_und_warning`).
|
||||
|
||||
**Rote Tests reparieren:** Erst klären, ob der Test veraltet ist oder der Code falsch. Bei veralteten Tests den Test
|
||||
an das neue Verhalten anpassen und in der Übergabenotiz begründen. Niemals Tests löschen oder mit `skip` stilllegen,
|
||||
um grün zu werden.
|
||||
|
||||
## 6. Wissen festhalten
|
||||
|
||||
| Situation | Was tun |
|
||||
|---|---|
|
||||
| Etwas hat überraschend Zeit gekostet | Eintrag in `docs/wissen/fallstricke.md` (Rubrik, Datum, Quelle) |
|
||||
| Ein Vertrag wurde geändert oder entdeckt | `docs/architektur/schnittstellen.md` anpassen |
|
||||
| Modul neu, verschoben, gewachsen | `docs/architektur/modulkarte.md` anpassen |
|
||||
| Team hat etwas entschieden | neue Datei `docs/entscheidungen/NNNN-<thema>.md` nach Vorlage |
|
||||
| Aufgabe abgegeben oder pausiert | Übergabenotiz im PR (Vorlage) und Zeile in `STATUS.md` |
|
||||
| Fachbegriff musste erklärt werden | `docs/wissen/glossar.md` |
|
||||
|
||||
Der Skill `sn-uebergabe` führt den Agenten am Ende jeder Aufgabe durch genau diese Liste. Das eigene Gedächtnis des
|
||||
Werkzeugs (Claude Auto-Memory) ist ein Notizzettel; teilbares Wissen gehört in die Dateien oben.
|
||||
|
||||
## 7. Was liegt wo, und was liest der Agent
|
||||
|
||||
| Datei | Wird gelesen |
|
||||
|---|---|
|
||||
| `AGENTS.md` (Projektwissen) | ja, über `CLAUDE.md` (`@AGENTS.md`) – kanonisch, ADR 0001 |
|
||||
| `CLAUDE.md` | ja, enthält nur den Verweis und wenige Claude-spezifische Zeilen |
|
||||
| `Plugin_*/AGENTS.md` | über `Plugin_*/CLAUDE.md`, wenn in dem Ordner gearbeitet wird |
|
||||
| `.agents/skills/` | kanonischer Ort; gelesen wird der Spiegel `.claude/skills/` |
|
||||
| `.claude/commands/` | Slash-Befehle, z. B. `/hallo` |
|
||||
| `.claude/settings.json` | Berechtigungen – sperrt `git push`, `merge`, `tag` |
|
||||
| `docs/` | auf Abruf, nicht automatisch |
|
||||
| `CLAUDE.local.md` | ja, lokal, nicht versioniert |
|
||||
|
||||
## 8. Release in Kürze
|
||||
|
||||
Nur Maintainer. Kanal-Branch (`unstable` oder `testing`) auf den gewünschten Stand bringen, `changelog.txt`-Block
|
||||
prüfen, Tag `v<JJ.M.N>-u` bzw. `-t` auf dem **Kopf des Kanal-Branches** setzen und pushen. Der Workflow baut den
|
||||
Branch, erzeugt `metadata.txt`, legt das Release an und aktualisiert den Feed. Details und Prüfliste: Skill `sn-release`.
|
||||
|
||||
## 9. Häufige Fragen
|
||||
|
||||
**Der Agent findet `sn_basis` nicht.** Er hat nicht `scripts/run_tests.py` benutzt oder wurde im falschen Ordner
|
||||
gestartet. Tests immer über das Skript; Werkzeug im Workspace-Root starten.
|
||||
|
||||
**Der Agent will `qgis.core` direkt importieren.** Auf den Wrapper verweisen; fehlt ein Symbol, zuerst im Wrapper mit
|
||||
Mock-Fallback ergänzen (siehe Muster in `qgiscore_wrapper.py`, Zeilen 73–260).
|
||||
|
||||
**Der Agent „repariert“ rote Tests durch Anpassen der Erwartung.** Nach Begründung fragen; nur akzeptieren, wenn das
|
||||
Verhalten bewusst geändert wurde und das Issue es abdeckt.
|
||||
|
||||
**Der Agent hat eine Datei komplett umgeschrieben.** Diff prüfen, auf das Issue zurückführen, Rest verwerfen.
|
||||
Regel „kein Refactoring nebenbei“ zitieren.
|
||||
|
||||
**Zwei Personen arbeiten am selben Modul.** In `STATUS.md` steht, wer woran arbeitet; vorher dort eintragen und
|
||||
kleine PRs machen. Konflikte in `unstable` löst der Maintainer mit beiden.
|
||||
|
||||
**Wie kommt das Plugin aus dem Workspace ohne Release in mein QGIS?** Die Klone im Workspace verknüpfen:
|
||||
`python scripts/link_qgis_profile.py --profil dev`. QGIS lädt dann `Plugin_SN_Basis` als `sn_basis` direkt aus der
|
||||
Arbeitskopie. Details in `04_werkzeuge_einrichten.md` Abschnitt 2.5.
|
||||
|
||||
**Windows und Symlinks.** `setup_workspace.py` und `run_tests.py` nutzen Junctions oder Kopien, wenn Symlinks nicht
|
||||
erlaubt sind. Nach Änderungen an Skills das Setup-Skript erneut ausführen, falls es „Kopie“ gemeldet hat.
|
||||
|
||||
## 10. Checklisten
|
||||
|
||||
**Vor dem PR**
|
||||
|
||||
- [ ] Issue verlinkt, Branch von `unstable`
|
||||
- [ ] `python scripts/run_tests.py <paket> --baseline` ohne NEU ROT
|
||||
- [ ] Bei UI/QGIS-Verhalten: Test in QGIS dokumentiert
|
||||
- [ ] Keine direkten Qt/QGIS-Importe, keine Debug-`print`, kein auskommentierter Code
|
||||
- [ ] `changelog.txt` ergänzt
|
||||
- [ ] Übergabenotiz im PR; Wissensbasis nachgezogen, falls nötig
|
||||
- [ ] Diff gelesen: nur, was das Issue verlangt
|
||||
|
||||
**Review**
|
||||
|
||||
- [ ] Fachlich richtig? (Verhalten, Begriffe, Dienste)
|
||||
- [ ] Verträge unberührt oder per Issue abgestimmt?
|
||||
- [ ] Tests vorhanden und sinnvoll (nicht nur angepasst)?
|
||||
- [ ] Umfang überschaubar (< 400 Zeilen)?
|
||||
- [ ] Nach dem Merge: `STATUS.md`
|
||||
|
||||
**Sitzungsende (auch bei Abbruch)**
|
||||
|
||||
- [ ] Zwischenstand committet oder gestasht und in der Übergabenotiz benannt
|
||||
- [ ] Offene Fragen im Issue notiert
|
||||
- [ ] `STATUS.md`-Zeile aktualisiert
|
||||
@@ -0,0 +1,243 @@
|
||||
# Werkzeuge einrichten: von null bis zur ersten Aufgabe
|
||||
|
||||
Stand 04.09.2026. Ergänzt [`03_arbeitsweise.md`](03_arbeitsweise.md) um die konkrete Einrichtung. Wer Abschnitt 2
|
||||
durchgearbeitet hat, kann beide Varianten aus Abschnitt 3 und 4 nutzen und jederzeit zwischen ihnen wechseln —
|
||||
sie greifen auf dieselben Dateien zu.
|
||||
|
||||
## 1. Zwei Varianten
|
||||
|
||||
Wir arbeiten mit **Claude Code**. Es gibt zwei Formen, dieselbe Software zu bedienen:
|
||||
|
||||
| Variante | Form | Wofür geeignet |
|
||||
|---|---|---|
|
||||
| **A · Claude Code CLI** | Terminal | lange, eigenständige Aufgaben; Skripte; Arbeiten über mehrere Repos |
|
||||
| **B · Claude Code in VS Code** | Erweiterung mit Chatpanel | Einstieg; Diffs im Editor sehen und einzeln annehmen |
|
||||
|
||||
Beide lesen `CLAUDE.md` (und darüber `AGENTS.md`), beide finden die Skills unter `.claude/skills/`, beide
|
||||
arbeiten über Git mit Gitea. **Empfehlung für Einsteiger: Variante B**, weil man jede Änderung als Diff sieht,
|
||||
bevor sie übernommen wird.
|
||||
|
||||
**Nicht genutzt und nicht unterstützt:** Cloud-Agenten (Claude Code Web, Codex Cloud, Copilot Coding Agent) —
|
||||
sie setzen GitHub voraus, wir arbeiten mit Gitea. Reine Chat-Oberflächen ohne Ordnerzugriff (claude.ai,
|
||||
chatgpt.com) sind für Fachfragen brauchbar, aber **nicht** für Änderungen an den Repos: Code-Fragmente per
|
||||
Kopieren einzubauen ist genau die Arbeitsweise, die zur heutigen Struktur geführt hat. Wer nur Chat hat,
|
||||
formuliert Issues nach [`docs/vorlagen/issue.md`](../vorlagen/issue.md) und lässt sie von jemandem mit Agent
|
||||
umsetzen.
|
||||
|
||||
## 2. Gemeinsame Grundlage (einmal pro Rechner)
|
||||
|
||||
### 2.1 Konten und Zugänge
|
||||
|
||||
- **Gitea-Konto** auf `entwicklung.flurneuordnung-sachsen.de` mit Schreibrecht in der Organisation `AG_QGIS`
|
||||
(beim Maintainer anfragen). Für HTTPS-Push ein **Zugriffstoken** anlegen:
|
||||
Gitea → Einstellungen → Anwendungen → Token, Berechtigung `repository: read/write`.
|
||||
- **Claude-Konto**: Abo (Pro, Max, Team, Enterprise) oder Console-Konto. Dienstliche E-Mail verwenden;
|
||||
die Abrechnung klärt die Behörde.
|
||||
- **Behördennetz:** ausgehendes HTTPS zu `claude.ai` und `api.anthropic.com` muss möglich sein. Bei Proxy die
|
||||
Variablen `HTTPS_PROXY` und `HTTP_PROXY` setzen; bei Problemen mit der Zertifikatsprüfung die IT ansprechen.
|
||||
|
||||
### 2.2 Git
|
||||
|
||||
- **Windows:** [Git for Windows](https://git-scm.com/downloads/win) mit den Standardoptionen. Enthält Git Bash
|
||||
und den Credential Manager, der sich das Gitea-Token nach dem ersten Push merkt. Claude Code setzt Git for
|
||||
Windows voraus.
|
||||
- **macOS/Linux:** Git über Xcode-Tools bzw. die Paketverwaltung.
|
||||
- Einmalig einstellen:
|
||||
```bash
|
||||
git config --global user.name "Vorname Nachname"
|
||||
git config --global user.email "dienstliche@adresse"
|
||||
```
|
||||
|
||||
### 2.3 Python
|
||||
|
||||
Benötigt: **Python ≥ 3.11** und das Paket `openpyxl` für `scripts/run_tests.py`.
|
||||
|
||||
- **Windows mit QGIS:** am einfachsten die OSGeo4W-Shell mitnutzen — das dortige `python` ist das QGIS-Python.
|
||||
Darin `python -m pip install openpyxl`.
|
||||
- **Alternativ:** Python von python.org installieren, beim Setup „Add to PATH" anhaken, dann
|
||||
`pip install openpyxl`.
|
||||
- Prüfen: `python --version` und `python -c "import openpyxl"` müssen beide ohne Fehler durchlaufen.
|
||||
|
||||
Die übrigen Skripte (`hallo.py`, `gitea.py`, `testkatalog.py`, `setup_workspace.py`) brauchen **nur die
|
||||
Standardbibliothek** — sie laufen auch ohne `openpyxl`.
|
||||
|
||||
### 2.4 Projektwissen holen
|
||||
|
||||
```bash
|
||||
git clone https://entwicklung.flurneuordnung-sachsen.de/AG_QGIS/Projektwissen.git AG_QGIS
|
||||
cd AG_QGIS
|
||||
python scripts/setup_workspace.py # klont alle Plugin-Repos, spiegelt die Skills
|
||||
python scripts/run_tests.py # erwartet: drei Suiten mit den Baseline-Zahlen
|
||||
```
|
||||
|
||||
Der Zielordner `AG_QGIS` ist bewusst anders benannt als das Repository: darin liegen später nicht nur das
|
||||
Projektwissen, sondern auch alle Plugin-Klone nebeneinander.
|
||||
|
||||
> **Solange `AG_QGIS/Projektwissen` auf Gitea noch nicht existiert**, schlägt der Clone mit „Repository not
|
||||
> found" fehl, obwohl die Anmeldung gelingt. Übergangslösung: den Ordner `AG_QGIS` aus dem Projektverzeichnis
|
||||
> auf dem Netzlaufwerk (`Projekte\QGIS Plan 41\AG_QGIS`) kopieren und darin `python scripts/setup_workspace.py`
|
||||
> ausführen.
|
||||
|
||||
### 2.5 QGIS für manuelle Tests
|
||||
|
||||
Empfohlen, für den Start nicht zwingend.
|
||||
|
||||
**Warum das nötig ist:** QGIS lädt Plugins nur aus dem Plugin-Ordner eines Profils, und zwar unter dem
|
||||
**Paketnamen** (`sn_basis`), nicht unter dem Repo-Namen (`Plugin_SN_Basis`). Die Klone sind genau der Inhalt, der
|
||||
sonst per Release-ZIP in QGIS landet. Statt zu kopieren oder ein Release zu bauen, wird der Klon in das Profil
|
||||
**verknüpft**; QGIS arbeitet dann direkt auf der Arbeitskopie.
|
||||
|
||||
- QGIS 3.44 LTR (Qt5) und nach Möglichkeit QGIS 4.2 (Qt6) installieren. Eigenes Profil `dev` anlegen
|
||||
(Einstellungen → Benutzerprofile → Neues Profil) und einmal damit starten. Im Profil `dev` die Plugins
|
||||
**nicht** aus dem Release-Feed installieren.
|
||||
- Verknüpfungen anlegen — unter Windows als Junction, ohne Administratorrechte:
|
||||
```
|
||||
python scripts/link_qgis_profile.py --profil dev # alle Plugins
|
||||
python scripts/link_qgis_profile.py --profil dev --status # prüfen
|
||||
python scripts/link_qgis_profile.py --profil dev --entfernen
|
||||
```
|
||||
Liegt das Profil woanders (OSGeo4W-Installation, Netzprofil):
|
||||
`--pfad "<…>\profiles\dev\python\plugins"`.
|
||||
- In QGIS: Erweiterungen → Verwalten → Einstellungen → „Auch experimentelle Erweiterungen anzeigen".
|
||||
Dann zuerst „LNO Sachsen | Plugin Basisfunktionen" (`sn_basis`) aktivieren, danach die Fachplugins.
|
||||
Menü und Toolbar „LNO Sachsen" erscheinen.
|
||||
- Nach jeder Codeänderung: Plugin „Plugin Reloader" (offizielles QGIS-Repository) → `sn_basis` neu laden, oder
|
||||
QGIS neu starten. Änderungen in `functions/` und `modules/` brauchen meist den Neustart, weil Module gecacht
|
||||
werden.
|
||||
|
||||
> Die QGIS-Mindestversion des Projekts steht noch auf 3.40, das seit Januar 2026 End of Life ist.
|
||||
> Siehe [`../konzept/07_qgis_tests_plan.md`](../konzept/07_qgis_tests_plan.md) Abschnitt 2.
|
||||
|
||||
## 3. Variante A: Claude Code CLI
|
||||
|
||||
1. **Installieren.**
|
||||
Windows (PowerShell): `irm https://claude.ai/install.ps1 | iex`
|
||||
macOS/Linux: `curl -fsSL https://claude.ai/install.sh | bash`
|
||||
Node.js wird nicht benötigt; der Installer aktualisiert sich selbst.
|
||||
2. **Anmelden:** `claude` starten, im Browser mit dem Claude-Konto anmelden.
|
||||
3. **Starten:** im Ordner `AG_QGIS` (nicht in einem Plugin-Unterordner) `claude` eingeben. Beim ersten Mal den
|
||||
Import `@AGENTS.md` bestätigen.
|
||||
4. **Prüfen:** `/context` listet `CLAUDE.md` und `AGENTS.md` unter „Memory files". `/hallo` und die Skills
|
||||
(`sn-aufgabe-bearbeiten`, `sn-tests`, `sn-ticket`, …) erscheinen im Menü.
|
||||
5. **Berechtigungen:** Der Standardmodus fragt vor Änderungen. Für Routinearbeit ist „Accept edits" praktikabel.
|
||||
`--dangerously-skip-permissions` niemals außerhalb einer Sandbox.
|
||||
Das Repo verbietet über `.claude/settings.json` ohnehin `git push`, `git merge` und `git tag` —
|
||||
siehe [`05_git_arbeitsweise.md`](05_git_arbeitsweise.md) Abschnitt 6.
|
||||
6. **Erste Aufgabe:** Abschnitt 6 dieser Seite.
|
||||
|
||||
Bei Arbeit in einem Plugin-Unterordner lädt Claude zusätzlich dessen `CLAUDE.md`. Für breite Codesuchen den
|
||||
Explore-Unteragenten nutzen (steht in `CLAUDE.md`). Die Auto-Memory ist lokal und wird nicht geteilt; Teilbares
|
||||
gehört nach `docs/` — Skill `sn-uebergabe`.
|
||||
|
||||
## 4. Variante B: Claude Code in VS Code
|
||||
|
||||
1. **VS Code installieren**, dann **den Ordner `AG_QGIS` öffnen** (Datei → Ordner öffnen) — nicht einen
|
||||
Plugin-Unterordner.
|
||||
2. **Erweiterung installieren:** `Strg+Shift+X` → „Claude Code" (Herausgeber Anthropic) → Installieren.
|
||||
Die Erweiterung bringt eine eigene CLI-Kopie mit; die Installation aus Variante A ist nur nötig, wenn man
|
||||
`claude` zusätzlich im integrierten Terminal nutzen will.
|
||||
3. **Anmelden:** Symbol in der Aktivitätsleiste → Sign in → Browser-Anmeldung.
|
||||
4. **Python-Interpreter wählen:** `Strg+Shift+P` → „Python: Select Interpreter" → das Python aus Abschnitt 2.3.
|
||||
5. **Arbeiten:** Panel öffnen. Markierter Code wird automatisch mitgesendet (`Alt+K` fügt einen Verweis ein).
|
||||
Diffs erscheinen im Editor und werden einzeln angenommen oder verworfen. Den Berechtigungsmodus wählt man
|
||||
unten im Eingabefeld; Pläne lässt man vor der Umsetzung im Plan-Modus prüfen.
|
||||
|
||||
### 4.1 Empfohlene Erweiterungen
|
||||
|
||||
| Erweiterung | Kennung | Wofür |
|
||||
|---|---|---|
|
||||
| Claude Code | `anthropic.claude-code` | der Agent selbst |
|
||||
| Python | `ms-python.python` | Syntax, Interpreterwahl, Debugger |
|
||||
| Markdown Preview Mermaid Support | `bierner.markdown-mermaid` | zeigt die Mermaid-Diagramme in `docs/` in der Vorschau |
|
||||
|
||||
### 4.2 Persönliche Einstellungen
|
||||
|
||||
**Diese Einstellungen gehören in die Benutzereinstellungen, nicht ins Repository.** Das Repo enthält bewusst
|
||||
keinen `.vscode/`-Ordner: er würde persönliche Pfade (OSGeo-Installation, QGIS-Profil, Benutzername) versionieren
|
||||
und bei jedem eine andere Datei erzeugen. Wer sie haben will, kopiert sie sich in die eigene `settings.json`
|
||||
(`Strg+Shift+P` → „Preferences: Open User Settings (JSON)").
|
||||
|
||||
```jsonc
|
||||
{
|
||||
// Aufgeräumte Datei- und Suchansicht
|
||||
"files.exclude": {
|
||||
"**/__pycache__": true,
|
||||
"**/.sn_testlauf": true
|
||||
},
|
||||
"search.exclude": {
|
||||
"**/.git": true,
|
||||
"**/__pycache__": true,
|
||||
"Plugin_SN_Plan41/assets/**": true
|
||||
},
|
||||
|
||||
// Einheitliche Dateiform — die Repos sind auf UTF-8 und LF eingestellt
|
||||
"files.encoding": "utf8",
|
||||
"files.eol": "\n",
|
||||
"[python]": { "editor.tabSize": 4, "editor.insertSpaces": true },
|
||||
"[markdown]": { "editor.wordWrap": "on" },
|
||||
|
||||
// Tests laufen über scripts/run_tests.py, nicht über den VS-Code-Testrunner:
|
||||
// die Pakete müssen dafür erst unter ihren Paketnamen verlinkt werden.
|
||||
"python.testing.unittestEnabled": false,
|
||||
"python.testing.pytestEnabled": false
|
||||
}
|
||||
```
|
||||
|
||||
Wer zusätzlich Code-Vervollständigung für die QGIS-API möchte, ergänzt die eigenen Pfade — **nur lokal**:
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"python.analysis.extraPaths": [
|
||||
"C:/OSGeo4W/apps/qgis/python",
|
||||
"C:/Users/<benutzer>/AppData/Roaming/QGIS/QGIS3/profiles/dev/python/plugins"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 5. Einschränkungen und Datenschutz
|
||||
|
||||
- **Gitea statt GitHub.** Alle Cloud-Agenten setzen GitHub voraus und entfallen damit. Lokale Agenten arbeiten
|
||||
problemlos mit Gitea über Git. Issues und Pull Requests entstehen im Gitea-Browser; lesend geht die API auch
|
||||
ohne Anmeldung — dafür gibt es `scripts/gitea.py` und den Skill `sn-ticket`.
|
||||
- **Code geht an den Anbieter.** Die Repos sind öffentlich; trotzdem gilt: keine Zugangsdaten, keine
|
||||
Verfahrensdaten, keine personenbezogenen Daten in Prompts, Dateien oder Testdaten. API-Schlüssel (VLN) nie in
|
||||
den Chat kopieren.
|
||||
- **Der Mensch drückt ab.** Agenten dürfen nicht pushen, mergen, taggen oder irgendetwas auf dem Gitea-Server
|
||||
verändern. Das ist in `.claude/settings.json` technisch hinterlegt, nicht nur als Bitte formuliert.
|
||||
- **Windows-Symlinks.** Die Skripte fallen auf Junctions oder Kopien zurück, wenn Symlinks nicht erlaubt sind.
|
||||
Nach Änderungen an Skills `python scripts/setup_workspace.py --nur-skills` erneut ausführen.
|
||||
|
||||
## 6. Von null zur ersten Aufgabe (etwa eine Stunde)
|
||||
|
||||
1. Abschnitt 2 durcharbeiten; `python scripts/run_tests.py` zeigt drei Suiten.
|
||||
2. Variante A oder B einrichten. Prüfung: der Agent nennt auf Nachfrage die Regeln aus `AGENTS.md`.
|
||||
3. `/hallo` ausführen — holt den Stand aller Klone.
|
||||
4. `python scripts/gitea.py tickets` — offene Tickets ansehen und eines auswählen. Für den Anfang eignet sich
|
||||
auch ein einzelner roter Test aus `docs/status/testbaseline.json` oder ein Eintrag in `fallstricke.md`.
|
||||
5. In `docs/status/STATUS.md` unter „Wer arbeitet woran" eintragen (kleiner Pull Request oder Nachricht an den
|
||||
Maintainer).
|
||||
6. Auftrag nach dem Muster in [`03_arbeitsweise.md`](03_arbeitsweise.md) Abschnitt 3 geben, mit dem Skill
|
||||
`sn-aufgabe-bearbeiten`.
|
||||
7. Ergebnis prüfen: `python scripts/run_tests.py <paket> --baseline` ohne `NEU ROT`, Diff gelesen.
|
||||
8. Branch pushen (selbst!), Pull Request im Gitea-Browser nach
|
||||
[`docs/vorlagen/pull_request.md`](../vorlagen/pull_request.md), Übergabenotiz aus dem Skill `sn-uebergabe`.
|
||||
9. Review durch eine Kollegin oder einen Kollegen; nach dem Merge `STATUS.md` nachziehen.
|
||||
|
||||
## 7. Prüfliste „Einrichtung fertig"
|
||||
|
||||
- [ ] `git --version` und `python --version` (≥ 3.11) laufen, `python -c "import openpyxl"` ohne Fehler
|
||||
- [ ] `AG_QGIS/` mit allen Repos vorhanden, `python scripts/setup_workspace.py` ohne Fehler
|
||||
- [ ] `python scripts/run_tests.py` zeigt drei Suiten mit den Baseline-Zahlen
|
||||
- [ ] `python scripts/hallo.py` läuft und zeigt den Stand aller Klone
|
||||
- [ ] `python scripts/gitea.py tickets` listet die offenen Tickets
|
||||
- [ ] Claude Code im Ordner `AG_QGIS` gestartet; `/context` zeigt `CLAUDE.md` und `AGENTS.md`
|
||||
- [ ] Gitea-Token gespeichert (Test: `git fetch` in einem Plugin-Repo ohne Passwortabfrage)
|
||||
- [ ] Optional: QGIS-Profil `dev` mit Verknüpfungen, `sn_basis` lädt ohne Fehler
|
||||
|
||||
## Quellen
|
||||
|
||||
- Claude Code: [VS-Code-Erweiterung](https://code.claude.com/docs/en/vs-code) ·
|
||||
[Gedächtnis und CLAUDE.md](https://code.claude.com/docs/en/memory) ·
|
||||
[Skills](https://code.claude.com/docs/en/skills) ·
|
||||
[Einstellungen und Berechtigungen](https://code.claude.com/docs/en/settings)
|
||||
- [Agent-Skills-Standard](https://agentskills.io/home)
|
||||
@@ -0,0 +1,282 @@
|
||||
# Git-Arbeitsweise — verbindlich für alle Repositories
|
||||
|
||||
Stand 04.09.2026. Diese Regeln gelten für **jedes** Repository der Organisation `AG_QGIS`, für Menschen und für
|
||||
KI-Agenten gleichermaßen. Die Kurzfassung für den Agenten steht in `AGENTS.md`; hier steht das Vollständige mit
|
||||
Begründung. Wer eine Abweichung braucht, macht ein Issue auf — nicht einfach einen Push.
|
||||
|
||||
> **Die goldene Regel**
|
||||
>
|
||||
> In `stable`, `testing` und `unstable` wird nicht entwickelt. Diese Branches nehmen ausschließlich fertige Arbeit
|
||||
> per Pull Request entgegen. Entwickelt wird in kurzlebigen Branches, die nach dem Merge gelöscht werden.
|
||||
>
|
||||
> **Und: Der Mensch drückt ab.** Agenten bereiten alles vor. Push, Merge und Tag macht eine Person mit ihrem
|
||||
> eigenen Zugang.
|
||||
|
||||
## 1. Die drei Kanäle
|
||||
|
||||
Jedes Plugin-Repository hat drei geschützte Branches. Sie entsprechen genau den drei QGIS-Plugin-Feeds im
|
||||
Repository `Repository` (`plugins.xml`, `plugins-testing.xml`, `plugins-unstable.xml`).
|
||||
|
||||
| Branch | Bedeutung | Feed | Tag-Suffix | Direkter Push |
|
||||
|---|---|---|---|---|
|
||||
| `stable` | stabile Releases, produktionsreif | `plugins.xml` | ohne Suffix | nein |
|
||||
| `testing` | Release-Kandidaten, wird getestet | `plugins-testing.xml` | `-t` | nein |
|
||||
| `unstable` | fertige Features, noch nicht vollständig getestet | `plugins-unstable.xml` | `-u` | nein |
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
F["feature/123-kurzname<br/>bugfix/456-kurzname"] -->|"Pull Request"| U["unstable<br/><i>Arbeitsstand</i>"]
|
||||
U -->|"Pull Request"| T["testing<br/><i>Release-Kandidat</i>"]
|
||||
T -->|"Pull Request"| M["stable<br/><i>stabil</i>"]
|
||||
U -.->|"Tag v26.9.1-u"| RU(["Feed unstable"])
|
||||
T -.->|"Tag v26.9.1-t"| RT(["Feed testing"])
|
||||
M -.->|"Tag v26.9.1"| RM(["Feed stable"])
|
||||
|
||||
style U fill:#fff4e6,stroke:#d9822b
|
||||
style T fill:#e6f2ff,stroke:#2b7dd9
|
||||
style M fill:#e8f6ec,stroke:#2f9e44
|
||||
```
|
||||
|
||||
**Stand heute (04.09.2026):** `stable` existiert in den Plugin-Repositories noch **nicht** und muss vom Maintainer
|
||||
angelegt werden (siehe `docs/konzept/08_repo_einrichten.md`). Bis dahin endet die Kette bei `testing`, und der
|
||||
stabile Feed bleibt leer. Der Branch `oldstable` in `Plugin_SN_Basis` und `Plugin_SN_Plan41` ist eine Altlast aus
|
||||
der Zeit davor; er wird nicht mehr bespielt und nach dem Anlegen von `stable` archiviert oder gelöscht.
|
||||
|
||||
> **Warum `stable` und nicht `main`?** Der Branchname ist hier nicht frei wählbar. Der Release-Workflow im
|
||||
> Repository `Repository` (Branch `hidden/workflows`) klont das Plugin-Repo und führt `git checkout "$CHANNEL"`
|
||||
> aus. `$CHANNEL` kommt aus dem Tag-Suffix und ist `unstable`, `testing` oder `stable`. **Der Branch muss also
|
||||
> genauso heißen wie der Kanal.** Ein Branch `main` würde den stabilen Release-Pfad brechen, solange die
|
||||
> Infrastruktur nicht mit angepasst wird (ADR 0007).
|
||||
|
||||
## 2. Der normale Weg: ein Feature
|
||||
|
||||
```bash
|
||||
# 1. Kanal-Branch aktualisieren (macht auch /hallo bzw. python scripts/hallo.py)
|
||||
cd Plugin_SN_Verfahrensgebiet
|
||||
git switch unstable
|
||||
git pull --ff-only
|
||||
|
||||
# 2. Eigenen Branch abzweigen — immer von unstable, nie von stable oder testing
|
||||
git switch -c feature/41-fbschl-feldname
|
||||
|
||||
# 3. In kleinen Schritten arbeiten und committen
|
||||
git add functions/verfahrensgebiet_shape_komplett.py tests/test_shape_komplett.py
|
||||
git commit -m "Shape-Import akzeptiert Feldnamen FBSchl. mit Punkt (#41)"
|
||||
|
||||
# 4. Tests
|
||||
python ../scripts/run_tests.py sn_verfahrensgebiet --baseline
|
||||
|
||||
# 5. Push — durch den Menschen, mit dessen Zugangsdaten
|
||||
git push -u origin feature/41-fbschl-feldname
|
||||
```
|
||||
|
||||
Danach im Gitea-Browser einen **Pull Request nach `unstable`** öffnen, nach der Vorlage
|
||||
`docs/vorlagen/pull_request.md`, mit Übergabenotiz (Skill `sn-uebergabe`).
|
||||
|
||||
Ein Pull Request sollte an einem Tag entstehen und in einer Viertelstunde reviewbar sein. Mehr als etwa
|
||||
400 geänderte Zeilen heißt: das Issue war zu groß.
|
||||
|
||||
## 3. Branch-Namen
|
||||
|
||||
| Präfix | Beispiel | Wofür |
|
||||
|---|---|---|
|
||||
| `feature/` | `feature/41-fbschl-feldname` | neue Funktion |
|
||||
| `bugfix/` | `bugfix/57-basis-laedt-ohne-plan41` | Fehlerbehebung |
|
||||
| `hotfix/` | `hotfix/61-absturz-beim-beenden` | dringender Fix auf `stable` |
|
||||
| `doku/` | `doku/git-arbeitsweise` | nur Dokumentation, ohne Issue zulässig |
|
||||
| `release/` | `release/26.9.1` | Release-Vorbereitung, nur Maintainer |
|
||||
|
||||
**Regeln für den Namen:** Kleinbuchstaben, ASCII, Bindestriche statt Leerzeichen. Keine Umlaute, kein `ß`, keine
|
||||
Punkte. Issue-Nummer direkt nach dem Präfix, damit Gitea den Branch dem Ticket zuordnet.
|
||||
|
||||
Gegenbeispiele aus dem Bestand, die es künftig nicht mehr geben soll:
|
||||
|
||||
| Vorhanden | Problem | Richtig wäre |
|
||||
|---|---|---|
|
||||
| `feature/Drucklayout-überarbeiten` | Umlaut, Großbuchstabe, keine Issue-Nummer | `feature/37-drucklayout` |
|
||||
| `bug/crash_on_exit` | Präfix `bug/` statt `bugfix/`, Unterstriche, keine Nummer | `bugfix/61-absturz-beim-beenden` |
|
||||
| `alkis-nur-laden-stop` | gar kein Präfix | `feature/43-alkis-stop-abfrage` |
|
||||
| `fix-fbschl-feldname` | Präfix `fix-` gibt es nicht | `bugfix/45-fbschl-feldname` |
|
||||
|
||||
## 4. Wo fange ich bei einem Fix an?
|
||||
|
||||
Das ist die Frage, bei der am meisten schiefgeht. Sie hängt nicht davon ab, wie dringend der Fehler ist, sondern
|
||||
davon, **in welchem Kanal er sitzt**.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A{"In welchem Kanal<br/>tritt der Fehler auf?"}
|
||||
A -->|"nur in unstable"| B["Branch von unstable<br/>PR nach unstable"]
|
||||
A -->|"in einem testing-Kandidaten"| C["Branch von testing<br/>PR nach testing"]
|
||||
A -->|"in einem stable-Release"| D["Branch von stable<br/>PR nach stable"]
|
||||
|
||||
B --> B2(["fertig"])
|
||||
C --> C2["Rückführung:<br/>PR testing → unstable"]
|
||||
D --> D2["Rückführung:<br/>PR stable → testing"]
|
||||
D2 --> D3["Rückführung:<br/>PR testing → unstable"]
|
||||
C2 --> E(["fertig"])
|
||||
D3 --> E
|
||||
|
||||
style C fill:#e6f2ff,stroke:#2b7dd9
|
||||
style D fill:#ffe9e9,stroke:#d94b2b
|
||||
style C2 fill:#fff4e6,stroke:#d9822b
|
||||
style D2 fill:#fff4e6,stroke:#d9822b
|
||||
style D3 fill:#fff4e6,stroke:#d9822b
|
||||
```
|
||||
|
||||
| Fehler sitzt in | Branch von | Pull Request nach | Danach zwingend |
|
||||
|---|---|---|---|
|
||||
| nur `unstable` | `unstable` | `unstable` | — |
|
||||
| `testing`-Kandidat | **`testing`** | `testing` | Rückführung `testing` → `unstable` |
|
||||
| `stable`-Release | **`stable`** | `stable` | Rückführung nach `testing` **und** `unstable` |
|
||||
|
||||
### Warum die Rückführung nicht optional ist
|
||||
|
||||
Ein Fix, der nur in `testing` landet, ist beim nächsten Hochstufen wieder weg: dann wird `unstable` nach `testing`
|
||||
gemergt, und `unstable` kennt den Fix nicht. Der Fehler kommt zurück, und niemand versteht warum. Deshalb gehört
|
||||
zu jedem Fix, der nicht in `unstable` beginnt, ein zweiter Pull Request — sofort, nicht „später".
|
||||
|
||||
```bash
|
||||
# Fix in einem testing-Kandidaten
|
||||
git switch testing
|
||||
git pull --ff-only
|
||||
git switch -c bugfix/61-absturz-beim-beenden
|
||||
# ... beheben, testen, committen ...
|
||||
git push -u origin bugfix/61-absturz-beim-beenden
|
||||
# PR nach testing öffnen und mergen lassen
|
||||
|
||||
# Rückführung, direkt im Anschluss
|
||||
git switch testing && git pull --ff-only
|
||||
git switch -c doku/rueckfuehrung-61-nach-unstable testing
|
||||
git push -u origin doku/rueckfuehrung-61-nach-unstable
|
||||
# PR nach unstable öffnen: "Rückführung Fix #61 aus testing"
|
||||
```
|
||||
|
||||
Führt die Rückführung zu Konflikten, weil `unstable` inzwischen weit vorausgelaufen ist, wird der Fix dort neu
|
||||
angewendet (`git cherry-pick <commit>`) — auch das als eigener Branch mit eigenem Pull Request.
|
||||
|
||||
## 5. Tags und Releases
|
||||
|
||||
Version ist `JJ.M.N` — Jahr, Monat, laufende Nummer im Monat. Beispiel: `26.9.1` ist das erste Release im
|
||||
September 2026. Jeder Kanal zählt getrennt hoch.
|
||||
|
||||
| Kanal | Tag | Wird gebaut aus |
|
||||
|---|---|---|
|
||||
| unstable | `v26.9.1-u` | Kopf von `unstable` |
|
||||
| testing | `v26.9.1-t` | Kopf von `testing` |
|
||||
| stable | `v26.9.1` | Kopf von `stable` |
|
||||
|
||||
**Achtung:** Der Workflow baut den **Kopf des Kanal-Branches**, nicht den getaggten Commit. Vor dem Taggen also
|
||||
sicherstellen, dass der Branch genau den gewünschten Stand hat.
|
||||
|
||||
Tags setzt ausschließlich ein Maintainer. Ablauf und Prüfliste: Skill `sn-release`.
|
||||
|
||||
## 6. Was der Agent darf und was nicht
|
||||
|
||||
| Der Agent darf | Der Agent darf nicht |
|
||||
|---|---|
|
||||
| `git fetch`, `git pull --ff-only`, `git status`, `git log`, `git diff` | `git push` in jeder Form |
|
||||
| lokale Branches anlegen und wechseln | `git merge`, `git rebase` |
|
||||
| `git add`, `git commit` auf einem Arbeitsbranch | `git tag` |
|
||||
| Tests laufen lassen, `changelog.txt` ergänzen | Pull Requests mergen |
|
||||
| Issue- und PR-Texte als Entwurf vorbereiten | `git reset --hard`, `git push --force`, `git clean` |
|
||||
| Tickets über `scripts/gitea.py` lesen | irgendetwas auf dem Gitea-Server verändern |
|
||||
| | `docs/status/testbaseline.json` ändern |
|
||||
|
||||
Das ist nicht nur eine Bitte. `.claude/settings.json` im Repo verbietet die entsprechenden Kommandos technisch;
|
||||
der Agent bekommt eine Ablehnung statt einer Ausführung. Wer die Regel für einen Einzelfall aussetzen will, tut
|
||||
das bewusst und lokal, nicht im Repo.
|
||||
|
||||
**Warum so streng:** Ein Push trägt den Namen einer Person und ihre Verantwortung. Ein Agent kann fachlich nicht
|
||||
beurteilen, ob ein Stand veröffentlichungsreif ist — und ein versehentlicher Push auf einen Kanal-Branch trifft
|
||||
sofort alle anderen Behörden.
|
||||
|
||||
## 7. Branch-Schutz auf Gitea einrichten (Maintainer)
|
||||
|
||||
Gitea 1.27, je Repository unter **Einstellungen → Branches** (englisch: *Settings → Branches*), dort eine neue
|
||||
Branch-Schutzregel anlegen. Für jeden der drei Kanäle eine Regel:
|
||||
|
||||
| Einstellung | Wert | Wirkung |
|
||||
|---|---|---|
|
||||
| Branch-Name | `stable` bzw. `testing`, `unstable` | betrifft genau diesen Branch |
|
||||
| Push deaktivieren | an | kein direkter Push, nur Merge über Pull Request |
|
||||
| Merge-Freigabe erforderlich | mindestens 1 | Review durch eine andere Person ist Pflicht |
|
||||
| Statusprüfungen erforderlich | an, sobald CI läuft | Tests müssen grün sein (Vorlage `docs/vorlagen/gitea_workflow_tests.yaml`) |
|
||||
| Veraltete Freigaben verwerfen | an | ein neuer Push macht das Review ungültig |
|
||||
|
||||
Zusätzlich den Standard-Branch auf `unstable` belassen — dort landet die tägliche Arbeit, und ein neu geklontes
|
||||
Repo steht damit sofort richtig.
|
||||
|
||||
## 8. Aufräumen
|
||||
|
||||
Nach dem Merge wird der Arbeitsbranch gelöscht — in Gitea per Schaltfläche direkt am gemergten Pull Request,
|
||||
lokal so:
|
||||
|
||||
```bash
|
||||
git switch unstable
|
||||
git pull --ff-only
|
||||
git branch -d feature/41-fbschl-feldname
|
||||
git fetch --prune
|
||||
```
|
||||
|
||||
Nicht gelöschte Branches sammeln sich an und niemand weiß später, ob sie noch etwas enthalten. Im Bestand liegen
|
||||
davon derzeit vier herum (`feature/Drucklayout-überarbeiten`, `print_layout`, `alkis-nur-laden-stop`,
|
||||
`fix-fbschl-feldname`) — die sind zu prüfen und dann zu entfernen.
|
||||
|
||||
## 9. Commit-Nachrichten
|
||||
|
||||
Deutsch, Präsens, erste Zeile höchstens 72 Zeichen, Issue-Nummer am Ende in Klammern.
|
||||
|
||||
```
|
||||
Shape-Import akzeptiert Feldnamen FBSchl. mit Punkt (#41)
|
||||
|
||||
DAVID-Exporte liefern das Feld teils mit, teils ohne Punkt. Die Suche
|
||||
prüft jetzt beide Schreibweisen, die Bedeutung der Schlüssel 901/10110F
|
||||
und 901/10120F bleibt unverändert.
|
||||
```
|
||||
|
||||
Was die Nachricht beantworten muss: **was** ändert sich und **warum**. Das *Wie* steht im Diff.
|
||||
Nicht brauchbar: „Fix", „Update", „Änderungen", „wip".
|
||||
|
||||
## 10. Sitzungsbeginn
|
||||
|
||||
Vor dem ersten Handgriff — auch wenn es „nur schnell etwas" ist:
|
||||
|
||||
```bash
|
||||
python scripts/hallo.py # oder in Claude Code: /hallo
|
||||
```
|
||||
|
||||
Das holt für alle Klone den Stand vom Server (`git fetch`), aktualisiert die Kanal-Branches per Fast-Forward,
|
||||
wo das gefahrlos möglich ist, und meldet alles Übrige, ohne es anzufassen. Es merged nicht, es stasht nicht, und
|
||||
es fasst keine ungesicherten Änderungen an.
|
||||
|
||||
Ohne diesen Schritt entstehen Konflikte, die niemand braucht — und im schlimmsten Fall wird ein Fehler behoben,
|
||||
den jemand anderes vor drei Tagen schon behoben hat.
|
||||
|
||||
## 11. Häufige Fehler
|
||||
|
||||
**„Ich habe direkt auf `unstable` committet."** Solange nichts gepusht ist, ist das reparabel:
|
||||
`git switch -c feature/<nr>-<name>` legt einen Branch auf dem aktuellen Stand an, danach
|
||||
`git switch unstable && git reset --hard origin/unstable`. Der `reset` ist hier zulässig, weil er nur die lokale
|
||||
Kopie auf den Serverstand zurücksetzt — trotzdem vorher `git log` prüfen.
|
||||
|
||||
**„Mein Branch ist hinter `unstable`."** Vor dem Pull Request aktualisieren:
|
||||
`git switch unstable && git pull --ff-only`, dann zurück auf den Arbeitsbranch und `git merge unstable`.
|
||||
Kein `rebase` auf bereits gepushten Branches — das schreibt Historie um, die andere schon haben.
|
||||
|
||||
**„`git pull` will einen Merge-Commit machen."** Dann ist der lokale Branch vorausgelaufen. `--ff-only` bricht
|
||||
in dem Fall bewusst ab. Erst klären, was die eigenen Commits sind, statt blind zu mergen.
|
||||
|
||||
**„Der Fix ist wieder weg."** Fast immer die fehlende Rückführung aus Abschnitt 4.
|
||||
|
||||
**„Ich habe im falschen Repo gearbeitet."** Passiert, weil alle Klone nebeneinander liegen.
|
||||
`git status` zeigt oben den Pfad; `python scripts/hallo.py` zeigt alle Repos auf einmal.
|
||||
|
||||
## Siehe auch
|
||||
|
||||
- `AGENTS.md` — Kurzfassung der harten Regeln
|
||||
- `docs/leitfaden/03_arbeitsweise.md` — der Arbeitszyklus vom Issue bis zum Merge
|
||||
- `docs/leitfaden/06_tests.md` — was vor dem Pull Request grün sein muss
|
||||
- `docs/entscheidungen/0007-branchmodell.md` — warum das Modell so aussieht
|
||||
- `docs/konzept/08_repo_einrichten.md` — `stable` anlegen, Branch-Schutz setzen
|
||||
- Skill `sn-release` — Tags und Releases
|
||||
@@ -0,0 +1,196 @@
|
||||
# Tests: was geprüft wird, wie man es liest, was Pflicht ist
|
||||
|
||||
Stand 04.09.2026. Für alle, die an den LNO-Plugins mitarbeiten — auch für Fachleute ohne Programmiererfahrung.
|
||||
Der Abschnitt 7 ist ausdrücklich für Sie geschrieben: dort geht es darum, zu **beurteilen**, ob eine Testsuite
|
||||
fachlich das Richtige prüft. Dafür muss man keinen Code lesen können.
|
||||
|
||||
## 1. Warum das hier ernster genommen wird als sonst
|
||||
|
||||
Ein großer Teil des Codes entsteht mit KI-Agenten. Ein Agent kann überzeugend erklären, dass etwas funktioniert,
|
||||
ohne dass es funktioniert. Er kann auch einen roten Test „reparieren", indem er die Erwartung an das falsche
|
||||
Verhalten anpasst. Tests sind der einzige schnelle, nachprüfbare Nachweis, den ein Review hat.
|
||||
|
||||
Deshalb gilt: **Neue Funktionalität ohne Test wird nicht gemergt.** Und: Tests werden nicht gelöscht, nicht
|
||||
übersprungen und nicht in ihrer Erwartung angepasst, um grün zu werden — außer das Verhalten wurde bewusst
|
||||
geändert, und der Pull Request begründet es.
|
||||
|
||||
## 2. Die drei Ebenen
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
M["<b>Manueller Test in QGIS</b><br/>wenige · Minuten bis Stunden · durch Menschen<br/><i>Bedienbarkeit, Optik, Zusammenspiel mit echten Daten</i>"]
|
||||
Q["<b>QGIS-Tests</b> — geplant, siehe konzept/07<br/>einige Dutzend · Minuten · automatisch<br/><i>Layer, WFS, Drucklayouts, Docks gegen echtes QGIS</i>"]
|
||||
K["<b>Mock-Tests</b> — heute 332 Stück<br/>viele · unter einer Sekunde · automatisch, ohne QGIS<br/><i>Logik, Parsing, Prüfregeln, Fehlerbehandlung</i>"]
|
||||
|
||||
M --- Q --- K
|
||||
|
||||
style M fill:#ffe9e9,stroke:#d94b2b
|
||||
style Q fill:#e6f2ff,stroke:#2b7dd9
|
||||
style K fill:#e8f6ec,stroke:#2f9e44
|
||||
```
|
||||
|
||||
Je weiter unten, desto schneller, billiger und häufiger. Ein Fehler soll möglichst weit unten auffallen — dort
|
||||
ist er in Sekunden zu finden statt in einer halben Stunde Klickerei.
|
||||
|
||||
**Mock-Tests** laufen ohne QGIS, weil aller Zugriff auf QGIS und Qt über die Wrapper in `sn_basis.functions`
|
||||
läuft. Fehlt QGIS, liefern die Wrapper Ersatzobjekte. Das ist der Grund für die Wrapper-Regel in `AGENTS.md` —
|
||||
sie ist nicht Selbstzweck, sondern das, was diese Ebene überhaupt möglich macht.
|
||||
|
||||
**QGIS-Tests** gibt es heute noch nicht automatisiert. Der Stufenplan steht in
|
||||
[`docs/konzept/07_qgis_tests_plan.md`](../konzept/07_qgis_tests_plan.md).
|
||||
|
||||
**Manuelle Tests** bleiben unersetzbar für alles, was ein Mensch sehen muss: Sieht der Plan richtig aus? Ist der
|
||||
Dialog bedienbar? Stimmen die Beschriftungen fachlich?
|
||||
|
||||
## 3. Was gehört auf welche Ebene
|
||||
|
||||
| Was geändert wurde | Mock-Test | QGIS-Test | Manuell |
|
||||
|---|---|---|---|
|
||||
| Rechenregel, Prüflogik, Schlüsselableitung | **Pflicht** | – | – |
|
||||
| Einlesen einer Datei (Shape, Excel, CSV, ZIP) | **Pflicht** | wenn Geometrie entsteht | – |
|
||||
| Fehlerbehandlung, Sonderfälle, leere Eingaben | **Pflicht** | – | – |
|
||||
| Aufruf eines Webdienstes (WFS, VLN, ALKIS) | **Pflicht** (Antwort nachgestellt) | empfohlen | bei neuem Dienst |
|
||||
| Layer anlegen, stylen, in den Baum hängen | soweit möglich | **Pflicht** | – |
|
||||
| Drucklayout, Planausgabe | Logik ja | **Pflicht** | **Pflicht** (Aussehen) |
|
||||
| Dialog, Dock, Tab, Signalverdrahtung | Logik dahinter | empfohlen | **Pflicht** |
|
||||
| Menü, Toolbar, Plugin-Ladevorgang | – | empfohlen | **Pflicht** |
|
||||
| Nur Dokumentation | – | – | – |
|
||||
|
||||
Faustregel: **Alles, was ohne sichtbare Oberfläche entscheidbar ist, gehört in einen Mock-Test.** Wenn sich etwas
|
||||
schlecht testen lässt, liegt das meistens daran, dass Fachlogik und Oberfläche vermischt sind — dann ist die
|
||||
Trennung die eigentliche Aufgabe, nicht der Test.
|
||||
|
||||
## 4. Die Pflicht im Detail
|
||||
|
||||
**Bei neuer Funktionalität:** mindestens ein Test je neuem beobachtbarem Verhalten. Nicht je Funktion — je
|
||||
*Verhalten*. Eine Funktion, die drei Fälle unterscheidet, braucht drei Tests.
|
||||
|
||||
**Bei Änderungen an bestehender Logik:**
|
||||
1. Vorher `python scripts/run_tests.py <paket> --baseline` laufen lassen und die Zahl merken.
|
||||
2. Ändern.
|
||||
3. Erneut laufen lassen. Keine Zeile `NEU ROT`.
|
||||
4. Die Tests, die das geänderte Verhalten abdecken, erweitern oder anpassen — mit Begründung im Pull Request.
|
||||
|
||||
**Bei einer Fehlerbehebung:** zuerst einen Test schreiben, der den Fehler zeigt (also rot ist), dann beheben.
|
||||
Sonst weiß niemand, ob der Fehler wirklich weg ist oder nur nicht mehr auffällt.
|
||||
|
||||
## 5. Wie ein guter Test aussieht
|
||||
|
||||
```python
|
||||
def test_fbschl_mit_punkt_wird_erkannt(self):
|
||||
"""Prüft, dass der Shape-Import auch das Feld 'FBSchl.' mit Punkt akzeptiert.
|
||||
|
||||
Manche DAVID-Exporte liefern den Feldnamen mit Punkt, andere ohne.
|
||||
Beide müssen zum selben Gebietstyp führen.
|
||||
"""
|
||||
layer = self._layer_mit_feldern({"FBSchl.": "901/10110F"})
|
||||
self.assertEqual(_ermittle_gebietstyp_aus_fbschl(layer), "Verfahrensgebiet")
|
||||
```
|
||||
|
||||
Drei Anforderungen, alle drei verbindlich:
|
||||
|
||||
1. **Der Name sagt, was geprüft wird**, auf Deutsch, aus fachlicher Sicht:
|
||||
`test_fbschl_mit_punkt_wird_erkannt` — nicht `test_case_3` oder `test_import`.
|
||||
2. **Ein deutscher Docstring, erste Zeile in der Form `Prüft, dass …`.** Diese Zeile landet im Testkatalog
|
||||
(Abschnitt 6) und ist für viele Leser der einzige Kontakt mit dem Test. Wenn nötig, danach ein Absatz mit dem
|
||||
fachlichen Hintergrund.
|
||||
3. **Ein Verhalten je Test.** Wenn im Namen ein „und" steht, sind es zwei Tests.
|
||||
|
||||
Weiter gilt: `unittest`, Datei `tests/test_<modul>.py`, kein Netzwerkzugriff, keine echten Verfahrensdaten, keine
|
||||
Abhängigkeit von der Reihenfolge der Tests. Muster liefern die vorhandenen Tests des jeweiligen Pakets.
|
||||
|
||||
## 6. Der Testkatalog
|
||||
|
||||
```bash
|
||||
python scripts/testkatalog.py
|
||||
```
|
||||
|
||||
erzeugt [`docs/tests/testkatalog.md`](../tests/testkatalog.md): jeder Testfall aller Pakete mit seiner
|
||||
Docstring-Zeile und seinem Status. Die Datei wird aus dem Code erzeugt und ist damit immer aktuell — sie wird
|
||||
**nicht von Hand bearbeitet**.
|
||||
|
||||
Wozu das gut ist: Man sieht auf zwei Seiten, was die Suite abdeckt, ohne eine einzige Testdatei zu öffnen. Der
|
||||
Abschnitt „Lücken" am Ende listet alle Tests ohne Docstring — das ist die Arbeitsliste, um den Katalog
|
||||
vollständig lesbar zu machen.
|
||||
|
||||
Nach dem Hinzufügen von Tests den Katalog neu erzeugen und mit committen. `python scripts/testkatalog.py --pruefen`
|
||||
meldet, wenn er veraltet ist.
|
||||
|
||||
## 7. Testkonzepte: prüfen, ob die Tests fachlich sinnvoll sind
|
||||
|
||||
Der Katalog beantwortet „was wird getestet". Er beantwortet **nicht** „ist das genug". Dafür gibt es je
|
||||
wichtigem Fachablauf eine eigene Seite in [`docs/tests/`](../tests/), nach der Vorlage
|
||||
[`docs/vorlagen/testkonzept.md`](../vorlagen/testkonzept.md).
|
||||
|
||||
Jede dieser Seiten hat denselben Aufbau:
|
||||
|
||||
1. **Ein Mermaid-Diagramm des fachlichen Ablaufs** — so, wie ein Sachbearbeiter ihn beschreiben würde.
|
||||
Keine Klassennamen, keine Dateipfade.
|
||||
2. **Eine Tabelle: Schritt → welcher Test deckt ihn ab → was bewusst nicht getestet wird, und warum.**
|
||||
3. **Ein Abschnitt „Bekannte Lücken"** mit Issue-Nummer, wo etwas fehlt.
|
||||
|
||||
So können Sie als Fachperson prüfen, ohne Code zu lesen:
|
||||
|
||||
- Kommt jeder Schritt Ihres Ablaufs in der Tabelle vor? Fehlt einer, fehlt wahrscheinlich auch der Test.
|
||||
- Sind die **Sonderfälle** dabei, die in der Praxis wirklich vorkommen — der leere Export, die doppelte
|
||||
Gemarkung, der Datensatz aus dem Nachbarlandkreis?
|
||||
- Steht bei „bewusst nicht getestet" eine Begründung, die Sie nachvollziehen können? „Zu aufwendig" ist keine.
|
||||
- Beschreibt der Test das *Ergebnis* oder nur, dass etwas *durchläuft*? „Prüft, dass der Import nicht abstürzt"
|
||||
ist deutlich weniger wert als „Prüft, dass aus 901/10110F der Gebietstyp Verfahrensgebiet wird".
|
||||
|
||||
Ein Beispiel liegt in [`docs/tests/verfahrensgebiet-shape-import.md`](../tests/verfahrensgebiet-shape-import.md).
|
||||
|
||||
Wenn Sie eine Lücke finden: Issue aufmachen (`python scripts/gitea.py entwurf issue Verfahrensgebiet` legt einen
|
||||
Entwurf an). Eine gefundene Testlücke ist ein vollwertiges Ticket.
|
||||
|
||||
## 8. Die Baseline
|
||||
|
||||
Nicht alle Tests sind grün. Der Ist-Stand steht in [`docs/status/testbaseline.json`](../status/testbaseline.json)
|
||||
und wirkt als Sperrklinke: **bekannte rote Tests dürfen bleiben, neue nicht dazukommen.**
|
||||
|
||||
```bash
|
||||
python scripts/run_tests.py sn_verfahrensgebiet --baseline
|
||||
```
|
||||
|
||||
- `NEU ROT` — nicht erlaubt. Ursache beheben, bevor der Pull Request aufgemacht wird.
|
||||
- `neu grün` — schön. Im Pull Request nennen und eine Baseline-Aktualisierung vorschlagen.
|
||||
- `unverändert gegenüber Baseline` — in Ordnung.
|
||||
|
||||
Die Baseline ändert **nur ein Maintainer** mit `--update-baseline`, nach Absprache. Ein Agent fasst sie nicht an.
|
||||
Das Grünmachen der bekannten roten Tests läuft über eigene Issues, nicht nebenbei.
|
||||
|
||||
## 9. Wenn ein Test rot ist
|
||||
|
||||
Erst die Frage beantworten: **Ist der Test veraltet oder ist der Code falsch?**
|
||||
|
||||
| Befund | Was zu tun ist |
|
||||
|---|---|
|
||||
| Der Code verhält sich falsch | Code beheben. Der Test bleibt, wie er ist. |
|
||||
| Das Verhalten wurde bewusst geändert (Issue deckt es ab) | Test anpassen, im Pull Request begründen, `changelog.txt` prüfen |
|
||||
| Der Test prüft etwas, das es nicht mehr gibt | Test entfernen — mit Begründung und nur, wenn das Verhalten wirklich entfallen ist |
|
||||
| `ModuleNotFoundError: sn_basis` | Nicht über `scripts/run_tests.py` gestartet, oder Klone fehlen |
|
||||
| `AttributeError: _MockQgs… has no attribute` | Mock im Wrapper ergänzen — nicht den Test umgehen |
|
||||
| Der Test hängt oder braucht Netz | Der Test ist falsch gebaut. Dienstantwort nachstellen. |
|
||||
|
||||
**Niemals** ein roter Test wird durch `@unittest.skip` oder durch Löschen grün gemacht. Das ist keine Lösung,
|
||||
das ist das Verstecken des Problems vor dem nächsten Leser.
|
||||
|
||||
Details zum Deuten einzelner Fehler: Skill `sn-tests`.
|
||||
|
||||
## 10. Vor dem Pull Request
|
||||
|
||||
- [ ] `python scripts/run_tests.py <paket> --baseline` ohne `NEU ROT`
|
||||
- [ ] Neue Funktionalität hat mindestens einen Test je neuem Verhalten
|
||||
- [ ] Jeder neue Test hat einen deutschen Docstring `"""Prüft, dass …"""`
|
||||
- [ ] `python scripts/testkatalog.py` neu erzeugt und mitcommittet
|
||||
- [ ] Betrifft die Änderung einen dokumentierten Fachablauf: die Seite in `docs/tests/` nachgezogen
|
||||
- [ ] Bei UI-, Layer-, WFS- oder Layoutverhalten: manueller QGIS-Test durchgeführt und im Pull Request
|
||||
unter „Wie geprüft" beschrieben — oder ausdrücklich als offener Punkt benannt
|
||||
|
||||
## Siehe auch
|
||||
|
||||
- Skill `sn-tests` — Tests ausführen und Fehler deuten
|
||||
- [`docs/konzept/07_qgis_tests_plan.md`](../konzept/07_qgis_tests_plan.md) — Ausbau zu echten QGIS-Tests
|
||||
- [`docs/entscheidungen/0003-mock-tests-baseline-pflicht.md`](../entscheidungen/0003-mock-tests-baseline-pflicht.md)
|
||||
- [`docs/entscheidungen/0009-tests-definition-of-done.md`](../entscheidungen/0009-tests-definition-of-done.md)
|
||||
- [`docs/architektur/schnittstellen.md`](../architektur/schnittstellen.md) — die Wrapper, die den Mock-Modus tragen
|
||||
Reference in New Issue
Block a user