74 lines
3.0 KiB
Python
Executable File
74 lines
3.0 KiB
Python
Executable File
#!/usr/bin/env python3
|
||
"""列出可執行的議題。輸入:open.json agents.json tabs.json owner/repo
|
||
阻擋關係只認 Gitea 原生 issue dependencies(tea api),不看議題內文。"""
|
||
import os, json, re, subprocess, sys
|
||
|
||
open_issues = json.load(open(sys.argv[1]))
|
||
agents = json.load(open(sys.argv[2]))
|
||
tabs = json.load(open(sys.argv[3]))
|
||
repo = sys.argv[4]
|
||
|
||
def find(o, k):
|
||
if isinstance(o, dict):
|
||
if k in o: return o[k]
|
||
for v in o.values():
|
||
r = find(v, k)
|
||
if r is not None: return r
|
||
elif isinstance(o, list):
|
||
for v in o:
|
||
r = find(v, k)
|
||
if r is not None: return r
|
||
|
||
def labels(i): return [l.strip() for l in (i.get("labels") or "").split() if l.strip()] if isinstance(i.get("labels"), str) else [l["name"] for l in i.get("labels") or []]
|
||
|
||
# Herdr 裡活著的 agent:名稱 issue-N,或掛在標籤 #N 的 tab 上
|
||
running = set()
|
||
for a in find(agents, "agents") or []:
|
||
m = re.fullmatch(r"issue-(\d+)", str(a.get("name") or ""))
|
||
if m: running.add(m.group(1))
|
||
agent_tabs = {a.get("tab_id") for a in find(agents, "agents") or []}
|
||
for t in find(tabs, "tabs") or []:
|
||
m = re.fullmatch(r"#(\d+)", str(t.get("label") or ""))
|
||
if m and t.get("tab_id") in agent_tabs: running.add(m.group(1))
|
||
|
||
def open_blockers(n):
|
||
"""擋住 #n 的 open 依賴。tea api 遇到 HTTP 錯誤仍 exit 0,所以回應不是陣列就當失敗,整個停下。"""
|
||
raw = subprocess.run(["tea", "api", "--login", os.environ["HERDR_ISSUE_LOGIN"], "--repo", repo, f"/repos/{{owner}}/{{repo}}/issues/{n}/dependencies"],
|
||
capture_output=True, text=True).stdout
|
||
try: d = json.loads(raw)
|
||
except Exception: d = None
|
||
if not isinstance(d, list):
|
||
sys.exit(f"herdr-issue: 讀取 #{n} 的依賴失敗:{raw.strip()[:300]}")
|
||
out = []
|
||
for i in d:
|
||
if i.get("state") != "open": continue
|
||
r = (i.get("repository") or {}).get("full_name", "")
|
||
out.append(("#" if r in ("", repo) else r + "#") + str(i["number"]))
|
||
return out
|
||
|
||
def spec(i):
|
||
return next((l for l in labels(i) if l.startswith("規格/#") or l.startswith("總覽/#")), "")
|
||
|
||
runnable, stale = [], []
|
||
for i in sorted(open_issues, key=lambda x: int(x["index"])):
|
||
ls = labels(i)
|
||
if "ready-for-agent" not in ls: continue
|
||
if "類型/規格" in ls or "類型/總覽" in ls: continue
|
||
n = i["index"]
|
||
if n in running: continue
|
||
if "in-progress" in ls:
|
||
stale.append(n); continue
|
||
if open_blockers(n): continue
|
||
runnable.append((n, spec(i), i["title"]))
|
||
|
||
if runnable:
|
||
print(f"可執行({len(runnable)}):編號\t規格\t標題")
|
||
for n, s, t in runnable:
|
||
print(f"#{n}\t{s}\t{t}")
|
||
else:
|
||
print("目前沒有可執行的議題。")
|
||
print()
|
||
print("Herdr 執行中:" + (" ".join(f"#{n}" for n in sorted(running, key=int)) or "無"))
|
||
if stale:
|
||
print("掛 in-progress 但沒有 agent(需處理,重跑 start 會接續):" + " ".join(f"#{n}" for n in stale))
|