From f1bcdc3d72099ab851cad03f010b814faab459f6 Mon Sep 17 00:00:00 2001 From: Jeffery Date: Wed, 26 Aug 2026 10:44:47 +0800 Subject: [PATCH] =?UTF-8?q?feat(wire-cli):=20=E6=96=B0=E5=A2=9E=E5=94=AF?= =?UTF-8?q?=E8=AE=80=E7=9A=84=E6=8E=A5=E7=B7=9A=E7=9B=A4=E9=BB=9E=E5=AD=90?= =?UTF-8?q?=E5=91=BD=E4=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit What: wire-cli.sh 新增 status 子命令,只讀設定檔判斷 jsc 標記段落在不在,不寫檔也不執行 hook。每個接線點印一行 item,以 status=wired、degraded、unwired、skipped 回報。 Why: 體檢類技能要問「hook 接線在不在」,但既有三個子命令都會動到環境:不帶子命令會重新接線、purge 會刪掉、smoke 會真的執行 hook。體檢不該有副作用。 How: 沿用底下接線區塊的同一組檔案位置與標記字串,共用 has_block、toml_root_key、json_top_key 三個判讀函式。codex 的 notify 另外單獨檢查是否落在根層,標記在、鍵被歸進表裡時 codex 讀不到,等同沒接。 Who: 供 jsc-cli:doctor 呼叫的接線檢查。 --- .claude-plugin/plugin.json | 2 +- .codex-plugin/plugin.json | 2 +- README.md | 2 +- plugin.json | 2 +- tools/wire-cli.sh | 86 +++++++++++++++++++++++++++++++++++++- 5 files changed, 88 insertions(+), 6 deletions(-) diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 5ccf44c..e21b3b4 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "jsc-hooks", - "version": "0.1.7", + "version": "0.1.8", "description": "跨 CLI hooks:STE100 語言強制、工時計時、技能用量記錄、SDLC 模型鎖、版本前置檢查", "skills": "./skills", "author": { diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index 9389335..ed01596 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "jsc-hooks", - "version": "0.1.7", + "version": "0.1.8", "description": "跨 CLI hooks:STE100 語言強制、工時計時、技能用量記錄、SDLC 模型鎖、版本前置檢查", "skills": "./skills" } diff --git a/README.md b/README.md index 2051741..bf02786 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,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` 回填。`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`)標記整段重寫,重跑等同先移除再重裝;寫完每個檔案會重讀驗證位置正確才回報成功(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 各跑一次,非零退出即為錯誤(唯一例外是 `sdlc-gate.sh check` 的 exit 2,那是階段鎖的設計行為),以 `status=ok\|failed` 回報 | +| `tools/wire-cli.sh` | 單一 CLI 的 hook 生命週期,共三個用法。`{cli}` 是接線:對應的設定編輯、包裝別名安裝、hook 檔建立,皆以 ``(或 `# 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 各跑一次,非零退出即為錯誤(唯一例外是 `sdlc-gate.sh check` 的 exit 2,那是階段鎖的設計行為),以 `status=ok\|failed` 回報。`status {cli}` 是唯讀盤點:只讀設定檔判斷標記段落在不在,不寫檔也不執行 hook,每個接線點印一行 `item{項目}{路徑}{present\|missing}`,以 `status=wired\|degraded\|unwired\|skipped` 回報(結束碼 0、1、5、3)。體檢類技能(`/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` 回報 | ## 失敗回報範本 diff --git a/plugin.json b/plugin.json index 79ba94f..5d43797 100644 --- a/plugin.json +++ b/plugin.json @@ -1,6 +1,6 @@ { "name": "jsc-hooks", - "version": "0.1.7", + "version": "0.1.8", "description": "跨 CLI hooks:STE100 語言強制、工時計時、技能用量記錄、SDLC 模型鎖、版本前置檢查", "skills": "./skills/" } diff --git a/tools/wire-cli.sh b/tools/wire-cli.sh index b3c4db8..2e9d70d 100755 --- a/tools/wire-cli.sh +++ b/tools/wire-cli.sh @@ -4,6 +4,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 status {claude|codex|copilot|antigravity|kiro} 唯讀盤點接線現況,不寫檔也不執行 hook # # purge 移除的是「所有 hook」,含非 jsc 的第三方項目。安裝一律先 purge 再接線:混著別人的 # hook 接線,出錯時分不清是誰的 hook 壞掉,也修不了。移除前每個要動的檔案先原樣複製到 @@ -41,6 +42,9 @@ # 4=failed(寫入或驗證沒過,接線沒生效;由 hooks-install 呼叫 report-error.sh 回報) # 結束碼(purge): 0=purged 2=用法錯誤 3=skipped 4=failed # 結束碼(smoke): 0=ok 2=用法錯誤 4=failed +# 結束碼(status): 0=wired 1=degraded 2=用法錯誤 3=skipped 5=unwired(該接的段落缺了至少一項) +# status 之外的動作都會寫檔,體檢類技能(/jsc-cli:doctor)只能呼叫 status。判讀邏輯跟接線 +# 共用同一組檔案位置與標記字串,分兩份實作就會各自漂移,體檢說沒接、實際上接著。 # # JSC_CLAUDE_SETTINGS_DIR 可覆寫 claude 使用者層設定檔目錄(預設 ~/.claude)。 # 有這個逃生門才測得動 purge 的 JSON 刪鍵:預設路徑是使用者自己的設定檔,拿真檔案試刪 @@ -53,14 +57,14 @@ HOOKS="$ROOT/hooks" . "$HOOKS/lib.sh" usage() { - echo "用法:wire-cli.sh [purge|smoke] {claude|codex|copilot|antigravity|kiro}" >&2 + echo "用法:wire-cli.sh [purge|smoke|status] {claude|codex|copilot|antigravity|kiro}" >&2 exit 2 } # 第一個參數是子命令時走新流程,否則沿用原本的「wire-cli.sh {cli}」接線。 action=wire case "${1:-}" in - purge|smoke) action="$1"; shift ;; + purge|smoke|status) action="$1"; shift ;; esac cli="${1:-}" @@ -620,6 +624,84 @@ if [ "$action" = smoke ]; then exit 4 fi +if [ "$action" = status ]; then + # 唯讀盤點:只讀設定檔判斷標記段落在不在,不寫檔,也不執行任何 hook。 + # 檔案位置與標記字串一律沿用底下接線區塊的同一份值,兩邊必須一起改。 + command -v "$(cli_bin "$cli")" >/dev/null 2>&1 || skip "未偵測到 $(cli_bin "$cli") 執行檔" + st_items=$(mktemp) || { printf 'status=failed reason=%s\n' "無法建立暫存檔"; exit 4; } + st_missing=0 + st_degrade="" + + # 記一個檢查點。$1=項目名 $2=路徑 $3=present|missing + st_item() { + printf 'item\t%s\t%s\t%s\n' "$1" "$2" "$3" >> "$st_items" + [ "$3" = present ] || st_missing=$((st_missing + 1)) + } + + # 檔案存在且帶有該標記才算接上。$1=項目名 $2=檔案 $3=開頭標記行 + st_block() { + if [ -f "$2" ] && has_block "$2" "$3"; then st_item "$1" "$2" present + else st_item "$1" "$2" missing; fi + } + + # 別名段落只要任何一個既有 rc 檔帶有標記就算接上。$1=項目名 $2=標記名 + st_rc_alias() { + for _rc in "$HOME/.bashrc" "$HOME/.zshrc" "$HOME/.config/fish/config.fish"; do + [ -f "$_rc" ] || continue + if has_block "$_rc" "# $2"; then st_item "$1" "$_rc" present; return 0; fi + done + st_item "$1" "$HOME/.bashrc" missing + } + + case "$cli" in + claude) + if [ -f "$HOOKS/hooks.json" ]; then st_item hooks.json "$HOOKS/hooks.json" present + else st_item hooks.json "$HOOKS/hooks.json" missing; fi ;; + + codex) + config="${CODEX_HOME:-$HOME/.codex}/config.toml" + st_block notify "$config" "# jsc-hooks" + # 標記在、鍵卻被歸進某張表時 codex 讀不到 notify,等同沒接,所以位置要單獨算一項 + if [ -f "$config" ] && toml_root_key "$config" notify; then + st_item notify-root "$config" present + else + st_item notify-root "$config" missing + fi + st_rc_alias alias jsc-hooks:codex + st_block ste100 "${CODEX_HOME:-$HOME/.codex}/AGENTS.md" "" + st_degrade="STE100 降級為 prompt 檔,SDLC 模型鎖降級為技能步驟檢查,無 pre-tool hook 可接版本前置檢查" ;; + + copilot) + st_rc_alias alias jsc-hooks:copilot + st_block ste100 "${JSC_COPILOT_INSTRUCTIONS:-$HOME/.config/copilot/copilot-instructions.md}" "" + st_degrade="STE100 降級為 prompt 檔,SDLC 模型鎖降級為技能步驟檢查,無 pre-tool hook 可接版本前置檢查" ;; + + antigravity) + st_rc_alias alias jsc-hooks:antigravity + st_block ste100 "${JSC_ANTIGRAVITY_RULES:-$HOME/.antigravity/AGENTS.md}" "" + st_degrade="STE100 降級為 prompt 檔,SDLC 模型鎖降級為技能步驟檢查,無 pre-tool hook 可接版本前置檢查" ;; + + kiro) + # kiro 的 hook 檔綁在工作區,這裡看的一律是目前工作目錄底下那一份 + for _f in ./.kiro/hooks/jsc-hooks-session-start.json ./.kiro/hooks/jsc-hooks.json; do + if [ -f "$_f" ] && json_top_key "$_f" run; then st_item "$(basename "$_f" .json)" "$_f" present + else st_item "$(basename "$_f" .json)" "$_f" missing; fi + done + st_degrade="SDLC 模型鎖降級為技能步驟檢查,無 pre-tool hook 可接版本前置檢查" ;; + esac + + if [ "$st_missing" -gt 0 ]; then + printf 'status=unwired reason=%s\n' "$st_missing 個接線項目缺漏,執行 /jsc-hooks:hooks-install 重新接線" + cat "$st_items"; rm -f "$st_items"; exit 5 + fi + if [ -n "$st_degrade" ]; then + printf 'status=degraded reason=%s\n' "$st_degrade" + cat "$st_items"; rm -f "$st_items"; exit 1 + fi + printf 'status=wired reason=%s\n' "hooks.json 自動接線全部五支 hook" + cat "$st_items"; rm -f "$st_items"; exit 0 +fi + case "$cli" in claude) bin=$(cli_bin claude)