feat(hooks): 工作階段開始時把助理的未讀提醒帶到前景

助理算得出哪幾筆到期、哪幾筆逾期,但那些結果只寫在監控頁上——人要自己
去翻,或自己跑一次狀態查詢。這一支把它帶到前景。

session-reminder.sh 只讀助理那一輪寫好的提醒佇列,一個判定都不做。
自己拿 due 欄與 next_run 去跟現在比就是第二套到期判定,跟助理那一套遲早
對不上,而對不上的那一天兩邊都說自己是對的。它也要快:這一支跑在每一個
工作階段的開頭。

只印佇列換來一個新的失效模式,正面處理:助理停了,佇列就不再更新,而一份
舊佇列讀起來跟新的一模一樣。所以佇列檔頭帶那一輪的時間戳與 epoch,這裡
算出它多舊;超過心跳門檻或心跳不新鮮,就明說這批提醒是多久以前算的、
助理現在的心跳是什麼狀態。門檻與狀態都取 heartbeat.sh 印的那一行。

佇列空又過期的那一種分兩路:待辦簿有東西才說話,零筆就安靜——人自己按停
也算零筆那一種,對著一個刻意的決定每個工作階段催一次是噪音不是提醒。
助理狀態目錄根本不存在時整支安靜退出,那台機器從沒啟動過助理。

一個工作階段只提一次,記號是 sessions/{代號}.reminded。接不到 session id
的 CLI 全部共用 default,所以 session-timer.sh 的 restart 分支順手清掉那個
記號——不清的話那支 CLI 從第二個工作階段起再也收不到提醒。清除掛在那裡
不掛在這裡:「這是不是新的工作階段」的判準只有那一支知道。

