import json, subprocess, html as H, re from collections import Counter, defaultdict from datetime import datetime, timezone NOW = datetime(2026, 10, 21, tzinfo=timezone.utc) days = lambda s: (NOW - datetime.fromisoformat(s.replace('Z', '+00:00'))).days STAFF = {'gante','Narsil','qubvel','kashif','XciD','asartran','ydshieh','amyeroberts','ArthurZucker','Rocketknight1','zucchini-nlp','stevhliu','SunMarc','MekkCyber','vasqu','molbap','Cyrilvallez','McPatate','eustlb','yonigozlan','LysandreJik','NielsRogge','itazap','remi-or','3outeille','IlyasMoutawwakil','ivarflakstad','manueldeprada','ebezzam','guarin','danieldk','drbh','merveenoyan','BenjaminBossan','qgallouedec','tarekziade','Wauplin','hanouticelina'} CORE = {'ArthurZucker', 'Cyrilvallez', 'LysandreJik', 'vasqu'} HFA = {'MEMBER', 'OWNER', 'COLLABORATOR'} isstaff = lambda login, assoc: bool(login) and (login in STAFF or assoc in HFA) and not re.search(r'(?i)bot', login) D = {int(k): v for k, v in json.load(open('rq.json')).items()} G = ['git', '-C', '/Users/arthurzucker/Work/transformers'] wait, appr = [], [] for n, p in D.items(): au = (p['author'] or {}).get('login') or '' revs = [r for r in p['reviews']['nodes'] if r['author'] and r['author']['login'] != au] srevs = [r for r in revs if isstaff(r['author']['login'], r['authorAssociation'])] scom = [c for c in p['comments']['nodes'] if c['author'] and c['author']['login'] != au and isstaff(c['author']['login'], c['authorAssociation']) and not (c['author']['login'] == 'ArthurZucker' and c['createdAt'] >= '2026-10-20')] # ponytail: today's triage pings aren't reviews acom = [c['createdAt'] for c in p['comments']['nodes'] if c['author'] and c['author']['login'] == au] commit = p['commits']['nodes'][0]['commit'] if p['commits']['nodes'] else {} ci = ((commit.get('statusCheckRollup') or {}).get('state')) or 'none' last_author = max([commit.get('committedDate') or p['createdAt']] + acom) last_staff = max([r['submittedAt'] for r in srevs if r['submittedAt']] + [c['createdAt'] for c in scom] or ['']) ready = max([e['createdAt'] for e in p['timelineItems']['nodes'] if e['__typename'] == 'ReadyForReviewEvent'] or [p['createdAt']]) reqs = [(r['requestedReviewer'] or {}).get('login') or (r['requestedReviewer'] or {}).get('name') for r in p['reviewRequests']['nodes']] reqs = [r for r in reqs if r] req_at = {e['requestedReviewer']['login']: e['createdAt'] for e in p['timelineItems']['nodes'] if e['__typename'] == 'ReviewRequestedEvent' and e.get('requestedReviewer')} conflict = p['mergeable'] == 'CONFLICTING' flags = (['conflicts'] if conflict else []) + (['CI red'] if ci in ('FAILURE', 'ERROR') else []) base = dict(n=n, t=p['title'], au=au, hf=isstaff(au, p['authorAssociation']), ci=ci, flags=flags, age=days(p['createdAt'])) approvals = [r for r in srevs if r['state'] == 'APPROVED'] if p['reviewDecision'] == 'APPROVED' or approvals: a = max(approvals, key=lambda r: r['submittedAt']) if approvals else None when = a['submittedAt'] if a else p['updatedAt']; who = a['author']['login'] if a else '?' core_ok = any(r['author']['login'] in CORE for r in approvals) after = (commit.get('committedDate') or '') > when files = [f['path'] for f in p['files']['nodes']] churn = int(subprocess.run(G + ['rev-list', '--count', f'--since={when}', 'origin/main', '--'] + files, capture_output=True, text=True).stdout.strip() or 0) if files else 0 gone = sum(1 for f in files if subprocess.run(G + ['cat-file', '-e', f'origin/main:{f}'], capture_output=True).returncode) if files else 0 why = [] if conflict: why.append('needs rebase (conflicts)') if ci in ('FAILURE', 'ERROR'): why.append('CI red') if after: why.append('pushed after approval → needs re-approval') if not core_ok: why.append('no core-maintainer approval yet' + (f' (approved by @{who})' if who != '?' else '')) if gone and gone >= len(files) / 2: why.append(f'likely superseded/outdated: {gone}/{len(files)} files gone from main') elif churn >= 15: why.append(f'likely outdated: main touched its files {churn}× since approval') if not why: why.append('nothing blocking → probably forgotten, ready to merge') if p['baseRefName'] != 'main': why.insert(0, f"targets `{p['baseRefName']}`, not main") appr.append({**base, 'who': who, 'when': when[:10], 'd': days(when), 'why': why, 'primary': why[0].split(' (')[0].split(':')[0], 'churn': churn}) continue if p['reviewDecision'] == 'CHANGES_REQUESTED' or (last_staff and last_staff > last_author): since = last_staff; who_last = (max(srevs + [{'submittedAt': c['createdAt'], 'author': c['author']} for c in scom], key=lambda r: r['submittedAt'] or '')['author']['login']) if (srevs or scom) else '?' wait.append({**base, 'bucket': 'waiting on author', 'since': since[:10], 'd': days(since), 'why': f'@{who_last} replied last ({since[:10]}); ball is with the author', 'who': who_last}); continue if not srevs and not scom: since = ready if reqs: r0 = reqs[0]; ra = req_at.get(r0, since) why = f'never reviewed · review requested from @{", @".join(reqs)} {days(ra)}d ago, no response'; bucket = 'assigned, no response' else: why = 'never reviewed · no reviewer assigned'; bucket = 'no reviewer assigned' else: since = last_author who_last = max(srevs + [{'submittedAt': c['createdAt'], 'author': c['author']} for c in scom], key=lambda r: r['submittedAt'] or '')['author']['login'] why = f'author updated after @{who_last}\'s review → needs re-review' + (f' (requested: @{", @".join(reqs)})' if reqs else ''); bucket = 'needs re-review' if base['hf']: bucket = 'HF-authored, no review'; why = 'maintainer PR · ' + why wait.append({**base, 'bucket': bucket, 'since': since[:10], 'd': days(since), 'why': why, 'who': ', '.join(reqs)}) QA = json.load(open('qa_verdicts.json')) def tag(r): q = QA.get(str(r['n'])) if not q: return '' return '✓ audited: still valid' if q['verdict'] == 'valid' else f"⚠ audit: {q['verdict']}" + (f" (#{q['by']})" if q.get('by') else '') for r in appr: r['why'] = r['why'] + ([tag(r)] if tag(r) else []) for r in wait: if tag(r): r['why'] = r['why'] + ' · ' + tag(r) appr = [r for r in appr if QA.get(str(r['n']), {}).get('state', 'OPEN') == 'OPEN'] wait = [r for r in wait if QA.get(str(r['n']), {}).get('state', 'OPEN') == 'OPEN'] json.dump({'wait': wait, 'appr': appr}, open('rq_rows.json', 'w')) DB = lambda d: '<7d' if d < 7 else '7–30d' if d < 30 else '30–90d' if d < 90 else '90–180d' if d < 180 else '>180d' DUR = ['<7d', '7–30d', '30–90d', '90–180d', '>180d'] def matrix(rows, keyf, keys, title): m = Counter((keyf(r), DB(r['d'])) for r in rows); tot = Counter(keyf(r) for r in rows) head = ''.join(f'
| {head} | total |
|---|