3bc7f3c5f5
leo 2026-08-27(inkstone/ISEP#30 comment 4322/4325/4327): 「這些話票上都沒有,你根本沒照規則做事,你的 hook 讓你這樣搞?」 「你用一個 output parser 把你給 subagent 的指令規範,分作幾點,每一點規定格式, 照這種散文寫法根本無法迭代」「警察也不能抓」 「交件方式不需要寫,定義在原則裡⋯⋯每次都一樣提取出來變成共通規定」 「(那些 session 事實)這些為什麼不寫到票裡?」「subagent 回覆時要表明身份」 病根:no-ticket-no-dispatch.sh 驗的是「有沒有一行【工單】owner/repo#N」, 而規則的原文是「派工單只寫票號」。⇒ 把 40 行任務全寫在 prompt 裡、票號補一行, 閘照樣放行。2026-08-27 一天內這樣做了 5 次,每次票上都沒有那份任務。 規則存在,閘只驗了它的殼——同款第 N 次(history-first/KBDB-first/stage-first)。 新增 hooks/dispatch-format-guard.sh(PreToolUse Task|Agent),兩件事: - 擋:【工單】以外還有實質內容就 exit 2,並指出那些內容該搬去哪 (每次都一樣 → 共通規定;這次才知道 → 寫進那張票。 判準「這句話換一張票還成立嗎?」) - 注入:合規的派工自動把共通規定送給收工方(交件方式、不准 push main、 org 是 inkstone…)——這是「派工單只剩票號」能成立的前提, leo 的驗收條件之一就是「收工方沒讀派工單也知道要貼回原票」 判準是結構不是文字(leo 2026-08-17 那條檢驗):問的是「這一行是不是【工單】欄位」 ——在不在,不是寫什麼。hooks/lib/dispatch_parse.py 全檔零個「命中某個詞就違規」的比對。 ⇒ 也因此不需要語意判官:免費、瞬間、每次結果一樣。 新增 hooks/reply-identity-guard.sh(PreToolUse Bash)+ scripts/ticket 內建檢查: 票上每一則留言第一行要有【身份】(總管/subagent/leo)。貼留言有兩條路,兩條都封 ——ticket-api-bypass-guard 是刻意放行「對既有票留言」的,只封正門等於沒封。 實害:多條線並行時總管寫的診斷被當成 subagent 的結論,而其中一則是錯的。 規約寫成文件:docs/governance/dispatch-and-reply-format.md (§2 那段就是被注入的那份共通規定本體——只有一份,改那裡等於改所有派工) 實測(離線、不打網路、不花錢): hooks/tests/dispatch-format-guard.test.sh 19/19 hooks/tests/reply-identity.test.sh 11/11 測資裡的 B⑨ 是真跡:產生 ISEP#30 這條線的那一次派工,一字未改。 另 4 份 leo 點名的違規派工拿不回來了——它們住在 prompt 裡,agent 一停就沒了, 這件事本身就是這條規則的證據(見 hooks/tests/fixtures/README.md,不用想像的例子替補)。 升版 0.4.0 → 0.5.0(產物按版本號分資料夾,不升版新閘不會被載入)。 tag 照慣例打在 merge commit 上,所以這條分支上 check-version-consistency.sh 是紅的。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
74 lines
3.8 KiB
Bash
Executable File
74 lines
3.8 KiB
Bash
Executable File
#!/bin/bash
|
||
# 管什麼: 直接打 Gitea API 貼留言到票上時,內文第一行沒有【身份】就擋。
|
||
# 為什麼: scripts/ticket 已經在正門擋了,但貼留言還有 API 這條側門——只封一條等於沒封。
|
||
# 誤觸時怎麼關: 內文第一行加【身份】<總管|subagent|leo>/<owner/repo>/<分支>,或在指令裡加 reply-identity-ok 留痕放行。
|
||
#
|
||
# reply-identity-guard.sh — PreToolUse(Bash):**票上的每一則留言都要認得出是誰寫的**
|
||
#
|
||
# 來由(leo 2026-08-27,inkstone/ISEP#30 comment 4325):
|
||
# 「**subagent 回覆時要表明身份**」
|
||
# 實害(同日):多條線並行,票上的留言看不出是誰寫的,
|
||
# **總管寫的診斷被當成 subagent 的結論,而其中一則是錯的**。
|
||
#
|
||
# 為什麼要有這一支,正門的檢查不夠:
|
||
# `scripts/ticket say` 已經在貼上去之前擋了。但**貼留言這個動作有兩條路**——
|
||
# 而 `ticket-api-bypass-guard.sh` 是**刻意放行**「對既有票的留言」的(它管的是開新票)。
|
||
# ⇒ 側門大開。這正是那支閘檔頭自己記過的一課:
|
||
# 「規範有、閘也有,但閘長在『工具』上,而那個動作有兩條路,只封了一條。」
|
||
#
|
||
# 判準是**結構**不是文字(leo 2026-08-17:「封的是動作,不是文字」):
|
||
# 本閘不看留言寫什麼,只看**那個欄位在不在**。
|
||
# 三個條件都命中才擋:① 打到 /issues/<N>/comments 這條端點 ② 指令裡有 POST
|
||
# ③ 內文找不到【身份】。純讀取(GET 撈留言)一律放行。
|
||
#
|
||
# 逃生口:指令裡加 `reply-identity-ok`(會留在指令歷史上)。
|
||
#
|
||
# 測試:hooks/tests/reply-identity.test.sh
|
||
set -uo pipefail
|
||
|
||
INPUT=$(cat)
|
||
CMD=$(printf '%s' "$INPUT" | python3 -c "
|
||
import sys, json
|
||
try: print(json.load(sys.stdin).get('tool_input', {}).get('command', ''))
|
||
except Exception: print('')
|
||
" 2>/dev/null)
|
||
[ -n "$CMD" ] || exit 0
|
||
|
||
# 逃生口(留痕)
|
||
case "$CMD" in *reply-identity-ok*) exit 0 ;; esac
|
||
# 正門有自己的閘,重複擋只會互相打架
|
||
case "$CMD" in *scripts/ticket*|*"ticket say"*|*"ticket decide"*|*"ticket close"*) exit 0 ;; esac
|
||
|
||
# ① 打到「某張票的留言」這條端點(帶票號的子路徑,這是本閘唯一管的東西)
|
||
printf '%s' "$CMD" | grep -qE 'issues/[0-9]+/comments' || exit 0
|
||
|
||
# ② 純讀取一律放行。同 ticket-api-bypass-guard 的理由:只認一個裸字就夠——
|
||
# ①已經確定這是留言端點,讀取的指令不會出現 POST,而且沒有跳脫的破口。
|
||
printf '%s' "$CMD" | grep -qw 'POST' || exit 0
|
||
|
||
# ③ 內文有沒有身份欄。只問「在不在」,不問「寫什麼」。
|
||
printf '%s' "$CMD" | grep -q '【身份】' && exit 0
|
||
|
||
cat >&2 <<'MSG'
|
||
🚫 貼到票上的留言,第一行要表明身份(leo 2026-08-27:「**subagent 回覆時要表明身份**」)
|
||
|
||
你正在用 Gitea API 直接貼留言,而內文裡找不到【身份】。
|
||
|
||
第一行照這個寫(角色三選一:總管/subagent/leo):
|
||
|
||
【身份】subagent/inkstone/ISEP/feat/my-branch
|
||
【身份】總管/inkstone/InkStoneCo/-
|
||
|
||
**這條管所有人,不是只管 subagent。** 總管寫在票上的東西同樣要標——
|
||
實害(2026-08-27):多條線並行時票上看不出誰寫的,
|
||
**總管寫的診斷被當成 subagent 的結論,而其中一則是錯的**。
|
||
|
||
── 走正門比較省事 ─────────────────────────────────────────────
|
||
scripts/ticket say <owner/repo#N> -F <內文檔>
|
||
它會回一行「定址」,那串就是下次派工要寫進【工單】的東西。
|
||
|
||
規約全文:docs/governance/dispatch-and-reply-format.md §3
|
||
真的有例外 → 指令裡加 reply-identity-ok(會留在指令歷史上)。
|
||
MSG
|
||
exit 2
|