接線與檢核一起改,不留一支沒人驗的 hook:hooks.json 與推導出來的
codex-hooks.json 各加一條、kiro 的 agentSpawn 加一條、claude 的 status
逐支列舉加一項、冒煙測試加一條並把預期條數從 17 改成 18、執行期錯誤掃描
的 jsc 判定加一支腳本名,還有散在文件與回報字串裡的「九支 hook」十處
全部改成十支。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-07 11:14:41 +08:00
co-authored by Claude Opus 5
parent 248bc90fc2
commit cd4cd8b936
11 changed files with 198 additions and 25 deletions
+17 -15
View File
@@ -3,7 +3,7 @@
# 用法:
# wire-cli.sh {claude|codex|copilot|antigravity|kiro} 接線
# wire-cli.sh purge {claude|codex|copilot|antigravity|kiro} 備份後移除該 CLI 的所有 hook
# wire-cli.sh smoke {claude|codex|copilot|antigravity|kiro} 跑一輪九支 hook,驗執行期
# wire-cli.sh smoke {claude|codex|copilot|antigravity|kiro} 跑一輪十支 hook,驗執行期
# wire-cli.sh status {claude|codex|copilot|antigravity|kiro} [--verdict]
# 唯讀盤點接線現況,不寫檔也不執行 hook。
# --verdict 只換結束碼語意,輸出一字不變
@@ -37,7 +37,7 @@
# 就好,不必在 SKILL.md 或 README 各抄一份——抄了就會在加減判定路徑時漂移。
#
# 接線行為(依 CLI 而定,皆為冪等:重跑只取代既有的 jsc 段落,不會重複疊加):
# claude — 什麼都不用寫,hooks/hooks.json 已自動接線九支 hook(PreToolUse matcher Skill)
# claude — 什麼都不用寫,hooks/hooks.json 已自動接線十支 hook(PreToolUse matcher Skill)
# codex — pre-tool hook 寫在 .codex-plugin/plugin.json 的 hooks 鍵,做 Codex 專屬覆寫
# (PreToolUse matcher Bash,接 restart-gate.sh 與 version-guard.sh)。
# Claude 用的 hooks/hooks.json 完全不動:那一份是 Skill matcher,而 Codex
@@ -74,7 +74,7 @@
#
# 覆蓋範圍要據實回報,不得暗示每個 CLI 都有保護,也不得再說「沒有 pre-tool hook」——
# 五支裡有四支都有,先前失效的原因是接錯位置,不是沒有位置可接:
# claude 九支 hook 全接,回報 wired
# claude 十支 hook 全接,回報 wired
# codex、copilot、antigravity 版本前置檢查與部署後重啟閘門都擋得下來,回報 wired。
# SDLC 模型鎖仍是技能步驟檢查,write-guard.sh 三種模式尚未接線,
# 註解範圍與繁中編碼仍是 sweep,reason 要逐項講明。
@@ -544,7 +544,8 @@ kiro_agent_json() {
],
"hooks": {
"agentSpawn": [
{ "command": "JSC_CLI=kiro sh \\"$WIRE_HOOKS/session-timer.sh\\" restart </dev/null", "timeout_ms": 10000 }
{ "command": "JSC_CLI=kiro sh \\"$WIRE_HOOKS/session-timer.sh\\" restart </dev/null", "timeout_ms": 10000 },
{ "command": "JSC_CLI=kiro sh \\"$WIRE_HOOKS/session-reminder.sh\\" </dev/null", "timeout_ms": 10000 }
],
"userPromptSubmit": [
{ "command": "JSC_CLI=kiro sh \\"$WIRE_HOOKS/restart-gate.sh\\"", "timeout_ms": 10000 },
@@ -1387,7 +1388,7 @@ if [ "$action" = smoke ]; then
smoke_n_sn=0; smoke_n_dn=0; smoke_n_cx=0; smoke_n_sh=0
# 預期條數(改動判定路徑時一起改):每一類都要有自己的計數器,印得出結果行卻沒人計數的
# 那一類會讓總數永遠對不上,斷言也就形同虛設。
# hook 模式 九支 hook 的每個接線模式各一條。sdlc-gate.sh、comment-scope.sh、
# hook 模式 十支 hook 的每個接線模式各一條。sdlc-gate.sh、comment-scope.sh、
# lang-guard.sh 與 write-guard.sh 各有多個模式,所以比 hook 支數多
# 模型與階段鎖 sdlc-gate.sh 取模型代號的四條來源判定路徑,加上「不知道能力就擋下」的
# 三種情形、check 的 fail-closed,以及階段鎖狀態檔的 CLI 區隔與舊格式相容
@@ -1399,7 +1400,7 @@ if [ "$action" = smoke ]; then
# 阻擋形態 deny.sh 四種形態(stderr 加 2、stdout deny JSON、kiro 注入、未知代號的保守預設)
# 跨 CLI 貫通 restart-gate.sh 吃五支 CLI 的真實負載,驗判定與輸出形態串得起來
# 接線形狀 每支 CLI 要寫出去的內容真的產出來一次,驗結構本身(正反案例各一組)
SMOKE_EXPECT_HOOK=17
SMOKE_EXPECT_HOOK=18
SMOKE_EXPECT_MODEL=12
SMOKE_EXPECT_WP=6
SMOKE_EXPECT_RS=16
@@ -1450,6 +1451,7 @@ if [ "$action" = smoke ]; then
}
smoke_one session-timer.sh mark
smoke_one session-reminder.sh
smoke_one sdlc-gate.sh check
smoke_one version-guard.sh
smoke_one restart-gate.sh
@@ -2240,7 +2242,7 @@ if [ "$action" = smoke ]; then
fi
if [ "$smoke_fails" -eq 0 ]; then
printf 'status=ok reason=%s\n' "九支 hook 的每個接線模式都跑得完,模型與階段鎖、工作包歸屬、部署後重啟閘門、寫入提交閘門、相依版本檢查、五支 CLI 的技能名解析、四種阻擋形態、跨 CLI 貫通與各 CLI 的接線形狀的每條路徑也各走過一次($smoke_breakdown),沒有執行期錯誤"
printf 'status=ok reason=%s\n' "十支 hook 的每個接線模式都跑得完,模型與階段鎖、工作包歸屬、部署後重啟閘門、寫入提交閘門、相依版本檢查、五支 CLI 的技能名解析、四種阻擋形態、跨 CLI 貫通與各 CLI 的接線形狀的每條路徑也各走過一次($smoke_breakdown),沒有執行期錯誤"
printf 'lines\t%s\n' "$smoke_lines"
cat "$smoke_out"; rm -f "$smoke_out"; exit 0
fi
@@ -2260,8 +2262,8 @@ if [ "$action" = status ]; then
st_missing=0
st_degrade=""
# 接上時的 reason 也要一支 CLI 一句:五支的覆蓋範圍不一樣,共用一句話就會把「codex 接了兩道閘門」
# 講成「codex 九支全接」。預設值給 claude,其餘各自在下面覆寫。
st_wired="hooks.json 自動接線全部九支 hook"
# 講成「codex 十支全接」。預設值給 claude,其餘各自在下面覆寫。
st_wired="hooks.json 自動接線全部十支 hook"
# 記一個檢查點。$1=項目名 $2=路徑 $3=present|missing|unverified
#
@@ -2336,10 +2338,10 @@ if [ "$action" = status ]; then
claude_hooks="$claude_root/hooks/hooks.json"
if [ -n "$claude_root" ] && [ -f "$claude_hooks" ]; then st_item hooks.json "$claude_hooks" present
else st_item hooks.json "${claude_hooks:-$HOME/.claude/plugins/installed_plugins.json}" missing; fi
# 九支 hook 全靠這一個檔宣告,只看檔案在不在會漏掉「檔在、某支沒接進去」。
# 每一支都列一項,一支都不省。reason 那行講的是「全部九支」,列舉卻只挑幾支的話,
# 十支 hook 全靠這一個檔宣告,只看檔案在不在會漏掉「檔在、某支沒接進去」。
# 每一支都列一項,一支都不省。reason 那行講的是「全部十支」,列舉卻只挑幾支的話,
# 拿這份輸出驗收接線的人會把沒列到的當成沒接——模型能力鎖就是這樣被誤判成沒接線的。
for _h in comment-scope lang-guard restart-gate skill-usage version-guard; do
for _h in comment-scope lang-guard restart-gate skill-usage session-reminder version-guard; do
if [ -f "$claude_hooks" ] && grep -qF "$_h.sh" "$claude_hooks" 2>/dev/null
then st_item "$_h" "$claude_hooks" present
else st_item "$_h" "$claude_hooks" missing; fi
@@ -2529,7 +2531,7 @@ if [ "$action" = status ]; then
then st_item "event-$_e" "$kr_agent" present
else st_item "event-$_e" "$kr_agent" missing; fi
done
for _g in session-timer.sh restart-gate.sh version-guard.sh ste100-guard.sh comment-scope.sh lang-guard.sh; do
for _g in session-timer.sh session-reminder.sh restart-gate.sh version-guard.sh ste100-guard.sh comment-scope.sh lang-guard.sh; do
if [ -f "$kr_agent" ] && grep -qF "$_g" "$kr_agent" 2>/dev/null
then st_item "${_g%.sh}" "$kr_agent" present
else st_item "${_g%.sh}" "$kr_agent" missing; fi
@@ -2577,7 +2579,7 @@ case "$cli" in
done
printf 'status=wired reason=%s\n' "hooks.json 自動接線"
echo "$WIRE_PATH_NOTE"
echo "[jsc] claude:由 hooks/hooks.json 自動接線全部九支 hook,無需寫入設定。"
echo "[jsc] claude:由 hooks/hooks.json 自動接線全部十支 hook,無需寫入設定。"
echo "[jsc] claude:只有 claude 有 pre-tool hook,版本前置檢查、部署後重啟閘門與 write-guard.sh 的三種模式只在這裡擋得下來;其他四個 CLI 這幾道閘門都接不上。"
echo "[jsc] claude:只有 claude 有 post-tool hook,comment-scope.sh 與 lang-guard.sh 的逐檔即時掃描只在這裡接得上;其他四個 CLI 改用 sweep 掃整個工作區,時機晚一輪或晚到工作階段結束。"
exit 0 ;;
@@ -2800,7 +2802,7 @@ case "$cli" in
# 兩層 glob 是技能看不看得到的關鍵:預設只掃一層,jsc 的技能在第二層。
grep -qF '/skills/*/*/SKILL.md' "$kr_agent" 2>/dev/null \
|| fail "$kr_agent 的 resources 少了兩層 glob,jsc 技能一支都載不到"
for _s in session-timer.sh restart-gate.sh version-guard.sh ste100-guard.sh comment-scope.sh lang-guard.sh; do
for _s in session-timer.sh session-reminder.sh restart-gate.sh version-guard.sh ste100-guard.sh comment-scope.sh lang-guard.sh; do
grep -qF "$_s" "$kr_agent" 2>/dev/null || fail "$kr_agent 的 hooks 沒有接到 $_s"
done
# 這個 agent 要被選用才算數,所以還要設 chat.defaultAgent。內建 agent 改不了,