feat(hooks-install): 將 hooks-install 的 CLI 佈線邏輯抽離為腳本

為什麼:
`skills/hooks-install/SKILL.md` 原本直接內嵌各 CLI(codex/copilot/antigravity/kiro)的詳細佈線步驟,包含逐步設定檔修改、wrapper alias 安裝、以及 `<!-- jsc-hooks -->` 標記區塊的冪等替換邏輯,違反 jsc-meta:skill-check 稽核準則中「確定性程序邏輯應由腳本執行,不應留給子代理每次自行解讀 prose」的要求。

做了什麼:
新增腳本 `tools/wire-cli.sh {cli}`,將原本內嵌於 SKILL.md 的佈線邏輯全數抽出,並支援冪等執行(標記區塊替換、可安全重跑),輸出機器可讀的 `status=wired|degraded|skipped reason=...` 首行訊息與對應結束碼(0=wired、1=degraded、2=bad usage、3=skipped)。改寫 `skills/hooks-install/SKILL.md`,改為依偵測到的 CLI 呼叫此腳本,並讀取其 status/結束碼判斷結果,不再自行內含佈線步驟。同步更新 `README.md`:在工具表新增 `tools/wire-cli.sh` 說明列,並改寫 hooks-install skill 的描述段落,反映新的腳本驅動流程與 `status=` 回報慣例。三份外掛清單 `plugin.json`、`.claude-plugin/plugin.json`、`.codex-plugin/plugin.json` 版本號自 0.0.3 升級為 0.0.4,對應此行為變更。

怎麼做:
建立獨立腳本承載原本的 CLI 佈線程序邏輯,改造 SKILL.md 為呼叫腳本並解讀其標準化輸出,同步更新文件與版本號。

