Annahme-Selbstheilung: Orphan-Branches entlarven, veraltete Branches auto-rebasen

Anlass: feature/cleanse-skill-index — ein Werkstatt-Worker hatte git init statt
Klonen gemacht (Orphan-Commit ohne gemeinsamen Vorfahren, Inhalt: ~/.hermes-Index-
Dateien statt Repo-Dateien). Solche Karten sahen aus wie normale Vorschlaege,
liefen bei Annahme aber IMMER in 'refusing to merge unrelated histories' — und
die Fehlermeldung ('bitte am PC aufloesen') war eine Sackgasse. Das passiert
oefter; darum drei Schichten dagegen:

1) ERKENNEN (Auftragsbuch): neue Karten-Flags 'verwaist' (kein merge-base mit
   main -> rote Markierung 'kein gemeinsamer Ursprung', Annehmen-Knopf fehlt,
   API+Runner halten zusaetzlich dicht) und 'leer' (Diff gegen main leer ->
   'bringt nichts'-Badge). Empfehlung auf der Karte: ablehnen mit Grund,
   Idee frisch in die Queue.

2) HEILEN (beide Runner, mc2 + lucy): kollidiert der Merge, weil main weiter-
   gelaufen ist, versucht der Runner automatisch einen Rebase des Branches auf
   main (isolierter Worktree; Gates laufen danach normal, Merge-Message sagt
   'auto-rebased'). Nur wenn auch der Rebase kollidiert, faellt die Karte durch —
   mit ehrlicher Meldung statt 'am PC aufloesen'.

3) VERHINDERN (werkstatt-SOUL): voll klonen (nie git init/--depth), Selbstcheck
   'git merge-base HEAD origin/main' + fetch/rebase vor JEDEM Push, nie
   ~/.hermes-Artefakte committen.

Doku: AUFTRAGSBUCH.md + FALLEN.md (Erkennungsmuster: Diff 0 Dateien + behind ~
ganze Historie) + OFFENE-FAEDEN. Geprueft: py_compile gruen, bash -n beide
Runner gruen, tsc+vite build gruen (dist dabei); merge-base-Verhalten am echten
kaputten Branch auf der Box verifiziert.
This commit is contained in:
Hitonabi
2026-07-13 09:34:23 +02:00
parent 9872c1b3f6
commit 552299a761
12 changed files with 158 additions and 40 deletions
+15
View File
@@ -141,6 +141,10 @@ def _repo_items(repo: str, statuses: dict, gutachten: dict) -> list[dict]:
if ahead == 0 and ((status or {}).get("state") not in ("laeuft", "rollback")):
continue # bereits in main enthalten → keine offene Entscheidung mehr
behind = int((_git(repo, ["rev-list", "--count", f"{ref}..origin/main"]).stdout or "0").strip() or 0)
# Kaputt aufgesetzte Branches entlarven (Worker machte git init/Shallow statt zu
# klonen): ohne gemeinsamen Vorfahren kann git NIE mergen — Annehmen wäre ein
# garantierter Fehllauf („refusing to merge unrelated histories").
verwaist = _git(repo, ["merge-base", "origin/main", ref]).returncode != 0
show = _git(repo, ["show", "-s", "--format=%s%x1f%b%x1f%ct%x1f%an", ref])
subject, body, cts, author = ((show.stdout or "").split("\x1f") + ["", "", "", ""])[:4]
ts_val = int(cts) if cts.strip().isdigit() else None
@@ -170,6 +174,11 @@ def _repo_items(repo: str, statuses: dict, gutachten: dict) -> list[dict]:
"empfehlung": stempel,
"ahead": ahead,
"behind": behind,
"verwaist": verwaist,
# Diff gegen main ist leer → der Branch brächte nichts (Inhalt schon in main
# oder Worker hat am Repo vorbei gearbeitet). Bei verwaist ist der Diff
# technisch leer (kein merge-base) — dann zählt nur das verwaist-Flag.
"leer": not verwaist and ahead > 0 and not files_raw,
"shortstat": stat,
"files": files,
"files_truncated": len(files_raw) > 60,
@@ -227,6 +236,12 @@ def accept(branch: str, repo: str = "mc2") -> dict:
state = (_statuses().get(key) or {}).get("state")
if state in ("laeuft", "rollback"):
return {"ok": False, "error": "Für diesen Vorschlag läuft bereits ein Annahme-Lauf."}
# Kaputt aufgesetzter Branch (kein gemeinsamer Vorfahre) → Merge kann NIE gelingen;
# gar nicht erst einen Runner starten (die UI blendet den Knopf aus, die API hält dicht).
if _git(repo, ["merge-base", "origin/main", f"origin/{branch}"]).returncode != 0:
return {"ok": False, "error": "Dieser Branch hat keinen gemeinsamen Ursprung mit main "
"(kaputt aufgesetzt, z. B. git init statt Klonen) — Annehmen ist technisch unmöglich. "
"Bitte ablehnen (gern mit Grund) und die Idee neu in die Queue geben."}
# Runner als /tmp-Kopie starten: deploy.sh resettet das Repo hart — das Original-Skript
# würde einem laufenden bash unter den Füßen getauscht (bekannte Selbst-Reset-Falle).