5ac06abc95
三張票同一族(誰在派、派給誰、派的內容住哪裡),做在同一條分支: #86 工人名單:agents/ 七位有名字的工人+scripts/roster+hooks/roster-guard.sh 派工用 Task 的 subagent_type 指名,派工單格式一個字都沒改; 指對名字就把那位的檔案原文注入(你是誰/先讀什麼/你的紅線)。 【身份】欄同時吃得下工人名字(原本三個角色照舊)。 #87 未經調查不寫診斷:hooks/diagnosis-evidence-guard.sh + investigate-first-stamp.sh 三個結構訊號(派過人查沒/有沒有走得過去的出處/有沒有份量), 一個關鍵字比對都沒有;轉述有出處不會被誤擋。 #88 回覆也是派工:不另造閘,把攔截點加掛上去。 hooks/lib/dispatch_parse.py 的 tool_channel() 一次列全所有通往 subagent 的路 (SendMessage/雲端 session・trigger/claude -p);擋下來時把那段內容原文印出來。 subagent 往上回報(to: "main")=交件不是派工,刻意不管。 順手修掉一個真的會咬人的 flake:dispatch-format-guard 原本開四支 python 各讀一個欄位, 機器忙的時候某個欄位會靜靜變空字串(實測連跑 10 次有 1 次「豁免了卻還是被擋」)。 四個欄位改成一次讀完。 版本號待總管定(plugin.json 只更新了描述裡的數字,版本沒動)。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016ZBu4Sa1cGntKFRBYNZ6xs
95 lines
3.3 KiB
Python
Executable File
95 lines
3.3 KiB
Python
Executable File
#!/usr/bin/env python3
|
||
"""roster — 工人名單(inkstone/ISEP#86)
|
||
|
||
python3 scripts/roster list 名單:有哪幾個工人、各自管什麼
|
||
python3 scripts/roster show <名字> 一個工人的全文(它開工時會讀到的東西)
|
||
python3 scripts/roster names 只印名字,一行一個(給腳本用)
|
||
python3 scripts/roster which <owner/repo> 這個 repo 該派給誰
|
||
|
||
leo(ISEP#86):「身為派工的人,我要工人有名字,我才知道票上這件事到底是誰做的。」
|
||
|
||
名單是**資料**(`agents/*.md`),不是程式:加一個工人=加一個檔,不改任何 hook。
|
||
派工時把名字放進 `Task` 的 `subagent_type`;名字不在名單上,
|
||
`hooks/roster-guard.sh` 會擋下來並說「沒有這個人」。
|
||
"""
|
||
import os
|
||
import sys
|
||
|
||
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "hooks", "lib"))
|
||
try:
|
||
import roster as R
|
||
except Exception as e: # pragma: no cover
|
||
print("🔴 讀不到 hooks/lib/roster.py:%s" % e, file=sys.stderr)
|
||
sys.exit(1)
|
||
|
||
|
||
def cmd_list():
|
||
ws = R.load()
|
||
if not ws:
|
||
print("🔴 名單是空的(找不到 %s 底下任何工人檔)" % R.roster_dir(), file=sys.stderr)
|
||
return 1
|
||
print("工人名單(%d 位) 來源:%s\n" % (len(ws), R.roster_dir()))
|
||
print(" %-18s %-26s %s" % ("名字", "負責 repo", "它是誰"))
|
||
print(" " + "─" * 100)
|
||
for w in ws:
|
||
print(" " + R.one_liner(w))
|
||
print("\n派工:把「名字」放進 Task 的 subagent_type;派工單本身仍然只有一行【工單】。")
|
||
print("看某一位的全文:python3 scripts/roster show <名字>")
|
||
return 0
|
||
|
||
|
||
def cmd_show(name):
|
||
w = R.find(name)
|
||
if not w:
|
||
print("🚫 名單上沒有「%s」這個人。現有的是:\n %s"
|
||
% (name, "\n ".join(R.names()) or "(空)"), file=sys.stderr)
|
||
return 2
|
||
print("# %s\n" % w["name"])
|
||
print("負責 repo:%s" % (w["repo"] or "(跨 repo)"))
|
||
print("一句話 :%s" % w["description"])
|
||
print("檔案 :%s\n" % w["path"])
|
||
print(w["body"])
|
||
return 0
|
||
|
||
|
||
def cmd_which(repo):
|
||
hits = [w for w in R.load() if w["repo"].lower() == (repo or "").strip().lower()]
|
||
if not hits:
|
||
print("🚫 名單上沒有負責「%s」的人。現有的是:" % repo, file=sys.stderr)
|
||
for w in R.load():
|
||
print(" " + R.one_liner(w), file=sys.stderr)
|
||
return 2
|
||
for w in hits:
|
||
print(w["name"])
|
||
return 0
|
||
|
||
|
||
def main():
|
||
argv = sys.argv[1:]
|
||
if not argv or argv[0] in ("-h", "--help", "help"):
|
||
print(__doc__)
|
||
return 0
|
||
verb, rest = argv[0], argv[1:]
|
||
if verb == "list":
|
||
return cmd_list()
|
||
if verb == "names":
|
||
for n in R.names():
|
||
print(n)
|
||
return 0
|
||
if verb == "show":
|
||
if not rest:
|
||
print("用法:scripts/roster show <名字>", file=sys.stderr)
|
||
return 1
|
||
return cmd_show(rest[0])
|
||
if verb == "which":
|
||
if not rest:
|
||
print("用法:scripts/roster which <owner/repo>", file=sys.stderr)
|
||
return 1
|
||
return cmd_which(rest[0])
|
||
print("不認得的動詞「%s」。\n%s" % (verb, __doc__), file=sys.stderr)
|
||
return 1
|
||
|
||
|
||
if __name__ == "__main__":
|
||
sys.exit(main())
|