Compare commits

..

1 Commits

Author SHA1 Message Date
Leo 770a0ee824 移除自造的待驗單機制,改由 Gitea 原生三格承接(inkstone/ISEP#60)
subagent-claim-worksheet.sh(SubagentStop 產待驗單)與 claim-verify-police.sh
(Stop 攔收工)這一套整組移除。它們守的東西違反 D58(不要硬做平台不支援的機制)
——「待驗單」只是一張沒有狀態、沒有持有人的 markdown,必然退化成雜訊。

改由 inkstone/ISEP#59/PR #58 的三個 Gitea 原生欄位承接同樣的情境:
子票相依(存在嗎/做完了嗎)、s/* tag(卡在哪)、指派(誰該動)。

- hooks/hooks.json:移除 Stop/SubagentStop 兩條註冊
- hooks/lib/path-resolve.sh:拿掉已刪檔案的註解引用
- docs/hooks-inventory.md、README.md:更新閘數量(48→46 檔、59→57 條註冊)
- docs/governance/sdd-gitea-governance.md:E12/E14 標記現況,新增 §8.4 說明
  移交對象;PR #58 未 merge 前這兩條實質仍是待建,誠實標記
- .claude-plugin/plugin.json:0.5.0 → 0.6.0(版本沒動=plugin update 是 no-op)

未動 docs/governance/DIVERGENCE-v0.5.0-to-v0.6.0.md——那是 2026-08-20 的
時間點快照(意見書),修改它等於竄改歷史記錄,不在本票範圍。

未動 InkStoneCo/.claude/pending-verification/{done,done-20260826,verified}/
三個歸檔目錄——它們是 InkStoneCo repo 裡的已提交檔案,跨 repo 且需要
InkStoneCo 自己的 git 流程處理,超出本票(ISEP repo)範圍,留給總管判斷。

測試:scripts/test-*.sh 全數 6 支通過;hooks/tests/*.test.sh 與 pristine
origin/main 基準比對,失敗特徵完全一致(環境既有問題,非本次改動引入);
claude plugin validate . 通過。check-version-consistency.sh 目前會報不一致
(0.6.0 vs tag v0.5.0)——這是預期的,等總管收斂 release 打 v0.6.0 tag 時解決。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 12:38:31 +08:00
19 changed files with 44 additions and 458 deletions
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "isep",
"description": "InkStone Environment Plugin —— leo 的 Claude Code 環境唯一真相源:48 支機械閘(59 條註冊,白話盤點見 docs/hooks-inventory.md)、7 支 slash command、2 支 skill、27 支腳本,外加治理規範與標籤真相源。本機與雲端裝同一份,沒有子集。",
"version": "0.5.1",
"description": "InkStone Environment Plugin —— leo 的 Claude Code 環境唯一真相源:46 支機械閘(57 條註冊,白話盤點見 docs/hooks-inventory.md)、7 支 slash command、2 支 skill、27 支腳本,外加治理規範與標籤真相源。本機與雲端裝同一份,沒有子集。",
"version": "0.6.0",
"keywords": [
"inkstone",
"guardrails",
+1 -1
View File
@@ -23,7 +23,7 @@
| | 數量 | 是什麼 |
|---|---|---|
| `hooks/` | 48 `hooks.json` | 全部機械閘(PreToolUseStopSubagentStopSessionStartPostToolUse 共 59 條註冊) |
| `hooks/` | 46 `hooks.json` | 全部機械閘(PreToolUseStopSubagentStopSessionStartPostToolUse 共 57 條註冊`ISEP#60` 移除 `claim-verify-police.sh``subagent-claim-worksheet.sh` 這對自造機制,改由 Gitea 原生三格承接見 `ISEP#59` |
| `commands/` | 7 支 | `/wiki-recall` `/ship-check` `/cp-write` … |
| `skills/` | 2 支 | |
| `scripts/` | 23 支 | `ticket``github-arm.sh``gitea-bootstrap.sh` … |
+17 -2
View File
@@ -274,9 +274,9 @@ s/triage ──驗傷通過──▶ s/backlog ──排進 milestone──▶ s
| E9 | 留言變成推理長文 | hook:超長**不 reject**,要求移去 wiki 再留指標 | ❌ 待建(放寬版,見 §8.3) |
| E10 | 就地修改治理檔案 | 安裝目錄唯讀 + hook,導向 ISEP 開 issue | ❌ 待建 |
| E11 | 本機雲端版本漂移 | SessionStart hook 比對版本,不一致 fail-fast | ◐ 現有 `skill-deploy-drift-guard.sh` 管的是 skill 不是 plugin 版本 |
| E12 | 宣稱交付但沒有 release | 版本三處一致閘:`plugin.json` tag release 存在;且 milestone open issues 0 不准打 tag | ◐ 現有 `delivery-police.sh``claim-verify-police.sh` 部分覆蓋 |
| E12 | 宣稱交付但沒有 release | 版本三處一致閘:`plugin.json` tag release 存在;且 milestone open issues 0 不准打 tag | ◐ 現有 `delivery-police.sh` 部分覆蓋(`claim-verify-police.sh` 已於 `ISEP#60` 移除,見下方腳註) |
| E13 | 審核被遺忘 | Stop hook**我自己開的** `s/review` 存在且**這回合完全沒碰它** → 擋一次 | ◐ 現有 `empty-handed-stop-guard.sh` 判準不同,兩者要合併不要疊加 |
| E14 | Subagent 空口宣稱完成 | SubagentStop hook | ✅ **已有** `subagent-claim-worksheet.sh` `empty-handed-stop-guard.sh` |
| E14 | Subagent 空口宣稱完成 | SubagentStop hook | ◐ 原本 `subagent-claim-worksheet.sh` `empty-handed-stop-guard.sh`;前者已於 `ISEP#60`2026-08-27)移除,見下方腳註 |
| E15 | 代理硬做只有人能做的事 | 代理端無對應 tool 或 token;唯一出口是開 `human/exec` 票 | ◐ 部分(credential 機制已擋一部分,D36 |
| E16 | 標籤漂移 | 走 Gitea labels API 依 `labels.yaml` 校正:缺的補、改的還原、多的**只告警不刪** | ❌ 待建(`ISEP#4` |
@@ -312,6 +312,21 @@ leo 2026-08-17 實測:文字層的閘那天 **8 次誤攔、0 次正確攔截*
**紅線寫得越細,命中關鍵字的機率越高 ⇒ 那些閘在懲罰謹慎。**
所以 E9 擋的是「長內容放錯地方」這個**動作**,處置是**導引**(移去 wiki 留指標),不是 reject。
### 8.4 E12/E14:自造的「待驗單」機制已移除,改由 §16 原生三格承接(`ISEP#60`
`subagent-claim-worksheet.sh`SubagentStop 產待驗單)與 `claim-verify-police.sh`
(Stop 攔收工,待驗單還在就擋)這一對於 2026-08-27 整組移除。
- **為什麼不是修它**:症狀是同一份待驗單重生三次、同內容兩個檔名;
但真正的問題是 D58——**待驗單是一張 markdown,沒有狀態、沒有持有人、沒有人會回頭看**,
必然退化成雜訊。Gitea 的 issue 三格本來就是為這件事做的。
- **它守的東西現在由誰守**:見 §16(`ISEP#59`/PR #58)——子票相依回答「這件事存在嗎/做完了嗎」、
`s/*` tag 回答「它卡在哪一段」、指派回答「現在誰該動」。三格合起來覆蓋原本 E14
想擋的「subagent 空口宣稱」與 E12 想擋的「宣稱交付但沒人跟進」。
- **現況誠實標記**:PR #58(§16 的落地)**尚未 merge**,所以 E12E14 這一格目前是
「舊機制已拆、新機制已提案但未生效」的過渡態,不是「已有替代品在跑」。
#58 merge 前,這兩條暫時回到 ❌ 待建的實質狀態,即使表格欄位寫的是 ◐。
---
## 9. 本規範的迭代
+14 -7
View File
@@ -16,6 +16,11 @@
> 現在的寫法是實際數出來的:
> `ls hooks/*.sh | wc -l` 46`hooks.json` 展開後的 command 條目 56。
> **一份會偷偷過期的盤點表,跟沒有盤點表差不多**——見本頁最後「怎麼跟實況對帳」那段。
>
> 📌 **`inkstone/ISEP#60`2026-08-27)之後這兩個數字各再減 2**:移除了
> `claim-verify-police.sh``subagent-claim-worksheet.sh` 這對自造機制。
> 本票只處理這兩支的落差,**不在本票範圍內重新對帳其餘既有落差**——
> 要重新核實整份,跑下面「這份表怎麼跟實況對帳」段落的指令。
---
@@ -77,7 +82,7 @@
| `micromanage-guard.sh` | 派工單寫得太細(指名檔案函式、編號步驟、要求每做一項回報一次…)就擋下——subagent 該被當成有能力的同事,不是照抄劇本的工具。 | 🛑 擋 |
| `irreversible-dispatch-guard.sh` | 派工單裡出現「刪分支」「drop table」「rm -rf」這類不可逆動作,卻沒寫「先停下來等回覆才執行」就擋下。 | 🛑 擋 |
| `no-ticket-no-dispatch.sh` | 派工單裡沒有寫工單號(`【工單】owner/repo#N`),或那張票已經關閉/根本不存在,就擋下——沒有票號的工作沒有人追得到進度。 | 🛑 擋 |
| `dispatch-format-guard.sh` | **派工單 票號,就這樣。** 票號以外還寫了別的東西(背景、驗收條件、紅線、交件方式、「這個 session 才知道的事」)就擋下,並告訴你那些內容該搬去哪:每次都一樣的 → 共通規定;這次才知道的 → **寫進那張票**。反過來,合規的派工它會**自動把共通規定注入**給收工方(交件方式、不准 push main、org 是 `inkstone`…),所以你不寫也不會漏。判準是「這一行是不是【工單】欄位」——**在不在**,不是寫了什麼,所以講得再謹慎也不會被多罰。純禮貌收尾(「謝謝」)不算違規;`(→ comment M` 不論全形或半形括號都認得(ISEP#65 修過一次:CLAUDE.md 自己規定的合格寫法曾經被這支閘自己擋下)。 | 🛑 擋(+注入) |
| `dispatch-format-guard.sh` | **派工單 票號,就這樣。** 票號以外還寫了別的東西(背景、驗收條件、紅線、交件方式、「這個 session 才知道的事」)就擋下,並告訴你那些內容該搬去哪:每次都一樣的 → 共通規定;這次才知道的 → **寫進那張票**。反過來,合規的派工它會**自動把共通規定注入**給收工方(交件方式、不准 push main、org 是 `inkstone`…),所以你不寫也不會漏。判準是「這一行是不是【工單】欄位」——**在不在**,不是寫了什麼,所以講得再謹慎也不會被多罰。 | 🛑 擋(+注入) |
## D2. AI 想開口問你問題的當下(PreToolUse / AskUserQuestion
@@ -111,15 +116,17 @@
| `delivery-police.sh` | AI 宣稱「這件事做完了」,卻看不到任何實測證據(畫面截圖、指令輸出、HTTP 狀態碼…)就擋下。 | 🛑 擋 |
| `wiki-first-police.sh` | AI 做完事卻沒有把結論寫回 wiki 就想收工,就擋下——下次(或別的 AI)查 wiki 會查不到這次做過什麼。 | 🛑 擋 |
| `unpushed-police.sh` | AI 改好的東西還留在本機、沒有真的推送出去給別人用,卻想收工,就擋下——「改對了但沒送到」跟沒改是一樣的。 | 🛑 擋 |
| `claim-verify-police.sh` | Subagent 交回來的「我做完了」宣稱還沒被驗證過(對應的待驗檔案還在),你這邊卻想收工,就擋下。 | 🛑 擋 |
> 🔴 **`claim-verify-police.sh` 已於 `inkstone/ISEP#60` 移除**(連同下面原本的 G 組 `subagent-claim-worksheet.sh`)。
> 這一對是自造的「待驗單」機制(一張沒有狀態、沒有持有人的 markdown),違反 D58
> 不要硬做平台不支援的機制。它守的情境現在改由 Gitea 原生三格承接
> (子票相依/`s/*` tag/指派——見 `inkstone/ISEP#59` 與 PR #58)。
## G. Subagent 把工作交回來的時候(SubagentStop
| 閘名 | 對你意味著什麼 | 動作 |
|---|---|---|
| `subagent-claim-worksheet.sh` | Subagent 一交回工作,就自動把它宣稱做了什麼寫成一張「待驗清單」檔案——之後總管收工前,`claim-verify-police.sh` 會檢查這張清單有沒有被處理掉。 | 📝 記錄 |
> 這個時機還掛了 `worklist-guard.sh``self-drive-police.sh``self-drive-judge.sh``delivery-police.sh``wiki-first-police.sh``unpushed-police.sh`,行為跟上面 F 組完全一樣,只是對象換成「subagent 交回來的這一輪」。詳見「重複掛載」一節。
`inkstone/ISEP#60` 之後,這個時機**沒有專屬掛載的閘**了(原本唯一的一支 `subagent-claim-worksheet.sh` 已移除)。
仍會掛到這個時機的是 `worklist-guard.sh``self-drive-police.sh``self-drive-judge.sh``delivery-police.sh``wiki-first-police.sh``unpushed-police.sh`
行為跟上面 F 組完全一樣,只是對象換成「subagent 交回來的這一輪」。詳見「重複掛載」一節。
## H. 動作做完之後,純粹記一筆(PostToolUse,全部不擋)
-82
View File
@@ -1,82 +0,0 @@
#!/usr/bin/env bash
# claim-verify-police.sh — 待驗工作單還在,就不准收工(Stop;leo 2026-08-18 立)
#
# 這支是 subagent-claim-worksheet.sh 的另一半。
# 那支在 SubagentStop 時把 subagent 的宣稱寫成一張檔案;這支在我要停下時檢查:
#
# **那張檔還在不在?**
#
# ── 為什麼判準是「檔案存不存在」 ────────────────────────────────────
# leo 2026-08-17:「你在**文字層**封路⋯⋯自然語言的變體是無限的,blacklist 永遠追不完。
# 封路哲學之所以有效,是因為它封的是**動作**——動作有限且可枚舉。」
# ⇒ 「我驗過了」這句話可以有無限種寫法,但**檔案只能被動作刪掉**。
# 所以這支不讀我的措辭,只看磁碟。
#
# ── 怎麼過這道閘(都是動作,都留痕)─────────────────────────────────
# ① 真的驗了 → 把證據寫進回覆/票,然後刪掉那個檔
# ② 有幾條不需要驗 → 在檔裡寫下理由,再刪掉(理由留在 git 或票上)
# ③ 是人閘 → 走三件機械動作(Human + 指派 + 它自己的票),再刪掉
#
# 🔴 **不准為了過閘而直接 rm 掉卻什麼都沒做。** 那等於自己拆掉自己的閘——
# 而這正是 leo 2026-08-10 說的「規則早就存在,但沒有任何機制驗證有沒有照做」。
#
# ── 迴圈安全 ────────────────────────────────────────────────────
# stop_hook_active 為真 ⇒ 已經擋過一次,放行。至多提醒一次,不卡死。
#
# ── 留痕 ────────────────────────────────────────────────────────
INPUT=$(cat)
DIR="$CLAUDE_PROJECT_DIR/.claude/pending-verification"
LOG="$CLAUDE_PROJECT_DIR/.claude/hooks/claim-verify-police.log"
ACTIVE=$(printf '%s' "$INPUT" | python3 -c '
import json,sys
try: print("1" if json.load(sys.stdin).get("stop_hook_active") else "0")
except Exception: print("0")
' 2>/dev/null)
if [ "$ACTIVE" = "1" ]; then
printf '%s\tSKIP:already-nudged\n' "$(date +%FT%T)" >> "$LOG" 2>/dev/null
exit 0
fi
# 只算「今天以內」的單子——放太久的(跨 session 殘留)不該無限期卡住新工作,
# 但仍會被列出來提醒。
FILES=$(ls -1 "$DIR"/claims-*.md 2>/dev/null)
if [ -z "$FILES" ]; then
printf '%s\tSKIP:no-pending\n' "$(date +%FT%T)" >> "$LOG" 2>/dev/null
exit 0
fi
N=$(printf '%s\n' "$FILES" | grep -c . )
printf '%s\tBLOCK:%s-pending\n' "$(date +%FT%T)" "$N" >> "$LOG" 2>/dev/null
{
echo "🔍 查證警察:有 $N 張**待驗工作單**還沒處理掉,不准收工。"
echo
echo "【leo 2026-08-18 的規格】"
echo " 「subagent 做完事⋯⋯**它說可以,你去驗證;它說不行,你想辦法,不行再報告**,"
echo " 這解決一半的未查證。你常常直接把 subagent 說的不經查證就回報,"
echo " **它視野小,它說的你有更多資訊去檢驗。**」"
echo
echo "━━ 還沒處理的單子 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
printf '%s\n' "$FILES" | while read -r f; do
[ -n "$f" ] || continue
OKN=$(grep -c '^- \[ \]' "$f" 2>/dev/null)
echo " · ${f#"$CLAUDE_PROJECT_DIR"/} $OKN 條待處理)"
done
echo
echo "━━ 怎麼過這道閘(三選一,都是動作)━━━━━━━━━━━━━━━━━━"
echo " ① **它說可以** → 你自己跑一次(測試/curl/瀏覽器/git),把證據貼出來,再刪掉那個檔"
echo " ② **它說不行** → 先換路徑、查憑證地圖;要說「被擋」就得**自己撞一次並貼拒絕原文**"
echo " ③ **真的是人閘** → 三件機械動作:\`Human\` 標籤 + 指派給 Leo **一張它自己的票**"
echo " leo 08-18:「要進入人閘,標示 human 叫我去開,**不是默默塞進錯的地方**」)"
echo
echo "🔴 **不准只是 rm 掉了事。** 那是自己拆掉自己的閘——"
echo " 正是 leo 08-10 講的「規則早就存在,但沒有任何機制驗證有沒有照做」。"
echo
echo "📌 為什麼有這道閘:2026-08-18 總管連續三次把**沒驗過的判斷**當成情報交出去,"
echo " 其中第二次還是為了「更正」第一次而發明的新猜測。"
echo " leo:「你給我不正確的資訊⋯⋯subagent 說不通你沒有驗。」"
} >&2
exit 2
-16
View File
@@ -267,25 +267,9 @@
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/unpushed-police.sh"
}
]
},
{
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/claim-verify-police.sh"
}
]
}
],
"SubagentStop": [
{
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/subagent-claim-worksheet.sh"
}
]
},
{
"hooks": [
{
+6 -37
View File
@@ -37,16 +37,12 @@
當日實證:文字層的閘 8 次誤攔、0 次正確攔截,**紅線寫得越細,命中關鍵字的機率越高
⇒ 那些閘在懲罰謹慎**。
本檔**沒有「命中某個詞就違規」的比對**。判準是「這一行是不是【工單】欄位」——
**在不在**,不是**寫什麼**。用到的正則有三種,全部在認形狀:
本檔**全檔零個「命中某個詞就違規」的比對**。判準是「這一行是不是【工單】欄位」——
**在不在**,不是**寫什麼**。用到的正則只有兩種,都在認形狀:
- `【某某】` 欄位標記本身
- `owner/repo#N`(可選外掛全形/半形括號的 `→ comment M`票號形狀
- 唯一例外(ISEP#65):`_COURTESY_CLOSERS` 是一份**白名單**,只用來認出
「這一行是不是零資訊的禮貌收尾」,作用方向與黑名單相反——認出的是
「不算違規」,不是「算違規」;且要求整行清乾淨標點後完全相等,不是
「包含」,所以不會反過來放走任何真正的內容。
措辭多謹慎、有沒有提到「紅線」「部署」「main」,本檔一律不看,仍然不會被
「寫得越小心越容易被罰」的病拖下水。
- `owner/repo#N` 票號形狀
措辭多謹慎、有沒有提到「紅線」「部署」「main」,本檔一律不看。
⇒ 也因此它不會像文字層的閘那樣「寫得越小心越容易被罰」。
━━ 派工單格式 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
【工單】inkstone/ISEP#30 → comment 4322
@@ -87,32 +83,9 @@ IDENTITY_ROLES = ("總管", "subagent", "leo")
_MARKER_RE = re.compile(r"^【([^】]{1,12})】\s*(.*)$")
_REF_RE = re.compile(r"^([A-Za-z0-9_.-]+)/([A-Za-z0-9_.-]+)#(\d+)\s*$")
# 🔴 inkstone/ISEP#65:頂層 CLAUDE.md 規定的合格格式帶著全形括號——
# 「【工單】owner/repo#N(→ comment M)」。舊版正則只吃「→ comment M」本體,
# 兩側的括號(全形/半形都要吃)沒被算進去,殘留的括號讓 _REF_RE 比不過,
# 於是**規則自己定義的合格範例會被自己的閘擋下**。這裡把左右括號都收進來,
# 兩種都設成可選(沒有括號的舊寫法、純 #issuecomment- 連結都還要能過)。
_COMMENT_RE = re.compile(r"[(]?\s*(?:#issuecomment-|→\s*comment\s*)(\d+)\s*[)]?")
_COMMENT_RE = re.compile(r"(?:#issuecomment-|→\s*comment\s*)(\d+)")
_FENCE_RE = re.compile(r"^\s*(```|~~~)")
# 🔴 inkstone/ISEP#65:票號後面只掛一句純禮貌收尾(例:「謝謝」),不該被當成
# 「派工單不只有票號」的違規——它不含任何要搬去票上或共通規定的實質內容。
# 這是**白名單,不是黑名單**:判準仍是結構(整行清乾淨標點後「等於」清單裡的
# 固定字串,不是「包含」),漏收某個禮貌講法頂多誤鎖一次(代價小,見 ISEP#65
# 的「優先做成低誤鎖」),但不會反過來放走任何真正的內容——真內容不會剛好
# 打出一個字不差的空話。全檔仍然沒有「命中某個詞就違規」的比對,這裡是唯一
# 一處字面比對,且作用方向相反:認出「不是內容」,不是認出「是違規」。
_COURTESY_CLOSERS = frozenset({
"謝謝", "謝謝你", "謝謝您", "謝謝囉", "多謝", "感謝", "感恩", "辛苦了", "收到",
"thanks", "thank you", "thx",
})
_COURTESY_STRIP_RE = re.compile(r"[\s。!!.,~,、]+$")
def _is_courtesy_closer(line):
s = _COURTESY_STRIP_RE.sub("", line.strip())
return s.lower() in _COURTESY_CLOSERS
def _split_lines(text):
"""切成 (是否在圍欄內, 原始行)。
@@ -173,10 +146,6 @@ def parse_dispatch(prompt):
continue
if raw.strip():
# 【工單】欄位本身只准佔它自己那一行;它下面的續行也是多的。
# 白名單豁免只在圍欄外生效——圍欄裡的東西本來就該一律算數(test ⑮),
# 這裡不額外放寬,避免有人把真內容包在圍欄裡再貼一句「謝謝」蒙混。
if not in_fence and _is_courtesy_closer(raw):
continue
extra.append((cur, raw))
return {
+4 -2
View File
@@ -16,8 +16,10 @@
# history-first-guard.sh、main-and-prod-push-guard.sh、no-ticket-no-dispatch.sh、
# not-my-branch-guard.sh、release-tag-guard.sh、skill-deploy-drift-guard.sh、
# stage-before-prod-guard.sh、unpushed-police.sh、wiki-first-police.sh。
# 另有 claim-verify-police.sh、subagent-claim-worksheet.sh、empty-handed-stop-guard.sh、
# issue-status-autoflip.sh 直接寫 `$CLAUDE_PROJECT_DIR`(無 `:-` fallback)——
# 另有 empty-handed-stop-guard.sh、issue-status-autoflip.sh 直接寫 `$CLAUDE_PROJECT_DIR`
# (無 `:-` fallback)——
# claim-verify-police.shsubagent-claim-worksheet.sh 已於 ISEP#60 整組移除,
# 它們守的東西改由 Gitea 原生三格承接,見 ISEP#59PR #58
# 這批在該變數未設時行為又不一樣,同一個病的另一種長相。
# 這些全部沒有本檔「先確認到底在不在 repo 裡」的判斷;本檔先在 sdd-guard.sh 落地,
# 其餘要不要跟進、要不要改用這支共用函式,另案處理,不在本票(#22)範圍內一次改完。
-196
View File
@@ -1,196 +0,0 @@
#!/usr/bin/env bash
# subagent-claim-worksheet.sh — subagent 交回時,把它的宣稱抽成一張待驗工作單
# SubagentStopleo 2026-08-18 立)
#
# ── leo 的原話(本閘的規格)─────────────────────────────────────────
# 「你如何確認說的都是真的?subagent 做完事,你應該在 SubagentStop 掛動作,
# **它說可以,你去驗證;它說不行,你想辦法,不行再報告**,
# 這解決一半的未查證。你常常直接把 subagent 說的不經查證就回報,
# **它視野小,它說的你有更多資訊去檢驗**。」
#
# ── 為什麼是「工作單檔案」而不是提醒文字 ─────────────────────────────
# leo 2026-08-17 診斷過:文字層封路是打不贏的軍火競賽,要封**動作**。
# 所以這支不勸、也不偵測我的措辭——它**在磁碟上放一個必須被處理掉的東西**。
# 配套的 Stop 閘(claim-verify-police.sh)看的是「那個檔還在不在」,
# 而檔案只能被動作消滅,不能被文案消滅。
#
# ── 兩類宣稱,處理方式不同(leo 的規格就是這兩條)──────────────────
# ✅ 它說可以 → 我去驗證(我有跨 repo 視野、有 token、有瀏覽器,它沒有)
# ⛔ 它說不行 → 我想辦法(換路徑/查憑證地圖/自己撞一次),真的不行才報告 leo
#
# ⚠️ 這裡的關鍵詞比對只用來**列出候選**,不用來判定對錯——
# 列多了我自己劃掉(要寫理由),列少了不影響我另外查。
# ⇒ 它的失敗模式是「多列幾條」,不是「擋錯事」。
#
# ── 留痕(InkStoneCo#48:36 支閘只有 2 支記錄自己做了什麼)─────────
INPUT=$(cat)
DIR="$CLAUDE_PROJECT_DIR/.claude/pending-verification"
LOG="$CLAUDE_PROJECT_DIR/.claude/hooks/subagent-claim-worksheet.log"
mkdir -p "$DIR" 2>/dev/null
OUT=$(printf '%s' "$INPUT" | DIR="$DIR" python3 -c '
import json, os, re, sys, hashlib
try:
d = json.load(sys.stdin)
except Exception:
print("SKIP:bad-json"); raise SystemExit
tp = d.get("transcript_path") or ""
if not tp or not os.path.exists(tp):
print("SKIP:no-transcript"); raise SystemExit
# ── 取 subagent 的交件 ────────────────────────────────────────────
# 🔴 2026-08-18 首次實跑就抓到自己的缺陷:`SubagentStop` 給的 `transcript_path`
# 指向**主對話**,不是子代理的紀錄 ⇒ 原本「取最後一則 assistant 訊息」
# 抓到的是**總管自己寫的句子**,這支 hook 當場退化成它要避免的「文字層自咬」。
#
# 正解:子代理的交件是以 `<task-notification>…<result>…</result>` 的形式
# 進到主對話的 **user 訊息**裡。只認那個區塊,其餘一律不看。
# ⇒ 判準回到「這段話是誰說的」,而不是「這段話長什麼樣」。
last = ""
try:
blob = []
with open(tp, "r", encoding="utf-8", errors="replace") as f:
for line in f:
line = line.strip()
if not line:
continue
try:
ev = json.loads(line)
except Exception:
continue
msg = ev.get("message") or {}
if msg.get("role") != "user":
continue
parts = msg.get("content")
if isinstance(parts, str):
txt = parts
elif isinstance(parts, list):
txt = "".join(p.get("text", "") for p in parts
if isinstance(p, dict) and p.get("type") == "text")
else:
txt = ""
if "<task-notification>" in txt:
blob.append(txt)
if blob:
m = re.search(r"<result>(.*?)</result>", blob[-1], re.S)
last = m.group(1) if m else ""
except Exception:
print("SKIP:read-fail"); raise SystemExit
if not last.strip():
print("SKIP:no-subagent-report"); raise SystemExit
if not last.strip():
print("SKIP:empty-report"); raise SystemExit
# ── 抽候選宣稱 ──────────────────────────────────────────────────
# 成功類:它說某件事成立/做好了 ⇒ 我去驗
OK = ("驗過", "測過", "通過", "全綠", "已部署", "部署完成", "可以用", "成立",
"完成", "修好", "已推", "已併", "沒問題", "正常", "一致", "對上",
"✅", "green", "passed", "verified", "deployed")
# 阻擋類:它說做不到 ⇒ 我想辦法,不行才報告 leo
NG = ("無法", "不行", "做不到", "被擋", "擋下", "失敗", "不存在", "找不到",
"沒有權限", "權限不足", "需要 leo", "要 leo", "請總管", "交給你",
"❌", "blocked", "denied", "cannot", "failed", "not found")
def lines_of(text):
out = []
for raw in text.split("\n"):
s = raw.strip().lstrip("-*#>| ").strip()
if len(s) < 8 or len(s) > 220:
continue
out.append(s)
return out
ls = lines_of(last)
ok_hits, ng_hits = [], []
for s in ls:
low = s.lower()
if any(k in s or k.lower() in low for k in NG):
ng_hits.append(s)
elif any(k in s or k.lower() in low for k in OK):
ok_hits.append(s)
# 兩類都沒有 ⇒ 它交的是純資料/純提問,沒有事實宣稱要驗
if not ok_hits and not ng_hits:
print("SKIP:no-claims"); raise SystemExit
sid = (d.get("session_id") or "nosid")[:8]
aid = (d.get("agent_id") or d.get("subagent_id") or "")[:10]
# 🔴 2026-08-21stamp 原本雜湊「交件路徑」⇒ 同樣的宣稱每回合生一個新檔名,
# 而且不知道總管已經驗過了。實際發作:同兩條 sdd-guard 宣稱連生四張單
# 1c97d461fcb285dc256de849394b97ae),驗掉一張下一回合又冒一張。
# ⇒ 改成雜湊**宣稱內容本身**:同樣的宣稱=同一個檔名 ⇒ 驗過就不再冒出來。
_claims = sorted(set(str(x) for x in (ok_hits + ng_hits)))
# 🔴 空清單就退回舊行為 —— 但那等於這支閘沒抓到任何宣稱,本來就會 SKIP,
# 所以這個 fallback 實際上不會被用到;留著只是不讓 stamp 變成空字串的雜湊。
_basis = "\n".join(_claims) if _claims else (tp + sid + aid)
stamp = hashlib.sha1(_basis.encode()).hexdigest()[:8]
_name = "claims-%s-%s.md" % (sid, stamp)
path = os.path.join(os.environ["DIR"], _name)
# 已經驗過並移進 verified/ 的,不要再生一次。
if os.path.exists(os.path.join(os.environ["DIR"], "verified", _name)):
print("SKIP:already-verified"); raise SystemExit
def block(title, items, howto, cap):
if not items:
return ""
body = "\n".join("- [ ] %s" % x for x in items[:cap])
more = ""
if len(items) > cap:
more = "\n- [ ] (還有 %d 條同類,讀原始交件)" % (len(items) - cap)
return "## %s\n\n%s\n%s%s\n\n" % (title, howto, body, more)
doc = []
doc.append("# 待驗工作單(subagent 交回的宣稱)\n")
doc.append("> leo 2026-08-18:「**它視野小,它說的你有更多資訊去檢驗。**」\n")
doc.append("> 交件全文在:`%s`\n" % tp)
doc.append("\n🔴 **這張單子只能用動作消滅,不能用文字消滅。**")
doc.append("逐條處理完之後,把本檔**刪掉或移走**;在那之前 Stop 閘會擋。\n\n")
doc.append(block(
"✅ 它說「可以」——我要自己驗一次",
ok_hits,
"判準:**我不採信,我自己跑一次**。它只看得到自己那個 repo 與自己那次呼叫;\n"
"我有跨 repo 視野、有 token、有瀏覽器。驗完把證據貼進回覆或票裡。\n",
12))
doc.append(block(
"⛔ 它說「不行」——我先想辦法,真的不行才報告 leo",
ng_hits,
"判準:**它的「不存在/被擋」往往只是「我這裡看不到」**。\n"
"先做三件:① 查憑證地圖/wiki ② 換一條路徑 ③ **自己真的撞一次並貼出拒絕原文**。\n"
"🔴 沒撞過就不准說「我被 X 擋住」——那是虛構的閘(2026-08-18 已第五次)。\n"
"真的是人閘 ⇒ 走三件機械動作:`Human` 標籤 + 指派給 Leo **一張它自己的票**\n"
"不是在別張票的留言裡寫一段話(leo 08-18:「不是默默塞進錯的地方」)。\n",
12))
with open(path, "w", encoding="utf-8") as f:
f.write("".join(doc))
print("WROTE:%s:ok=%d:ng=%d" % (os.path.basename(path), len(ok_hits), len(ng_hits)))
' 2>/dev/null)
printf '%s\t%s\n' "$(date +%FT%T)" "${OUT:-SKIP:no-output}" >> "$LOG" 2>/dev/null
case "$OUT" in
WROTE:*)
F=$(printf '%s' "$OUT" | cut -d: -f2)
N=$(printf '%s' "$OUT" | sed 's/.*ok=\([0-9]*\):ng=\([0-9]*\)/\1 成功宣稱、\2 阻擋宣稱/')
cat <<EOF
🔍 交件查證:subagent 交回了 $N,已列成待驗工作單。
【leo 2026-08-18 的規格】
「它說可以,你去驗證;它說不行,你想辦法,不行再報告。
**它視野小,它說的你有更多資訊去檢驗。**」
工作單:.claude/pending-verification/$F
🔴 **不要直接把它說的轉給 leo。** 逐條處理完,把那個檔刪掉;在那之前 Stop 閘會擋一次。
· 它說「可以」→ 你自己跑一次(測試/curl/瀏覽器/git,看那條宣稱是什麼)
· 它說「不行」→ 先換路徑、查憑證地圖;**要說「被擋」就得自己撞一次並貼原文**
EOF
;;
esac
exit 0
-53
View File
@@ -61,32 +61,6 @@ t 0 "② 合規:兩張票就兩行【工單】" \
t 0 "③ 合規:用 #issuecomment- 定址也認得" \
"$(payload '【工單】inkstone/ISEP#30#issuecomment-4327')"
# ── inkstone/ISEP#65CLAUDE.md 規定的合格格式(全形/半形括號包住 → comment M)
# 曾經被本閘自己擋下——這裡把它釘成迴歸測試,不再走回頭路。
t 0 "③b 合規:全形括號包住 → comment M(CLAUDE.md 標準寫法,曾被自己的閘擋下)" \
"$(payload '【工單】inkstone/ISEP#65(→ comment 4515')"
t 0 "③c 合規:半形括號變體" \
"$(payload '【工單】inkstone/ISEP#65(→ comment 4515)')"
# ── inkstone/ISEP#65 test 4:不該擋的三種雜訊 ────────────────────────
t 0 "③d 不該擋:票號後面只有空行" \
"$(payload '【工單】inkstone/ISEP#65
')"
t 0 "③e 不該擋:票號後面只有一句「謝謝」(零資訊禮貌收尾,白名單豁免)" \
"$(payload '【工單】inkstone/ISEP#65
謝謝')"
t 0 "③f 不該擋:禮貌收尾帶標點也認得(「謝謝!」)" \
"$(payload '【工單】inkstone/ISEP#65
謝謝!')"
t 2 "③g 白名單不是漏洞:禮貌詞混在真內容裡照樣算數" \
"$(payload '【工單】inkstone/ISEP#65
辛苦了,順便告訴你 main 現在是 8e28041')"
t 0 "④ 不是派工的動作(Bash)→ 一律不管" \
'{"tool_name":"Bash","tool_input":{"command":"ls"}}'
@@ -158,33 +132,6 @@ t 2 "⑮ 內容躲在圍欄裡也算數(圍欄只讓裡面的【】不被當
【就地】main 現在是 8e7e265
```')"
# ── inkstone/ISEP#65 test 5:拿「寫完規則之後總管又犯的 8 次」當回歸樣本 ──
# 內容是從各自票的真實內文摘錄(hooks/tests/fixtures/README.md 記著來歷),
# 不是想像出來的例子。8 種形狀都要被擋,證明這不是只補了一個特例。
t 2 "⑮b 回歸:ISEP#60 那次的內容(自造第四套機制的診斷)" \
"$(payload "$FIX/real-content-isep60.txt")"
t 2 "⑮c 回歸:ISEP#61 那次的內容(push-guard 戳記機制失敗史)" \
"$(payload "$FIX/real-content-isep61.txt")"
t 2 "⑮d 回歸:ISEP#64 那次的內容(PR 分診現況數字)" \
"$(payload "$FIX/real-content-isep64.txt")"
t 2 "⑮e 回歸:Arcrun#142 那次的內容(庫索引從沒被產生過)" \
"$(payload "$FIX/real-content-arcrun142.txt")"
t 2 "⑮f 回歸:Arcrun#144 那次的內容(樹狀圖驗收記錄)" \
"$(payload "$FIX/real-content-arcrun144.txt")"
t 2 "⑮g 回歸:Arcrun#165 那次的內容(MCP 徹查現場)" \
"$(payload "$FIX/real-content-arcrun165.txt")"
t 2 "⑮h 回歸:arcrun-rag#104 那次的內容(收檔實測數字)" \
"$(payload "$FIX/real-content-arcrunrag104.txt")"
t 2 "⑮i 回歸:InkStoneCo#102 那次的內容(milestone 打假背景)" \
"$(payload "$FIX/real-content-inkstoneco102.txt")"
echo
echo "── C 群:訊息本身 ──────────────────────────────────────────────"
clean
-18
View File
@@ -12,21 +12,3 @@ leo 2026-08-27 要的測資是「今天實際發生的 5 次違規派工」
其餘的覆蓋率改用「一條規則一個最小案例」來補(見 dispatch-format-guard.test.sh B 群)。
**不要用想像出來的例子替補那 4 份**,那會讓測試看起來比實際更綠。
---
## 第二批:`real-content-*.txt`inkstone/ISEP#65
leo 2026-08-27 又在同一天抓到總管**寫完規則之後又犯了 8 次**(八條線的派工單,
每一條都帶了「這個 session 才知道的事」):`ISEP#60``#61``#64`
`Arcrun#142``#144``#165``arcrun-rag#104``InkStoneCo#102`
🔴 **原始的違規派工 prompt 本身一樣拿不回來**(同一個道理:agent 一停就蒸發),
但這 8 次的**內容已經被總管補寫回各自的票**(ISEP#65 body 明講)。
這裡的 8 份 `real-content-*.txt` 是**從那些票的真實內文摘錄**(一字未改地複製,
只是截取一段,並在最前面加回 `【工單】owner/repo#N` 讓它符合派工單的殼)——
不是想像出來的例子,是真的存在於 Gitea 上、總管真的寫過的句子,只是原本的位置
錯了(在 prompt 裡,不在票上)。
用途:驗證 dispatch-format-guard.sh 修完 ISEP#65 之後,**這 8 種「票號 + 這個
session 才知道的事」的形狀全部會被擋下**——不是只驗一個最小案例。
-5
View File
@@ -1,5 +0,0 @@
【工單】inkstone/Arcrun#142
實查結論:這兩樣東西(庫索引與全庫摘要)從沒被產生過,不是壞掉,是根本沒做。
於是「開場就知道有哪些子庫、各裝了什麼」這件事永遠靠現查,查不到就當作沒有。
相關:inkstone/Arcrun#81、#87、#100(總圖說 0 條關聯,實際 1854 條)。
-5
View File
@@ -1,5 +0,0 @@
【工單】inkstone/Arcrun#144
leo 明講「不要現在執行,記下來」,本票只是把它接住,開工前要先確認優先序。
測試 OK,出現了樹狀圖,但不符合需求——達成的是 mindmap 向右向上分佈,
上下間距很大、展開字很小,用戶要擁有觸控板否則很難閱讀。
-5
View File
@@ -1,5 +0,0 @@
【工單】inkstone/Arcrun#165
不要先修,先查清楚再說——現在連斷在哪都還沒人講得出來。
已知的現場(總管 2026-08-26 用 MCP 實查,不是讀 code 推的):同一個知識庫
youlinyuga3bse),同一套 MCP 工具,四次查詢分數與筆數都對不上。
-5
View File
@@ -1,5 +0,0 @@
【工單】inkstone/arcrun-rag#104
現況(總管 2026-08-14 實測,非推測):leo 把 5 個資料夾接上重裝後的 leo21c,
App 顯示你的檔案 12022 共幾份、819 已送上去、11183 排隊中、20 送不上去。
逐資料夾實數(find 計 .md/.pdf/.txt,已排除 node_modules.git)。
-5
View File
@@ -1,5 +0,0 @@
【工單】inkstone/InkStoneCo#102
背景(同一天稍早,leo 對同一件事說過的重話):昨天要完成 51,你現在還有沒派的,
去看看 milestone 一大堆沒有一個完成,現在整理,不要詐騙,去看清楚哪些完成的。
Gitea 的百分比只數票的開關,它不知道票裡的東西通了沒。
-6
View File
@@ -1,6 +0,0 @@
【工單】inkstone/ISEP#60
`subagent-claim-worksheet.sh``SubagentStop` 產待驗單)與 `claim-verify-police.sh`
(`Stop` 擋收工)這一套自造的第四套機制整個移除,它守的東西改由 Gitea 原生的三格承接
(子票相依/tag/指派——見 `inkstone/ISEP#59` 與 PR `#58`)。
`#issuecomment-4289` 量到同一份待驗單重生三次、同內容兩個檔名。
-5
View File
@@ -1,5 +0,0 @@
【工單】inkstone/ISEP#61
`main-and-prod-push-guard.sh` 的戳記機制已經走進死路:原設計用
`CLAUDE_CODE_CHILD_SESSION=1` 判斷 subagent,實測結果總管主 session 也是 1,
那個變數不是身分標記,誰都擋。改成戳記(`/tmp/.main-push-ok`,總管手動寫)也失敗。
-6
View File
@@ -1,6 +0,0 @@
【工單】inkstone/ISEP#64
現況(總管實查,2026-08-27 12:40):open PR 總數 23,總管已關掉 8 個「內容早就在
main」的空 PR,剩 15 個要分診。已關的 8 個實測 git merge-base --is-ancestor 證明
HEAD 已是 main 的祖先,diff +0/-0Arcrun#166#161#158#155#153、
arcrun-rag#142#141#139。