TP36 A4/A5/A6: Index/Status-SSoT-Cutover nach Gitea abgeschlossen

- teilprojekte.md: Quick-Index-Tabelle + "Ist-Stand (Kurz)"-Block entfernt (-493 Z.),
  Kopf zeigt aufs Gitea-Board (Labels status:*/thema:*); Governance/Genehmigte Ausnahmen
  unter eigenem Header behalten; TP36 reframed (Nautobot->Gitea, Scope = Index/Status,
  Nautobot nur noch Live-Geraetedaten). Alle 33 ### TPnn-Prosa-Abschnitte intakt.
- gitea-tp-sync.py: an das neue SSoT-Modell angepasst - aktive TPs sind SSoT direkt in
  Gitea, das Tool gleicht nur noch das Archiv ab und fasst offene Issues nie an. Dry-Run 0/0.
- CLAUDE.md: Pflichtlektuere #1 = Gitea-Board (Status) + Prosa-Datei (Detail); neue Sektion
  "SSoT & Branch-Konvention"; TP-Management-Sektion nachgezogen.

/projektstatus-Skill (beide globalen Kopien ~/.claude + ~/.config/opencode) auf die
Gitea-API umgestellt (Modus A Board / Modus B Markdown-Fallback) - liegt ausserhalb des Repos.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-09-07 13:30:24 +02:00
parent 674e5fff0d
commit a0ba87d555
3 changed files with 70 additions and 553 deletions

View File

@@ -3,11 +3,15 @@
TP36 (reframed) — teilprojekte.md Index/Status → Gitea Issues (admin/bf-infra).
Idempotenter Sync (get-or-create, Match ueber "TPnn" im Titel):
- Quick-Index-Tabelle in docs/teilprojekte.md -> aktive Issues (open/closed je Marker)
- Archiv-Header in docs/teilprojekte-archiv.md -> closed Issues (nur TPs, die NICHT aktiv sind)
- Archiv-Header in docs/teilprojekte-archiv.md -> closed Issues (nur TPs, die in Gitea
NICHT offen sind; ein reopentes TP mit Archiv-Zeiger bleibt offen).
- Labels status:* (Marker) + thema:* (Gruppierung) werden vorab get-or-create.
SSoT-Modell: Gitea fuehrt Index+Status; die per-TP-Prosa bleibt in docs/teilprojekte.md.
SSoT-Modell (seit TP36-Cutover 2026-09-07): Gitea fuehrt Index+Status — aktive TPs werden
DIREKT als Gitea-Issue angelegt/gepflegt, NICHT mehr aus einer Datei-Tabelle abgeleitet
(die Quick-Index-Tabelle wurde aus teilprojekte.md entfernt). Dieses Werkzeug haelt nur noch
die ARCHIVIERTEN (closed) TPs mit teilprojekte-archiv.md idempotent abgeglichen und fasst
offene Issues nie an. Die per-TP-Prosa bleibt in docs/teilprojekte.md / teilprojekte-archiv.md.
Token: ~/.hermes/.env (GITEA_ACCESS_TOKEN, GITEA_HOST) — wird NIE ausgegeben.
Aufruf:
@@ -100,25 +104,9 @@ def api(method, path, body=None):
# --------------------------------------------------------------------------- parse
def parse_active():
"""Quick-Index-Zeilen: > | TP44 | ⏳ | Kurztitel … | Datum |"""
out = []
row = re.compile(r"^>\s*\|\s*(TP[\d.]+)\s*\|\s*([^\s|]+)\s*\|\s*(.+?)\s*\|\s*(.*?)\s*\|\s*$")
for line in ACTIVE.read_text().splitlines():
m = row.match(line)
if not m:
continue
tp, marker, title, date = m.groups()
num = tp[2:]
# Titel auf den Kern kuerzen (vor dem ersten " — " / " (" wenn lang)
short = re.split(r"\s+—\s+|\s*\(", title)[0].strip()
short = re.sub(r"\*\*|`", "", short)[:80]
out.append({"tp": tp, "num": num, "marker": marker.strip(),
"title": short, "date": date.strip(), "src": "active"})
return out
def parse_archiv(active_nums):
def parse_archiv(exclude_nums):
"""Archiv-Header -> archivierte TPs. exclude_nums = TP-Nummern, die in Gitea OFFEN sind
(reopente TPs mit Archiv-Zeiger) -> die bleiben offen, nicht als closed abgleichen."""
out = []
hdr = re.compile(r"^#{2,4}\s*(TP[\d.\-A-Za-z]+)\s*—\s*(.+?)\s*▸", )
seen = set()
@@ -128,7 +116,7 @@ def parse_archiv(active_nums):
continue
tp, title = m.groups()
num = tp[2:]
if num in active_nums or num in seen:
if num in exclude_nums or num in seen:
continue
seen.add(num)
short = re.sub(r"\*\*|`", "", title).strip()[:80]
@@ -164,22 +152,26 @@ def all_issues():
def main():
print(f"Gitea {HOST} repo {OWNER_REPO} mode={'COMMIT' if COMMIT else 'DRY-RUN'}")
active = parse_active()
active_nums = {a["num"] for a in active}
archiv = parse_archiv(active_nums)
items = active + archiv
print(f"Geparst: {len(active)} aktive + {len(archiv)} archivierte = {len(items)} TP\n")
st, existing_labels = api("GET", f"/repos/{OWNER_REPO}/labels?limit=100")
label_id = ensure_labels(existing_labels if isinstance(existing_labels, list) else [])
issues = all_issues()
by_tp = {}
open_nums = set()
for iss in issues:
# Titel ist "TPxx — …"; volle ID inkl. Buchstaben/Bindestrich (TP14.2a, TP-R) bis zum " — "
m = re.match(r"^(TP[\w.\-]*?)\s+—", iss["title"])
if m:
by_tp[m.group(1)] = iss
if iss.get("state") == "open":
open_nums.add(m.group(1)[2:])
# Aktive TPs sind seit dem TP36-Cutover SSoT direkt in Gitea -> nur das Archiv abgleichen,
# und offene (reopente) TPs davon ausschliessen, damit sie nicht faelschlich geschlossen werden.
items = parse_archiv(open_nums)
print(f"Geparst: {len(items)} archivierte TP (aktive TPs = SSoT in Gitea: "
f"{len(open_nums)} offen, nicht angetastet)\n")
created = updated = 0
for it in items: