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:
2026-09-04 11:57:26 +02:00
co-authored by Claude Sonnet 5
commit ab416fb34c
57 changed files with 7202 additions and 0 deletions
+203
View File
@@ -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
+243
View File
@@ -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)
+282
View File
@@ -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
+196
View File
@@ -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