工人有名字+回覆也是派工+未經調查不寫診斷(inkstone/ISEP#86/#87/#88)
三張票同一族(誰在派、派給誰、派的內容住哪裡),做在同一條分支: #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
This commit is contained in:
Executable
+94
@@ -0,0 +1,94 @@
|
||||
#!/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())
|
||||
Reference in New Issue
Block a user