新 repo 的工人不再靠人記得:課程工人 course-hand+開場點名「有票卻沒工人」(inkstone/ISEP#118)
- agents/course-hand.md:管 inkstone/ax-courses、course_gen、llm-wiki-template(c7358 leo「你可以製作一個」) 假設:llm-wiki-template 也歸課程工人(同為課程素材),總管不同意就把它拆出去 - hooks/lib/roster.py:`負責 repo` 可列多個(、或逗號),新增 which()/uncovered();find() 與閘本身未動 - scripts/roster which 改走 roster.which - scripts/isep-nag 第④類:org 裡有開著的票、名單上卻沒人負責的 repo,只進開場長版、不進 Telegram - 測試:roster-guard 25→31、overdue-nag 36→43;兩種突變(拿掉票數條件/只認第一個 repo)都會紅 - 文件:worker-roster.md 回答「新 repo 的工人誰在什麼時候加」;README/plugin.json 工人 8、scripts 65(樹上實數) 待總管定版(改了 agents/scripts,要升版才裝得上)。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+53
-6
@@ -20,10 +20,15 @@ leo 整天上課,一天看兩眼。**不叫,就等於這件事不會發生
|
||||
(機器帳號 `claude-code` 在 `bots` 團隊,`/orgs/…/hooks` 回 403,裝不了)。
|
||||
**但腳本版本身是完整可用的,不是半成品。**
|
||||
|
||||
━━ 撈三種東西 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
━━ 撈四種東西 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
① 逾期的 milestone 期限過了、票還沒關 ⇒ 那個版本交不出來了
|
||||
② 掛著等 leo 的票 `Human` 或指派給 leo,**標等了幾天**
|
||||
③ 棒子掉在地上的票 `s/doing`/`s/review` 卻很多天沒動 ⇒ 沒有人在做
|
||||
④ 有票卻沒有工人的 repo 名單(`agents/`)上沒人負責、卻有開著的票
|
||||
⇒ 派工一定撞 roster-guard(inkstone/ISEP#118)
|
||||
**只進長版**(開場給總管看),不進 Telegram 短版——補工人是總管的事,不吵 leo。
|
||||
它回答的是「新 repo 的工人誰在什麼時候加」:建 repo 之後的**第一個 session 開場**,
|
||||
由這支點名;在那之前沒有任何東西會提醒。
|
||||
|
||||
每一條都用白話寫(`CLAUDE.md` 規則五):一眼看懂、代號附一句人話、
|
||||
講「這對你意味什麼」而不是系統內部狀態。
|
||||
@@ -133,10 +138,12 @@ def collect(fixture=None):
|
||||
return {"milestones": [], "issues": [], "error":
|
||||
"拿不到 Gitea token(GITEA_TOKEN_CLAUDE_CODE 沒設,remote 也沒帶憑證)"}
|
||||
|
||||
# org 的 repo 清單**一律撈一次**:④「有票卻沒工人」要看整個 org,
|
||||
# 而且這一通本來就要打(沒設 ISEP_NAG_REPOS 時),不多花 API。
|
||||
org_rows = api("/orgs/%s/repos?limit=50" % ORG, tok) or []
|
||||
repos = os.environ.get("ISEP_NAG_REPOS", "").split()
|
||||
if not repos:
|
||||
rows = api("/orgs/%s/repos?limit=50" % ORG, tok) or []
|
||||
repos = [r.get("full_name") for r in rows if r.get("full_name")]
|
||||
repos = [r.get("full_name") for r in org_rows if r.get("full_name")]
|
||||
|
||||
# 🔴 平行撈,因為這支掛在 SessionStart 上——那是 leo 每次開工的關鍵路徑。
|
||||
# 循序版實測 16 秒(16 個 repo + 5 頁票),**開場等 16 秒沒有人會忍受它,
|
||||
@@ -175,7 +182,7 @@ def collect(fixture=None):
|
||||
issues.extend(rows)
|
||||
if len(rows) < 50:
|
||||
break
|
||||
return {"milestones": milestones, "issues": issues}
|
||||
return {"milestones": milestones, "issues": issues, "repos": org_rows}
|
||||
|
||||
|
||||
# ══ 二、找出「該叫的事」═══════════════════════════════════════════════════
|
||||
@@ -227,7 +234,25 @@ def findings(data, now):
|
||||
waiting.sort(key=lambda x: -x["quiet_days"])
|
||||
dropped.sort(key=lambda x: -x["quiet_days"])
|
||||
return {"overdue_milestones": over, "waiting_on_leo": waiting,
|
||||
"dropped_batons": dropped}
|
||||
"dropped_batons": dropped,
|
||||
"repos_without_worker": no_worker(data)}
|
||||
|
||||
|
||||
def no_worker(data):
|
||||
"""④ 有票卻沒有工人的 repo(inkstone/ISEP#118)。
|
||||
|
||||
回 list(可能是空的);**名單讀不到或 org 清單沒撈到 ⇒ 回 None**,
|
||||
報告裡講「沒查到」,不講成「每個 repo 都缺人」或「都有人」。
|
||||
"""
|
||||
rows = data.get("repos")
|
||||
if not rows:
|
||||
return None
|
||||
try:
|
||||
sys.path.insert(0, os.path.join(HERE, "..", "hooks", "lib"))
|
||||
import roster
|
||||
except Exception:
|
||||
return None
|
||||
return roster.uncovered(rows)
|
||||
|
||||
|
||||
# ══ 三、講出來(白話)═════════════════════════════════════════════════════
|
||||
@@ -260,12 +285,13 @@ def full_report(f, now, err=None):
|
||||
" ⇒ **這不等於「沒有事情逾期」**,這是「我沒查到」。兩件事不要講成同一句。"]
|
||||
return "\n".join(out)
|
||||
|
||||
n = sum(len(v) for v in f.values())
|
||||
n = sum(len(f[k]) for k in ("overdue_milestones", "waiting_on_leo", "dropped_batons"))
|
||||
if n == 0:
|
||||
out += ["", "✅ **查過了,沒有。** 三類都乾淨:",
|
||||
" · 沒有逾期還開著票的 milestone",
|
||||
" · 沒有掛著等你的票",
|
||||
" · 沒有超過 %d 天沒動的 s/doing/s/review" % STALE_DAYS]
|
||||
out += worker_section(f)
|
||||
return "\n".join(out)
|
||||
|
||||
if f["overdue_milestones"]:
|
||||
@@ -295,9 +321,30 @@ def full_report(f, now, err=None):
|
||||
_clip(i["title"], 38)))
|
||||
out += _tail(f["dropped_batons"], MAX_ROWS)
|
||||
out.append(" ⇒ 對你意味著:這些不是在做,是沒人接。要嘛派人,要嘛改回 s/todo。")
|
||||
out += worker_section(f)
|
||||
return "\n".join(out)
|
||||
|
||||
|
||||
def worker_section(f):
|
||||
"""④ 只進長版(開場注入給總管看的那份),**不進 Telegram 短版**:
|
||||
派工是總管的事,不是 leo 的事——拿它去吵 leo 就是把總管的活推給他。"""
|
||||
rows = f.get("repos_without_worker")
|
||||
if rows is None:
|
||||
return ["", "👷 有票卻沒有工人的 repo:**沒查到**(org 清單或工人名單讀不到)"
|
||||
"——這不等於「每個 repo 都有人」。"]
|
||||
if not rows:
|
||||
return ["", "👷 每個有開著票的 repo,名單上都有工人。"]
|
||||
out = ["", "👷 **有票卻沒有工人的 repo(%d 個)**——派工到這裡會被 roster-guard 擋,"
|
||||
"只剩開例外閘繞過去:" % len(rows)]
|
||||
for r in rows[:MAX_ROWS]:
|
||||
out.append(" · %s(%d 張票開著)" % (r["repo"], r["open"]))
|
||||
out += _tail(rows, MAX_ROWS)
|
||||
out.append(" ⇒ 對總管意味著:**先補工人再派工**——在 ISEP `agents/` 加一個 `.md`"
|
||||
"(規約 docs/governance/worker-roster.md),或把它寫進既有工人的 `負責 repo`。"
|
||||
"不要開 `/tmp/.roster-ok-*` 了事,那樣下一次還是會撞。")
|
||||
return out
|
||||
|
||||
|
||||
def short_report(f, now, err=None):
|
||||
"""Telegram 版:leo 在手機上一眼掃完、回一個詞。**寧短勿長。**"""
|
||||
if err:
|
||||
|
||||
+1
-1
@@ -53,7 +53,7 @@ def cmd_show(name):
|
||||
|
||||
|
||||
def cmd_which(repo):
|
||||
hits = [w for w in R.load() if w["repo"].lower() == (repo or "").strip().lower()]
|
||||
hits = R.which(repo) # 一個工人可以管好幾個 repo(inkstone/ISEP#118)
|
||||
if not hits:
|
||||
print("🚫 名單上沒有負責「%s」的人。現有的是:" % repo, file=sys.stderr)
|
||||
for w in R.load():
|
||||
|
||||
Reference in New Issue
Block a user