import os, json, urllib.request URL = os.environ["REDMINE_URL"] KEY = os.environ["REDMINE_API_KEY"] def api(method, path, body=None): data = json.dumps(body).encode() if body is not None else None req = urllib.request.Request(URL + path, data=data, headers={"X-Redmine-API-Key": KEY, "Content-Type": "application/json"}, method=method) resp = urllib.request.urlopen(req, timeout=15) raw = resp.read().decode() return json.loads(raw) if raw.strip() else {} # 1. Versions (roadmap milestones) versions = {} for name, due, status in [ ("V1 Core", "2026-08-28", "closed"), ("Beta", "2026-08-31", "open"), ("Production", "2026-09-01", "open"), ("Phase 3 - Integrations", "2026-09-07", "open"), ]: try: v = api("POST", "/projects/MOPAC/versions.json", {"version": {"name": name, "due_date": due, "status": status, "sharing": "descendants"}}) versions[name] = v["version"]["id"] print("version created:", name, v["version"]["id"]) except Exception as e: print("version exists/err:", name, getattr(e, "code", e)) if not versions: for v in api("GET", "/projects/MOPAC/versions.json")["versions"]: versions[v["name"]] = v["id"] print("versions:", versions) # 2. Categories cats = {} for name in ["Quota & Backpressure", "Model Selection", "Task Management", "Deployment", "Briefing", "Secrets", "Integrations", "Infrastructure", "Documentation"]: try: c = api("POST", "/projects/MOPAC/issue_categories.json", {"issue_category": {"name": name}}) cats[name] = c["issue_category"]["id"] print("cat created:", name) except Exception: pass for c in api("GET", "/projects/MOPAC/issue_categories.json")["issue_categories"]: cats[c["name"]] = c["id"] print("cats:", len(cats)) # 3. Update majors: category, version, estimates updates = [ (490, "Quota & Backpressure", "Beta", 8), (491, "Quota & Backpressure", "Beta", 3), (492, "Model Selection", "Production", 5), (493, "Task Management", "Production", 5), (494, "Deployment", "Beta", 6), (495, "Briefing", "Beta", 8), (496, "Task Management", "Phase 3 - Integrations", 12), (497, "Secrets", "Beta", 5), (498, "Integrations", "Phase 3 - Integrations", 40), (499, "Documentation", "Phase 3 - Integrations", 6), ] for iid, cat, ver, hrs in updates: api("PUT", "/issues/%d.json" % iid, {"issue": { "category_id": cats[cat], "fixed_version_id": versions[ver], "estimated_hours": hrs}}) print("updated", iid, cat, ver, str(hrs) + "h") # 4. Relations: quota blocks dispatcher pieces for to in (492, 493): try: api("POST", "/issues/490/relations.json", {"relation": {"issue_to_id": to, "relation_type": "blocks"}}) print("relation 490 blocks", to) except Exception: pass # 5. Children of 498 (integration fleet) children = [ ("Google suite (docs/tasks/keep/gmail) MCP/CLI", "Normal", 8), ("IMAP client integration", "Low", 5), ("Linkwarden integration (tag-driven intake)", "Normal", 5), ("Google Chat integration", "Low", 5), ("Signal integration (QR login)", "Normal", 8), ("Discord integration", "Low", 5), ("Redmine Go CLI (replaces bash/python glue)", "Normal", 8), ("Discourse Go client (tracked in 495, child for roadmap)", "Normal", 4), ("Gitea Go client", "Normal", 5), ("Browser automation (Chrome/Firefox/WebKit)", "Low", 12), ("Linux SSH/MCP fleet (Kali, OpenVAS for security queue)", "Normal", 12), ("Generic REST API driver", "Normal", 5), ("Cloudron API integration", "Normal", 5), ("HomeAssistant integration", "Low", 4), ("Kubernetes integration", "Low", 8), ] for subject, prio, hrs in children: prio_id = 4 if prio == "Normal" else 3 body = { "issue": { "project_id": "MOPAC", "subject": "[fleet] " + subject, "priority_id": prio_id, "category_id": cats["Integrations"], "fixed_version_id": versions["Phase 3 - Integrations"], "estimated_hours": hrs, "parent_issue_id": 498, "tracker_id": 2, "description": "Child of umbrella #498 (MCP/CLI outbound integration fleet). One integration = one standalone ukrrs repo; loose coupling, config-driven, exec+JSON contracts. OAuth one-time browser flow preferred; QR for Signal. Language tiers apply (node = last resort + consult Charles). See SPEC-20260829-charles-brief.md.", } } r = api("POST", "/issues.json", body) print("child:", r["issue"]["id"], subject) # 6. Two brief items not yet ticketed extra = [ ("High-security provider routing (ITAR/TS-SCI: dedicated LiteLLM + internal models)", "Infrastructure", "Phase 3 - Integrations", 3, 8, "Spec: Provider routing section. Some orchestrated work is ITAR/TS-SCI/proprietary and must route to internally hosted models via dedicated OpenWebUI instances; likely needs a dedicated high-security LiteLLM instance. Low priority, KEY requirement - design constraint to respect while building everything else."), ("Session/context quality: long-running rapport + auto-compact handling", "Model Selection", "Production", 3, 8, "Spec: workflow section. Crush+GLM-5.2 rapport: agents working hours-long bodies of work with auto-compaction. Meaty projects need bigger active working memory. Design how MOPAC conductor sessions persist/compact/resume across long work."), ] for subject, cat, ver, prio, hrs, desc in extra: r = api("POST", "/issues.json", {"issue": { "project_id": "MOPAC", "subject": subject, "priority_id": prio, "category_id": cats[cat], "fixed_version_id": versions[ver], "estimated_hours": hrs, "tracker_id": 2, "description": desc}}) print("extra:", r["issue"]["id"], subject[:50])