13 KiB
name, description
| name | description |
|---|---|
| hooks-install | Wire jsc hooks (STE100 guard, session timer, skill usage logger, SDLC model gate, plugin version guard, post-deploy restart gate, 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 eight hooks (ste100-guard.sh, session-timer.sh, skill-usage.sh, sdlc-gate.sh, version-guard.sh, restart-gate.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.
The wiring commands stored in user config use $JSC_HOME/current/jsc-hooks, not the versioned plugin cache path and not the development checkout. tools/wire-cli.sh {cli} creates or refreshes that symlink before it writes notify, shell aliases or Kiro hook JSON, then verifies the linked scripts exist. If the filesystem cannot create the symlink, the script must say so and explicitly fall back to the current root; it must never write a silent broken path.
Only claude has PreToolUse, PostToolUse and UserPromptSubmit, so only claude reports wired. On codex, copilot, antigravity and kiro neither the version guard nor the post-deploy restart gate can 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. On those four the restart gate blocks no skill call whatsoever: the state file is still written and still cleared at the next session start, so the restart itself rests on the jsc-cli:deploy closing message.
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
- 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. - 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 onestatus=purged|skipped|failed reason=...line and you have noted the backup directory path from its[jsc]output. - For each installed CLI, run
tools/wire-cli.sh {cli}. The script owns both the wiring and its verification: it refreshes$JSC_HOME/current/jsc-hooks, writes the config, alias or hook file inside a<!-- jsc-hooks -->(or# jsc-hooks) marker block, re-reads every file it wrote, confirms the block is present and correctly placed, and confirms the stored runtime paths resolve to existing scripts 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 onestatus=line and none exited 2. - For each installed CLI, run
tools/wire-cli.sh smoke {cli}. This runs all eight hooks once each, every wired mode included, plus each decision path of the work-package check and of the restart gate, 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 onestatus=ok|failed reason=...line plus 31 result lines: 14 hook mode lines, 5 work-package decision lines, and 12 restart-gate decision and cleanup lines. The line count is higher than the hook count becausesdlc-gate.sh,comment-scope.shandlang-guard.sheach have multiple wired modes. - For each installed CLI, run
tools/scan-hook-errors.sh --cli {cli}. Only claude keeps hook results in its native records and can answercleanorerrors; codex, copilot, antigravity and kiro answerunavailable, and their runtime evidence comes from step 4 alone. Done when every CLI has printed onestatus=clean|errors|unavailable reason=...line and the fourunavailableCLIs are reported as exactly that, not as clean. - For each error —
purgefailed, wiring failed, smoke failed, or a scanned error withjsc=true— runtools/report-error.sh --hook {script name} --exit {code} --summary "{reason}" --cli {cli}with the script's[jsc]output on stdin, then hand the failure tojsc-hooks:repair, which MUST run as a sub agent and must finish by opening a PR againstdevelop. Aborting the remaining installs here is allowed as long as the repair starts. A scanned error withjsc=falsebelongs to a third-party hook: report it and leave it alone. Done when each error has either anERROR_{HASH}page name on stdout, or an empty exit 0 meaningJSC_WIKI_REPO_ERRORandJSC_WIKI_REPOare both unset — in that second case carry the reason into step 7 instead. Skip this step when every CLI passed all four checks. - 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 againstdevelop.
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.shsets the first two itself. session-timer.shtakesstart(keep an existing start time),restart(always overwrite it, for a CLI with no session id — kiro),markandreport.wire-cli.shpicks the right one per CLI; do not hand-edit the generated hook files.startandrestartalso clear the restart gate whenever they decide this SessionStart is a new session, so the wiring of those two events is what lowers the gate after a restart — a CLI wired without them keeps the gate up until the user setsJSC_RESTART_GATE=off.restart-gate.shblocks jsc skill calls while$JSC_HOME/restart-required.d/{cli}exists — one file per CLI, named after the CLI code — so a freshly deployed skill set is not used by a process still running the old one. Each CLI reads only its own file: another CLI's file never blocks this one, and a restart clears only the file of the CLI that restarted.jsc-cli:deploywrites the current CLI's file throughrestart-gate.sh require {install|update} [{domain}...]at the end of an install or update;restart-gate.sh reportprints one line per file, so it is visible which CLIs still owe a restart. A leftover old-format single file at$JSC_HOME/restart-requiredblocks every CLI and is deleted on the nextclear— transitional only, andhooks/restart-gate.shrecords when it can be dropped. Exempt skills stay callable —jsc-cli:deploy,jsc-hooks:hooks-install,jsc-gitea:wiki,jsc-log:worklog,jsc-log:learn,jsc-meta:*,jsc-ask:ask,jsc-git:pr,jsc-git:commit— because the change report and the worklog still have to be finished after a deploy, and the first six reach that finish line only through the last three: the deploy asks for its mode, the report closes with a PR. The gate matches skill names, not call chains, so a nested call to anything off the list is blocked all the same.hooks/restart-gate.showns the list; guidelines.md「部署後重啟閘門」carries the same nine with a reason per entry. Escape hatch:JSC_RESTART_GATE=off.purgereaches the user-level config only. Hooks that another plugin ships in its ownhooks.jsonstay 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. status claudereads Claude Code'sinstalled_plugins.jsonand checks theinstallPaththat the CLI actually loads. It must not check only thehooks.jsonnext to thewire-cli.shthat happens to be running, because a development checkout can otherwise hide a broken installed plugin.smoketreatssdlc-gate.sh checkexit 2 as healthy: that exit is the stage lock blocking a turn on purpose, not a runtime error.comment-scope.shandlang-guard.shexit 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;sweepdepends 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.shtakes three modes:prompt(inject the rule summary at UserPromptSubmit), no argument at all (scan the file just written at PostToolUse, readingfile_pathfrom stdin JSON orJSC_CHANGED_FILE), andsweep [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 withJSC_COMMENT_SCOPE=off. The rule text itself lives in one place only,jsc-review'sreferences/comment-scope.md; never restate the list anywhere in this repo.lang-guard.shtakes the same three modes ascomment-scope.shand 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.mdand plain-text files, because those are exactly the non-code output the rule targets. It flags three things — simplified characters (word list inhooks/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 byiconv; noiconvskips 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 withJSC_LANG_GUARD=off. The rule text lives only injsc-meta'sreferences/ste100.md.jsc-wrap.shruns both sweeps after the CLI exits and always returns the CLI's own exit code. Asweephit 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.shis 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 byjsc-log:worklogandjsc-log:stats.