17a74d7d3a
leo 2026-08-27:「前面說過每個回覆要戴上已經花了總時長,這為什麼沒出現?」
「這應該寫在 ISEP,隨時看自己拖了多久」
重點在後面那句:不是要總管記得戴,是要它長在機器上。
總管當時答「我沒做,現在開始戴」——而那正是這條規則第一次失效的方式。
一支閘掛兩個事件,是同一件事的兩半(票上點名的失效模式就在這裡):
UserPromptSubmit → 注入算好的那一行(模型不必自己算,也算不準)
Stop → 查核這一回合的回覆裡到底有沒有那一行,沒有就擋一次
只做前半=又一個會被忽略的提醒;只做後半=罰它做一件拿不到資料的事。
判準不是關鍵字黑名單,是「那個被要求的輸出元素在不在」——
whitelist-of-one:要求一個機器產生的標記在場,不是猜哪些字不該在場。
換講法照樣要帶標記,多寫什麼都不會觸發。
時長從這段對話的第一則訊息算起,理由寫在 hooks/lib/countdown.py 檔頭:
「任務」在機器上沒有起點,而 CLAUDE.md 規則三點七「一段對話=一個 release」
剛好讓對話起點就是這個交付的起點——這個數字沒有人要維護,也不會說謊。
resume 取較早的那個:接關不是重新開始。
連帶修好票上的兩個卡點(prod-write-guard):
卡點一 發一則 Telegram 跟部署工作流在閘眼裡一模一樣 ⇒ 判準改看打的是哪一個
named webhook(路徑形狀+名字),放行範圍只有 notify_leo 一個名字
卡點二 連「把卡點寫進票裡」都被同一支閘擋(同款第八次)⇒ 剝掉內文再判,
起始行保留,所以真的在部署的寫法照樣擋
(修這支的過程又撞了一次同款——那就是這一格最好的證據)
測試:countdown 20/20、prod-write-guard 29→37/37,全程離線
(時鐘定住、狀態走環境變數、不打網路)。
實測:起點 06:28 台北、現在 08:03 ⇒ 期望「已過 1 小時 35 分」,實得同一字串。
plugin.json 0.9.0 → 0.10.0(版本沒動=沒有人吃得到)。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
70 lines
2.5 KiB
Bash
Executable File
70 lines
2.5 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# countdown-milestone-refresh.sh — 把「主線 milestone 的期限」寫進倒數用的快取
|
||
# (inkstone/ISEP#63 的第二個基準)
|
||
#
|
||
# 為什麼是一支獨立腳本、而不是寫在 hook 裡:
|
||
# 倒數那一行走在 **UserPromptSubmit** 上,也就是每一則訊息的關鍵路徑。
|
||
# 在那裡打 HTTP = leo 每講一句話都先等一次網路。
|
||
# ⇒ hook 只讀快取(`hooks/lib/countdown.py` 從不打網路),
|
||
# 快取由本支在 **SessionStart** 更新一次。不是輪詢、不掛排程(守 D14 紅線)。
|
||
#
|
||
# 寫出來的檔:`$ISEP_COUNTDOWN_STATE_DIR/milestone-due`,一行 `YYYY-MM-DD|<名稱>`。
|
||
# 拿不到 token/拿不到期限 ⇒ **不寫、不報錯、不留舊資料以外的東西**:
|
||
# 倒數那一行寧可少一段,也不要顯示一個編出來的日期。
|
||
set -uo pipefail
|
||
|
||
HOST="${TICKET_HOST:-https://git.uncle6.me}"
|
||
TOKEN="${GITEA_TOKEN_CLAUDE_CODE:-${GITEA_TOKEN:-}}"
|
||
REPOS="${ISEP_COUNTDOWN_REPOS:-inkstone/InkStoneCo inkstone/ISEP}"
|
||
|
||
DIR="${ISEP_COUNTDOWN_STATE_DIR:-$HOME/.claude/isep-countdown}"
|
||
mkdir -p "$DIR" 2>/dev/null || exit 0
|
||
OUT="$DIR/milestone-due"
|
||
|
||
[ -n "$TOKEN" ] || exit 0
|
||
|
||
RAW=""
|
||
for repo in $REPOS; do
|
||
body=$(curl -s --max-time 4 -H "Authorization: token $TOKEN" \
|
||
"$HOST/api/v1/repos/$repo/milestones?state=open" 2>/dev/null) || continue
|
||
RAW="$RAW$body"$'\n'
|
||
done
|
||
|
||
printf '%s' "$RAW" | ISEP_OUT="$OUT" python3 -c '
|
||
import json, os, sys
|
||
from datetime import datetime, timezone
|
||
|
||
best = None
|
||
for chunk in sys.stdin.read().split("\n"):
|
||
chunk = chunk.strip()
|
||
if not chunk:
|
||
continue
|
||
try:
|
||
rows = json.loads(chunk)
|
||
except Exception:
|
||
continue
|
||
if not isinstance(rows, list):
|
||
continue
|
||
for m in rows:
|
||
due = (m or {}).get("due_on") or ""
|
||
if not due:
|
||
continue # 沒設期限的 milestone 不是節拍器
|
||
try:
|
||
dt = datetime.fromisoformat(due.replace("Z", "+00:00"))
|
||
except Exception:
|
||
continue
|
||
if dt.tzinfo is None:
|
||
dt = dt.replace(tzinfo=timezone.utc)
|
||
# 最近的那一個=現在真的在追的那一個
|
||
if best is None or dt < best[0]:
|
||
best = (dt, (m or {}).get("title") or "主線")
|
||
|
||
if best is None:
|
||
raise SystemExit(0)
|
||
dt, title = best
|
||
with open(os.environ["ISEP_OUT"], "w") as f:
|
||
f.write("%s|%s\n" % (dt.astimezone().strftime("%Y-%m-%d"), title))
|
||
' 2>/dev/null || true
|
||
|
||
exit 0
|