Files
hooks/references/behaviors.md
jiantw83andClaude Opus 5 cd4cd8b936 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>
2026-09-07 11:14:41 +08:00

11 KiB
Raw Permalink Blame History

jsc-hooks 技能行為清單

本頁記錄 jsc-hooks 每支技能的行為基準,供技能驗證比對。技能異動時,在同一個 PR 內一起更新這一頁。

hooks-install

項目 內容
觸發時機 裝好或更新完 jsc 技能組之後,要把十支 hook 接線到每一支已安裝的 CLI 時用;jsc-cli:deploy 收尾會把偵測到的 CLI 清單交給它。不用於撰寫新的 hook,也不用於單獨修一支壞掉的 hook,那是 jsc-hooks:repair 的事
關鍵步驟 先跑前置步驟解出兩個字面絕對路徑:readlink -f "$JSC_HOME/current" 解出連結農場根目錄(跨 domain 呼叫用它),readlink -f "$JSC_HOME/current/jsc-hooks" 解出 jsc-hooks 的實體根目錄(只有 wire-cli.sh 從這裡跑。它會改寫自己正踩著的那條連結,但它自己已經把 HERE 與 ROOT 解成實體路徑,ln -sfn 不會再把連結指向自己,所以從實體根目錄跑現在是多一層保險、不是唯一防線;照做的理由是舊版腳本還在別的機器上跑,那些版本走連結跑仍會把連結寫成指向自己、全機器 hook 一起失效)、兩個路徑各解一次不重解、各自在同一步用 [ -d ] 查過印出來的目錄真的存在(JSC_HOME 沒設時第一條會印出 /current、結束碼 0,非空又是絕對路徑,只查前三項擋不下來),任何一條解不出來、不是絕對路徑、或目錄不存在就停手回報是哪一條沒解出來並叫人跑 jsc-cli:deploy,不接任何線也不猜路徑、不退回帶版本號的快取路徑、之後每一次腳本呼叫都用解出來的字面絕對路徑開頭、取得 CLI 清單(呼叫端交來的優先,沒有才自己跑 detect-clis.sh)、第一支 CLI 單獨跑完整條管線(它負責更新共用的 {連結農場根}/jsc-hooks 連結)、其餘 CLI 一支一個 sub agent 並行、每支 CLI 依序走 purge、接線、status、smoke、scan 五道關卡、讀每道關卡自己印的第一行判定、任一關卡出錯就寫 ERROR_{HASH} 並轉給 jsc-hooks:repair(異常頁與索引目錄頁分屬兩個存取庫,各自解析:異常頁由 report-error.sh 走 wiki-repo ERROR,目錄頁由 wiki-contents.sh 走 wiki-repo CONTENTS;只解不出目錄頁的存取庫時異常頁照寫、索引跳過,回報要講明那一頁沒被索引)、目錄頁上這一筆是一個 H2 區塊,標題就是異常頁頁名,時間、頁名、存取庫名稱、觸發 hook、退出碼、摘要各一條條列,寫成 - {欄位名}:{值},頁上不留 markdown 表格、「頁名」那一條指向異常頁的連結一律寫成 [{文字}]({連結}),網址取 gitea.sh wiki-url,寫進去之前先過 jsc-gitea/tools/link-check.sh,結束碼 0 才寫連結、驗不過那一條只留純文字頁名而那一條與異常頁照寫(report-error.sh 內部做完,結束碼不變)、目錄頁的讀回、比對與整頁寫回一律交給 jsc-gitea/tools/wiki-contents.sh upsert ERROR 2 {頁名} {區塊檔} {範本},report-error.sh 只組自己那一個區塊,找得到同名 H2 就整塊換掉、找不到就附加到頁尾、逐 CLI 回報五道關卡的結果、最後由主代理呼叫一次 tools/report-status.sh skill-end jsc-hooks:hooks-install {status} {結束碼} {detail} 記下整輪怎麼結束。腳本在同一個存取庫,用 tools/ 相對路徑;這一筆只由主代理寫一次,寫在並行的各 CLI sub agent 裡會變成五筆互相矛盾的結局。腳本不在就安靜跳過,回報失敗不得變成接線失敗
外部呼叫 readlink -f(前置步驟解兩個根目錄,各一次)、tools/wire-cli.sh purge、tools/wire-cli.sh {cli}、tools/wire-cli.sh status、tools/wire-cli.sh smoke、tools/scan-hook-errors.sh、tools/report-error.sh、jsc-cli/tools/detect-clis.sh、jsc-hooks:repair 技能、jsc-gitea:wiki(寫 ERROR_{HASH} 時經 report-error.sh)、jsc-gitea/tools/gitea.sh wiki-url 與 jsc-gitea/tools/link-check.sh(同樣經 report-error.sh,取目錄頁那一條的網址並驗它連得到)、jsc-gitea/tools/wiki-contents.sh upsert(同樣經 report-error.sh,把那一個 H2 區塊 upsert 進索引目錄頁);接線腳本內部另呼叫 hooks/skill-name.sh 與 hooks/deny.sh 做冒煙斷言
完成條件 前置步驟解出的兩個根目錄都是一條存在的絕對路徑(各自用 [ -d ] 查過),而且整個流程沒有任何一次腳本呼叫帶著未展開的變數或波浪號,每一支偵測到的 CLI 都有五道關卡各一行判定,沒有任何一道回結束碼 2,smoke 的 lines 條數與它自己的斷言相符,claude、codex、copilot、antigravity 回 wired 而 kiro 回 degraded(CLI 擋不下技能叫用),四支非 claude 的執行期錯誤掃描一律據實回 unavailable,各 CLI 的形狀與觸發驗證等級分開寫進回報(codex、antigravity、kiro 形狀實證,copilot 形狀未證;kiro 觸發部分實證,其餘未驗證),每一筆錯誤都帶一個 ERROR_{HASH} 結果與一條對 develop 的修正 PR 連結,而且目錄頁那一條的連結驗不過時,回報要講明那一條只有純文字頁名、沒有連結
可驗證跡象 各 CLI 的設定檔多出 jsc 段落:codex 的 config.toml 標記段落、hooks/codex-hooks.json(從 hooks/hooks.json 推導,matcher Skill 換成 Bash)與 .codex-plugin/plugin.json 指過去的 hooks 路徑字串、copilot 的 ~/.copilot/settings.json 頂層 hooks 鍵(matcher skill,合併不覆寫,enabledPlugins 與第三方條目原樣保留)與 $COPILOT_HOME 底下的指引檔、antigravity 的 ~/.gemini/config/hooks.json 的 jsc 段落(PreToolUse 為 Grouped、matcher ^view_file$,PreInvocation 維持 Flat)、kiro 的 ~/.kiro/agents/jsc.json(hooks 為 agentSpawn、userPromptSubmit、stop 三個合法事件加 timeout_ms、兩層 skill:// glob 的 resources、明列的 tools,並通過 kiro-cli agent validate)與 ~/.kiro/settings/cli.json 的 chat.defaultAgent=jsc;四支非 claude 的接線命令都以 JSC_CLI={代號} 前綴自帶 CLI 代號,缺了它兩道閘門解不出技能名、一律安靜放行,所以 status 把它列成單獨一項;另有 {連結農場根}/jsc-hooks 符號連結建立或更新,而且它指向 jsc-hooks 的實體根目錄、不是指向自己(readlink -f 解得出一個存在的目錄,裡面有 hooks/session-timer.sh 與 tools/jsc-wrap.sh)、各 CLI 設定裡存下來的接線命令也都是展開後的字面絕對路徑,只有存放庫自帶的 hooks/hooks.json 保留 ${JSC_HOME:-$HOME/.jsc}/current/jsc-hooks 這段變數寫法,由 hook 自己的 shell 在執行當下展開、$JSC_HOME/backup/hooks/{cli}/{時間戳}/ 留下 purge 前的備份、出錯時 wiki 多一頁 ERROR_{HASH}(落在 JSC_WIKI_REPO_ERROR 解出的存取庫)並在索引目錄頁補一個 H2 區塊(落在 JSC_WIKI_REPO_CONTENTS 解出的另一個存取庫,由 wiki-contents.sh upsert 進去;H2 標題就是那一頁的頁名,底下六條條列依序是時間、頁名、存取庫名稱、觸發 hook、退出碼、摘要,「頁名」那一條寫成 [{頁名}]({絕對網址}),網址取自 gitea.sh wiki-url 且已經過 link-check.sh 驗到結束碼 0;驗不過那一條只有純文字頁名,report-error.sh 在 stderr 留一行 [jsc] 講明是哪一種原因;那一頁上不會有 markdown 表格)、修正路徑留下一條對 develop 的 PR 。接線完成後 $JSC_HOME/usage/events.jsonl 會逐行長出 {kind:hook} 事件,每支 hook 每次執行一筆,欄位含 status 與實際結束碼;跑過技能之後另有 {kind:skill,phase:start}。事件寫不進去不影響任何 hook 的結束碼這支技能自己收尾時,同一個 $JSC_HOME/usage/events.jsonl 尾端會多一筆 {kind:skill,phase:end},name 是 jsc-hooks:hooks-install,整輪只有一筆,status 與那次結局相符,exit 是決定結局的那道關卡的結束碼;report-status.sh 不在那台機器上就沒有這一筆,接線結果一字不變。事件寫不進去不影響任何 hook 的結束碼,也不影響本技能的結局

repair

項目 內容
觸發時機 hooks-install 或 report-error.sh 回報某一支 hook 失敗時用,或是重新接線之後那支 hook 還是一直失敗時用。不用於例行接線,也不用於與 hook 無關的修改
關鍵步驟 從 ERROR_{HASH} 讀失敗情境(沒有頁就讀失敗的 status= 那一行,讀不到就停下來問)、跑 detect-clis.sh、每一支偵測到的 CLI 各開一個唯讀 sub agent 診斷並交回根因、要改的檔案與驗證指令、挑最小的修正改進 hooks 存取庫(技能名解析改 hooks/skill-name.sh、阻擋形態改 hooks/deny.sh,兩支是唯一真實來源,不在閘門裡各補一份)、跑 wire-cli.sh smoke {cli} 驗到 exit 0、跑 sync-skill-manifest.sh . 同步版本、以 jsc-git:pr 對 develop 開 PR、收尾呼叫 tools/report-status.sh skill-end jsc-hooks:repair {status} {結束碼} {detail} 記下這次修正怎麼結束(腳本在同一個存取庫,用 tools/ 相對路徑,比照 tools/wire-cli.sh;檔案不在就安靜跳過,回報失敗不得變成修正失敗)
外部呼叫 jsc-gitea:wiki、jsc-cli/tools/detect-clis.sh、tools/wire-cli.sh smoke、jsc-meta/tools/sync-skill-manifest.sh、jsc-git:pr;診斷階段另以 sub agent 叫用各支已安裝的 AI CLI
完成條件 修正已經落在磁碟上、wire-cli.sh smoke 對受影響的 CLI 回 exit 0、sync-skill-manifest.sh 回 exit 0 而且三份 manifest 版本一致,最後拿到一條對 develop 的 PR 連結;開不出 PR 時要講明修正已套用但尚未合併、帶上分支名與失敗原因。每一條路線都要走完最後一步:呼叫 report-status.sh skill-end,狀態五選一——修正落地、smoke 回 exit 0、三份 manifest 版本一致而且拿到 PR 連結是 ok;smoke 過了但東西沒送出去是 degraded,也就是開不出 PR 只剩分支,或 manifest 沒對齊;修不好是 failed,也就是診斷繞回去以後 smoke 還是回 exit 4,或同步版本踩到環境錯誤,壞掉的接線還是壞的;沒有可修的項目是 aborted,也就是讀不到任何失敗情境。本技能豁免版本閘門與部署後重啟閘門,沒有別的閘門擋得住它,所以不會用 blocked。腳本不在磁碟上就跳過,這一步照樣算走完
可驗證跡象 hooks 存取庫多一個修正提交與一條推上去的分支、develop 上多一條 PR、三份 manifest 與 README 技能清單版本一致、wire-cli.sh smoke 由失敗轉為 exit 0。收尾在 $JSC_HOME/usage/events.jsonl 留下這一輪的 skill-end 事件,status 取 ok、blocked、failed、degraded 或 aborted,中途停下的那幾輪也照寫——只有 start 沒有配對 end 會被讀成中斷;不論走哪一條路線,$JSC_HOME/usage/events.jsonl 尾端都會多一筆 {kind:skill,phase:end} 事件,name 是 jsc-hooks:repair,status 與那次結局相符,exit 是決定結局的那支工具的結束碼;report-status.sh 不在那台機器上就沒有這一筆,修正結果一字不變