Files
hooks/skills/hooks-install/SKILL.md
T
jiantw83 fe3c5723a5 feat(hooks-install): 文件同步到七支 hook
What:`README.md` 的 hook 一覽表新增 `lang-guard.sh` 一列(事件、三種模式、逃生門),所有「六支 hook」改七支,環境變數表補 `JSC_LANG_GUARD`,`tools/jsc-wrap.sh` 那列補收尾 sweep;`skills/hooks-install/SKILL.md` 六支改七支並補覆蓋範圍與備註,`description` 的括號清單加上這支;`AGENTS.md` 清單同步,並新增一條繁中無亂碼規則。

Why:文件寫六支、實際跑七支,使用者與 sub agent 都會照文件辦事,少接的那一支永遠沒有人發現。`AGENTS.md` 是各 CLI 讀得到的規則檔,新規則不寫進去就只剩 hook 在擋,模型自己不會知道。

How:README 維持 STE100 繁中;SKILL.md 維持整份英文,`description` 保持 4 句、保留觸發時機。規則正文一律不在本存取庫留副本,只指向 `jsc-meta` 的 `references/ste100.md`,字表位置指向 `hooks/simplified.txt`。

Who:`jsc-hooks` 的文件層,讀者是使用者與執行 `hooks-install` 的 sub agent。
2026-08-27 09:50:03 +08:00

53 lines
9.6 KiB
Markdown

