#!/usr/bin/env bash # 管什麼: 要把一段有份量的東西寫上票,而這個 session **沒派人查過那張票**、 # 那段話裡也**沒有任何走得過去的出處** → 擋一次。 # 為什麼: 未經調查的診斷寫在票上就長得像事實,工人會照著它去驗證而不是去查—— # 等於用權威把那個 repo 真正的專家的判斷力關掉。 # 誤觸時怎麼關: 把出處補上(檔案:行號/commit/comment 號/票號/網址/貼實測輸出), # 或指令裡加 `evidence-ok`(留痕)。同一 session 同一張票只擋一次。 # # diagnosis-evidence-guard.sh — 未經調查不寫診斷(PreToolUse: Bash;inkstone/ISEP#87) # # ━━ 來由(leo,inkstone/ISEP#87)━━━━━━━━━━━━━━━━━━━━━━━━━━━ # 「身為派人去查的人,我要自己在拿到調查結果前寫不出診斷, # 我才不會用猜的結論把工人的判斷力關掉。」 # 「**票裡寫未經調查的診斷**」是 2026-08-27 當天列出的錯之一。 # # ⇒ 順序倒過來:**先派人查 → 拿到查的結果 → 才可以寫診斷。** # 我的診斷只能建立在別人查回來的東西上面,不能建立在我的印象上面。 # # ━━ 這支跟既有那兩支的分界(三支不重疊)━━━━━━━━━━━━━━━━━━━━━ # `subagent-first-guard.sh` 鐵律 2 的**前半**:要改 code 卻一次工都沒派過 # `micromanage-guard.sh` 替對方決定**做法**(給指令照跑、指名檔案函式…) # **本支** 替對方決定**答案**(把還沒查證的結論寫進票) # 前兩支都不會在「寫東西上票」這個動作上開口,所以不會兩支同時開罵。 # # ━━ 🔴 判準:三個結構訊號,一個關鍵字都沒有 ━━━━━━━━━━━━━━━━━━━ # 票上寫死:「『根因』『因為』『應該是』這類詞**一律不准當判準**」 # (leo 2026-08-17 已證偽文字層封路:當日 8 次誤攔、0 次正確攔截, # 而且**紅線寫得越細,命中關鍵字的機率越高 ⇒ 那些閘在懲罰謹慎**)。 # # 三個訊號,全部是結構: # ① **這個 session 有沒有派人查過那張票** # —— `investigate-first-stamp.sh` 的戳記(在不在,不是寫什麼) # ② **這段話裡有沒有走得過去的出處** # —— 檔案:行號/commit/comment 號/票號/網址/檔案路徑/貼出來的實測輸出。 # 方向是**放行**(認出來就放行),與黑名單相反: # 比對錯了只會**少擋一次**,不會多罰一次(同 pr-verdict-guard ③)。 # ③ **這段話有沒有份量**(扣掉身份欄與空白後的字數,門檻 60 字, # `ISEP_DIAGNOSIS_FLOOR` 可調) # —— 一句「收到,我來看」(6 字)不可能是診斷。長度是結構,不是措辭。 # 門檻怎麼定的:拿一段真的診斷(「根因是 KV 額度爆掉⋯⋯」)實量 **73 字**, # 拿最短的合理回覆實量 **6 字**——60 落在兩者之間,靠短的那邊。 # **不是猜的,是量過的**(第一版寫 120,那段真的診斷就漏抓了)。 # # 三個訊號的組合:①有 ⇒ 放行(查過了)。①無 + ②有 ⇒ 放行(轉述有出處, # 票上驗收條件 4 指明不能誤擋)。①無 + ②無 + ③夠長 ⇒ 擋一次。 # # ━━ 誠實邊界(做不到的事就寫出來)━━━━━━━━━━━━━━━━━━━━━━━━ # · PreToolUse 只看得到指令文字與 `-F` 指到的檔(同 comment-carries-task-guard)。 # · 本閘**認不出「這段話是不是診斷」**——沒有任何機器認得出來, # 而想認出來的那條路(句型比對)已經被證偽。它認的是 # 「**一段有份量、卻沒有任何出處、也沒人去查過**」的東西。 # 漏抓的情況:診斷寫得很短、或診斷裡剛好夾著一個無關的網址。 # 這是刻意的取捨——**誤攔比漏擋嚴重**,一支天天誤擋的閘會被學會忽略。 # # 逃生口:指令裡加 `evidence-ok`(會留在指令歷史上)。同 session 同票只擋一次。 # # 測試:hooks/tests/diagnosis-evidence-guard.test.sh(離線、不打網路、不留測試票) set -uo pipefail INPUT=$(cat) HERE="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)" VERDICT=$(printf '%s' "$INPUT" | DEG_LIB="$HERE/lib" python3 -c ' import json, os, re, sys sys.path.insert(0, os.environ.get("DEG_LIB", "")) try: import evidence except Exception: print("SKIP:no-lib"); raise SystemExit try: d = json.load(sys.stdin) except Exception: print("SKIP:bad-payload"); raise SystemExit cmd = (d.get("tool_input") or {}).get("command") or "" if not cmd: print("SKIP:no-cmd"); raise SystemExit if "evidence-ok" in cmd: # 逃生口(留痕) print("SKIP:escape"); raise SystemExit sid = re.sub(r"[^A-Za-z0-9_.-]", "_", str(d.get("session_id") or "nosid"))[:64] # ── ① 這是不是「把東西寫上票」的動作 ──────────────────────────────── # 兩條路都要認(`ticket-api-bypass-guard.sh` 檔頭記過那一課: # 「認動作的方式漏了一條路」是這一族的通病)。 target = None # (owner, repo, num) 或 ("", repo_or_?, "new") m = re.search(r"scripts/ticket\s+(\w+)", cmd) or re.search(r"\bticket\s+(say|new|decide|subtask)\b", cmd) verb = m.group(1) if m else "" if verb in ("say", "decide", "subtask"): r = re.search(r"([A-Za-z0-9_.-]+)/([A-Za-z0-9_.-]+)#(\d+)", cmd) target = (r.group(1), r.group(2), r.group(3)) if r else ("", "", "?") elif verb == "new": r = re.search(r"ticket\s+new\s+([\w.-]+)", cmd) target = ("inkstone", r.group(1) if r else "?", "new") else: is_post = re.search(r"\bPOST\b", cmd) or re.search(r"requests\.post|urlopen|\.post\(", cmd) c = re.search(r"/repos/([\w.-]+)/([\w.-]+)/issues/(\d+)/comments", cmd) n = re.search(r"/repos/([\w.-]+)/([\w.-]+)/issues\b", cmd) if is_post and c: target = (c.group(1), c.group(2), c.group(3)) elif is_post and n: target = (n.group(1), n.group(2), "new") if not target: print("SKIP:not-a-ticket-write"); raise SystemExit owner, repo, num = target # ── ② 這個 session 派過人去查那張票了嗎(戳記在不在)────────────────── stamp_dir = os.environ.get("ISEP_STAMP_DIR", "/tmp") if num not in ("new", "?"): if os.path.exists(os.path.join(stamp_dir, ".investigated-%s-%s_%s_%s" % (sid, owner, repo, num))): print("SKIP:investigated"); raise SystemExit body, src = evidence.body_of(cmd) # ── ③ 有出處嗎(放行方向的比對)────────────────────────────────── hits = evidence.sources(body) if hits: print("SKIP:has-source:%s" % hits[0][0]); raise SystemExit # ── ④ 有份量嗎(長度是結構,不是措辭)──────────────────────────── n_chars = evidence.substance(body) try: floor = int(os.environ.get("ISEP_DIAGNOSIS_FLOOR", "60")) except Exception: floor = 60 if n_chars < floor: print("SKIP:too-short:%d" % n_chars); raise SystemExit print("FIRE::" + json.dumps({"owner": owner, "repo": repo, "num": num, "chars": n_chars, "src": src, "sid": sid}, ensure_ascii=False)) ' 2>/dev/null) || VERDICT="SKIP:hook-error" [ -n "${VERDICT:-}" ] || VERDICT="SKIP:empty" case "$VERDICT" in FIRE::*) ;; *) exit 0 ;; esac REF=$(printf '%s' "${VERDICT#FIRE::}" | python3 -c ' import json, sys d = json.load(sys.stdin) print("%s/%s#%s" % (d["owner"], d["repo"], d["num"]) if d["num"] not in ("new", "?") else ("新的一張票(%s)" % d["repo"])) ' 2>/dev/null) SID=$(printf '%s' "${VERDICT#FIRE::}" | python3 -c 'import json,sys; print(json.load(sys.stdin)["sid"])' 2>/dev/null) KEY=$(printf '%s' "$REF" | tr -c 'A-Za-z0-9' '_') WARNED="${ISEP_STAMP_DIR:-/tmp}/.diagnosis-guard-warned-$SID$KEY" [ -f "$WARNED" ] && exit 0 # 同一 session 同一張票只擋一次,不鬼打牆 date +%s > "$WARNED" 2>/dev/null || true DEG_REF="$REF" DEG_SID="$SID" python3 - <<'PY' >&2 import os print("""🔎 這段要寫上 %s 的東西,**沒有人查過,也沒有任何出處**。 【inkstone/ISEP#87】「身為派人去查的人,我要自己在**拿到調查結果前寫不出診斷**, 我才不會用猜的結論把工人的判斷力關掉。」 · 診斷寫完了,**工人就不知道自己要做什麼**——它會照著你的結論去驗證,而不是去查 · **你不是那個 repo 的專家,它才是**。你的結論通常是猜的, 但因為寫在票上,看起來就像事實 ━━ 為什麼是現在擋你(三個結構訊號,一個關鍵字都沒有)━━━━━━━━━━━━ ① 這個 session **沒有派人去查這張票**(`investigate-first-stamp` 沒有戳記) ② 這段話裡**找不到任何走得過去的出處** ③ 而它**有份量**(不是一句「收到」) ━━ 兩條出路,選一條 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ A. **先派人去查**(順序倒過來,這是本票要的) 派工單就一行:【工單】%s 它查完貼回票上,你再寫診斷——那時①就成立了。 B. **這段本來就是轉述別人查到的,那就把出處帶上**(帶了就不會被擋) 檔案:行號  hooks/lib/evidence.py:42 commit    8e28041 comment 號  inkstone/ISEP#87 → comment 4327 網址/票號  https://git.uncle6.me/... 實測輸出   用 ``` 圍欄把指令與輸出原樣貼上 🔴 出處不是格式要求,是**讀的人可以自己走過去確認**。 「我記得是這樣」與「它就在那一行」的差別,只有這個分得出來。 真的有例外 → 指令裡加 `evidence-ok` 重送(會留在指令歷史上)。 同一張票本 session 只擋這一次。""" % (os.environ.get("DEG_REF", ""), os.environ.get("DEG_REF", ""))) PY exit 2