Hooks
What skills-constitution runs automatically, and when. A hook is a command Claude Code fires at a fixed moment, without you asking for it.
$ npx -y skills add jiabaobei/skills-constitution --agent claude-codeShips with skills-constitution. Installing the plugin gets these hooks.
What fires, and when
SessionStart
Fires once when a session begins, and again after a context compaction. It is where a plugin sets up its environment, or restores state the compaction dropped.
- Matches
startup|resume|clear|compactbash "${CODEBUDDY_PLUGIN_ROOT}/hooks/session-start.sh"
UserPromptSubmit
Fires before Claude sees each prompt you send. A plugin can use it to inject context, so the same instruction reaches the model every turn instead of only at session start.
- Matches
.*bash "${CODEBUDDY_PLUGIN_ROOT}/hooks/user-prompt-submit.sh" "$USER_PROMPT"
In the plugin's words
How skills-constitution describes its own hook set.
skills-constitution — 宪法强制拦截钩子(SessionStart 注入 + UserPromptSubmit 校验)v2.23.0;CodeBuddy/Claude Code 插件机制版,WorkBuddy 用 scripts/constitution-gate.py(见 SKILL.md 安装章节)
Where it lives
- hooks/session-start.ps1GitHub
Read the script
# SessionStart hook (Windows): 注入宪法上下文 $PluginRoot = $env:CODEBUDDY_PLUGIN_ROOT if ([string]::IsNullOrWhiteSpace($PluginRoot)) { $PluginRoot = Split-Path -Parent $PSScriptRoot } $HookDir = Join-Path $PluginRoot "hooks" $Output = Join-Path $HookDir "injected-context.json" $PreHook = Join-Path $PluginRoot "scripts\pre-hook.py" $MemoryFile = Join-Path $env:USERPROFILE ".workbuddy\MEMORY.md" $TreeFile = Join-Path $PluginRoot "skill_tree.json" # 确保目录存在 New-Item -ItemType Directory -Force -Path $HookDir | Out-Null # 检查 pre-hook.py if (-not (Test-Path -LiteralPath $PreHook -PathType Leaf)) { @{status="error"; message="pre-hook.py not found"} | ConvertTo-Json -Compress | Set-Content -Path $Output exit 0 } $memoryLen = 0 $treeCats = 0 if (Test-Path -LiteralPath $MemoryFile) { $memoryLen = (Get-Item $MemoryFile).Length } if (Test-Path -LiteralPath $TreeFile) { try { $tree = Get-Content $TreeFile -Raw | ConvertFrom-Json $treeCats = $tree.categories.Count } catch {} } # 调用 pre-hook.py $taskDesc = $env:CODEBUDDY_TASK_DESC if ([string]::IsNullOrWhiteSpace($taskDesc)) { $taskDesc = "默认任务" } $pyResult = & python $PreHook --task $taskDesc --json --memory $MemoryFile --tree $TreeFile 2>$null if (-not $pyResult) { $pyResult = "{}" } try { $result = $pyResult | ConvertFrom-Json $injectedCats = ($result.injected_categories -join ",") $sadCandidates = ($result.sad_candidates | Select-Object -First 3 | ForEach-Object { $_.name }) -join ";" } catch { $injectedCats = "" $sadCandidates = "" } $context = @{ status = "ready" hook = "SessionStart" memory_file = $MemoryFile memory_len = $memoryLen tree_file = $TreeFile tree_categories = $treeCats injected_categories = $injectedCats sad_candidates = $sadCandidates timestamp = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ") } $context | ConvertTo-Json -Compress | Set-Content -Path $Output Write-Host "宪法上下文已注入: memory=${memoryLen}字 tree=${treeCats}分类" exit 0 - hooks/session-start.shRunsGitHub
Read the script
#!/usr/bin/env bash # SessionStart hook: 注入宪法上下文到会话 # 产出 injected-context.json,并把「完整注入块」输出到 stdout(平台会注入 Agent 上下文) # # v2.27.0 单进程化 + 省 token: # ① python 分支从约 6 次进程启动(生成1+JSON解析3+写上下文1)合并为 1 次 # (pre-hook.py --context-out: 原子写入 injected-context.json + stdout 直出注入块) # ② 解释器检测结果缓存 .py-interp(与 user-prompt-submit.sh 共用),热路径 0 次探针 # ③ 路径转换改 bash 内建(替代 sed 子进程) # ④ bash 兜底注入块不再引导 Agent「整读技能树文件」——技能树可达数十万字符, # 极度费 token;改为引导运行 pre-hook 按任务过滤(约 3 千字符) # v2.25.1: 解释器存活探针(3s 限时,Windows Store 占位 python 不再挂起) # v2.13.3: stderr 捕获 / 兜底文案动态化 set -uo pipefail if [[ -n "${CODEBUDDY_PLUGIN_ROOT:-}" ]]; then PLUGIN_ROOT="${CODEBUDDY_PLUGIN_ROOT}" else _SK_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PLUGIN_ROOT="$(dirname "${_SK_SCRIPT_DIR}")" fi if [[ ! -f "${PLUGIN_ROOT}/SKILL.md" ]]; then echo "【宪法钩子·错误】PLUGIN_ROOT 定位失败(${PLUGIN_ROOT} 下无 SKILL.md)。请设置环境变量 CODEBUDDY_PLUGIN_ROOT 指向 skills-constitution 目录。" >&2 exit 1 fi HOOK_DIR="${PLUGIN_ROOT}/hooks" OUTPUT="${HOOK_DIR}/injected-context.json" mkdir -p "${HOOK_DIR}" # 路径转换: Git Bash /c/... → Windows C:/...(bash 内建实现,零进程启动) to_win() { case "$1" in /[a-zA-Z]/*) printf '%s\n' "${1:1:1}:${1:2}" ;; *) printf '%s\n' "$1" ;; esac } MEMORY_FILE="${HOME}/.workbuddy/MEMORY.md" TREE_FILE="${PLUGIN_ROOT}/skill_tree.json" MEMORY_FILE_WIN="$(to_win "${MEMORY_FILE}")" TREE_FILE_WIN="$(to_win "${TREE_FILE}")" memory_len=0 tree_categories=0 INJECTION="" FALLBACK_REASON="" python_branch_ok="no" # ---- 解释器检测: 存活探针(v2.25.1) + 结果缓存(v2.27.0) ---- PY_CMD="" PY_CACHE="${HOOK_DIR}/.py-interp" if [[ -r "${PY_CACHE}" ]]; then _cached="" { read -r _cached; } < "${PY_CACHE}" 2>/dev/null || _cached="" [[ -n "${_cached}" && -x "${_cached}" ]] && PY_CMD="${_cached}" fi if [[ -z "${PY_CMD}" ]]; then _HAVE_TIMEOUT="" command -v timeout >/dev/null 2>&1 && _HAVE_TIMEOUT=1 for c in python3 python py; do command -v "$c" >/dev/null 2>&1 || continue _cand="$(command -v "$c")" # v2.27.0: 垫片真身解析——同目录优先取原生 .exe(少一层 bash 垫片进程; # 慢机器每进程约 2-3s,直接决定钩子能否压进宿主超时) _cdir="${_cand%/*}" _real="" for _alt in python.exe python3.exe; do if [[ -x "${_cdir}/${_alt}" ]]; then _real="${_cdir}/${_alt}"; break; fi done [[ -z "${_real}" ]] && _real="${_cand}" if [[ -n "${_HAVE_TIMEOUT}" ]] && ! timeout 3 "${_real}" -c "pass" 2>/dev/null; then continue; fi PY_CMD="${_real}" printf '%s\n' "${PY_CMD}" > "${PY_CACHE}" 2>/dev/null || true break done fi # ---- 有 python: 单次调用生成注入块 + 原子写入上下文(v2.27.0) ---- PRE_HOOK="${PLUGIN_ROOT}/scripts/pre-hook.py" if [[ -n "${PY_CMD}" && -f "${TREE_FILE}" && -f "${PRE_HOOK}" ]]; then PRE_HOOK_WIN="$(to_win "${PRE_HOOK}")" OUTPUT_WIN="$(to_win "${OUTPUT}")" TASK_DESC="${CODEBUDDY_TASK_DESC:-默认任务}" INJECTION=$("${PY_CMD}" "${PRE_HOOK_WIN}" --task "${TASK_DESC}" --context-out "${OUTPUT_WIN}" 2>"${HOOK_DIR}/pre-hook.err" || true) [[ -n "${INJECTION}" ]] && python_branch_ok="yes" fi # ---- bash 兜底注入块(无 python / python 分支失败时) ---- if [[ -z "${INJECTION}" ]]; then if [[ -f "${MEMORY_FILE}" ]]; then memory_len=$(wc -c < "${MEMORY_FILE}" 2>/dev/null || echo 0) fi if [[ -f "${TREE_FILE}" ]]; then tree_categories=$(grep -o '"[a-z][a-z0-9_-]*": \[' "${TREE_FILE}" 2>/dev/null | grep -v '"categories":' | wc -l 2>/dev/null | tr -d '\r\n ' || echo 0) fi if [[ -z "${PY_CMD}" ]]; then FALLBACK_REASON="no_python" FALLBACK_TAG="bash 兜底版,无 python 环境" else FALLBACK_REASON="python_branch_failed" FALLBACK_TAG="bash 兜底版(python 分支失败,已降级)" fi INJECTION="## ⚡ 宪法 Pre-hook 注入(${FALLBACK_TAG}) > 本会话 Skills 宪法已启用。**任何专业任务开始前必须执行【宪法三查】并显式汇报**: > ① 查记忆:已读用户级 MEMORY.md 与项目记忆 > ② 查技能树:已读技能索引,**必须列出命中的技能名清单**(名称+依据),禁止只写\"已读技能树\" > ③ 匹配必用:有匹配 → 无条件优先加载该技能执行;无匹配 → 说明\"技能树无匹配\"再走通用能力 > > 违规判定:跳过三查直接干 / 有匹配但不用 / 查了但不列技能名(空头汇报)均视为违规。 > > ⚠️ 省 token 死规则:**禁止整读技能树文件**(可达数十万字符)。需要技能详情时运行: > \`\`\` > python \"${PLUGIN_ROOT}/scripts/pre-hook.py\" --task \"当前任务\" > \`\`\` > 只输出按任务过滤的轻量注入块(约 3 千字符)。" # 兜底也要产出上下文文件(UserPromptSubmit 校验依赖 status=ready) if [[ -z "${PY_CMD}" ]]; then cat > "${OUTPUT}" << EOF { "status": "ready", "hook": "SessionStart", "memory_file": "${MEMORY_FILE_WIN}", "memory_len": ${memory_len}, "tree_file": "${TREE_FILE_WIN}", "tree_categories": ${tree_categories:-0}, "injected_categories": "", "sad_candidates": "", "python": "none", "python_branch_ok": "no", "fallback_reason": "${FALLBACK_REASON}", "injection_len": ${#INJECTION}, "debug": {"pre_hook_stderr": "bash fallback"}, "timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)" } EOF else # python 在但 pre-hook 分支失败: 复用 refresh_injection 原子写上下文 "${PY_CMD}" -c " import sys sys.path.insert(0, r'$(to_win "${PLUGIN_ROOT}/scripts")') import importlib.util as ilu spec = ilu.spec_from_file_location('ph_refresh', r'$(to_win "${PRE_HOOK}")') m = ilu.module_from_spec(spec) spec.loader.exec_module(m) m.refresh_injection('默认任务', r'$(to_win "${OUTPUT}")') " 2>>"${HOOK_DIR}/pre-hook.err" || true fi fi # ---- 完整注入块输出到 stdout(平台注入 Agent 上下文)---- printf '%s\n' "${INJECTION}" exit 0 - hooks/user-prompt-submit.shRunsGitHub
Read the script
#!/usr/bin/env bash # UserPromptSubmit hook: 宪法分类器 + 注入上下文保活 # 简单任务 → 通道A跳过 | 专业任务 → 通道B(上下文保活,不拦) # # v2.27.0 重写(钩子单进程化): # 旧版整个钩子要起 9 次进程(解释器探针 3 + stdin解析 1 + sed 2 + 分类器 1 # + 结果解析 1 + 状态检查 1),在慢机器(每进程约2s)上实测 33~39s, # 远超宿主 20s 上限 → 任务被超时拦截。现改为: # ① 解释器缓存 .py-interp(首次探测后落盘,热路径 0 次进程启动) # ② stdin 用 read builtin 限时读取(零进程启动) # ③ 路径转换用 bash 内建(替代 sed) # ④ exec 单次 python pre-hook.py --hook-mode 完成分类+上下文保活 # 热路径总进程启动:1 次(首次 3~4 次)。全程 fail-open。 set -uo pipefail if [[ -n "${CODEBUDDY_PLUGIN_ROOT:-}" ]]; then PLUGIN_ROOT="${CODEBUDDY_PLUGIN_ROOT}" else _SK_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PLUGIN_ROOT="$(dirname "${_SK_SCRIPT_DIR}")" fi if [[ ! -f "${PLUGIN_ROOT}/SKILL.md" ]]; then exit 0 fi PRE_HOOK="${PLUGIN_ROOT}/scripts/pre-hook.py" if [[ ! -f "${PRE_HOOK}" ]]; then exit 0 fi HOOK_DIR="${PLUGIN_ROOT}/hooks" # ---- 路径转换(Git Bash /c/... → Windows C:/...),bash 内建实现,零进程启动 ---- to_win() { case "$1" in /[a-zA-Z]/*) printf '%s\n' "${1:1:1}:${1:2}" ;; *) printf '%s\n' "$1" ;; esac } PRE_HOOK_WIN="$(to_win "${PRE_HOOK}")" # ---- 解释器检测:v2.25.1 存活探针 + v2.27.0 结果缓存 ---- # Windows 上 python 可能是 Microsoft Store 占位别名(启动即挂起),必须真跑一次; # 探针通过后把绝对路径写入缓存,后续每次钩子 0 次探针进程。 PY_CMD="" PY_CACHE="${HOOK_DIR}/.py-interp" if [[ -r "${PY_CACHE}" ]]; then _cached="" { read -r _cached; } < "${PY_CACHE}" 2>/dev/null || _cached="" if [[ -n "${_cached}" && -x "${_cached}" ]]; then PY_CMD="${_cached}" fi fi if [[ -z "${PY_CMD}" ]]; then _HAVE_TIMEOUT="" command -v timeout >/dev/null 2>&1 && _HAVE_TIMEOUT=1 for c in python3 python py; do command -v "$c" >/dev/null 2>&1 || continue _cand="$(command -v "$c")" # v2.27.0: 垫片真身解析——同目录优先取原生 .exe(少一层 bash 垫片进程; # 慢机器每进程约 2-3s,直接决定钩子能否压进宿主超时) _cdir="${_cand%/*}" _real="" for _alt in python.exe python3.exe; do if [[ -x "${_cdir}/${_alt}" ]]; then _real="${_cdir}/${_alt}"; break; fi done [[ -z "${_real}" ]] && _real="${_cand}" if [[ -n "${_HAVE_TIMEOUT}" ]] && ! timeout 3 "${_real}" -c "pass" 2>/dev/null; then continue; fi PY_CMD="${_real}" printf '%s\n' "${PY_CMD}" > "${PY_CACHE}" 2>/dev/null || true break done fi if [[ -z "${PY_CMD}" ]]; then exit 0 # 无可用解释器:降级放行(fail-open,不卡任务) fi # ---- 读取任务描述:$1 优先,其次 stdin JSON(builtin 限时读取) ---- # 任务文本经管道喂给 python(写方是本钩子自己,写完即 EOF,python 不会挂起) if [[ -n "${1:-}" ]]; then exec "${PY_CMD}" "${PRE_HOOK_WIN}" --hook-mode --task "${1}" else _payload="" read -r -t 2 _payload || true if [[ -z "${_payload}" ]]; then exit 0 # 无任务描述:放行 fi printf '%s' "${_payload}" | "${PY_CMD}" "${PRE_HOOK_WIN}" --hook-mode fi exit 0
Read the script before you install anything that runs on your machine. This is the one part of a plugin that acts without being asked.
Skills 宪法 —— 凌驾于全部技能/工具之上的元规则,强制 Agent 先查后用、有匹配必用、无匹配必搜。跨平台通用(WorkBuddy / Claude / ChatGPT / Cursor / Gemini / ...) English: README_EN.md

