diff --git a/hooks/hooks.json b/hooks/hooks.json new file mode 100644 index 0000000..729bf36 --- /dev/null +++ b/hooks/hooks.json @@ -0,0 +1,19 @@ +{ + "hooks": { + "SessionStart": [ + { "hooks": [ { "type": "command", "command": "sh \"${CLAUDE_PLUGIN_ROOT}/hooks/session-timer.sh\" start" } ] } + ], + "UserPromptSubmit": [ + { "hooks": [ { "type": "command", "command": "sh \"${CLAUDE_PLUGIN_ROOT}/hooks/ste100-guard.sh\"" } ] } + ], + "Stop": [ + { "hooks": [ { "type": "command", "command": "sh \"${CLAUDE_PLUGIN_ROOT}/hooks/session-timer.sh\" mark" } ] } + ], + "SessionEnd": [ + { "hooks": [ { "type": "command", "command": "sh \"${CLAUDE_PLUGIN_ROOT}/hooks/session-timer.sh\" mark" } ] } + ], + "PostToolUse": [ + { "matcher": "Skill", "hooks": [ { "type": "command", "command": "sh \"${CLAUDE_PLUGIN_ROOT}/hooks/skill-usage.sh\"" } ] } + ] + } +} diff --git a/hooks/lib.sh b/hooks/lib.sh new file mode 100755 index 0000000..baa3468 --- /dev/null +++ b/hooks/lib.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env sh +# lib.sh — jsc hooks 共用函式。所有 hook 腳本 source 此檔。 +# 輸入相容:Claude 式 stdin JSON、或環境變數(codex/copilot/antigravity/kiro 接線時設定)。 +# 缺資料時安靜降級,hook 永遠 exit 0,不可中斷宿主 CLI。 + +JSC_HOME="${JSC_HOME:-$HOME/.jsc}" +mkdir -p "$JSC_HOME/sessions" "$JSC_HOME/usage" 2>/dev/null || true + +# 讀完 stdin(可能為空;非阻塞宿主) +read_stdin() { + if [ -t 0 ]; then STDIN_JSON=""; else STDIN_JSON=$(cat 2>/dev/null || true); fi +} + +# 從 stdin JSON 取字串欄位(naive 但足夠應付各 CLI 的扁平 payload) +json_str() { # $1=欄位名 + printf '%s' "$STDIN_JSON" | tr -d '\n' \ + | sed -n "s/.*\"$1\"[[:space:]]*:[[:space:]]*\"\([^\"]*\)\".*/\1/p" | head -n1 +} + +# 目前 session id:stdin JSON > 環境變數 > 固定值 +session_id() { + sid=$(json_str session_id) + [ -n "$sid" ] || sid="${JSC_SESSION_ID:-${CLAUDE_SESSION_ID:-default}}" + printf '%s' "$sid" +} + +# 目前 CLI 名稱:環境變數 > 依 stdin 特徵猜測 > unknown +cli_name() { + if [ -n "${JSC_CLI:-}" ]; then printf '%s' "$JSC_CLI" + elif [ -n "${CLAUDE_PLUGIN_ROOT:-}" ]; then printf 'claude' + else printf 'unknown'; fi +} + +now_epoch() { date +%s; } +now_iso() { date -u +%Y-%m-%dT%H:%M:%SZ; } diff --git a/hooks/session-timer.sh b/hooks/session-timer.sh new file mode 100755 index 0000000..dc9368f --- /dev/null +++ b/hooks/session-timer.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env sh +# session-timer.sh — 記錄工作階段花費時間(供 jsc-log:worklog 取用)。 +# 用法: +# session-timer.sh start # SessionStart:記錄起始時間 +# session-timer.sh mark # Stop/SessionEnd:更新最後活動時間 +# session-timer.sh report [sid] # 印出 {sid} {seconds};無紀錄印 0 +HERE=$(dirname "$0"); . "$HERE/lib.sh" +read_stdin +sid=$(session_id) +case "${1:-mark}" in + start) + f="$JSC_HOME/sessions/$sid.start" + [ -f "$f" ] || now_epoch > "$f" ;; + mark) + now_epoch > "$JSC_HOME/sessions/$sid.end" ;; + report) + rsid="${2:-$sid}" + s=$(cat "$JSC_HOME/sessions/$rsid.start" 2>/dev/null || echo "") + e=$(cat "$JSC_HOME/sessions/$rsid.end" 2>/dev/null || now_epoch) + if [ -n "$s" ]; then echo "$rsid $((e - s))"; else echo "$rsid 0"; fi ;; +esac +exit 0 diff --git a/hooks/skill-usage.sh b/hooks/skill-usage.sh new file mode 100755 index 0000000..a669b66 --- /dev/null +++ b/hooks/skill-usage.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env sh +# skill-usage.sh — 記錄技能使用與呼叫鏈(供 jsc-log:stats 統計)。 +# Claude: PostToolUse(matcher=Skill) 接線;其他 CLI 由 wrapper 以 +# JSC_SKILL= skill-usage.sh 呼叫。 +# 產出: +# $JSC_HOME/usage/skills.jsonl {ts,cli,session,skill} +# $JSC_HOME/usage/chains.jsonl {ts,cli,session,from,to}(同 session 內前一技能 → 本技能) +HERE=$(dirname "$0"); . "$HERE/lib.sh" +read_stdin +skill="${JSC_SKILL:-$(json_str skill)}" +[ -n "$skill" ] || exit 0 +sid=$(session_id); cli=$(cli_name); ts=$(now_iso) +printf '{"ts":"%s","cli":"%s","session":"%s","skill":"%s"}\n' \ + "$ts" "$cli" "$sid" "$skill" >> "$JSC_HOME/usage/skills.jsonl" +last_f="$JSC_HOME/sessions/$sid.lastskill" +last=$(cat "$last_f" 2>/dev/null || echo "") +if [ -n "$last" ]; then + printf '{"ts":"%s","cli":"%s","session":"%s","from":"%s","to":"%s"}\n' \ + "$ts" "$cli" "$sid" "$last" "$skill" >> "$JSC_HOME/usage/chains.jsonl" +fi +printf '%s' "$skill" > "$last_f" +exit 0 diff --git a/hooks/ste100-guard.sh b/hooks/ste100-guard.sh new file mode 100755 index 0000000..c9e89e4 --- /dev/null +++ b/hooks/ste100-guard.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env sh +# ste100-guard.sh — 於每次使用者提示時注入語言規則(hook > prompt 的強制層)。 +# Claude: UserPromptSubmit 的 stdout 會成為額外 context。 +# 其他 CLI: 由 hooks-install 以各自的規則檔(AGENTS.md 等)落地,本腳本仍可被 wrapper 呼叫。 +echo "[jsc] 輸出規則:所有回覆基於 STE100 使用繁體中文(短句、一句一指令、主動語態、術語一致、UTF-8 無亂碼)。" +exit 0 diff --git a/skills/hooks-install/SKILL.md b/skills/hooks-install/SKILL.md new file mode 100644 index 0000000..5583b83 --- /dev/null +++ b/skills/hooks-install/SKILL.md @@ -0,0 +1,30 @@ +--- +name: hooks-install +description: Wire jsc hooks (STE100 guard, session timer, skill usage logger) into every installed AI CLI. Detect CLIs via jsc-cli detect-clis.sh, then apply native hook config per CLI or fall back to instruction files. Use after installing or updating the jsc plugin set; not for writing new hooks. +--- + +# hooks-install — 將 jsc hooks 接線到所有已安裝的 CLI + +目標:讓三支 hook(`ste100-guard.sh`、`session-timer.sh`、`skill-usage.sh`)在每個 CLI 生效。 +細節流程**必須以 sub agent 執行**,主 agent 只回報結果摘要。 + +## 步驟 + +1. 執行 `jsc-cli/tools/detect-clis.sh` 取得已安裝的 CLI 與路徑。 +2. 對每個 CLI 依下表接線(hook 優先,無 hook 系統就降級為規則檔): + +| CLI | 接線方式 | +| --- | --- | +| claude | plugin 安裝時自動載入 `jsc-hooks/hooks/hooks.json`,只需驗證:`claude plugin list` 有 `jsc-hooks` 且 `/hooks` 顯示已註冊 | +| codex | `~/.codex/config.toml` 設定 `notify` 指向 `session-timer.sh mark`(帶 `JSC_CLI=codex`);STE100 規則追加到 `~/.codex/AGENTS.md`(prompt 降級) | +| copilot | 無 hook 系統:STE100 規則追加到 `~/.config/copilot/copilot-instructions.md`(prompt 降級) | +| antigravity | 無公開 hook 系統:STE100 規則追加到全域 rules 檔(prompt 降級) | +| kiro | 於工作區 `.kiro/hooks/` 建立 agent hook,觸發時執行對應腳本(帶 `JSC_CLI=kiro`) | + +3. 規則檔追加內容一律使用 `ste100-guard.sh` 的輸出文字,且以 `` 標記包住,重複執行時整段取代不重複追加。 +4. 回報每個 CLI 的接線結果(成功 / 降級為 prompt / 跳過原因)。 + +## 注意 + +- Hook 腳本同時接受 stdin JSON 與環境變數(`JSC_CLI`、`JSC_SESSION_ID`、`JSC_SKILL`),wrapper 只需設環境變數。 +- 資料落在 `$JSC_HOME`(預設 `~/.jsc`),供 `jsc-log:worklog` 與 `jsc-log:stats` 取用。