From bcd734ccbac90b407309d255b939e9ad28a39d4d Mon Sep 17 00:00:00 2001 From: rootiest Date: Mon, 19 Jan 2026 19:42:19 -0500 Subject: [PATCH] fix: beads-gitea sync --- .gitea/sync_beads.py | 110 +++++++++++++++++++++++++++++-------------- 1 file changed, 75 insertions(+), 35 deletions(-) diff --git a/.gitea/sync_beads.py b/.gitea/sync_beads.py index c7c0e70..9bc9b53 100644 --- a/.gitea/sync_beads.py +++ b/.gitea/sync_beads.py @@ -1,54 +1,94 @@ import json import os import requests +import sys -# Configuration +# Configuration from Environment TOKEN = os.getenv("GITEA_TOKEN") URL = os.getenv("GITEA_URL") REPO = os.getenv("REPO_NAME") HEADERS = {"Authorization": f"token {TOKEN}", "Content-Type": "application/json"} -def get_gitea_issues(): - """Fetch all issues from Gitea to map by their Beads ID.""" - url = f"{URL}/api/v1/repos/{REPO}/issues?state=all" - resp = requests.get(url, headers=HEADERS) - resp.raise_for_status() - # We store the Beads ID in the title or a hidden comment to track them - return {issue['title'].split(']')[0][1:]: issue for issue in resp.json() if ']' in issue['title']} def sync(): - beads_file = ".beads/issues.jsonl" - if not os.path.exists(beads_file): - print("No beads found.") - return + beads_path = ".beads/issues.jsonl" - gitea_issues = get_gitea_issues() + if not os.path.exists(beads_path): + print(f"❌ ERROR: {beads_path} not found.") + sys.exit(1) - with open(beads_file, "r") as f: + print(f"🔍 Reading Beads from: {beads_path}") + + # 1. Fetch existing issues from Gitea + try: + # state=all includes closed issues so we don't create duplicates of finished tasks + api_url = f"{URL}/api/v1/repos/{REPO}/issues?state=all" + resp = requests.get(api_url, headers=HEADERS) + resp.raise_for_status() + # Map by [ID] to identify existing ones + existing = { + i["title"].split("]")[0][1:]: i for i in resp.json() if "]" in i["title"] + } + print(f"📡 Found {len(existing)} existing issues in Gitea.") + except Exception as e: + print(f"❌ Gitea API Connection Failed: {e}") + sys.exit(1) + + # 2. Parse the Beads JSONL file + processed_count = 0 + with open(beads_path, "r") as f: for line in f: - data = json.loads(line) - bid = data.get("id") - title = data.get("title") - desc = data.get("description", "") - # Mapping Beads status to Gitea states - is_closed = data.get("status") in ["closed", "done", "finished"] - target_state = "closed" if is_closed else "open" + processed_count += 1 + line = line.strip() + if not line: + continue - payload = { - "title": f"[{bid}] {title}", - "body": f"{desc}\n\n---\n**Beads ID:** `{bid}`\n**Priority:** {data.get('priority', 'N/A')}", - "state": target_state - } + print(f"DEBUG: Processing line -> {line}") + try: + data = json.loads(line) + bid = data.get("id") + title = data.get("title") + # Matches your 'cat' output: "issue_type":"task" + itype = data.get("issue_type", "unknown") + + if not bid: + print("⚠️ Skipping line: No ID found.") + continue + + unique_title = f"[{bid}] {title}" + payload = { + "title": unique_title, + "body": f"{data.get('description', 'No description provided.')}\n\n---\n**Beads ID:** `{bid}`\n**Type:** `{itype}`", + "state": "closed" + if data.get("status") in ["closed", "done"] + else "open", + } + + if bid in existing: + print( + f"✅ Updating Gitea Issue #{existing[bid]['number']} for {bid}" + ) + requests.patch( + f"{URL}/api/v1/repos/{REPO}/issues/{existing[bid]['number']}", + headers=HEADERS, + json=payload, + ) + else: + print(f"➕ Creating New Gitea Issue for {bid}") + r = requests.post( + f"{URL}/api/v1/repos/{REPO}/issues", + headers=HEADERS, + json=payload, + ) + r.raise_for_status() + + except Exception as e: + print(f"⚠️ Error processing bead: {e}") + print(f"🏁 Finished. Processed {processed_count} beads.") + if processed_count == 0: + print("❌ ERROR: File was found but it was EMPTY. Check your git push.") + sys.exit(1) - if bid in gitea_issues: - # Update existing issue if state or title changed - issue_number = gitea_issues[bid]['number'] - print(f"Updating Issue #{issue_number} ({bid})") - requests.patch(f"{URL}/api/v1/repos/{REPO}/issues/{issue_number}", headers=HEADERS, json=payload) - else: - # Create new issue - print(f"Creating New Issue for {bid}") - requests.post(f"{URL}/api/v1/repos/{REPO}/issues", headers=HEADERS, json=payload) if __name__ == "__main__": sync()