---
name: hooks-install
description: Wire jsc hooks (STE100 guard, session timer, skill usage logger, SDLC model gate, plugin version guard, comment scope scanner, language guard) into every installed AI CLI, purging all pre-existing hooks first — third-party ones included, backed up before removal. Drive it per CLI through tools/wire-cli.sh purge, tools/wire-cli.sh, tools/wire-cli.sh smoke and tools/scan-hook-errors.sh. Hand any hook error, wiring or runtime, to jsc-hooks:repair, which must finish with a PR against develop; aborting the rest of the install to start that repair is allowed. Use after installing or updating the jsc plugin set; not for writing new hooks.
---
# hooks-install — wire jsc hooks into every installed CLI
Goal: make the seven hooks (`ste100-guard.sh`, `session-timer.sh`, `skill-usage.sh`, `sdlc-gate.sh`, `version-guard.sh`, `comment-scope.sh`, `lang-guard.sh`) effective in every CLI, with nothing else wired alongside them.
Install on a clean slate. Every CLI is purged of all hooks first, third-party ones included, so a later failure has exactly one owner. `tools/wire-cli.sh purge` backs up every file it touches before it removes anything, so the removal stays reversible.
Only claude has PreToolUse, PostToolUse and UserPromptSubmit, so only claude reports `wired`. On codex, copilot, antigravity and kiro the version guard cannot be wired at all and the SDLC gate degrades to the skill-step check, so all four report `degraded` — report that gap as the script words it instead of implying every CLI is covered.
`comment-scope.sh` and `lang-guard.sh` both reach all five, wired at the same set of places, but on a different event and at a different moment each. Report the timing per CLI; never state it as one uniform behaviour:
| CLI | Scanning moment | Wired through |
| --- | --- | --- |
| claude | Per file, the instant it is written | PostToolUse |
| codex | End of every turn, over the whole git worktree | `notify` in `config.toml` |
| kiro | On every prompt submit, over the whole git worktree — it sees what the previous turn wrote | `userPromptSubmit` in `.kiro/hooks/jsc-hooks.json` |
| copilot, antigravity | Once, when the session ends | `tools/jsc-wrap.sh` teardown |
The table above holds for both scanners. The `sweep` mode reads `git diff HEAD`, so its coverage matches what claude sees; only the feedback delay differs. Outside a git worktree `sweep` exits 0 in silence and nothing is scanned at all — say so when the user works outside git. Both `prompt` rule reminders still go into every rule file alongside the STE100 block, because a warning that arrives a turn late is worth less than not writing the offending text in the first place.
The lock file still works on those four because the SDLC skills call `sdlc-gate.sh lock {stage}` directly — that call is where the capability-tag comparison happens, so the gate keeps its force even where the prompt hook cannot be wired.
The gate needs `$JSC_HOME/model-tags.tsv`; when it is missing, report that `jsc-cli:models` (or `jsc-cli/tools/model-tags.sh sync`) must run once, because `sdlc-gate.sh lock` refuses to lock without it.
Treat any hook error as repair work, whether it appeared while wiring or while running. Stopping the remaining installs to start that repair is the right call; leaving a broken hook wired is not.
The detailed flow **MUST run as a sub agent**; the main agent only reports the summary.
## Steps
1. Run `jsc-cli/tools/detect-clis.sh`. Done when you hold the list of installed CLIs; when the list is empty, report that and stop.
2. For each installed CLI, run `tools/wire-cli.sh purge {cli}`. The script backs up every file it touches, removes all hooks, re-reads each file to confirm the removal, and restores the backup by itself when a check fails. Done when every CLI has printed exactly one `status=purged|skipped|failed reason=...` line and you have noted the backup directory path from its `[jsc]` output.
3. For each installed CLI, run `tools/wire-cli.sh {cli}`. The script owns both the wiring and its verification: it writes the config, alias or hook file inside a `<!-- jsc-hooks -->` (or `# jsc-hooks`) marker block, re-reads every file it wrote, and confirms the block is present and correctly placed before it prints a success status. Trust its first line, `status=wired|degraded|skipped|failed reason=...`. Exit 2 means a bad CLI name, not a wiring outcome — fix the name and rerun. Done when every installed CLI has printed exactly one `status=` line and none exited 2.
4. For each installed CLI, run `tools/wire-cli.sh smoke {cli}`. This runs all seven hooks once each, every wired mode included, and catches what the wiring check cannot see: a hook that is wired correctly and still fails when it executes. Done when every CLI has printed one `status=ok|failed reason=...` line plus one result line per hook.
5. For each installed CLI, run `tools/scan-hook-errors.sh --cli {cli}`. Only claude keeps hook results in its native records and can answer `clean` or `errors`; codex, copilot, antigravity and kiro answer `unavailable`, and their runtime evidence comes from step 4 alone. Done when every CLI has printed one `status=clean|errors|unavailable reason=...` line and the four `unavailable` CLIs are reported as exactly that, not as clean.
6. For each error — `purge` failed, wiring failed, smoke failed, or a scanned error with `jsc=true` — run `tools/report-error.sh --hook {script name} --exit {code} --summary "{reason}" --cli {cli}` with the script's `[jsc]` output on stdin, then hand the failure to `jsc-hooks:repair`, which **MUST run as a sub agent** and must finish by opening a PR against `develop`. Aborting the remaining installs here is allowed as long as the repair starts. A scanned error with `jsc=false` belongs to a third-party hook: report it and leave it alone. Done when each error has either an `ERROR_{HASH}` page name on stdout, or an empty exit 0 meaning `JSC_WIKI_REPO_ERROR` and `JSC_WIKI_REPO` are both unset — in that second case carry the reason into step 7 instead. Skip this step when every CLI passed all four checks.
7. Report four results per CLI — purge, wiring, smoke, scan — each with the reason its script printed, plus any `ERROR_{HASH}` page name and repair PR URL. Done when every detected CLI has exactly one status per check and every repair has a PR against `develop`.
## Notes
- Every hook script accepts both stdin JSON and environment variables (`JSC_CLI`, `JSC_SESSION_ID`, `JSC_SKILL`, `JSC_TOOL_NAME`, `JSC_MODEL`); `jsc-wrap.sh` sets the first two itself.
- `session-timer.sh` takes `start` (keep an existing start time), `restart` (always overwrite it, for a CLI with no session id — kiro), `mark` and `report`. `wire-cli.sh` picks the right one per CLI; do not hand-edit the generated hook files.
- `purge` reaches the user-level config only. Hooks that another plugin ships in its own `hooks.json` stay active, and uninstalling that plugin is the only way to clear them — say so when reporting, and treat their errors as third-party.
- Backups land in `$JSC_HOME/backup/hooks/{cli}/{yyyyMMdd_HHmmss}/`, one directory per purge run, under the original file names. Hand that path to the user whenever a purge removed something.
- `smoke` treats `sdlc-gate.sh check` exit 2 as healthy: that exit is the stage lock blocking a turn on purpose, not a runtime error. `comment-scope.sh` and `lang-guard.sh` exit 2 count as healthy for the same reason — the scan found something and warned about it. Their no-argument mode has no file name during smoke and exits 0 in silence; `sweep` depends on the worktree it runs in, so it answers 2 whenever that worktree happens to carry an offending comment, a simplified character or a mojibake sequence. None of these is a broken hook.
- `comment-scope.sh` takes three modes: `prompt` (inject the rule summary at UserPromptSubmit), no argument at all (scan the file just written at PostToolUse, reading `file_path` from stdin JSON or `JSC_CHANGED_FILE`), and `sweep [dir]` (scan every file the git worktree changed, for the four CLIs with no post-tool hook). All scanning modes read only the lines a diff added, skip markdown and binary files, and turn off entirely with `JSC_COMMENT_SCOPE=off`. The rule text itself lives in one place only, `jsc-review`'s `references/comment-scope.md`; never restate the list anywhere in this repo.
- `lang-guard.sh` takes the same three modes as `comment-scope.sh` and is wired at the same places, but it scans differently on purpose: it reads the whole file rather than comment lines only, and it does scan `.md` and plain-text files, because those are exactly the non-code output the rule targets. It flags three things — simplified characters (word list in `hooks/simplified.txt`, the single source of truth for this repo; a missing list skips that check in silence), mojibake (U+FFFD and double-encoding remnants), and non-UTF-8 encoding (decided by `iconv`; no `iconv` skips that check). It skips binaries, generated files, and the three files whose subject is those very characters (`simplified.txt`, `ste100-guard.sh`, `lang-guard.sh`). Turn it off with `JSC_LANG_GUARD=off`. The rule text lives only in `jsc-meta`'s `references/ste100.md`.
- `jsc-wrap.sh` runs both sweeps after the CLI exits and always returns the CLI's own exit code. A `sweep` hit warns on stderr and changes nothing else — never let a language or comment warning turn a successful CLI run into a failed one.
- `tools/report-error.sh` is operator- or skill-invoked only. Never wire it to fire from a failing hook: hooks stay silent and exit 0, and a failing hook that reports itself can loop.
- Data lands in `$JSC_HOME` (default `~/.jsc`), consumed by `jsc-log:worklog` and `jsc-log:stats`.