Files
the-nexus/mimo-swarm/scripts/auto-merger.py
Claude (Opus 4.6) 63435753e2
Some checks failed
Deploy Nexus / deploy (push) Failing after 3s
Staging Verification Gate / verify-staging (push) Failing after 4s
[claude] Fix mimo swarm worker tool access — add -t terminal,code_execution (#1203) (#1204)
2026-04-11 00:40:46 +00:00

143 lines
4.1 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Auto-Merger — merges approved PRs via squash merge.
Checks:
1. PR has at least 1 approval review
2. PR is mergeable
3. No pending change requests
4. From mimo swarm (safety: only auto-merge mimo PRs)
Squash merges, closes issue, cleans up branch.
"""
import json
import os
import urllib.request
import urllib.error
from datetime import datetime, timezone
GITEA_URL = "https://forge.alexanderwhitestone.com"
TOKEN_FILE = os.path.expanduser("~/.config/gitea/token")
LOG_DIR = os.path.expanduser("~/.hermes/mimo-swarm/logs")
REPO = "Timmy_Foundation/the-nexus"
def load_token():
with open(TOKEN_FILE) as f:
return f.read().strip()
def api_get(path, token):
url = f"{GITEA_URL}/api/v1{path}"
req = urllib.request.Request(url, headers={
"Authorization": f"token {token}",
"Accept": "application/json",
})
try:
with urllib.request.urlopen(req, timeout=30) as resp:
return json.loads(resp.read())
except:
return None
def api_post(path, token, data=None):
url = f"{GITEA_URL}/api/v1{path}"
body = json.dumps(data or {}).encode()
req = urllib.request.Request(url, data=body, headers={
"Authorization": f"token {token}",
"Content-Type": "application/json",
}, method="POST")
try:
with urllib.request.urlopen(req, timeout=30) as resp:
return resp.status, resp.read().decode()
except urllib.error.HTTPError as e:
return e.code, e.read().decode() if e.fp else ""
def api_delete(path, token):
url = f"{GITEA_URL}/api/v1{path}"
req = urllib.request.Request(url, headers={
"Authorization": f"token {token}",
}, method="DELETE")
try:
with urllib.request.urlopen(req, timeout=30) as resp:
return resp.status
except:
return 500
def log(msg):
ts = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
print(f"[{ts}] {msg}")
log_file = os.path.join(LOG_DIR, f"merger-{datetime.now().strftime('%Y%m%d')}.log")
with open(log_file, "a") as f:
f.write(f"[{ts}] {msg}\n")
def main():
token = load_token()
log("=" * 50)
log("AUTO-MERGER — checking approved PRs")
prs = api_get(f"/repos/{REPO}/pulls?state=open&limit=20", token)
if not prs:
log("No open PRs")
return
merged = 0
skipped = 0
for pr in prs:
pr_num = pr["number"]
head_ref = pr.get("head", {}).get("ref", "")
body = pr.get("body", "") or ""
mergeable = pr.get("mergeable", False)
# Only auto-merge mimo PRs
is_mimo = "mimo" in head_ref.lower() or "Automated by mimo" in body
if not is_mimo:
continue
# Check reviews
reviews = api_get(f"/repos/{REPO}/pulls/{pr_num}/reviews", token) or []
approvals = [r for r in reviews if r.get("state") == "APPROVED"]
changes_requested = [r for r in reviews if r.get("state") == "CHANGES_REQUESTED"]
if changes_requested:
log(f" SKIP #{pr_num}: has change requests")
skipped += 1
continue
if not approvals:
log(f" SKIP #{pr_num}: no approvals yet")
skipped += 1
continue
# Attempt squash merge
merge_title = pr["title"]
merge_msg = f"Squash merge #{pr_num}: {merge_title}\n\n{body}"
status, response = api_post(f"/repos/{REPO}/pulls/{pr_num}/merge", token, {
"Do": "squash",
"MergeTitleField": merge_title,
"MergeMessageField": f"Closes #{pr_num}\n\nAutomated merge by mimo swarm.",
})
if status == 200:
merged += 1
log(f" MERGED #{pr_num}: {merge_title[:50]}")
# Delete the branch
if head_ref and head_ref != "main":
api_delete(f"/repos/{REPO}/git/refs/heads/{head_ref}", token)
log(f" Deleted branch: {head_ref}")
else:
log(f" MERGE FAILED #{pr_num}: status={status}, {response[:200]}")
log(f"Merge complete: {merged} merged, {skipped} skipped")
if __name__ == "__main__":
main()