Compare commits
1 Commits
main
...
claude/iss
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10196b3804 |
184
scripts/reassign_fenrir.py
Normal file
184
scripts/reassign_fenrir.py
Normal file
@@ -0,0 +1,184 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Reassign Fenrir's orphaned issues to active wizards based on issue type."""
|
||||
|
||||
import json
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
import time
|
||||
|
||||
TOKEN = "dc0517a965226b7a0c5ffdd961b1ba26521ac592"
|
||||
BASE_URL = "https://forge.alexanderwhitestone.com/api/v1"
|
||||
REPO = "Timmy_Foundation/the-nexus"
|
||||
|
||||
HEADERS = {
|
||||
"Authorization": f"token {TOKEN}",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
# Wizard assignments
|
||||
EZRA = "ezra" # Architecture, docs, epics, planning
|
||||
ALLEGRO = "allegro" # Code implementation, UI, features
|
||||
BEZALEL = "bezalel" # Execution, ops, testing, infra, monitoring
|
||||
|
||||
def classify_issue(number, title):
|
||||
"""Classify issue based on number and title."""
|
||||
title_upper = title.upper()
|
||||
|
||||
# Skip the triage issue itself and the permanent escalation issue
|
||||
if number == 823:
|
||||
return None # Skip - this is the issue we're working on
|
||||
if number == 431:
|
||||
return EZRA # Master escalation -> Ezra (archivist)
|
||||
|
||||
# Allegro self-improvement milestones (M0-M7) -> Bezalel (execution/ops)
|
||||
import re
|
||||
if re.match(r'^M\d+:', title):
|
||||
return BEZALEL
|
||||
|
||||
# EPIC/GRAND EPIC/CONSOLIDATION/FRONTIER -> Ezra (architecture)
|
||||
if any(tag in title_upper for tag in [
|
||||
'[EPIC]', '[GRAND EPIC]', 'EPIC:', '[CONSOLIDATION]', '[FRONTIER]',
|
||||
'[CRITIQUE]', '[PROPOSAL]', '[RETROSPECTIVE', '[REPORT]',
|
||||
'EPIC #', 'GRAND EPIC'
|
||||
]):
|
||||
return EZRA
|
||||
|
||||
# Allegro self-improvement epic -> Bezalel
|
||||
if 'ALLEGRO SELF-IMPROVEMENT' in title_upper or 'ALLEGRO HYBRID PRODUCTION' in title_upper:
|
||||
return BEZALEL
|
||||
|
||||
# Ops/Monitoring/Infra/Testing -> Bezalel
|
||||
if any(tag in title_upper for tag in [
|
||||
'[OPS]', '[MONITORING]', '[PRUNE]', '[OFFLOAD]', '[BUG]',
|
||||
'[CRON]', '[INSTALL]', '[INFRA]', '[TRAINING]', '[CI/',
|
||||
'[TRIAGE]', # triage/ops tasks
|
||||
]):
|
||||
return BEZALEL
|
||||
|
||||
# Allegro backlog items -> Allegro
|
||||
if '[ALLEGRO-BACKLOG]' in title_upper:
|
||||
return ALLEGRO
|
||||
|
||||
# Reporting -> Bezalel
|
||||
if any(tag in title_upper for tag in ['[REPORT]', 'BURN-MODE', 'PERFORMANCE REPORT']):
|
||||
return BEZALEL
|
||||
|
||||
# Fleet management/wizard ops -> Bezalel
|
||||
if any(tag in title_upper for tag in ['FLEET', 'WIZARD', 'GHOST WIZARD', 'TIMMY', 'BRING LIVE']):
|
||||
# But EPICs about fleet -> Ezra (already handled above)
|
||||
return BEZALEL
|
||||
|
||||
# Code implementation: NEXUS UI/3D, MIGRATION, UI, UX, PORTALS, CHAT, PANELS -> Allegro
|
||||
if any(tag in title_upper for tag in [
|
||||
'[NEXUS]', '[MIGRATION]', '[UI]', '[UX]', '[PORTALS]', '[PORTAL]',
|
||||
'[CHAT]', '[PANELS]', '[DATA]', '[PERF]', '[RESPONSIVE]', '[A11Y]',
|
||||
'[VISUAL]', '[AUDIO]', '[MEDIA]', '[CONCEPT]', '[BRIDGE]',
|
||||
'[AUTH]', '[VISITOR]', '[IDENTITY]', '[SESSION]', '[RELIABILITY]',
|
||||
'[HARNESS]', '[VALIDATION]', '[SOVEREIGNTY]', '[M6-P',
|
||||
'PROSE ENGINE', 'AUTO-SKILL', 'GITEA_API', 'CRON JOB',
|
||||
'HEARTBEAT DAEMON', 'FLEET HEALTH JSON',
|
||||
'[FENRIR] NEXUS', # fenrir's nexus issues -> allegro
|
||||
]):
|
||||
return ALLEGRO
|
||||
|
||||
# Default: Bezalel for anything else
|
||||
return BEZALEL
|
||||
|
||||
|
||||
def get_all_fenrir_issues():
|
||||
"""Fetch all open issues assigned to fenrir."""
|
||||
issues = []
|
||||
page = 1
|
||||
while True:
|
||||
url = f"{BASE_URL}/repos/{REPO}/issues?assignee=fenrir&state=open&limit=50&page={page}"
|
||||
req = urllib.request.Request(url, headers={"Authorization": f"token {TOKEN}"})
|
||||
with urllib.request.urlopen(req) as resp:
|
||||
data = json.loads(resp.read())
|
||||
if not data:
|
||||
break
|
||||
issues.extend(data)
|
||||
if len(data) < 50:
|
||||
break
|
||||
page += 1
|
||||
return issues
|
||||
|
||||
|
||||
def reassign_issue(number, assignee):
|
||||
"""Reassign an issue to a new wizard."""
|
||||
url = f"{BASE_URL}/repos/{REPO}/issues/{number}"
|
||||
body = json.dumps({"assignees": [assignee]}).encode()
|
||||
req = urllib.request.Request(url, data=body, headers=HEADERS, method="PATCH")
|
||||
try:
|
||||
with urllib.request.urlopen(req) as resp:
|
||||
return resp.status, None
|
||||
except urllib.error.HTTPError as e:
|
||||
return e.code, e.read().decode()
|
||||
|
||||
|
||||
def main():
|
||||
print("Fetching all fenrir issues...")
|
||||
issues = get_all_fenrir_issues()
|
||||
print(f"Found {len(issues)} open issues assigned to fenrir\n")
|
||||
|
||||
# Classify
|
||||
assignments = {EZRA: [], ALLEGRO: [], BEZALEL: [], None: []}
|
||||
for issue in issues:
|
||||
num = issue["number"]
|
||||
title = issue["title"]
|
||||
wizard = classify_issue(num, title)
|
||||
assignments[wizard].append((num, title))
|
||||
|
||||
print(f"Classification:")
|
||||
print(f" Ezra (architecture): {len(assignments[EZRA])} issues")
|
||||
print(f" Allegro (code): {len(assignments[ALLEGRO])} issues")
|
||||
print(f" Bezalel (execution): {len(assignments[BEZALEL])} issues")
|
||||
print(f" Skip (unchanged): {len(assignments[None])} issues")
|
||||
print()
|
||||
|
||||
# Show classification
|
||||
for wizard, label in [(EZRA, 'EZRA'), (ALLEGRO, 'ALLEGRO'), (BEZALEL, 'BEZALEL')]:
|
||||
print(f"\n--- {label} ---")
|
||||
for num, title in assignments[wizard]:
|
||||
print(f" #{num}: {title[:70]}")
|
||||
|
||||
print(f"\n--- SKIPPED ---")
|
||||
for num, title in assignments[None]:
|
||||
print(f" #{num}: {title[:70]}")
|
||||
|
||||
# Execute reassignments
|
||||
print("\n\nExecuting reassignments...")
|
||||
results = {"success": [], "failed": []}
|
||||
|
||||
for wizard in [EZRA, ALLEGRO, BEZALEL]:
|
||||
for num, title in assignments[wizard]:
|
||||
status, error = reassign_issue(num, wizard)
|
||||
if status in (200, 201):
|
||||
print(f" ✓ #{num} -> {wizard}")
|
||||
results["success"].append((num, wizard))
|
||||
else:
|
||||
print(f" ✗ #{num} -> {wizard} (HTTP {status}: {error[:100] if error else 'unknown'})")
|
||||
results["failed"].append((num, wizard, error))
|
||||
time.sleep(0.1) # Rate limiting
|
||||
|
||||
print(f"\n\nSummary:")
|
||||
print(f" Successfully reassigned: {len(results['success'])}")
|
||||
print(f" Failed: {len(results['failed'])}")
|
||||
|
||||
# Save results for PR
|
||||
with open("/tmp/reassignment_results.json", "w") as f:
|
||||
json.dump({
|
||||
"total": len(issues),
|
||||
"ezra": [(n, t) for n, t in assignments[EZRA]],
|
||||
"allegro": [(n, t) for n, t in assignments[ALLEGRO]],
|
||||
"bezalel": [(n, t) for n, t in assignments[BEZALEL]],
|
||||
"skipped": [(n, t) for n, t in assignments[None]],
|
||||
"success_count": len(results["success"]),
|
||||
"failed_count": len(results["failed"]),
|
||||
"failed_details": [(n, w, str(e)) for n, w, e in results["failed"]],
|
||||
}, f, indent=2)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user