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
+109
View File
@@ -0,0 +1,109 @@
#!/usr/bin/env python3
"""Richtet den AG_QGIS-Workspace ein bzw. aktualisiert ihn.
1. Klont fehlende Repositories aus workspace.toml in Unterordner (Default-Branch).
2. Holt für vorhandene Klone den Stand vom Server (git fetch) und zeigt Branch,
Vorsprung/Rückstand und ungesicherte Änderungen an. Ein Pull passiert nur
mit --pull und nur bei sauberem Arbeitsverzeichnis (fast-forward).
3. Spiegelt die Skills aus .agents/skills/ nach .claude/skills/ (Claude Code liest
nur dort; der Agent-Skills-Standard und andere Werkzeuge lesen .agents/skills/).
Aufrufe:
python scripts/setup_workspace.py # klonen + Status + Skills
python scripts/setup_workspace.py --pull # zusätzlich fast-forward pull
python scripts/setup_workspace.py --nur-skills
"""
from __future__ import annotations
import argparse
import os
import shutil
import subprocess
import tomllib
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
def git(*args: str, cwd: Path) -> tuple[int, str]:
p = subprocess.run(["git", *args], cwd=cwd, capture_output=True, text=True, errors="replace")
return p.returncode, (p.stdout + p.stderr).strip()
def sync_skills() -> None:
quelle = ROOT / ".agents" / "skills"
ziel = ROOT / ".claude" / "skills"
if not quelle.is_dir():
print("Keine Skills in .agents/skills/ gefunden.")
return
ziel.parent.mkdir(exist_ok=True)
# Alten Spiegel entfernen (Symlink, Junction oder Kopie)
if ziel.is_symlink() or (ziel.exists() and not ziel.is_dir()):
ziel.unlink()
elif ziel.is_dir():
try:
os.rmdir(ziel) # leere Junction/Ordner
except OSError:
shutil.rmtree(ziel)
art = "symlink"
try:
os.symlink(quelle, ziel, target_is_directory=True)
except (OSError, NotImplementedError):
art = "kopie"
if os.name == "nt":
try:
import _winapi # type: ignore[attr-defined]
_winapi.CreateJunction(str(quelle), str(ziel))
art = "junction"
except Exception:
pass
if art == "kopie":
shutil.copytree(quelle, ziel, ignore=shutil.ignore_patterns("__pycache__"))
anzahl = len([p for p in quelle.iterdir() if (p / "SKILL.md").is_file()])
print(f"Skills: {anzahl} aus .agents/skills/ nach .claude/skills/ gespiegelt ({art}).")
if art == "kopie":
print(" Hinweis: Kopie statt Link – nach Änderungen an .agents/skills/ dieses Skript erneut ausführen.")
def main() -> int:
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument("--pull", action="store_true", help="saubere Klone per fast-forward aktualisieren")
ap.add_argument("--nur-skills", action="store_true", help="nur Skills spiegeln, kein Git")
args = ap.parse_args()
if not args.nur_skills:
with (ROOT / "workspace.toml").open("rb") as f:
manifest = tomllib.load(f)
ws = manifest["workspace"]
basis_url = f"{ws['gitea']}/{ws['org']}"
print(f"Workspace: {ROOT}\nGitea: {basis_url}\n")
for repo in manifest["repos"]:
name, branch = repo["name"], repo["branch"]
pfad = ROOT / name
if not (pfad / ".git").is_dir():
print(f"[{name}] klone Branch {branch} ...")
rc, out = git("clone", "--branch", branch, f"{basis_url}/{name}.git", name, cwd=ROOT)
print(" " + ("ok" if rc == 0 else f"FEHLER: {out}"))
continue
git("fetch", "--quiet", "--prune", cwd=pfad)
_, aktuell = git("rev-parse", "--abbrev-ref", "HEAD", cwd=pfad)
_, dirty = git("status", "--porcelain", cwd=pfad)
n_dirty = len([z for z in dirty.splitlines() if z.strip()])
_, ab = git("rev-list", "--left-right", "--count", f"HEAD...origin/{aktuell}", cwd=pfad)
vor, _, zurueck = ab.partition("\t") if "\t" in ab else ("?", "", "?")
meldung = f"[{name}] Branch {aktuell} (+{vor}/-{zurueck.strip()} zu origin), {n_dirty} ungesicherte Änderungen"
if aktuell != branch:
meldung += f" <- Hinweis: Manifest erwartet {branch}"
if args.pull and n_dirty == 0 and aktuell == branch:
rc, out = git("pull", "--ff-only", "--quiet", cwd=pfad)
meldung += " -> pull " + ("ok" if rc == 0 else f"FEHLER: {out.splitlines()[-1] if out else rc}")
print(meldung)
print()
sync_skills()
return 0
if __name__ == "__main__":
raise SystemExit(main())