#!/usr/bin/env python3 """Sync Hermes kanban board state to Gitea issue labels.""" import json import os import subprocess import urllib.request GITEA_API = os.getenv("GITEA_API", "http://127.0.0.1:3000/api/v1") GITEA_TOKEN = os.getenv("GITEA_TOKEN", "") REPO = os.getenv("GITEA_REPO", "stackchain/stackchain-hackathon") LABEL_MAP = { "todo": "P1", "ready": "ready", "in_progress": "in_progress", "review": "review", "done": "done", "blocked": "blocked", "triage": "triage", } def _gitea(path, body=None, method="GET"): url = f"{GITEA_API}/{path}" data = json.dumps(body).encode() if body else None req = urllib.request.Request(url, data=data, method=method) req.add_header("Accept", "application/json") req.add_header("Authorization", f"token {GITEA_TOKEN}") if data: req.add_header("Content-Type", "application/json") with urllib.request.urlopen(req) as r: return json.loads(r.read().decode()) def list_issues(): return _gitea(f"repos/{REPO}/issues?state=all&limit=100") def list_labels(): return _gitea(f"repos/{REPO}/labels") def find_label(name, labels): matches = [l for l in labels if l["name"] == name] return matches[0]["id"] if matches else None def create_label(name): colors = { "P1": "00ccff", "P2": "cccccc", "ready": "0e8a16", "in_progress": "fbca04", "review": "d93f0b", "done": "00cc00", "blocked": "ff4444", "triage": "cccccc", } return _gitea( f"repos/{REPO}/labels", body={"name": name, "color": colors.get(name, "cccccc")}, method="POST", ) def edit_issue(number, labels=None): if labels is None: return _gitea( f"repos/{REPO}/issues/{number}", body={"labels": labels}, method="PATCH", ) def main(): kanban_json = subprocess.check_output( ["hermes", "kanban", "list", "--json"], text=True ) tasks = json.loads(kanban_json) labels = list_labels() label_ids = {l["name"]: l["id"] for l in labels} issues = {i["title"].split(" ", 1)[1].strip(): i for i in list_issues() if i.get("pull_request") is None} for t in tasks: title = t.get("title", "") issue_key = title.split(" ", 1)[1].strip() if title.startswith("#") else title prefix = LABEL_MAP.get(t.get("status", "")) if not prefix: continue gitea_label = label_ids.get(prefix) if gitea_label is None: create_label(prefix) label_ids[prefix] = find_label(prefix, list_labels()) or len(label_ids) + 1 if issue_key in issues: issue = issues[issue_key] current = [l["id"] for l in issue.get("labels", [])] if gitea_label and gitea_label not in current: current.append(gitea_label) edit_issue(issue["number"], labels=list(set(current))) if __name__ == "__main__": main()