影響範圍:
`skills/hooks-install/SKILL.md`、`README.md`、`plugin.json`、`.claude-plugin/plugin.json`、`.codex-plugin/plugin.json`、新增檔案 `tools/wire-cli.sh`;影響 hooks-install skill 的執行流程與所有安裝 jsc-hooks 外掛的 CLI 佈線行為。
This commit is contained in:
2026-08-24 14:47:46 +08:00
parent c4909e1a1f
commit 69eee91f27
6 changed files with 163 additions and 13 deletions
+149
View File
@@ -0,0 +1,149 @@
#!/usr/bin/env sh
# wire-cli.sh — 把 jsc 四支 hook 接線到單一 CLI(供 hooks-install 技能呼叫)。
# 用法: wire-cli.sh {claude|codex|copilot|antigravity|kiro}
# 行為(依 CLI 而定,皆為冪等:重跑只取代既有的 jsc-hooks 標記段落,不會重複疊加):
# claude — 什麼都不用寫,hooks.json 已自動接線四支 hook
# codex — 在 config.toml 設 notify(呼叫 session-timer.sh mark,JSC_CLI=codex);
# 在 AGENTS.md 附加 STE100 規則段落(prompt 降級)
# copilot — 在 shell rc 檔加上 copilot 別名,轉呼叫 tools/jsc-wrap.sh copilot;
# 在 copilot-instructions.md 附加 STE100 規則段落
# antigravity — 在 shell rc 檔加上 agy 別名,轉呼叫 tools/jsc-wrap.sh antigravity;
# 在全域規則檔附加 STE100 規則段落
# kiro — 在工作區 .kiro/hooks/ 下建立 jsc-hooks.json(JSC_CLI=kiro)
#
# 輸出: 第一行固定為 `status={wired|degraded|skipped} reason=...`(可供程式判讀),
# 其後為人類可讀的繁中說明。
# 結束碼: 0=wired(已完整接線) 1=degraded(降級為 prompt/技能步驟檢查)
# 2=用法錯誤 3=skipped(該 CLI 未偵測到執行檔,略過)
set -u
HERE=$(cd "$(dirname "$0")" && pwd)
ROOT=$(cd "$HERE/.." && pwd)
HOOKS="$ROOT/hooks"
cli="${1:-}"
case "$cli" in
claude|codex|copilot|antigravity|kiro) ;;
*)
echo "用法:wire-cli.sh {claude|codex|copilot|antigravity|kiro}" >&2
exit 2 ;;
esac
# 每個 CLI 對應的實際執行檔名稱(antigravity 的執行檔是 agy,其餘與 CLI 代號同名)
cli_bin() {
case "$1" in
antigravity) printf 'agy' ;;
kiro) printf 'kiro-cli' ;;
*) printf '%s' "$1" ;;
esac
}
# STE100 規則段落的唯一來源:ste100-guard.sh 的實際輸出
ste100_text() { sh "$HOOKS/ste100-guard.sh" 2>/dev/null | sed '/^exit /d'; }
# 以標記整段取代(冪等);標記不存在就在檔尾新增;檔案不存在就建立。
# $1=檔案 $2=開頭標記行 $3=結尾標記行 $4=標記之間要寫入的內容
replace_block() {
file="$1"; bopen="$2"; bshut="$3"; content="$4"
dir=$(dirname "$file")
mkdir -p "$dir" 2>/dev/null || return 1
touch "$file" 2>/dev/null || return 1
block=$(printf '%s\n%s\n%s' "$bopen" "$content" "$bshut")
if grep -qF "$bopen" "$file" 2>/dev/null; then
awk -v bopen="$bopen" -v bshut="$bshut" -v block="$block" '
$0==bopen { print block; skip=1; next }
$0==bshut { skip=0; next }
skip { next }
{ print }
' "$file" > "$file.jsc-tmp" 2>/dev/null && mv "$file.jsc-tmp" "$file"
else
printf '\n%s\n' "$block" >> "$file"
fi
}
# 找出可寫入別名的 shell rc 檔;都不存在就以 ~/.bashrc 為預設(自動建立)。
# 印出找到/建立的 rc 檔路徑,一行一個。
rc_files() {
found=""
for f in "$HOME/.bashrc" "$HOME/.zshrc" "$HOME/.config/fish/config.fish"; do
[ -f "$f" ] && { printf '%s\n' "$f"; found=1; }
done
[ -n "$found" ] || printf '%s\n' "$HOME/.bashrc"
}
skip() { # $1=reason
printf 'status=skipped reason=%s\n' "$1"
echo "[jsc] 略過:$1"
exit 3
}
case "$cli" in
claude)
printf 'status=wired reason=%s\n' "hooks.json 自動接線"
echo "[jsc] claude:由 hooks/hooks.json 自動接線全部四支 hook,無需寫入設定。"
exit 0 ;;
codex)
command -v "$(cli_bin codex)" >/dev/null 2>&1 || skip "未偵測到 codex 執行檔"
CODEX_HOME="${CODEX_HOME:-$HOME/.codex}"
config="$CODEX_HOME/config.toml"
agents="$CODEX_HOME/AGENTS.md"
notify_line="notify = [\"env\", \"JSC_CLI=codex\", \"sh\", \"$HOOKS/session-timer.sh\", \"mark\"]"
replace_block "$config" "# jsc-hooks" "# /jsc-hooks" "$notify_line" \
|| skip "無法寫入 $config"
replace_block "$agents" "<!-- jsc-hooks -->" "<!-- /jsc-hooks -->" "$(ste100_text)" \
|| skip "無法寫入 $agents"
printf 'status=degraded reason=%s\n' "STE100 降級為 prompt 檔,SDLC 模型鎖降級為技能步驟檢查"
echo "[jsc] codex:已設定 $config 的 notify 呼叫 session-timer.sh mark。"
echo "[jsc] codex:已在 $agents 寫入 STE100 規則段落(prompt 降級)。"
echo "[jsc] codex:SDLC 模型鎖降級為技能步驟檢查,鎖檔仍由 sdlc-gate.sh lock 寫入。"
exit 1 ;;
copilot)
bin=$(cli_bin copilot)
command -v "$bin" >/dev/null 2>&1 || skip "未偵測到 copilot 執行檔"
instr="${JSC_COPILOT_INSTRUCTIONS:-$HOME/.config/copilot/copilot-instructions.md}"
alias_line="alias $bin='sh \"$HERE/jsc-wrap.sh\" copilot'"
rc_files | while IFS= read -r rc; do
replace_block "$rc" "# jsc-hooks:copilot" "# /jsc-hooks:copilot" "$alias_line"
done
replace_block "$instr" "<!-- jsc-hooks -->" "<!-- /jsc-hooks -->" "$(ste100_text)" \
|| skip "無法寫入 $instr"
printf 'status=wired reason=%s\n' "wrapper 別名 + 規則檔已接線"
echo "[jsc] copilot:已在 shell rc 加上 $bin 別名,轉呼叫 tools/jsc-wrap.sh copilot。"
echo "[jsc] copilot:已在 $instr 寫入 STE100 規則段落。"
exit 0 ;;
antigravity)
bin=$(cli_bin antigravity)
command -v "$bin" >/dev/null 2>&1 || skip "未偵測到 antigravity(agy)執行檔"
rules="${JSC_ANTIGRAVITY_RULES:-$HOME/.antigravity/AGENTS.md}"
alias_line="alias $bin='sh \"$HERE/jsc-wrap.sh\" antigravity'"
rc_files | while IFS= read -r rc; do
replace_block "$rc" "# jsc-hooks:antigravity" "# /jsc-hooks:antigravity" "$alias_line"
done
replace_block "$rules" "<!-- jsc-hooks -->" "<!-- /jsc-hooks -->" "$(ste100_text)" \
|| skip "無法寫入 $rules"
printf 'status=wired reason=%s\n' "wrapper 別名 + 規則檔已接線"
echo "[jsc] antigravity:已在 shell rc 加上 $bin 別名,轉呼叫 tools/jsc-wrap.sh antigravity。"
echo "[jsc] antigravity:已在 $rules 寫入 STE100 規則段落。"
exit 0 ;;
kiro)
command -v "$(cli_bin kiro)" >/dev/null 2>&1 || skip "未偵測到 kiro-cli 執行檔"
hookdir="./.kiro/hooks"
hookfile="$hookdir/jsc-hooks.json"
mkdir -p "$hookdir" 2>/dev/null || skip "無法建立 $hookdir"
cat > "$hookfile" 2>/dev/null <<EOF || skip "無法寫入 $hookfile"
{
"name": "jsc-hooks",
"description": "jsc session timer + STE100 guard bridge (auto-generated by jsc-hooks:hooks-install, do not edit by hand)",
"on": ["sessionStart", "sessionEnd", "userPromptSubmit"],
"env": { "JSC_CLI": "kiro" },
"run": "sh \\"$HOOKS/session-timer.sh\\" mark; sh \\"$HOOKS/ste100-guard.sh\\""
}
EOF
printf 'status=degraded reason=%s\n' "SDLC 模型鎖降級為技能步驟檢查"
echo "[jsc] kiro:已建立 $hookfile(JSC_CLI=kiro)。"
echo "[jsc] kiro:SDLC 模型鎖降級為技能步驟檢查,鎖檔仍由 sdlc-gate.sh lock 寫入。"
exit 1 ;;
esac