Files
Thore CimbalandClaude Fable 5 c23bb54a92 feat: slice 4 - the open management issues live in the repo
Gate 4, slice 4: 26 open management issues imported from live git.lab
(read-only, descriptions included as authorized; GitLab iid = file id,
labels/milestone/priority/due/host/area mapped into frontmatter, the
import aborts instead of inventing a missing milestone or priority).
Two new issues close the F-004 gap where work was really still open
(0033 OVERMIND-01, 0034 CFGMON-11 incl. the plaintext npm-token
rotation); CFGMON-12/13 already route to verified git.lab issues,
MATRIX-05 is done and needs none (agreed with sorb). The three wiki
task blocks now reference their issues, roadmap.md hands all counts to
the generated STATUS.md and states M1-M5 per ADR-0010 (closing F-001
in the canonical prose), pruefe_prosa joins the CI validate job, and
the import protocol under docs/sources/migration/ records every
intervention into imported text.

Verified: validate 0/0 over 29 issue files, gen_status --check current
(distribution line M1 9 - M2 17 - M4 2 plus per-issue milestone and
priority), pruefe_prosa 0 errors with clones and 0 errors/15 unchecked
citations in offline CI mode, drift check green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 12:00:00 +00:00

124 lines
4.8 KiB
Python

#!/usr/bin/env python3
"""import_issues.py — Einmal-Import der offenen management-Issues.
Migrationsakte, kein Dauerwerkzeug (Design 2026-08-11, Slice 4;
ADR-0012). Liest die offenen Issues des Projekts
axion1337.chat/management read-only von git.lab (Token nur per
Dateipfad, Wert erscheint nirgends) und schreibt je Issue eine
kanonische Datei docs/issues/<iid>-<slug>.md. GitLab-iid = Datei-id —
keine dritte Nummernwelt. Kommentare und Verlauf bleiben auf GitLab;
der Dateikopf verlinkt dorthin.
Abbildung (alt → Schema):
ohne status-Label → open · status:next → next · status:doing →
in-progress · status:wartet → waiting (wartegrund: Verweis auf den
GitLab-Verlauf; Präzisierung im nächsten Refinement) · Meilenstein
"Mn — …" → Mn · priority:x → x · due_date → due · host:x → host ·
area:x → area. Fehlt Meilenstein oder Priorität, bricht der Import
ab — das wäre ein Befund, kein Füllwert.
Relative Upload-Pfade in Beschreibungen werden auf absolute
git.lab-URLs umgeschrieben, damit der Link-Check nicht ins Leere prüft.
Usage: python3 import_issues.py <repo-root> [tokenpfad]
"""
from __future__ import annotations
import json
import re
import sys
import urllib.request
from pathlib import Path
API = "https://git.lab/api/v4/projects/axion1337.chat%2Fmanagement/issues"
UPLOADS = "https://git.lab/axion1337.chat/management"
UMLAUTE = str.maketrans({"ä": "ae", "ö": "oe", "ü": "ue", "ß": "ss",
"Ä": "ae", "Ö": "oe", "Ü": "ue", "é": "e"})
def slug(titel: str) -> str:
s = titel.translate(UMLAUTE).lower()
s = re.sub(r"[^a-z0-9]+", "-", s).strip("-")
if len(s) > 48:
s = s[:48].rsplit("-", 1)[0]
return s or "ohne-titel"
def hole(token: str):
issues, seite = [], 1
while True:
req = urllib.request.Request(
f"{API}?state=opened&per_page=100&page={seite}",
headers={"PRIVATE-TOKEN": token})
with urllib.request.urlopen(req) as antwort:
batch = json.load(antwort)
issues += batch
if len(batch) < 100:
return sorted(issues, key=lambda i: i["iid"])
seite += 1
def main() -> int:
root = Path(sys.argv[1])
tokenpfad = Path(sys.argv[2] if len(sys.argv) > 2
else Path.home() / ".config/gitlab-lab/token")
token = tokenpfad.read_text(encoding="utf-8").strip()
ziel = root / "docs/issues"
geschrieben = []
for i in hole(token):
iid, titel, labels = i["iid"], i["title"].strip(), i["labels"]
ms = (i.get("milestone") or {}).get("title", "")
m = re.match(r"^(M\d)\b", ms)
prio = [l.split(":")[1] for l in labels if l.startswith("priority:")]
if not m or len(prio) != 1:
sys.exit(f"ABBRUCH: #{iid} ohne eindeutigen Meilenstein/"
f"Priorität ({ms!r}, {prio!r}) — Befund, kein Füllwert.")
status = "open"
wartegrund = ""
if "status:doing" in labels:
status = "in-progress"
elif "status:next" in labels:
status = "next"
elif "status:wartet" in labels:
status = "waiting"
wartegrund = ("Grund im GitLab-Verlauf benannt (Import "
"2026-08-11); im nächsten Refinement präzisieren")
host = [l.split(":")[1] for l in labels if l.startswith("host:")]
area = [l.split(":")[1] for l in labels if l.startswith("area:")]
zeilen = ["---", "type: issue", f'id: "{iid:04d}"',
f"status: {status}", f"created: {i['created_at'][:10]}",
f"milestone: {m.group(1)}", f"priority: {prio[0]}"]
if i.get("due_date"):
zeilen.append(f"due: {i['due_date']}")
if host:
zeilen.append(f"host: {host[0]}")
if area:
zeilen.append(f"area: {area[0]}")
if wartegrund:
zeilen.append(f"wartegrund: {wartegrund}")
zeilen += [f'gitlab_iid: "{iid}"', "related: []", "---", ""]
beschreibung = (i.get("description") or "").replace("\r\n", "\n")
beschreibung = beschreibung.replace("](/uploads/",
f"]({UPLOADS}/uploads/")
kopf = (f"# {titel}\n\n"
f"> Import aus [management#{iid}]({i['web_url']}) "
f"(2026-08-11). Kommentare und Verlauf bleiben dort; "
f"kanonisch ist ab jetzt diese Datei (ADR-0012).\n\n")
datei = ziel / f"{iid:04d}-{slug(titel)}.md"
datei.write_text("\n".join(zeilen) + kopf + beschreibung.rstrip()
+ "\n", encoding="utf-8")
geschrieben.append(datei.name)
for name in geschrieben:
print(name)
print(f"import: {len(geschrieben)} Issues geschrieben")
return 0
if __name__ == "__main__":
sys.exit(main())