#!/bin/sh # mistake-needs-ticket-guard.sh — PreToolUse(Write|Edit|MultiEdit): # **往 mistakes.md 寫一條新教訓,如果機制擋得住,就必須附票號。** # # 🔴 立這條的原話(leo 2026-08-16,一口氣講了三次,因為前兩次我又拿去寫成文件): # 「以後你自己寫入 mistake 的東西,**如果是機制可以防止的都要貼上票號**。」 # 「要不然你的 mistake 根本**不會協助你迭代**。」 # 「**沒迭代寫個 mistake 做什麼?下一次又犯同樣錯誤,你就說上次寫過。**」 # # 【當天的活教材,就是這條規則本身要治的病】 # `mistakes.md:220` 那條(08-14)結尾自己寫著: # 「📌 **待修**:`check` 應該要有唯讀模式,否則這個坑對每一個第一次用它的人都會再發生一次。」 # 它**沒有票號** ⇒ 沒有任何人/任何看板會撈到它 ⇒ 躺了兩天 ⇒ # 08-16 總管**原封不動照撞一次**,還在回覆裡說「這個我兩天前寫過」。 # ⇒ 那句「我寫過」正是 leo 預言的那句話。**寫下來不是迭代,開票才是。** # # 【判準:什麼叫「機制可以防止」】 # 不是所有教訓都要票。要票的是**下一次可以被機器擋下來**的那種: # · 用錯工具/用錯順序/漏做一步 → 可以做成 hook 或 preflight # · 兩份真相漂移/欄位漏填 → 可以做成不變式 # · 「我以為 X 其實 Y」的事實誤解 → 通常**不需要**票(那是知識,不是閘) # 本閘不猜,用一個便宜的近似:**新增的段落裡出現「待修/應該要/下次要/ # 要改成/可以做成」這類「有人得動手」的字樣,就要求同段落有票號。** # # 【放行的寫法】同一段裡出現任一種票號定址即可: # inkstone/arcrun-rag#88 / Leo/Arcrun#136 / 本 repo 的 #142 # 真的不需要票(純知識型教訓):該段加 `no-ticket-needed` 一詞(留痕,說明理由)。 set -eu PAYLOAD=$(cat 2>/dev/null || echo '{}') RESULT=$(printf '%s' "$PAYLOAD" | python3 -c ' import sys, json, re try: d = json.load(sys.stdin) except Exception: sys.exit(0) ti = d.get("tool_input") or {} path = ti.get("file_path") or "" if not path.endswith("mistakes.md"): sys.exit(0) # 收集這次「新寫進去」的文字(Write 全文;Edit 只看 new_string) chunks = [] if "content" in ti: chunks.append(ti["content"]) if "new_string" in ti: chunks.append(ti["new_string"]) for e in (ti.get("edits") or []): if isinstance(e, dict) and "new_string" in e: chunks.append(e["new_string"]) text = "\n".join(c for c in chunks if isinstance(c, str)) if not text.strip(): sys.exit(0) # 「有人得動手」的訊號——出現這些就代表它是可被機制防止的 ACTION = re.compile(r"待修|應該要|下次要|要改成|可以做成|該做成|還沒做|尚未修|TODO|待辦") # 票號定址:owner/repo#N 或裸 #N TICKET = re.compile(r"(?:[A-Za-z0-9._-]+/[A-Za-z0-9._-]+)?#\d+") # 逐「段」判斷(空行分段),只報第一個有問題的段落 for para in re.split(r"\n\s*\n", text): if not ACTION.search(para): continue if "no-ticket-needed" in para: continue if TICKET.search(para): continue hit = ACTION.search(para).group(0) snippet = " ".join(para.split())[:120] print(json.dumps({"hit": hit, "snippet": snippet}, ensure_ascii=False)) break ' 2>/dev/null || true) [ -n "$RESULT" ] || exit 0 HIT=$(printf '%s' "$RESULT" | python3 -c 'import sys,json;print(json.load(sys.stdin)["hit"])' 2>/dev/null || echo "待修") SNIP=$(printf '%s' "$RESULT" | python3 -c 'import sys,json;print(json.load(sys.stdin)["snippet"])' 2>/dev/null || echo "") cat >&2 < # 先查這件事該掛哪張既有票 scripts/ticket say -F <檔> # 有既有票就貼進去 scripts/ticket new -F <檔> # 真的沒有才開新票 然後把票號寫進這一段:\`inkstone/arcrun-rag#88\` ② **這條純粹是知識型教訓、沒有東西要動手**(例如「我以為 X 其實 Y」): 該段加一詞 \`no-ticket-needed\`,並在旁邊寫一句為什麼不需要閘 EOF exit 2