移除自造的待驗單機制,改由 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>
This commit is contained in:
@@ -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
|
||||
@@ -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": [
|
||||
{
|
||||
|
||||
@@ -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.sh/subagent-claim-worksheet.sh 已於 ISEP#60 整組移除,
|
||||
# 它們守的東西改由 Gitea 原生三格承接,見 ISEP#59/PR #58)
|
||||
# 這批在該變數未設時行為又不一樣,同一個病的另一種長相。
|
||||
# 這些全部沒有本檔「先確認到底在不在 repo 裡」的判斷;本檔先在 sdd-guard.sh 落地,
|
||||
# 其餘要不要跟進、要不要改用這支共用函式,另案處理,不在本票(#22)範圍內一次改完。
|
||||
|
||||
@@ -1,196 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# subagent-claim-worksheet.sh — subagent 交回時,把它的宣稱抽成一張待驗工作單
|
||||
# (SubagentStop;leo 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-21:stamp 原本雜湊「交件路徑」⇒ 同樣的宣稱每回合生一個新檔名,
|
||||
# 而且不知道總管已經驗過了。實際發作:同兩條 sdd-guard 宣稱連生四張單
|
||||
# (1c97d461/fcb285dc/256de849/394b97ae),驗掉一張下一回合又冒一張。
|
||||
# ⇒ 改成雜湊**宣稱內容本身**:同樣的宣稱=同一個檔名 ⇒ 驗過就不再冒出來。
|
||||
_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
|
||||
Reference in New Issue
Block a user