新 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:
2026-09-14 13:13:12 +08:00
parent 25da674615
commit d2f706a16c
11 changed files with 304 additions and 19 deletions
+78
View File
@@ -200,6 +200,84 @@ else
printf ' ❌ ⑲ 真實的 subagent_typeisep:isep-hand)認不出來——就是這個造成 8/28 的死鎖\n'; FAIL=$((FAIL+1))
fi
echo "── 驗收 5:課程工人(inkstone/ISEP#118):一個工人管三個 repo ──────────"
for r in inkstone/ax-courses inkstone/course_gen inkstone/llm-wiki-template; do
N=$((N+1))
if [ "$(python3 "$ROOT/scripts/roster" which "$r" 2>/dev/null)" = "course-hand" ]; then
printf ' ✅ ⑳ `roster which %s` ⇒ course-hand\n' "$r"; PASS=$((PASS+1))
else
printf ' ❌ ⑳ `roster which %s` 沒回 course-hand(下一次派工又要開例外閘)\n' "$r"; FAIL=$((FAIL+1))
fi
done
# 真實派工的形狀:plugin 前綴 + 只有一行【工單】
t 0 "㉑ 派 isep:course-hand 去 inkstone/ax-courses#6 ⇒ 放行(09-14 實撞開例外閘的那一張)" \
"$(payload '【工單】inkstone/ax-courses#6' 'isep:course-hand' 'Agent')"
N=$((N+1))
inj3=$(printf '%s' "$(payload '【工單】inkstone/ax-courses#6' 'isep:course-hand' 'Agent')" | bash "$HOOK" 2>/dev/null)
if printf '%s' "$inj3" | python3 -c '
import sys, json
c = json.load(sys.stdin)["hookSpecificOutput"]["additionalContext"]
for want in ("course-hand", "inkstone/ax-courses", "inkstone/course_gen", "pdftoppm", "## 紅線"):
assert want in c, want
' 2>/dev/null; then
printf ' ✅ ㉒ 注入的卡片帶著它管的三個 repo 與課件驗收紅線(pdftoppm\n'; PASS=$((PASS+1))
else
printf ' ❌ ㉒ 課程工人的注入缺東西\n'; FAIL=$((FAIL+1))
fi
echo "── 驗收 6:多 repo 解析與「有票卻沒工人」的判準(離線假名單)────────"
mkdir -p "$TMP/r6"
cat > "$TMP/r6/multi.md" <<'MD'
---
name: multi-hand
description: 測試用
---
**負責 repo**acme/a、acme/b, acme/c(括號後面不算 acme/zzz
## 開工前先讀
## 紅線
MD
cat > "$TMP/r6/single.md" <<'MD'
---
name: single-hand
description: 測試用
---
**負責 repo**acme/solo
## 開工前先讀
## 紅線
MD
N=$((N+1))
if python3 - "$ROOT" "$TMP/r6" <<'PY' 2>/dev/null
import sys, os
sys.path.insert(0, os.path.join(sys.argv[1], "hooks", "lib"))
import roster
d = sys.argv[2]
assert [w["name"] for w in roster.which("acme/b", d)] == ["multi-hand"], "頓號後面的 repo 認不出來"
assert [w["name"] for w in roster.which("acme/c", d)] == ["multi-hand"], "逗號後面的 repo 認不出來"
assert [w["name"] for w in roster.which("ACME/SOLO", d)] == ["single-hand"], "大小寫不分壞了"
assert roster.which("acme/zzz", d) == [], "括號裡的字被當成 repo"
assert roster.which("acme/nobody", d) == [], "不存在的 repo 被認領了"
rows = [
{"full_name": "acme/a", "open_issues_count": 3}, # 有人 ⇒ 不叫
{"full_name": "acme/lonely", "open_issues_count": 2}, # 沒人、有票 ⇒ 叫
{"full_name": "acme/idle", "open_issues_count": 0}, # 沒人、沒票 ⇒ 不叫
{"full_name": "acme/old", "open_issues_count": 9, "archived": True}, # 封存 ⇒ 不叫
{"full_name": "acme/busy", "open_issues_count": 5}, # 沒人、有票 ⇒ 叫,排前面
]
got = roster.uncovered(rows, d)
assert got == [{"repo": "acme/busy", "open": 5}, {"repo": "acme/lonely", "open": 2}], got
assert roster.uncovered(rows, os.path.join(d, "不存在")) is None, "名單讀不到時沒有回 None"
PY
then
printf ' ✅ ㉓ 頓號/逗號都認得、括號後不算;只叫「沒人又有票」、不叫封存與沒票;名單讀不到回 None\n'; PASS=$((PASS+1))
else
printf ' ❌ ㉓ 多 repo 解析或 uncovered 判準壞了\n'; FAIL=$((FAIL+1))
fi
echo
printf '結果:%s 通過 / %s 失敗(共 %s 條)\n' "$PASS" "$FAIL" "$N"
[ "$FAIL" -eq 0 ] || exit 1