fix(hooks-install): 使用穩定路徑寫入 hook 接線

This commit is contained in:
2026-08-28 11:22:08 +08:00
parent de6b012356
commit 4e23e49159
3 changed files with 131 additions and 25 deletions
+3 -3
View File
@@ -31,7 +31,7 @@ Marketplace 統一為 `jsc`(https://gitea.jsc.idv.tw/plugins/meta.git),安
| `hooks/lang-guard.sh` | UserPromptSubmit、PostToolUse(Write、Edit、MultiEdit)、codex `notify`、kiro `userPromptSubmit`、`tools/jsc-wrap.sh` 收尾 | 所有非程式碼輸出一律繁體中文、UTF-8、無亂碼、無簡體字,共三種模式。`prompt`:在每次提示注入規則摘要(適用範圍與自我檢查各一行),五個 CLI 都接得到。無參數:寫檔後的逐檔掃描,從 stdin JSON 取 `file_path`(或環境變數 `JSC_CHANGED_FILE`),只有 claude 的 PostToolUse 接得上。`sweep [dir]`:掃整個 git 工作區這次改過的所有檔案,給沒有 post-tool hook 的四個 CLI 用,找不到 git 就安靜 exit 0。接線位置與掃描時機跟 `comment-scope.sh` 完全一樣,見下面那張表。偵測三項:簡體字(字表在 `hooks/simplified.txt`,讀不到就安靜跳過這一項)、亂碼(U+FFFD 替代字元與雙重編碼殘骸)、非 UTF-8 編碼(用 `iconv` 判定,沒有 `iconv` 就跳過)。三項都掃整個檔案、不只掃註解行,`.md` 與純文字檔照掃——那些正是「非程式碼輸出」的主場,這兩點跟 `comment-scope.sh` 刻意不同。掃描深度仍只看 `git diff HEAD` 的新增行、不翻舊帳,命中就把警告與最多三行證據送到 stderr 並以 exit 2 交回模型就地修正(不擋寫入)。二進位檔(只認 NUL 位元組)與 `*.lock`、`*.min.js`、`*.map` 這類產生檔跳過;`hooks/simplified.txt`、`hooks/ste100-guard.sh`、`hooks/lang-guard.sh` 也跳過,那三份檔案裡的簡體字與亂碼樣本是被討論的對象,不是被使用。規則正文的唯一來源在 `jsc-meta` 的 `references/ste100.md`。逃生門 `JSC_LANG_GUARD=off` |
| `hooks/sdlc-gate.sh` | UserPromptSubmit、PreToolUse(Skill) | SDLC 階段能力標籤閘門與模型鎖:`lock {stage}` 由 jsc-sdlc 階段技能呼叫,從 transcript 讀出實際模型 id 比對該階段必要標籤(`$JSC_HOME/model-tags.tsv`),不符就拒絕上鎖;`check` 在模型不符時以 exit 2 擋下該輪提示(其他 hook 一律 exit 0,此處是刻意例外);`unlock` 為逃生門。另含工作包 PR 閘門:`wp-lock {owner}/{repo} {index} [{工作包代號}]` 記下一筆未結清的工作包 PR、`wp-unlock {owner}/{repo} {index}` 結清那一筆(檔案不存在也算成功)、`wp-claim {owner}/{repo} {工作包代號} [{PR 編號}] [{分析頁頁名}]` 記下這個存取庫目前領取哪一包、`wp-unclaim {owner}/{repo}` 交回、`wp-report` 印出所有未結清、`wp-check {prompt|skill}` 為 hook 模式。狀態檔一個工作包一支,在 `$JSC_HOME/wp/{owner}-{repo}-{index}.pr`,**刻意不綁 session**——PR 沒合併時換一個工作階段照樣要擋;一個工作包一支鎖檔是為了讓好幾個互不相依的工作包能同時記在案,不會互相覆蓋掉對方的鎖。`wp-check prompt` 只注入提醒、絕不擋提示(擋了連「去修那支 PR」的對話都送不出去);`wp-check skill` 在有未結清 PR 時以 exit 2 擋下 `plan`、`analyze`、`maintain`,但一律放行 `implement`(結清 PR 正是 implement 的步驟,擋它會鎖死流程)——這一層是整個存取庫共用的粗粒度提醒,「某個候選工作包能不能挑」的細粒度判斷在 `jsc-sdlc/tools/wp-gate.sh check-deps`,不是這裡。另外會比對歸屬:未結清的 PR 不屬於目前領取的工作包時,`prompt` 多注入一行「那幾支交給領取它的工作階段」,`skill` 在擋下 `plan`、`analyze`、`maintain` 時一併點名,`implement` 仍放行但收到同一則提醒。逃生門 `JSC_WP_GATE=off`。這道閘門只讀檔案、不打網路,PR 的真實合併狀態由 `jsc-sdlc/tools/wp-gate.sh` 查證 |
Claude 由 `hooks/hooks.json` 自動接線八支 hook;其他 CLI 用 `hooks-install` 技能接線、改裝包裝啟動器,或降級為規則檔。
Claude 由 `hooks/hooks.json` 自動接線八支 hook;其他 CLI 用 `hooks-install` 技能接線、改裝包裝啟動器,或降級為規則檔。寫進使用者設定的長期命令一律指向 `$JSC_HOME/current/jsc-hooks`,不指向帶版號的 plugin 快取目錄,也不指向開發存取庫。
> 覆蓋範圍要據實看待:只有 claude 同時有 PreToolUse、PostToolUse 與 UserPromptSubmit,八支 hook 全接得上,回報 `wired`。codex、copilot、antigravity、kiro 都沒有 pre-tool hook,接不上 `version-guard.sh` 的版本前置檢查,也接不上 `restart-gate.sh` 的部署後重啟閘門,SDLC 模型鎖也只剩技能步驟檢查,這四個 CLI 一律回報 `degraded`,靠 `/jsc-cli:deploy` 定期更新。重啟閘門在這四個 CLI 上一次技能呼叫都擋不下來:那一支自己那份狀態檔照樣寫、下一個工作階段開始照樣清,只是中間沒有判定點,重啟得靠 `/jsc-cli:deploy` 收尾的提示自己動手。codex 另外沒有工作階段開始事件,計時改由 `tools/jsc-wrap.sh` 的 `codex` 別名在啟動當下開始;沒走別名啟動時,時間從第一輪回應算起。
@@ -115,7 +115,7 @@ Claude 由 `hooks/hooks.json` 自動接線八支 hook;其他 CLI 用 `hooks-in
| `tools/jsc-wrap.sh` | 沒有完整 hook 系統的 CLI 的包裝啟動器:匯出 `JSC_CLI`、`JSC_SESSION_ID`,前後接 `session-timer.sh`,結束時自動跑 `scan-logs.sh` 回填,再依序跑一次 `comment-scope.sh sweep` 與 `lang-guard.sh sweep` 掃整個 git 工作區的註解範圍與繁中編碼(copilot 與 antigravity 沒有任何逐輪事件,整個工作階段只有這裡掃得到)。兩次收尾掃描一律不影響結束碼:包裝器原樣回傳 CLI 自己的結束碼,`sweep` 命中只把警告印到 stderr。`JSC_CLI` 存 CLI 代號,實際執行的是對應的執行檔(antigravity 是 agy、kiro 是 kiro-cli) |
| `tools/scan-logs.sh` | 離線回填:解析 copilot、antigravity、codex 的原生日誌,把技能用量與階段界線補進 `$JSC_HOME`,重掃不重複 |
| `tools/report-error.sh` | 失敗回報流程:把一筆 hook 或工具異常寫成 wiki 的 `ERROR_{HASH}`,並在 `ERROR_CONTENTS` 附上一列索引。wiki 位置由 `jsc-gitea` 的 `gitea.sh wiki-repo ERROR` 解析,解析不出來就安靜降級。由操作者手動執行,或由 `hooks-install` 在 `wire-cli.sh` 回報 `status=failed` 時執行;**不接在失敗的 hook 上自動觸發**(hook 一律安靜 exit 0,自我回報會疊出迴圈) |
| `tools/wire-cli.sh` | 單一 CLI 的 hook 生命週期,共三個用法。`{cli}` 是接線:對應的設定編輯、包裝別名安裝、hook 檔建立,皆以 `<!-- jsc-hooks -->`(或 `# jsc-hooks`)標記整段重寫,重跑等同先移除再重裝;寫完每個檔案會重讀驗證位置正確才回報成功(codex 的 `notify` 必須是根層鍵、kiro 的 JSON 必須成對且 `on`、`run` 在最上層),以 `status=wired\|degraded\|skipped\|failed` 回報。`purge {cli}` 是移除:把該 CLI 的**所有** hook 清掉,含非 jsc 的第三方項目,動到的檔案先原樣備份到 `$JSC_HOME/backup/hooks/{cli}/{yyyyMMdd_HHmmss}/`,備份失敗就不移除,移除後重讀驗證,驗不過自動還原備份,以 `status=purged\|skipped\|failed` 回報。`smoke {cli}` 是執行期冒煙測試:八支 hook 的每個接線模式各跑一次,非零退出即為錯誤,另外用一份暫時的 `$JSC_HOME` 狀態檔把工作包歸屬的四條判定路徑(查無歸屬、自己的工作包、別的工作包、逃生門)與重啟閘門的五條判定路徑(狀態檔不存在、狀態檔存在、豁免技能、逃生門、取不到技能名)各跑一次並比對結束碼,再驗一次重啟閘門的清除機制真的清得掉,驗的是判定結果本身,不只是腳本跑得完(例外有三個:`sdlc-gate.sh check` 的 exit 2 是階段鎖的設計行為,`comment-scope.sh` 與 `lang-guard.sh` 掃描模式的 exit 2 是掃到違規的設計行為——`sweep` 在髒工作區本來就會回 2,不算 hook 壞掉),以 `status=ok\|failed` 回報。`status {cli}` 是唯讀盤點:只讀設定檔判斷標記段落在不在,不寫檔也不執行 hook,每個接線點印一行 `item<TAB>{項目}<TAB>{路徑}<TAB>{present\|missing}`,以 `status=wired\|degraded\|unwired\|skipped` 回報(結束碼 0、1、5、3)。體檢類技能(`/jsc-cli:doctor`)只能用這個子命令,另外三個都會動到環境 |
| `tools/wire-cli.sh` | 單一 CLI 的 hook 生命週期,共三個用法。`{cli}` 是接線:先建立或更新 `$JSC_HOME/current/jsc-hooks` 指向目前這版 plugin,接著把對應的設定編輯、包裝別名安裝、hook 檔建立成穩定路徑,皆以 `<!-- jsc-hooks -->`(或 `# jsc-hooks`)標記整段重寫,重跑等同先移除再重裝;寫完每個檔案會重讀驗證位置正確才回報成功(codex 的 `notify` 必須是根層鍵、kiro 的 JSON 必須成對且 `on`、`run` 在最上層),也會確認寫入路徑能解到既有腳本。檔案系統不能建立 symlink 時,會明確回報並退回目前根目錄,不會靜默寫出壞路徑。`status=wired\|degraded\|skipped\|failed` 回報接線結果。`purge {cli}` 是移除:把該 CLI 的**所有** hook 清掉,含非 jsc 的第三方項目,動到的檔案先原樣備份到 `$JSC_HOME/backup/hooks/{cli}/{yyyyMMdd_HHmmss}/`,備份失敗就不移除,移除後重讀驗證,驗不過自動還原備份,以 `status=purged\|skipped\|failed` 回報。`smoke {cli}` 是執行期冒煙測試:八支 hook 的每個接線模式各跑一次,非零退出即為錯誤,另外用一份暫時的 `$JSC_HOME` 狀態檔把工作包歸屬的五條判定路徑與重啟閘門的十二條判定與清除路徑各跑一次並比對結束碼,驗的是判定結果本身,不只是腳本跑得完(例外有三個:`sdlc-gate.sh check` 的 exit 2 是階段鎖的設計行為,`comment-scope.sh` 與 `lang-guard.sh` 掃描模式的 exit 2 是掃到違規的設計行為——`sweep` 在髒工作區本來就會回 2,不算 hook 壞掉),以 `status=ok\|failed` 回報。`status {cli}` 是唯讀盤點:只讀設定檔判斷標記段落在不在,不寫檔也不執行 hook,每個接線點印一行 `item<TAB>{項目}<TAB>{路徑}<TAB>{present\|missing}`,也會把帶版號快取路徑、開發存取庫路徑與不存在的腳本列為缺項;`status claude` 讀 Claude Code 實際載入的 `installed_plugins.json`,不再檢查目前腳本旁邊那份 `hooks.json`。體檢類技能(`/jsc-cli:doctor`)只能用這個子命令,另外三個都會動到環境 |
| `tools/scan-hook-errors.sh` | 掃 CLI 原生紀錄找 hook 的執行期錯誤(接線寫對、跑起來出錯)。只有 claude 有 hook 結果紀錄,掃 `~/.claude/projects/**/*.jsonl` 的 `hook_non_blocking_error` 與非空 `hookErrors`;codex、copilot、antigravity、kiro 沒有等價紀錄,一律回報 `unavailable` 並指向 `wire-cli.sh smoke {cli}`。每筆錯誤附加一行 JSON 到 `$JSC_HOME/errors/hooks.jsonl`,`jsc` 欄位標明是不是 jsc 自己的 hook(第三方 hook 的錯誤只回報,不由 jsc 修正);去重與 `scan-logs.sh` 同法,重掃只讀新增段落,以 `status=clean\|errors\|unavailable` 回報 |
## 失敗回報範本
@@ -136,7 +136,7 @@ Claude 由 `hooks/hooks.json` 自動接線八支 hook;其他 CLI 用 `hooks-in
### `hooks-install`
把八支 hook 接線到所有已安裝的 CLI,每個 CLI 走四道關卡:先 `tools/wire-cli.sh purge {cli}` 備份後移除所有 hook(含非 jsc 的第三方項目,乾淨起跑才分得清後續失敗是誰的),再 `tools/wire-cli.sh {cli}` 接線(claude 由 `hooks.json` 自動接線,無需寫入),接著 `tools/wire-cli.sh smoke {cli}` 驗執行期,最後 `tools/scan-hook-errors.sh --cli {cli}` 掃原生紀錄。codex、copilot、antigravity 由接線腳本裝上 `tools/jsc-wrap.sh` 包裝別名補上計時與用量回填(結束時自動跑 `tools/scan-logs.sh`),語言規則仍重寫到各自的規則檔(以 `<!-- jsc-hooks -->` 標記整段取代,等同先移除再重裝,不重複追加)。codex、copilot、antigravity、kiro 的 SDLC 模型鎖降級為技能步驟檢查,鎖檔仍由 SDLC 技能直接呼叫 `sdlc-gate.sh lock` 寫入;這四個 CLI 沒有 pre-tool hook,版本前置檢查與部署後重啟閘門都接不上(重啟閘門在那四支上一次技能呼叫都擋不下來,狀態檔照樣寫、下個工作階段照樣清);也沒有 post-tool hook,`comment-scope.sh` 接不到逐檔即時掃描,改用 `sweep` 掃整個 git 工作區——codex 每輪結束、kiro 每輪提示送出時、copilot 與 antigravity 只有工作階段結束時掃一次,腳本會在 `reason` 裡講明各自的時機,只有 claude 回報 `wired`,也只有 claude 掃得到執行期錯誤紀錄。任一關卡出錯(purge、接線、冒煙失敗,或掃到 `jsc=true` 的執行期錯誤)就先寫 `ERROR_{HASH}`,再交給 `repair` 技能接手並以 `develop` PR 收尾;此時允許中止剩下的安裝,但修正一定要開始。掃到 `jsc=false` 的第三方 hook 錯誤只回報,不轉修正。
把八支 hook 接線到所有已安裝的 CLI,每個 CLI 走四道關卡:先 `tools/wire-cli.sh purge {cli}` 備份後移除所有 hook(含非 jsc 的第三方項目,乾淨起跑才分得清後續失敗是誰的),再 `tools/wire-cli.sh {cli}` 接線(claude 由 `hooks.json` 自動接線,無需寫入;其他 CLI 的持久命令會寫成 `$JSC_HOME/current/jsc-hooks` 穩定路徑),接著 `tools/wire-cli.sh smoke {cli}` 驗執行期,最後 `tools/scan-hook-errors.sh --cli {cli}` 掃原生紀錄。codex、copilot、antigravity 由接線腳本裝上 `tools/jsc-wrap.sh` 包裝別名補上計時與用量回填(結束時自動跑 `tools/scan-logs.sh`),語言規則仍重寫到各自的規則檔(以 `<!-- jsc-hooks -->` 標記整段取代,等同先移除再重裝,不重複追加)。codex、copilot、antigravity、kiro 的 SDLC 模型鎖降級為技能步驟檢查,鎖檔仍由 SDLC 技能直接呼叫 `sdlc-gate.sh lock` 寫入;這四個 CLI 沒有 pre-tool hook,版本前置檢查與部署後重啟閘門都接不上(重啟閘門在那四支上一次技能呼叫都擋不下來,狀態檔照樣寫、下個工作階段照樣清);也沒有 post-tool hook,`comment-scope.sh` 接不到逐檔即時掃描,改用 `sweep` 掃整個 git 工作區——codex 每輪結束、kiro 每輪提示送出時、copilot 與 antigravity 只有工作階段結束時掃一次,腳本會在 `reason` 裡講明各自的時機,只有 claude 回報 `wired`,也只有 claude 掃得到執行期錯誤紀錄。任一關卡出錯(purge、接線、冒煙失敗,或掃到 `jsc=true` 的執行期錯誤)就先寫 `ERROR_{HASH}`,再交給 `repair` 技能接手並以 `develop` PR 收尾;此時允許中止剩下的安裝,但修正一定要開始。掃到 `jsc=false` 的第三方 hook 錯誤只回報,不轉修正。
### `repair`