Skip to content
Agent Orchestration
Hook

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.

From plugin
skills-constitution
2321 skill2 hooks
Install
$ npx -y skills add jiabaobei/skills-constitution --agent claude-code

Ships 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.

  • Matchesstartup|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"
Read hooks/hooks.json

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.

Ships withskills-constitution

Skills 宪法 —— 凌驾于全部技能/工具之上的元规则,强制 Agent 先查后用、有匹配必用、无匹配必搜。跨平台通用(WorkBuddy / Claude / ChatGPT / Cursor / Gemini / ...) English: README_EN.md

Get the whole plugin
Stats
232
Stars
8
Forks
Active
Maintenance
Python
Language
MIT
License
5d ago
Last commit
2mo ago
Created
3d ago
Added

Repo: jiabaobei/skills-constitution