Hermes-Update: neue Commits im flachen Klon richtig zählen (281 statt 42 044); Wochenbericht duzt
- maintenance._hermes_bereich: --ancestry-path, wenn HEAD Vorfahr von origin/<branch> ist (seit dem Paketmanager ist der Checkout ein flacher Klon); sonst der einfache Bereich wie bisher - Wochenbericht: Lucy duzt, nennt nur als Aufgabe, was ausdrücklich auf den Commander wartet; ungetestete Radar-Kandidaten testet das Radar nachts selbst Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
934088a8d9
commit
6904bcbfb0
@@ -191,6 +191,19 @@ def _swap_update_available() -> bool:
|
|||||||
_comp_cache = {"ts": 0.0, "data": []}
|
_comp_cache = {"ts": 0.0, "data": []}
|
||||||
|
|
||||||
|
|
||||||
|
def _hermes_bereich(path: str, branch: str) -> list[str]:
|
||||||
|
"""Der Bereich der neuen Commits für git rev-list/log. Der Hermes-Checkout ist seit dem Paketmanager (25.09.2026)
|
||||||
|
ein flacher Klon: HEAD..origin/main zählte dann die ganze Vorgeschichte hinter der flachen Grenze mit (26.09.:
|
||||||
|
42 044 statt 281). Mit --ancestry-path zählen nur Nachfahren von HEAD. Ist HEAD kein Vorfahr (Geschichte oben
|
||||||
|
umgeschrieben), bleibt es beim einfachen Bereich — sonst sähe die Box gar kein Update mehr."""
|
||||||
|
try:
|
||||||
|
vorfahr = subprocess.run(["git", "-C", path, "merge-base", "--is-ancestor", "HEAD", f"origin/{branch}"],
|
||||||
|
capture_output=True, text=True, timeout=8).returncode == 0
|
||||||
|
except (OSError, subprocess.SubprocessError):
|
||||||
|
vorfahr = False
|
||||||
|
return ["--ancestry-path", f"HEAD..origin/{branch}"] if vorfahr else [f"HEAD..origin/{branch}"]
|
||||||
|
|
||||||
|
|
||||||
def _hermes_agent_update() -> dict:
|
def _hermes_agent_update() -> dict:
|
||||||
"""Hermes-Agent wird aus **git** aktualisiert (CLI `hermes update` = git pull origin <branch>).
|
"""Hermes-Agent wird aus **git** aktualisiert (CLI `hermes update` = git pull origin <branch>).
|
||||||
Darum HEAD vs. origin/<branch> prüfen (fetch + behind-count) — NICHT GitHub-Releases: die
|
Darum HEAD vs. origin/<branch> prüfen (fetch + behind-count) — NICHT GitHub-Releases: die
|
||||||
@@ -209,7 +222,7 @@ def _hermes_agent_update() -> dict:
|
|||||||
capture_output=True, text=True, timeout=60)
|
capture_output=True, text=True, timeout=60)
|
||||||
info["reachable"] = (fetch.returncode == 0)
|
info["reachable"] = (fetch.returncode == 0)
|
||||||
if fetch.returncode == 0:
|
if fetch.returncode == 0:
|
||||||
cnt = subprocess.run(["git", "-C", path, "rev-list", "--count", f"HEAD..origin/{branch}"],
|
cnt = subprocess.run(["git", "-C", path, "rev-list", "--count", *_hermes_bereich(path, branch)],
|
||||||
capture_output=True, text=True, timeout=8)
|
capture_output=True, text=True, timeout=8)
|
||||||
behind = int(cnt.stdout.strip() or "0") if cnt.returncode == 0 else 0
|
behind = int(cnt.stdout.strip() or "0") if cnt.returncode == 0 else 0
|
||||||
info["behind"] = behind
|
info["behind"] = behind
|
||||||
@@ -534,7 +547,7 @@ def hermes_update_details() -> dict:
|
|||||||
subprocess.run(["git", "-C", path, "fetch", "-q", "origin", branch],
|
subprocess.run(["git", "-C", path, "fetch", "-q", "origin", branch],
|
||||||
capture_output=True, text=True, timeout=60)
|
capture_output=True, text=True, timeout=60)
|
||||||
log = subprocess.run(["git", "-C", path, "log", "--pretty=format:%h\x1f%s\x1f%cr",
|
log = subprocess.run(["git", "-C", path, "log", "--pretty=format:%h\x1f%s\x1f%cr",
|
||||||
f"HEAD..origin/{branch}"], capture_output=True, text=True, timeout=10)
|
*_hermes_bereich(path, branch)], capture_output=True, text=True, timeout=10)
|
||||||
commits = []
|
commits = []
|
||||||
for line in (log.stdout or "").splitlines():
|
for line in (log.stdout or "").splitlines():
|
||||||
parts = line.split("\x1f")
|
parts = line.split("\x1f")
|
||||||
|
|||||||
@@ -39,10 +39,11 @@ BAUSTEIN = {"app": "App", "os": "Pakete", "pakete": "Pakete", "docker": "Docker"
|
|||||||
"kernel": "alte Kernel", "neustart": "Neustart"}
|
"kernel": "alte Kernel", "neustart": "Neustart"}
|
||||||
NICHT_GLATT = {"zurueckgerollt": "zurückgerollt", "festgehalten": "festgehalten", "fehler": "gescheitert"}
|
NICHT_GLATT = {"zurueckgerollt": "zurückgerollt", "festgehalten": "festgehalten", "fehler": "gescheitert"}
|
||||||
PROSA = """Du bist Lucy, die Systemadministratorin dieses Homelabs, und schreibst dem Commander seinen Wochenbericht.
|
PROSA = """Du bist Lucy, die Systemadministratorin dieses Homelabs, und schreibst dem Commander seinen Wochenbericht.
|
||||||
Schreib drei bis fünf Sätze Fließtext: wie die Woche lief, was wichtig ist und was er tun sollte. Beginne mit \
|
Schreib drei bis fünf Sätze Fließtext: wie die Woche lief und was wichtig ist. Beginne mit „Commander,“ und sprich ihn \
|
||||||
„Commander,“. Knapp, sachlich, ohne Emojis, ohne Ausrufezeichen, ohne Aufzählung. Nimm nur, was in den Fakten steht — \
|
mit „du“ an, nie mit „Sie“. Knapp, sachlich, ohne Emojis, ohne Ausrufezeichen, ohne Aufzählung. Nimm nur, was in den \
|
||||||
keine Zahl, kein Name, kein Ereignis, das dort fehlt. Die Fakten und die Rückfragen stehen unter deinem Absatz ohnehin \
|
Fakten steht — keine Zahl, kein Name, kein Ereignis, das dort fehlt. Zu tun hat er nur, was dort ausdrücklich auf ihn \
|
||||||
noch einmal; wiederhole sie nicht Punkt für Punkt.
|
wartet („wartet auf deine Entscheidung“); alles andere erledigen der Orchestrator und du selbst. Die Fakten und die \
|
||||||
|
Rückfragen stehen unter deinem Absatz noch einmal; wiederhole sie nicht Punkt für Punkt.
|
||||||
|
|
||||||
Fakten dieser Woche:
|
Fakten dieser Woche:
|
||||||
{fakten}"""
|
{fakten}"""
|
||||||
@@ -237,7 +238,8 @@ def _radar(f: dict) -> str | None:
|
|||||||
if bestanden:
|
if bestanden:
|
||||||
teile.append(f"bestanden und wartet auf deine Entscheidung: {_liste(bestanden)}")
|
teile.append(f"bestanden und wartet auf deine Entscheidung: {_liste(bestanden)}")
|
||||||
if wartend:
|
if wartend:
|
||||||
teile.append(f"{wartend} {'Kandidat wartet' if wartend == 1 else 'Kandidaten warten'} auf den Test")
|
teile.append(f"{wartend} {'Kandidat ist' if wartend == 1 else 'Kandidaten sind'} noch ungetestet (das Radar "
|
||||||
|
"testet nachts selbst)")
|
||||||
return "; ".join(teile) or None
|
return "; ".join(teile) or None
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -159,3 +159,28 @@ def test_zurueckgehaltene_pakete_sind_kein_update(monkeypatch):
|
|||||||
monkeypatch.setattr(maintenance.subprocess, "run",
|
monkeypatch.setattr(maintenance.subprocess, "run",
|
||||||
lambda befehl, **kw: subprocess.CompletedProcess(befehl, 100, stdout="", stderr=""))
|
lambda befehl, **kw: subprocess.CompletedProcess(befehl, 100, stdout="", stderr=""))
|
||||||
assert maintenance._os_upgradable() == 0 and "Paketliste nicht lesbar" in maintenance.PRUEF_FEHLER["os"]
|
assert maintenance._os_upgradable() == 0 and "Paketliste nicht lesbar" in maintenance.PRUEF_FEHLER["os"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_hermes_commits_im_flachen_klon(monkeypatch, tmp_path):
|
||||||
|
"""26.09.2026: Der Hermes-Checkout ist ein flacher Klon — HEAD..origin/main zählte 42 044 statt 281 neuer Commits.
|
||||||
|
Ist HEAD ein Vorfahr, zählt nur der Weg von HEAD aus (--ancestry-path); sonst der einfache Bereich."""
|
||||||
|
monkeypatch.setattr(maintenance.system, "find_hermes_agent_git", lambda: {"path": str(tmp_path), "hash": "59004a6"})
|
||||||
|
aufrufe: list[list[str]] = []
|
||||||
|
vorfahr = {"code": 0}
|
||||||
|
|
||||||
|
def run(befehl, **kw):
|
||||||
|
aufrufe.append(befehl)
|
||||||
|
if "--is-ancestor" in befehl:
|
||||||
|
return subprocess.CompletedProcess(befehl, vorfahr["code"], stdout="", stderr="")
|
||||||
|
if "--count" in befehl:
|
||||||
|
return subprocess.CompletedProcess(befehl, 0, stdout="281\n" if "--ancestry-path" in befehl else "42044\n",
|
||||||
|
stderr="")
|
||||||
|
return subprocess.CompletedProcess(befehl, 0, stdout="main\n" if "--abbrev-ref" in befehl else "d0288be\n",
|
||||||
|
stderr="")
|
||||||
|
|
||||||
|
monkeypatch.setattr(maintenance.subprocess, "run", run)
|
||||||
|
assert maintenance._hermes_agent_update()["behind"] == 281
|
||||||
|
vorfahr["code"] = 1
|
||||||
|
assert maintenance._hermes_agent_update()["behind"] == 42044
|
||||||
|
zaehlen = [b for b in aufrufe if "--count" in b]
|
||||||
|
assert zaehlen[0][-2:] == ["--ancestry-path", "HEAD..origin/main"] and zaehlen[1][-1] == "HEAD..origin/main"
|
||||||
|
|||||||
@@ -85,7 +85,8 @@ def test_feste_saetze():
|
|||||||
"Sicherungen: Stufe gelb; zu alt oder fehlend: Arcane; Probe-Wiederherstellung gescheitert: NetBird.",
|
"Sicherungen: Stufe gelb; zu alt oder fehlend: Arcane; Probe-Wiederherstellung gescheitert: NetBird.",
|
||||||
"Zertifikate: mc.example: wird nicht erneuert, noch 2 Tage gültig.",
|
"Zertifikate: mc.example: wird nicht erneuert, noch 2 Tage gültig.",
|
||||||
"Lücken-Scan: immich (3 kritisch).",
|
"Lücken-Scan: immich (3 kritisch).",
|
||||||
"Modell-Radar: bestanden und wartet auf deine Entscheidung: Qwen4-30B; 2 Kandidaten warten auf den Test.",
|
("Modell-Radar: bestanden und wartet auf deine Entscheidung: Qwen4-30B; 2 Kandidaten sind noch ungetestet "
|
||||||
|
"(das Radar testet nachts selbst)."),
|
||||||
"Lucy: 1× selbst gehandelt; 1× auf deine Bitte; 2 Rückfragen beantwortet oder abgelaufen, davon 1× ja.",
|
"Lucy: 1× selbst gehandelt; 1× auf deine Bitte; 2 Rückfragen beantwortet oder abgelaufen, davon 1× ja.",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user