From 770a0ee824af386ee38a93fee80263f016d3388e Mon Sep 17 00:00:00 2001 From: richblack Date: Thu, 27 Aug 2026 12:38:31 +0800 Subject: [PATCH] =?UTF-8?q?=E7=A7=BB=E9=99=A4=E8=87=AA=E9=80=A0=E7=9A=84?= =?UTF-8?q?=E5=BE=85=E9=A9=97=E5=96=AE=E6=A9=9F=E5=88=B6=EF=BC=8C=E6=94=B9?= =?UTF-8?q?=E7=94=B1=20Gitea=20=E5=8E=9F=E7=94=9F=E4=B8=89=E6=A0=BC?= =?UTF-8?q?=E6=89=BF=E6=8E=A5=EF=BC=88inkstone/ISEP#60=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .claude-plugin/plugin.json | 4 +- README.md | 2 +- docs/governance/sdd-gitea-governance.md | 19 ++- docs/hooks-inventory.md | 19 ++- hooks/claim-verify-police.sh | 82 ---------- hooks/hooks.json | 16 -- hooks/lib/path-resolve.sh | 6 +- hooks/subagent-claim-worksheet.sh | 196 ------------------------ 8 files changed, 37 insertions(+), 307 deletions(-) delete mode 100755 hooks/claim-verify-police.sh delete mode 100755 hooks/subagent-claim-worksheet.sh diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index e4eea3a..a2e9673 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -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.0", + "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", diff --git a/README.md b/README.md index ee275f4..49e1fff 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ | | 數量 | 是什麼 | |---|---|---| -| `hooks/` | 48 支 + `hooks.json` | 全部機械閘(PreToolUse/Stop/SubagentStop/SessionStart/PostToolUse 共 59 條註冊) | +| `hooks/` | 46 支 + `hooks.json` | 全部機械閘(PreToolUse/Stop/SubagentStop/SessionStart/PostToolUse 共 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` … | diff --git a/docs/governance/sdd-gitea-governance.md b/docs/governance/sdd-gitea-governance.md index b72cb39..766bd44 100644 --- a/docs/governance/sdd-gitea-governance.md +++ b/docs/governance/sdd-gitea-governance.md @@ -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**,所以 E12/E14 這一格目前是 + 「舊機制已拆、新機制已提案但未生效」的過渡態,不是「已有替代品在跑」。 + #58 merge 前,這兩條暫時回到 ❌ 待建的實質狀態,即使表格欄位寫的是 ◐。 + --- ## 9. 本規範的迭代 diff --git a/docs/hooks-inventory.md b/docs/hooks-inventory.md index 23695ea..b352cd5 100644 --- a/docs/hooks-inventory.md +++ b/docs/hooks-inventory.md @@ -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` 這對自造機制。 +> 本票只處理這兩支的落差,**不在本票範圍內重新對帳其餘既有落差**—— +> 要重新核實整份,跑下面「這份表怎麼跟實況對帳」段落的指令。 --- @@ -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,全部不擋) diff --git a/hooks/claim-verify-police.sh b/hooks/claim-verify-police.sh deleted file mode 100755 index 1db3cbe..0000000 --- a/hooks/claim-verify-police.sh +++ /dev/null @@ -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 diff --git a/hooks/hooks.json b/hooks/hooks.json index feded4a..3ac930b 100644 --- a/hooks/hooks.json +++ b/hooks/hooks.json @@ -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": [ { diff --git a/hooks/lib/path-resolve.sh b/hooks/lib/path-resolve.sh index ee5f13a..a19663d 100644 --- a/hooks/lib/path-resolve.sh +++ b/hooks/lib/path-resolve.sh @@ -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)範圍內一次改完。 diff --git a/hooks/subagent-claim-worksheet.sh b/hooks/subagent-claim-worksheet.sh deleted file mode 100755 index 6d3cfe4..0000000 --- a/hooks/subagent-claim-worksheet.sh +++ /dev/null @@ -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 當場退化成它要避免的「文字層自咬」。 -# -# 正解:子代理的交件是以 `` 的形式 -# 進到主對話的 **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 "" in txt: - blob.append(txt) - if blob: - m = re.search(r"(.*?)", 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 <