fix(hermes-auto-improve): squash merge + correction PR existante dans create_github_pr

- GitHub repo configuré squash-only (allow_merge_commit=false, allow_rebase_merge=false)
- create_github_pr() vérifie si une PR hermes/daily-work est déjà ouverte :
  si oui → met à jour titre+body (pas de PR dupliquée)
  si non → crée une nouvelle PR
- Supprime le bug de silence sur "a pull request for branch X already exists"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
ALI YESILKAYA 2026-06-05 11:56:56 +02:00 committed by Alkatrazz
parent e62832fd71
commit d79ebf7d36
2 changed files with 63 additions and 20 deletions

View file

@ -125,12 +125,29 @@ def rag_ingest() -> str:
# ── Créer la PR GitHub ───────────────────────────────────────────
def _get_open_hermes_pr():
"""Retourne le numéro et l'URL de la PR Hermes ouverte, ou None."""
r = subprocess.run(
["gh", "pr", "list", "--repo", "Alkatrazz24/Funk-lab",
"--head", BRANCH, "--state", "open", "--json", "number,url"],
capture_output=True, text=True, timeout=30, cwd=str(REPO_DIR)
)
if r.returncode != 0:
return None
try:
prs = json.loads(r.stdout)
return prs[0] if prs else None
except (json.JSONDecodeError, IndexError):
return None
def create_github_pr(summary: str) -> dict:
today = datetime.now().strftime("%Y-%m-%d")
diff = git(["diff", "--stat", "origin/main...HEAD"])
stats = diff.stdout.strip() or "Aucune modification depuis main"
body = f"""## Rapport quotidien Hermes — {today}
title = f"[Hermes] Rapport documentation — {today}"
body = f"""## Rapport quotidien Hermes — {today}
Ce Pull Request est créé automatiquement par Hermes à 22h.
@ -144,26 +161,38 @@ Ce Pull Request est créé automatiquement par Hermes à 22h.
### Comment reviewer
1. Vérifier les rapports dans `admin/hermes/builtin/`
2. Merger si tout semble correct, ou fermer pour recommencer demain
2. Merger quand tout semble correct (squash merge configuré)
> Généré par `hermes-doc-worker` · branche `{BRANCH}`
"""
existing = _get_open_hermes_pr()
if existing:
# Met à jour le titre et le corps de la PR existante
subprocess.run(
["gh", "pr", "edit", str(existing["number"]),
"--repo", "Alkatrazz24/Funk-lab",
"--title", title,
"--body", body],
capture_output=True, text=True, timeout=30, cwd=str(REPO_DIR)
)
return {"html_url": existing["url"],
"number": existing["number"],
"title": title,
"updated": True}
r = subprocess.run(
[
"gh", "pr", "create",
"--title", f"[Hermes] Rapport documentation — {today}",
"--body", body,
"--head", BRANCH,
"--base", "main",
"--repo", "Alkatrazz24/Funk-lab",
],
capture_output=True, text=True, timeout=30,
cwd=str(REPO_DIR),
["gh", "pr", "create",
"--title", title,
"--body", body,
"--head", BRANCH,
"--base", "main",
"--repo", "Alkatrazz24/Funk-lab"],
capture_output=True, text=True, timeout=30, cwd=str(REPO_DIR)
)
if r.returncode == 0:
pr_url = r.stdout.strip()
return {"html_url": pr_url, "title": f"[Hermes] Rapport documentation — {today}"}
return {"html_url": pr_url, "title": title}
return {"error": r.stderr.strip() or "gh pr create a échoué"}

View file

@ -31,6 +31,25 @@ _running = False
_last_result = None
def _extract_last_json(text: str):
"""Find the outermost JSON object at the end of mixed text+JSON output."""
end = text.rfind("}")
if end < 0:
return None
depth = 0
for i in range(end, -1, -1):
if text[i] == "}":
depth += 1
elif text[i] == "{":
depth -= 1
if depth == 0:
try:
return json.loads(text[i:end + 1])
except json.JSONDecodeError:
return None
return None
def run_script(extra_args: list = None):
global _running, _last_result
with _lock:
@ -50,13 +69,8 @@ def run_script(extra_args: list = None):
if result.stderr:
log.write(f"STDERR: {result.stderr}")
out = result.stdout
start = out.rfind("{")
end = out.rfind("}") + 1
if start >= 0 and end > start:
_last_result = json.loads(out[start:end])
else:
_last_result = {"error": "no_json", "stdout": out[-500:]}
out = result.stdout
_last_result = _extract_last_json(out) or {"error": "no_json", "stdout": out[-500:]}
except subprocess.TimeoutExpired:
_last_result = {"error": "timeout"}