#!/usr/bin/env python3 """Open exactly one Eastern-time Daily Slop Drop cycle on the recurring issue.""" from __future__ import annotations import argparse import datetime as dt import json import os import urllib.error import urllib.request from zoneinfo import ZoneInfo BASE = "https://forge.alexanderwhitestone.com/git/api/v1" REPO = "stackchain/stackchain-lab-loop" TITLE = "[Recurring] Daily AI Slop Drop collaboration" def request(method: str, path: str, token: str, payload=None): data = None if payload is None else json.dumps(payload).encode() req = urllib.request.Request( BASE + path, data=data, method=method, headers={"Authorization": f"token {token}", "Content-Type": "application/json", "Accept": "application/json"}, ) try: with urllib.request.urlopen(req, timeout=20) as response: raw = response.read() return json.loads(raw) if raw else None except urllib.error.HTTPError as exc: raise RuntimeError(f"Gitea {method} failed with HTTP {exc.code}: {exc.read().decode()}") from exc def cycle_marker(day: dt.date) -> str: return f"[CYCLE {day.isoformat()}]" def cycle_body(day: dt.date) -> str: marker = cycle_marker(day) return f"""{marker} **Timmy pass:** propose and build one concrete experience that improves on the prior drop. **Vincent pass:** challenge the concept, remove slop, and contribute one material improvement before final review. **Required audience package:** - a polished MP4 walkthrough that plays directly in chat; - the primary still or poster; - any interactive/source artifact; - verification evidence and one sharp discussion question. Post only decisions, evidence, artifacts, blockers, or explicit handoffs. No progress updates or acknowledgments.""" def find_issue(token: str): rows = request("GET", f"/repos/{REPO}/issues?state=all&type=issues&limit=100", token) for row in rows: if row.get("title") == TITLE: return row raise RuntimeError(f"Recurring issue not found: {TITLE}") def main() -> int: parser = argparse.ArgumentParser() parser.add_argument("--date", help="Eastern date override, YYYY-MM-DD") args = parser.parse_args() token = os.environ.get("GITEA_TOKEN", "") if not token: raise RuntimeError("GITEA_TOKEN is required") day = dt.date.fromisoformat(args.date) if args.date else dt.datetime.now(ZoneInfo("America/New_York")).date() issue = find_issue(token) comments = request("GET", f"/repos/{REPO}/issues/{issue['number']}/comments?limit=100", token) marker = cycle_marker(day) if any(marker in (comment.get("body") or "") for comment in comments): print(json.dumps({"status": "already_open", "date": day.isoformat(), "issue": issue["number"]})) return 0 labels = request("GET", f"/repos/{REPO}/labels?limit=100", token) label_ids = {label["name"]: label["id"] for label in labels} preserve = { label["name"] for label in issue.get("labels", []) if not label["name"].startswith("agent:") and not label["name"].startswith("state:") } desired = preserve | {"agent:timmy", "state:ready", "series:daily-slop", "cadence:daily"} missing = desired - label_ids.keys() if missing: raise RuntimeError(f"Missing labels: {sorted(missing)}") request("POST", f"/repos/{REPO}/issues/{issue['number']}/comments", token, {"body": cycle_body(day)}) request("PATCH", f"/repos/{REPO}/issues/{issue['number']}", token, { "labels": [label_ids[name] for name in sorted(desired)], "assignee": "timmy", }) verified = request("GET", f"/repos/{REPO}/issues/{issue['number']}", token) names = {label["name"] for label in verified.get("labels", [])} if not {"agent:timmy", "state:ready", "series:daily-slop", "cadence:daily"}.issubset(names): raise RuntimeError("Cycle label verification failed") print(json.dumps({"status": "opened", "date": day.isoformat(), "issue": issue["number"], "url": issue["html_url"]})) return 0 if __name__ == "__main__": raise SystemExit(main())