diff --git a/.agents/skills/sn-aufgabe-bearbeiten/SKILL.md b/.agents/skills/sn-aufgabe-bearbeiten/SKILL.md index 1f52770..33d5d4e 100644 --- a/.agents/skills/sn-aufgabe-bearbeiten/SKILL.md +++ b/.agents/skills/sn-aufgabe-bearbeiten/SKILL.md @@ -29,6 +29,8 @@ Ohne diesen Schritt wird womöglich etwas behoben, das längst behoben ist. ## 3. Branch Immer von `main`. Vollständig: `docs/leitfaden/05_git_arbeitsweise.md`. +Kein `feature/`- oder `bugfix/`-Branch ohne Issue-Nummer. Gibt es kein Issue, zuerst einen Entwurf vorbereiten +(Skill `sn-ticket`) und warten, bis der Mensch es angelegt hat. Nur `doku/…` kommt ohne Issue aus. ```bash git switch main && git pull --ff-only @@ -39,10 +41,13 @@ git switch -c feature/- # oder bugfix/… ; klein, ASCII, - Kleine Schritte, jeder für sich prüfbar. Nach jedem Schritt `python -m pytest -q`. - Regeln aus `AGENTS.md`: nur Standardbibliothek, kein Umbau nebenbei, Stil der Datei beibehalten, Deutsch mit echten Umlauten. -- **Neues Verhalten des Konverters bekommt einen Test** in `.agents/skills/sn-bpmn/test_md_to_bpmn.py` – pytest, - ohne Netzwerk, ein Test je Verhalten. Bei einem Bugfix zuerst den Test schreiben, der den Fehler zeigt. +- **Neue Logik bekommt einen Test** – Konverter in `.agents/skills/sn-bpmn/test_md_to_bpmn.py`, Skripte in + `tests/test_.py`. pytest, ohne Netzwerk, ein Test je Verhalten. Bei einem Bugfix zuerst den Test + schreiben, der den Fehler zeigt. - **Jeder neue oder geänderte Test bekommt einen deutschen Docstring**, erste Zeile `"""Prüft, dass …"""`. -- Ändert sich die Markdown-Syntax oder ein Element: `Anleitung.md` bzw. `Elementkatalog.md` mitziehen. +- Ändert sich die Markdown-Syntax oder ein Element: `Anleitung.md` bzw. `Elementkatalog.md` mitziehen. Syntax und + Ablageformat sind Verträge (`AGENTS.md`): vorher alle Modelle unter `prozesse/` prüfen, danach + `python scripts/pruefe_prozesse.py --erzeugen`. ## 5. Nachweis - `python -m pytest -q` → alles grün. @@ -52,5 +57,7 @@ git switch -c feature/- # oder bugfix/… ; klein, ASCII, ## 6. Abschluss - Commit-Nachrichten deutsch, Präsens, erste Zeile ≤ 72 Zeichen, Issue-Nummer am Ende. -- Pull-Request-Text nach `docs/vorlagen/pull_request.md` vorbereiten; Übergabenotiz mit Skill `sn-uebergabe`. +- Pull-Request-Text **vollständig** nach `docs/vorlagen/pull_request.md` vorbereiten: jeder Abschnitt, auch + „Änderungen an Syntax und Ablageformat“ (notfalls „keine“) und die Checkliste mit ehrlich gesetzten Haken. + Übergabenotiz mit Skill `sn-uebergabe`. - **Hier ist Schluss.** Push, Pull Request, Merge und alles auf Gitea macht der Mensch. diff --git a/AGENTS.md b/AGENTS.md index 6ec080c..05a4197 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -34,8 +34,8 @@ Sie bleibt bewusst kurz: Details liegen in `docs/` und werden nur bei Bedarf gel ### Tests - Vor jedem Commit: `python -m pytest -q` – alle Tests grün. -- **Neues Verhalten von `md_to_bpmn.py` ohne Test wird nicht gemergt.** Bei einer Fehlerbehebung zuerst der Test, - der den Fehler zeigt. +- **Neue Funktionalität ohne Test wird nicht gemergt** – im Konverter wie in den Skripten. Mindestens ein Test je + neuem beobachtbarem Verhalten, ohne Netzwerk. Bei einer Fehlerbehebung zuerst der Test, der den Fehler zeigt. - **Jeder neue oder geänderte Test bekommt einen deutschen Docstring**, erste Zeile in der Form `"""Prüft, dass …"""`. - Tests werden nicht gelöscht, nicht mit `skip` stillgelegt 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. @@ -53,6 +53,9 @@ Sie bleibt bewusst kurz: Details liegen in `docs/` und werden nur bei Bedarf gel - Keine Python-Abhängigkeiten außer der Standardbibliothek und `pytest` ohne Issue-Absprache und Eintrag in `fallstricke.md`. Einzige eingebettete Fremdbibliothek ist bpmn-js unter `werkzeuge/vorschau/bpmn-js/` – nicht von Hand ändern, Aktualisierung nach `werkzeuge/vorschau/README.md`. +- **Die Markdown-Syntax und das Ablageformat unter `prozesse/` sind Verträge.** Wer sie ändert, prüft vorher alle + Modelle unter `prozesse/`, zieht `Anleitung.md` bzw. `prozesse/README.md` nach und nennt die Änderung im Pull + Request unter „Änderungen an Syntax und Ablageformat“. - Kein Umbau „nebenbei". Strukturänderungen nur über eigenes Issue. ### Allgemein @@ -65,11 +68,13 @@ Sie bleibt bewusst kurz: Details liegen in `docs/` und werden nur bei Bedarf gel 1. `python scripts/hallo.py` – Stand holen. 2. Gitea-Issue lesen (Skill `sn-ticket`, `python scripts/gitea.py ticket Projektwissen `) oder als Entwurf vorbereiten. Ziel und Abnahmekriterien klären, bevor etwas geändert wird. -3. Branch `feature/-` bzw. `bugfix/-` von `main`. +3. Branch `feature/-` bzw. `bugfix/-` von `main`. Ohne Issue kein + solcher Branch: erst den Entwurf, dann legt der Mensch das Issue an (Ausnahme: `doku/…`). 4. In kleinen Schritten umsetzen; nur betroffene Dateien und Abschnitte lesen. 5. `python -m pytest -q` (enthält `pruefe_prozesse.py`) und `python scripts/pruefe_umlaute.py`. Erzeugte BPMN-Dateien zusätzlich in PICTURE importieren und das Ergebnis im PR nennen. -6. Pull-Request-Text nach `docs/vorlagen/pull_request.md` vorbereiten, inklusive Übergabenotiz (Skill `sn-uebergabe`). +6. Pull-Request-Text vollständig nach `docs/vorlagen/pull_request.md` vorbereiten – alle Abschnitte einschließlich + Checkliste –, mit Übergabenotiz (Skill `sn-uebergabe`). **Push und Pull Request macht der Mensch.** 7. Neue Erkenntnisse in `docs/wissen/` oder `docs/entscheidungen/` festhalten, nicht nur im lokalen Gedächtnis des Werkzeugs. diff --git a/docs/entscheidungen/0001-uebernahme-aus-ag-qgis.md b/docs/entscheidungen/0001-uebernahme-aus-ag-qgis.md index 6592730..1151b3a 100644 --- a/docs/entscheidungen/0001-uebernahme-aus-ag-qgis.md +++ b/docs/entscheidungen/0001-uebernahme-aus-ag-qgis.md @@ -9,7 +9,9 @@ sind Regeln, Skripte, Skills und eine Wissensbasis für die KI-gestützte Zusamm ## Entscheidung `AG_Prozesse/Projektwissen` wird **neu angelegt** und übernimmt ausgewählte Dateien aus -`AG_QGIS/Projektwissen` (Stand `main` @ `decd2a0`, `sn-bpmn` von `BPMN-Skill` @ `6efcac4`) – **ohne Git-Historie**. +`AG_QGIS/Projektwissen` (Stand `main` @ `a0bb40a`, `sn-bpmn` von `BPMN-Skill` @ `6efcac4`) – **ohne Git-Historie**. +*(Korrigiert am 2026-09-23: Hier stand zunächst `decd2a0`; das ist ein Commit auf dem Branch +`feature/16-test-audit`, kopiert wurde aber von `main`.)* QGIS- und Plugin-Spezifisches (Testrunner, Testkatalog, QGIS-Profil, ALKIS/WFS-Wissen, Modulkarte, Release) wird nicht übernommen. diff --git a/docs/leitfaden/03_arbeitsweise.md b/docs/leitfaden/03_arbeitsweise.md index 573c10f..4690e30 100644 --- a/docs/leitfaden/03_arbeitsweise.md +++ b/docs/leitfaden/03_arbeitsweise.md @@ -122,11 +122,15 @@ kleine PRs machen. **Vor dem PR** -- [ ] Issue verlinkt, Branch von `main` +- [ ] Issue verlinkt, Branch von `main` mit Issue-Nummer im Namen - [ ] `python -m pytest -q` grün, `python scripts/pruefe_umlaute.py` ohne Funde +- [ ] Neue Funktionalität hat mindestens einen Test je neuem Verhalten +- [ ] Jeder neue oder geänderte Test hat einen deutschen Docstring `"""Prüft, dass …"""` +- [ ] Keine Debug-`print`, kein auskommentierter Code +- [ ] Änderungen an Markdown-Syntax oder Ablageformat im PR benannt (oder „keine“) - [ ] Bei geänderter BPMN-Ausgabe: PICTURE-Import dokumentiert, `.bpmn` unter `prozesse/` neu erzeugt - [ ] Keine personenbezogenen Daten, keine Originalunterlagen -- [ ] Übergabenotiz im PR; Wissensbasis nachgezogen, falls nötig +- [ ] PR-Text vollständig nach Vorlage, mit Checkliste und Übergabenotiz; Wissensbasis nachgezogen, falls nötig - [ ] Diff gelesen: nur, was das Issue verlangt **Review** diff --git a/docs/status/STATUS.md b/docs/status/STATUS.md index 844745f..a0a6a76 100644 --- a/docs/status/STATUS.md +++ b/docs/status/STATUS.md @@ -17,7 +17,9 @@ Skills sind übernommen, alles QGIS- und Plugin-Spezifische ist entfernt. Kern i ## Offene Baustellen (priorisiert) -1. **Ersten Stand auf Gitea bringen** (`main`) und Branch-Schutz für `main` setzen (nur per Pull Request). +1. **Repo `Projektwissen` in Gitea öffentlich schalten** (Repo → Einstellungen → Sichtbarkeit). Die Organisation ist + öffentlich, das Repo nicht; ohne das liest `scripts/gitea.py` keine Tickets, und die Skills `sn-ticket` und + `sn-aufgabe-bearbeiten` laufen ins Leere. 2. **ADR 0003 (Ablage in `prozesse/`) vom Team annehmen lassen**, dann den ersten echten Prozess ablegen. 3. **Glossar um BPMN-/PICTURE-Begriffe ergänzen** und die Flurbereinigungsbegriffe auf das prüfen, was für Prozesse gebraucht wird. @@ -28,6 +30,11 @@ Skills sind übernommen, alles QGIS- und Plugin-Spezifische ist entfernt. Kern i ## Erledigt +- 23.09.2026 **Regeln für PR- und Issue-Texte an AG_QGIS angeglichen:** Vertragsabschnitt in der PR-Vorlage + („Änderungen an Syntax und Ablageformat“), Vertragsfrage in der Issue-Vorlage, Testpflicht wieder für jede neue + Funktionalität, vollständige Checklisten, kein Arbeitsbranch ohne Issue. `gitea.py` meldet ein nicht öffentliches + Repo jetzt als Fehler statt „keine Tickets“ und stürzt bei Zeichen außerhalb von cp1252 nicht mehr ab. + - 23.09.2026 **BPMN-Vorschau** im Browser, offline: `scripts/vorschau.py` und `werkzeuge/vorschau/vorschau.html` auf Basis von bpmn-js 18.29.1 (lokal eingebettet, MIT mit Wasserzeichen-Auflage). Anzeige direkt in Gitea wäre Sache der Server-Administration (externer Renderer), Issue-Entwurf liegt vor. @@ -37,6 +44,6 @@ Skills sind übernommen, alles QGIS- und Plugin-Spezifische ist entfernt. Kern i Die in `Anleitung.md` genannten, aber nie committeten Beispiele liegen jetzt unter `.agents/skills/sn-bpmn/beispiele/`. -- 23.09.2026 **Repository aus AG_QGIS/Projektwissen abgeleitet** (Stand `decd2a0` bzw. `BPMN-Skill` @ `6efcac4`), +- 23.09.2026 **Repository aus AG_QGIS/Projektwissen abgeleitet** (Stand `main` @ `a0bb40a` bzw. `BPMN-Skill` @ `6efcac4`), ohne Historie. Skripte auf die Organisation `AG_Prozesse` umgestellt, Hauptbranch `main`, Test über `pytest` (32 Tests grün), `pruefe_umlaute.py` prüft jetzt den Workspace selbst. diff --git a/docs/vorlagen/issue.md b/docs/vorlagen/issue.md index a66e0a2..bf4c9c8 100644 --- a/docs/vorlagen/issue.md +++ b/docs/vorlagen/issue.md @@ -4,6 +4,7 @@ ## Betroffen Bereich: Prozessmodell (`prozesse//`) | Konverter `md_to_bpmn.py` | Skill | Skripte | Doku Dateien (falls bekannt): +Ändert Markdown-Syntax oder Ablageformat unter `prozesse/`? nein | ja: ## Fachlicher Hintergrund @@ -11,7 +12,7 @@ Dateien (falls bekannt): ## Abnahmekriterien - [ ] - [ ] -- [ ] Bei Änderungen am Konverter: Test vorhanden, `python -m pytest -q` grün +- [ ] Test je neuem Verhalten vorhanden (Konverter, Skripte), `python -m pytest -q` grün - [ ] PICTURE-Import: ## Nicht Teil dieses Issues diff --git a/docs/vorlagen/pull_request.md b/docs/vorlagen/pull_request.md index c78b500..1a047ef 100644 --- a/docs/vorlagen/pull_request.md +++ b/docs/vorlagen/pull_request.md @@ -7,11 +7,16 @@ - Bei Modellen unter `prozesse/`: `.bpmn` mit `python scripts/pruefe_prozesse.py --erzeugen` erzeugt - PICTURE-Import: | nicht nötig, weil … +## Änderungen an Syntax und Ablageformat +keine | + ## Übergabenotiz ## Checkliste - [ ] Nur Änderungen zum Issue +- [ ] Keine Debug-`print`, kein auskommentierter Code - [ ] Keine personenbezogenen Daten, keine internen Originalunterlagen - [ ] Doku des Skills nachgezogen (Anleitung, Elementkatalog, Modellierregeln) oder nicht betroffen - [ ] Wissensbasis nachgezogen (oder „nichts Neues“) diff --git a/docs/wissen/fallstricke.md b/docs/wissen/fallstricke.md index 7f81770..5874e9d 100644 --- a/docs/wissen/fallstricke.md +++ b/docs/wissen/fallstricke.md @@ -50,6 +50,13 @@ Kurz, konkret, mit Datum und Quelle. Neue Einträge oben in der passenden Rubrik ## Umgebung und Werkzeuge +- **Öffentliche Organisation heißt nicht öffentliches Repo.** In Gitea haben Organisation und Repo getrennte + Sichtbarkeit. `AG_Prozesse` war öffentlich, `Projektwissen` privat: Die Weboberfläche zeigte es angemeldeten + Personen normal, die API ohne Anmeldung lieferte 404. `scripts/gitea.py` liest bewusst ohne Token und braucht deshalb + ein öffentliches Repo; es prüft das jetzt vor jeder Abfrage. Prüfen ohne Anmeldung: + `curl -s -o /dev/null -w "%{http_code}" https://entwicklung.flurneuordnung-sachsen.de/api/v1/repos/AG_Prozesse/Projektwissen` + muss `200` liefern. (23.09.2026) + - **Gitea ordnet Commits über die E-Mail-Adresse zu.** Stimmt `user.email` nicht mit einer im Gitea-Konto hinterlegten Adresse überein, erscheinen die Commits ohne Zuordnung zum Benutzer. Es gilt die dienstliche Adresse, die im Gitea-Konto hinterlegt ist. Wer den Rechner auch privat nutzt, setzt sie je Repository mit diff --git a/scripts/gitea.py b/scripts/gitea.py index 6050157..b96daf2 100644 --- a/scripts/gitea.py +++ b/scripts/gitea.py @@ -32,6 +32,7 @@ from __future__ import annotations import argparse import json import shutil +import sys import tomllib import urllib.error import urllib.parse @@ -186,6 +187,26 @@ def hole_issues(basis: str, org: str, repo: str, state: str, art: str = "issues" ) from None +def pruefe_sichtbar(basis: str, org: str, namen: list[str]) -> None: + """Bricht mit klarer Meldung ab, wenn ein Repo ohne Anmeldung nicht lesbar ist. + + Ohne diese Prüfung liefert die Sammelsuche über die Organisation für ein privates + Repo einfach keine Treffer – das sieht aus wie „keine offenen Tickets“. + """ + unsichtbar = [] + for name in namen: + try: + hole_json(baue_url(basis, f"/repos/{urllib.parse.quote(org)}/{urllib.parse.quote(name)}")) + except NichtGefunden: + unsichtbar.append(name) + if unsichtbar: + raise NichtGefunden( + f"Ohne Anmeldung nicht lesbar: {', '.join(f'{org}/{n}' for n in unsichtbar)}. " + "Das Repo ist privat oder existiert nicht. scripts/gitea.py liest bewusst ohne Token; " + "das Repo muss in Gitea öffentlich sein (Repo → Einstellungen → Sichtbarkeit)." + ) + + def hole_issues_org(basis: str, org: str, state: str, art: str = "issues", suchtext: str | None = None) -> list[dict]: """Issues ALLER Repos der Organisation über den Sammelendpunkt. @@ -549,6 +570,11 @@ def befehl_entwurf(basis: str, org: str, repo: str, art: str, nummer: int | None # ------------------------------------------------------------------ main ------ def main() -> int: + # Windows-Konsolen und Pipes laufen oft mit cp1252; Zeichen wie „→“ in Tickettexten + # oder Meldungen führten sonst zum Absturz der Ausgabe. + for strom in (sys.stdout, sys.stderr): + if hasattr(strom, "reconfigure"): + strom.reconfigure(encoding="utf-8", errors="replace") breite_eltern = argparse.ArgumentParser(add_help=False) breite_eltern.add_argument("--breite", type=int, help="Terminalbreite erzwingen (Standard: automatisch)") @@ -596,6 +622,8 @@ def main() -> int: return 2 repo = loese_repo(args.repo, namen) if getattr(args, "repo", None) else None + if not (args.befehl == "entwurf" and args.art == "issue"): # nur der Issue-Entwurf kommt ohne Netz aus + pruefe_sichtbar(basis, org, [repo] if repo else namen) if args.befehl == "tickets": return befehl_tickets(basis, org, repo, namen, args) diff --git a/tests/test_gitea.py b/tests/test_gitea.py new file mode 100644 index 0000000..c7341f2 --- /dev/null +++ b/tests/test_gitea.py @@ -0,0 +1,85 @@ +"""Tests für scripts/gitea.py – ohne Netzwerk, die Gitea-API wird ersetzt.""" +import sys +from pathlib import Path + +import pytest + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT / "scripts")) + +import gitea # noqa: E402 + +BASIS = "https://gitea.example" +MANIFEST = {"workspace": {"gitea": BASIS, "org": "AG_Test", "repo": "Projektwissen"}, "repos": []} + + +def api_mit_sichtbaren(*sichtbar): + """Ersatz für hole_json: nur die genannten Repos existieren, Issue-Listen sind leer.""" + def hole_json(url): + if "/issues" in url: + return [] + name = url.rsplit("/", 1)[-1] + if name in sichtbar: + return {"name": name} + raise gitea.NichtGefunden("Repo/Issue nicht gefunden: " + url) + return hole_json + + +def starte(monkeypatch, capsys, *argumente): + monkeypatch.setattr(gitea, "lade_manifest", lambda: MANIFEST) + monkeypatch.setattr(sys, "argv", ["gitea.py", *argumente]) + code = gitea.main() + return code, capsys.readouterr().out + + +def test_pruefe_sichtbar_laesst_oeffentliches_repo_durch(monkeypatch): + """Prüft, dass ein ohne Anmeldung lesbares Repo keinen Fehler auslöst.""" + monkeypatch.setattr(gitea, "hole_json", api_mit_sichtbaren("Projektwissen")) + gitea.pruefe_sichtbar(BASIS, "AG_Test", ["Projektwissen"]) + + +def test_pruefe_sichtbar_nennt_nicht_lesbare_repos(monkeypatch): + """Prüft, dass ein privates Repo mit Namen und Hinweis auf die Sichtbarkeit gemeldet wird.""" + monkeypatch.setattr(gitea, "hole_json", api_mit_sichtbaren("Projektwissen")) + with pytest.raises(gitea.NichtGefunden) as fehler: + gitea.pruefe_sichtbar(BASIS, "AG_Test", ["Projektwissen", "Geheim"]) + assert "AG_Test/Geheim" in str(fehler.value) + assert "AG_Test/Projektwissen" not in str(fehler.value) + assert "Sichtbarkeit" in str(fehler.value) + + +def test_tickets_bei_privatem_repo_meldet_fehler_statt_keine_tickets(monkeypatch, capsys): + """Prüft, dass 'tickets' bei unsichtbarem Repo mit Exit-Code 1 abbricht, statt 'keine Tickets' zu melden.""" + monkeypatch.setattr(gitea, "hole_json", api_mit_sichtbaren()) + code, ausgabe = starte(monkeypatch, capsys, "tickets") + assert code == 1 + assert "Ohne Anmeldung nicht lesbar" in ausgabe + assert "Keine passenden Tickets" not in ausgabe + + +def test_tickets_bei_oeffentlichem_repo_ohne_tickets(monkeypatch, capsys): + """Prüft, dass ein sichtbares Repo ohne offene Tickets weiterhin normal mit Exit-Code 0 endet.""" + monkeypatch.setattr(gitea, "hole_json", api_mit_sichtbaren("Projektwissen")) + code, ausgabe = starte(monkeypatch, capsys, "tickets") + assert code == 0 + assert "Keine passenden Tickets" in ausgabe + + +def test_issue_entwurf_braucht_kein_netz(monkeypatch, capsys, tmp_path): + """Prüft, dass 'entwurf issue' ohne jede API-Anfrage eine Entwurfsdatei anlegt.""" + def kein_netz(url): + raise AssertionError(f"unerwartete Anfrage: {url}") + monkeypatch.setattr(gitea, "hole_json", kein_netz) + monkeypatch.setattr(gitea, "ROOT", tmp_path) + monkeypatch.setattr(gitea, "ENTWURFSORDNER", tmp_path / "entwuerfe") + code, _ = starte(monkeypatch, capsys, "entwurf", "issue", "Projektwissen") + assert code == 0 + assert len(list((tmp_path / "entwuerfe").glob("*-issue-Projektwissen.md"))) == 1 + + +def test_ausgabe_mit_zeichen_ausserhalb_cp1252_stuerzt_nicht_ab(monkeypatch, capsys): + """Prüft, dass die Fehlermeldung mit '→' auch bei einer cp1252-Konsole ausgegeben wird.""" + monkeypatch.setattr(gitea, "hole_json", api_mit_sichtbaren()) + code, ausgabe = starte(monkeypatch, capsys, "pulls") + assert code == 1 + assert "→" in ausgabe