Skip to content
Development
Hook

Hooks

What vibe-dev runs automatically, and when. A hook is a command Claude Code fires at a fixed moment, without you asking for it.

From plugin
vibe-dev
529 skills24 agents7 hooks
Install
> /plugin marketplace add andrewcigan/vibe-dev-plugin
> /plugin install vibe-dev@vibe-dev

Ships with vibe-dev. Installing the plugin gets these hooks.

What fires, and when

PreToolUse

  • MatchesWrite|Edit|MultiEdit|Bashbash "${CLAUDE_PLUGIN_ROOT}/hooks/dispatch-pre-tool-use.sh"

PostToolUse

  • MatchesBash|Edit|Write|MultiEditbash "${CLAUDE_PLUGIN_ROOT}/hooks/dispatch-post-tool-use.sh"

Stop

  • bash "${CLAUDE_PLUGIN_ROOT}/hooks/dispatch-stop.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.

  • bash "${CLAUDE_PLUGIN_ROOT}/hooks/dispatch-user-prompt.sh"

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.

  • bash "${CLAUDE_PLUGIN_ROOT}/hooks/dispatch-session-start.sh"

MessageDisplay

  • bash "${CLAUDE_PLUGIN_ROOT}/hooks/dispatch-message-display.sh"

PreCompact

  • bash "${CLAUDE_PLUGIN_ROOT}/hooks/pre-compact.sh"
Read hooks/hooks.json

Where it lives

  • hooks/dispatch-message-display.shRunsGitHub
    Read the script
    #!/bin/bash
    # Vibe Dev v6 — MessageDisplay dispatcher (точка входа на событие MessageDisplay). Язык-ловец.
    #
    # Поток: stdin JSON -> guard (vibe-проект?) -> профиль -> clarity-detector по .message_text.
    # Нарушение (жаргон / развилка без «что теряешь» / человеко-дни) -> (1) запись в
    # .harness/clarity-violations.log (метрика повторов), (2) displayContent = оригинал + флаг
    # ловца на экране пользователя.
    #
    # ЧЕСТНО: MessageDisplay display-only (контракт code.claude.com/docs/en/hooks) — меняет только
    # экран, оригинал сохраняется в transcript и его читает Claude. То есть это НЕ enforcement
    # поведения модели (заставить меня говорить иначе хук не может), а ДЕТЕКТОР: меряет + подсвечивает,
    # чтобы нарушение не прошло тихо. Дисциплину держит rules/decision-format.md. standard/strict.
    
    set -u
    DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
    . "$DIR/lib/hook-io.sh"
    export VIBE_HOOK_EVENT="MessageDisplay"   # для журнала срабатываний (v9 F0.1)
    
    hook_read_stdin
    CWD="$(hook_field '.cwd')"
    [ -z "$CWD" ] && CWD="$PWD"
    
    hook_is_vibe_project "$CWD" || hook_emit_pass
    
    PROFILE="$(hook_profile "$CWD")"
    ROOT="$(hook_plugin_root)"
    profile_in "standard,strict" "$PROFILE" || hook_emit_pass
    
    # hook_run_check (fail-loud): краш детектора -> пометка на экране + crash-артефакт.
    ISSUES="$(HOOK_PAYLOAD="$HOOK_INPUT" hook_run_check "$CWD" "clarity-detector" text "$ROOT/hooks/checks/clarity-detector.sh" "$CWD")"
    if [ -n "$(printf '%s' "$ISSUES" | tr -d '[:space:]')" ]; then
      MSG="$(hook_field '.message_text')"
      case "$ISSUES" in
        "⚠️ сторож"*)
          # Детектор УПАЛ — это не «нарушение языка»: в лог нарушений не пишем, краш показываем.
          hook_emit_display "$MSG
    
    $ISSUES"
          ;;
      esac
      # (1) метрика повторов — лог нарушений (для /audit и саморефлексии).
      mkdir -p "$CWD/.harness" 2>/dev/null
      printf '%s\t%s\n' "$(date '+%Y-%m-%d %H:%M' 2>/dev/null || echo '?')" "$ISSUES" >> "$CWD/.harness/clarity-violations.log" 2>/dev/null
      # (2) флаг на экране пользователя (оригинал + пометка). Только экран — Claude читает оригинал.
      hook_emit_display "$MSG
    
    ⚠️ ловец непонятного: $ISSUES. Это для тебя сигнал — можешь попросить сказать проще/по-деловому."
    fi
    hook_emit_pass
    
  • hooks/dispatch-post-tool-use.shRunsGitHub
    Read the script
    #!/bin/bash
    # Vibe Dev v6 — PostToolUse dispatcher (точка входа на событие PostToolUse).
    #
    # ⚠️ Живая модель событий (проверка 2026-06-10, движок 2.1.170): PostToolUse для Bash
    # приходит НЕ на всех командах, и полей tool_response.exit_code/success НЕТ:
    #   - падение С ВЫВОДОМ (stdout или stderr) -> события НЕТ вообще;
    #   - тихое падение (exit!=0 без вывода)    -> событие ЕСТЬ, в tool_response появляется
    #     returnCodeInterpretation ("Condition is false" и т.п.) — признак не-успеха;
    #   - чистый успех                          -> событие есть, returnCodeInterpretation нет.
    # Поэтому:
    #   Bash: чистый успех (нет interrupted и нет returnCodeInterpretation) -> СБРОС счётчика
    #         повторов; secret-mask по .tool_response.stdout — на любом пришедшем событии.
    #   Edit/Write/MultiEdit -> сброс счётчика повторов (структурное изменение = прогресс).
    # Инкремент счётчика повторов живёт в PreToolUse-диспетчере (приходит на каждый запуск).
    #
    # warn/inject (не block — PostToolUse не может отменить уже выполненный инструмент). Активен
    # standard,strict; minimal — off.
    
    set -u
    DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
    . "$DIR/lib/hook-io.sh"
    export VIBE_HOOK_EVENT="PostToolUse"   # для журнала срабатываний (v9 F0.1)
    
    hook_read_stdin
    CWD="$(hook_field '.cwd')"
    [ -z "$CWD" ] && CWD="$PWD"
    
    # Guard: только vibe-target-проекты.
    hook_is_vibe_project "$CWD" || hook_emit_pass
    
    PROFILE="$(hook_profile "$CWD")"
    ROOT="$(hook_plugin_root)"
    
    # Активен только в standard,strict.
    profile_in "standard,strict" "$PROFILE" || hook_emit_pass
    
    TOOL="$(hook_field '.tool_name')"
    
    # v9 F3.3: пин модели субагента — сверка с реестром по факту старта, а не по фронтматтеру.
    if [ "$TOOL" = "Agent" ]; then
      PIN="$(HOOK_PAYLOAD="$HOOK_INPUT" hook_run_check "$CWD" "model-pin" text "$ROOT/hooks/checks/model-pin-verify.sh" "$CWD" "$ROOT")"
      if [ -n "$(printf '%s' "$PIN" | tr -d '[:space:]')" ]; then
        hook_emit_context "PostToolUse" "$(printf '%s' "$PIN" | sed 's/^WARN\t//')"
      fi
    fi
    
    case "$TOOL" in
      Bash)
        # Чистый успех = нет interrupted (^C) и нет returnCodeInterpretation (тихое падение
        # exit!=0 без вывода) -> сброс счётчика повторов.
        INTERRUPTED="$(hook_field '.tool_response.interrupted')"
        RCI="$(hook_field '.tool_response.returnCodeInterpretation')"
        if [ "$INTERRUPTED" != "true" ] && [ -z "$RCI" ]; then
          rm -f "$CWD/.harness/bash-repeat-state" 2>/dev/null
        fi
        # secret-mask (F8): живой токен в выводе -> updatedToolOutput (маска) + additionalContext
        # (предупреждение). Живая проверка 2026-06-10 (2.1.170): updatedToolOutput движком
        # ИГНОРИРУЕТСЯ (вывод у модели остаётся как был — safe-деградация), поэтому рабочий канал —
        # additionalContext: громкое предупреждение «не переиспользуй литерал, предложи ротацию».
        # Оба поля шлём в одном объекте: поддержит будущий движок маску — включится сама.
        MASKED="$(HOOK_PAYLOAD="$HOOK_INPUT" hook_run_check "$CWD" "secret-mask" text "$ROOT/hooks/checks/secret-mask-output.sh" "$CWD")"
        if [ -n "$(printf '%s' "$MASKED" | tr -d '[:space:]')" ]; then
          case "$MASKED" in
            "⚠️ сторож"*) hook_emit_context "PostToolUse" "$MASKED" ;;  # краш чекера -> предупреждение
            *)
              WARN_TEXT="⚠️ Vibe Dev (secret-mask): в выводе команды был ЖИВОЙ токен/ключ. Не печатай и не переиспользуй его литералом — обращайся только как \$ИМЯ_ПЕРЕМЕННОЙ из .env. Токен уже засветился в контексте — предложи пользователю ротацию."
              jq -cn --arg o "$MASKED" --arg c "$WARN_TEXT" \
                '{hookSpecificOutput:{hookEventName:"PostToolUse", updatedToolOutput:$o, additionalContext:$c}}'
              exit 0
              ;;
          esac
        fi
        ;;
      Edit|Write|MultiEdit)
        # Структурное изменение -> сброс счётчика повторов (это прогресс, не слепой retry).
        rm -f "$CWD/.harness/bash-repeat-state" 2>/dev/null
        ;;
    esac
    hook_emit_pass
    
  • hooks/dispatch-pre-tool-use.shRunsGitHub
    Read the script
    #!/bin/bash
    # Vibe Dev v6 — PreToolUse dispatcher (единая точка входа на событие PreToolUse).
    #
    # Поток: stdin JSON -> разбор полей -> guard (vibe-проект?) -> профиль -> роутинг по tool_name
    #        -> сбор вердиктов проверок -> ОДИН stdout JSON (block | warn) или тихий pass.
    #
    # Проверки (hooks/checks/*.sh) печатают строки "<VERDICT><TAB><msg>" (BLOCK|WARN), пусто = OK.
    # Диспетчер — единственное место, которое реально эмитит ответ Claude Code.
    #
    # Контракт: docs/hooks-contract-verified-2026-06-03.md
    
    set -u
    DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
    . "$DIR/lib/hook-io.sh"
    export VIBE_HOOK_EVENT="PreToolUse"   # для журнала срабатываний (v9 F0.1)
    
    hook_read_stdin
    TOOL="$(hook_field '.tool_name')"
    CWD="$(hook_field '.cwd')"
    [ -z "$CWD" ] && CWD="$PWD"
    FILE="$(hook_field '.tool_input.file_path')"
    
    # Guard: трогаем только vibe-target-проекты (есть .harness/ или feature_list.json).
    hook_is_vibe_project "$CWD" || hook_emit_pass
    
    PROFILE="$(hook_profile "$CWD")"
    ROOT="$(hook_plugin_root)"
    TAB="$(printf '\t')"
    
    BLOCKS=""
    WARNS=""
    
    # add_verdict <multiline-output-проверки> — раскладывает строки по аккумуляторам.
    # Используется heredoc (не pipe) чтобы переменные остались в текущем shell (bash 3.2).
    add_verdict() {
      local _v _m
      while IFS="$TAB" read -r _v _m; do
        [ -z "$_v" ] && continue
        case "$_v" in
          BLOCK) BLOCKS="${BLOCKS}- ${_m}
    " ;;
          WARN)  WARNS="${WARNS}- ${_m}
    " ;;
        esac
      done <<EOF
    $1
    EOF
    }
    
    # --- Роутинг ---
    case "$TOOL" in
      Write|Edit|MultiEdit)
        case "$FILE" in
          *feature_list.json)
            if profile_in "standard,strict" "$PROFILE"; then
              # HOOK_PAYLOAD прокидывает намерение (tool_input.content/old_string/edits) в проверку,
              # чтобы валидировать то, что записывается, а не старое состояние диска.
              # hook_run_check (fail-loud): краш проверки -> WARN + crash-артефакт, не молчаливый pass.
              add_verdict "$(HOOK_PAYLOAD="$HOOK_INPUT" hook_run_check "$CWD" "state-transition" verdict "$ROOT/hooks/checks/state-transition.sh" "$FILE" "$CWD" "$ROOT" "$TOOL")"
            fi
            ;;
        esac
        # secret-scan (v7 P14): хардкод ЖИВОГО ключа в src -> block. Все профили (safety, как bulk-api).
        # Escape: цель .env-семейство ИЛИ маркер secret-scan-off (фраза «ключ тестовый/забей»).
        add_verdict "$(HOOK_PAYLOAD="$HOOK_INPUT" hook_run_check "$CWD" "secret-scan" verdict "$ROOT/hooks/checks/secret-scan-write.sh" "$CWD" "$FILE" "$TOOL")"
        # folder-scope (v7 P9 → v8 L5-F3): запись ВНЕ корня -> WARN по умолчанию (промоушн из log-only).
        # Все профили, безвредно (warn, не block; block отложен до замера % ложных на корпусе).
        add_verdict "$(HOOK_PAYLOAD="$HOOK_INPUT" hook_run_check "$CWD" "folder-scope" verdict "$ROOT/hooks/checks/folder-scope.sh" "$CWD" "$FILE")"
        # concurrent-write advisory для shared-форматов (json/csv/jsonl/yaml), standard/strict
        if profile_in "standard,strict" "$PROFILE"; then
          add_verdict "$(HOOK_PAYLOAD="$HOOK_INPUT" hook_run_check "$CWD" "concurrent-write" verdict "$ROOT/hooks/checks/concurrent-write.sh" "$FILE" "$CWD")"
          # model-swap-guard: правка вносит модель/настройку, влияющую на каждый вывод -> warn (smoke).
          add_verdict "$(HOOK_PAYLOAD="$HOOK_INPUT" hook_run_check "$CWD" "model-swap-guard" verdict "$ROOT/hooks/checks/model-swap-guard.sh" "$CWD")"
          # research-гейт архитектуры (F6): docs/ARCHITECTURE*.md без docs/research/*.md и без
          # хук-маркера research-skipped -> block (распоряжение пользователя 2026-06-10).
          add_verdict "$(HOOK_PAYLOAD="$HOOK_INPUT" hook_run_check "$CWD" "architecture-research" verdict "$ROOT/hooks/checks/architecture-research-gate.sh" "$FILE" "$CWD")"
        fi
        ;;
      Bash)
        # bulk-API gate активен во ВСЕХ профилях (про деньги/safety, learn-режим не понижает).
        add_verdict "$(HOOK_PAYLOAD="$HOOK_INPUT" hook_run_check "$CWD" "bulk-api" verdict "$ROOT/hooks/checks/bulk-api.sh" "$CWD" "$ROOT")"
        # bash-repeat (анти-залипание №2): инкремент счётчика класса команды ЗДЕСЬ — PreToolUse
        # приходит на каждый запуск; PostToolUse приходит только на успехе и сбрасывает (живая
        # модель событий 2.1.170, проверка 2026-06-10). На пороге (3-й запуск без успеха) -> warn.
        if profile_in "standard,strict" "$PROFILE"; then
          add_verdict "$(HOOK_PAYLOAD="$HOOK_INPUT" hook_run_check "$CWD" "bash-repeat" verdict "$ROOT/hooks/checks/bash-repeat-counter.sh" "$CWD")"
        fi
        # v9 F1.3, первый рубеж: журнал фич правится только через record-change. Сторож переходов
        # видит вносимое содержимое при Write/Edit и НЕ видит записи командой оболочки — дыра
        # подтверждалась дважды независимыми проверками.
        add_verdict "$(HOOK_PAYLOAD="$HOOK_INPUT" hook_run_check "$CWD" "feature-list-bash" verdict "$ROOT/hooks/checks/feature-list-bash-guard.sh" "$CWD")"
        # Второй рубеж, фаза «до»: снимок состояния журнала фич. Не зависит от формы команды.
        HOOK_PAYLOAD="$HOOK_INPUT" hook_run_check "$CWD" "feature-list-drift" verdict "$ROOT/hooks/checks/feature-list-drift.sh" "$CWD" before >/dev/null
        ;;
    esac
    
    # v9 F3.2: роль верхнего уровня (архитектура/план/критика/аудит) не пишет код — решение
    # владельца «Fable рассуждает, код не пишет». Действует и на правку файлов, и на запись
    # командой оболочки: иначе замок обходится одной строкой, как это уже случалось.
    add_verdict "$(HOOK_PAYLOAD="$HOOK_INPUT" hook_run_check "$CWD" "role-scope" verdict "$ROOT/hooks/checks/role-scope-guard.sh" "$CWD")"
    
    # lock-protect (F6, lock-паттерн): .harness/locks/* пишут только хуки — запись агентом
    # (Write/Edit/Bash-redirect) блокируется во ВСЕХ профилях (это инфраструктура согласий).
    add_verdict "$(HOOK_PAYLOAD="$HOOK_INPUT" hook_run_check "$CWD" "locks-protect" verdict "$ROOT/hooks/checks/locks-protect.sh" "$CWD")"
    
    # closing-mode (F7, П6): во время закрытия сессии — деградация прав: запись только в
    # state-файлы, Bash только git/read-only/скрипты плагина. standard/strict.
    if profile_in "standard,strict" "$PROFILE"; then
      add_verdict "$(HOOK_PAYLOAD="$
  • hooks/dispatch-session-start.shRunsGitHub
    Read the script
    #!/bin/bash
    # Vibe Dev v6 — SessionStart dispatcher (точка входа на событие SessionStart). H6 loop-замыкание.
    #
    # Поток: stdin JSON -> guard (vibe-проект?) -> профиль -> handoff-pending-probe
    #        -> inject additionalContext (warn о возможном пропуске handoff) или тихий pass.
    #
    # Активен standard,strict; minimal — off. Контракт SessionStart:
    # docs/hooks-contract-verified-2026-06-03.md §4 (stdout идёт как контекст).
    
    set -u
    DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
    . "$DIR/lib/hook-io.sh"
    export VIBE_HOOK_EVENT="SessionStart"   # для журнала срабатываний (v9 F0.1)
    
    hook_read_stdin
    CWD="$(hook_field '.cwd')"
    [ -z "$CWD" ] && CWD="$PWD"
    
    # Guard: только vibe-target-проекты.
    hook_is_vibe_project "$CWD" || hook_emit_pass
    
    # Активация (v6.2 F2): heartbeat «хуки живы» + перевод pending-профиля в боевой.
    # ДО фильтра профиля: heartbeat — свидетельство активации для ЛЮБОГО профиля.
    hook_write_heartbeat "$CWD"
    ACTIVATED="$(hook_activate_pending_profile "$CWD")"
    
    PROFILE="$(hook_profile "$CWD")"
    ROOT="$(hook_plugin_root)"
    
    # Активен только в standard,strict.
    if ! profile_in "standard,strict" "$PROFILE"; then
      [ -n "$ACTIVATED" ] && hook_emit_context "SessionStart" "✅ Vibe Dev: enforcement активирован живым хуком — профиль «${ACTIVATED}» подтверждён."
      hook_emit_pass
    fi
    
    # hook_run_check (fail-loud): краш проверки -> WARN-строка + crash-артефакт.
    VERDICT="$(HOOK_PAYLOAD="$HOOK_INPUT" hook_run_check "$CWD" "handoff-probe" verdict "$ROOT/hooks/checks/handoff-pending-probe.sh" "$CWD")"
    MSG="$(printf '%s' "$VERDICT" | awk -F'\t' '$1=="WARN"{print $2; exit}')"
    
    # Crash-probe (F1): на старте сессии сообщить о крашах сторожей из прошлых сессий —
    # fail-loud не должен оставаться незамеченным (иначе деградирует обратно в тихий fail-open).
    CRASHES=""
    if [ -d "$CWD/.harness/hook-crashes" ]; then
      CRASH_LIST="$(ls -1 "$CWD/.harness/hook-crashes" 2>/dev/null | sed 's/\.log$//' | tr '\n' ' ')"
      if [ -n "$(printf '%s' "$CRASH_LIST" | tr -d '[:space:]')" ]; then
        CRASHES="⚠️ Vibe Dev: в прошлых сессиях ПАДАЛИ сторожа: ${CRASH_LIST}— их проверки тогда НЕ выполнялись. Открой .harness/hook-crashes/*.log, почини причину (или сообщи о баге плагина), затем удали логи. Пока логи на месте — действия, которые сторожа охраняют, требуют ручной проверки."
      fi
    fi
    
    # Сборка inject: активация + handoff-warn + crash-probe (что есть).
    OUT=""
    [ -n "$ACTIVATED" ] && OUT="✅ Vibe Dev: enforcement активирован живым хуком — профиль «${ACTIVATED}» подтверждён."
    if [ -n "$MSG" ]; then
      [ -n "$OUT" ] && OUT="$OUT
    
    "
      OUT="${OUT}${MSG}"
    fi
    if [ -n "$CRASHES" ]; then
      [ -n "$OUT" ] && OUT="$OUT
    
    "
      OUT="${OUT}${CRASHES}"
    fi
    
    # v9 F5.3: разросшиеся журналы — счёт, который иначе не виден. Замер по живым проектам:
    # журнал фич доходил до 1,16 МБ (≈290 тысяч токенов на каждое чтение), потому что ротация
    # искала состояние, которого нет в графе переходов, и не срабатывала ни разу.
    JOURNALS="$(HOOK_PAYLOAD="$HOOK_INPUT" hook_run_check "$CWD" "journal-size" text "$ROOT/hooks/checks/journal-size-probe.sh" "$CWD")"
    if [ -n "$(printf '%s' "$JOURNALS" | tr -d '[:space:]')" ]; then
      [ -n "$OUT" ] && OUT="$OUT
    
    "
      OUT="${OUT}${JOURNALS}"
    fi
    
    # Канал доставки правок (v7): установлен новый мажор плагина, а проект закреплён ниже.
    UPGRADE="$(hook_upgrade_nudge "$CWD")"
    if [ -n "$UPGRADE" ]; then
      [ -n "$OUT" ] && OUT="$OUT
    
    "
      OUT="${OUT}${UPGRADE}"
    fi
    
    # C1 (v7 Волна 2): бриф возврата — активные фичи + тупики + слепок сжатия + recall-фраза.
    BRIEF="$(hook_cold_start_brief "$CWD")"
    if [ -n "$BRIEF" ]; then
      [ -n "$OUT" ] && OUT="$OUT
    
    "
      OUT="${OUT}${BRIEF}"
    fi
    
    [ -n "$OUT" ] && hook_emit_context "SessionStart" "$OUT"
    hook_emit_pass
    
  • hooks/dispatch-stop.shRunsGitHub
    Read the script
    #!/bin/bash
    # Vibe Dev v6.2 — ЕДИНЫЙ Stop dispatcher (F3). На событии Stop живёт несколько сторожей —
    # без единой точки они каскадят блоки и зацикливают ход. Здесь: приоритеты + общий cap.
    #
    # Приоритеты (один block за раз; после доделки Stop сработает снова и проверит заново):
    #   1. stop-intent (H19): обещал действие, не сделал -> заставить ДОДЕЛАТЬ (block).
    #   2. clarity-gate (F4): сообщение непонятно пользователю -> короткий аддендум (block).
    #   3. (v6.3, слот) wave-continue: прогон утверждённого блока фич. Пререквизит — passthrough
    #      вопросов: ход с вопросом пользователю wave НЕ продолжает (HALT, не давим вопрос).
    #
    # Общий cap: ≤3 block на цепочку хода (свой, ниже системного cap 8). Цепочка = от промпта
    # пользователя до промпта (UserPromptSubmit-диспетчер сбрасывает счётчик). При переполнении —
    # pass с записью в .harness/stop-cap-log (видно /audit), НЕ бесконечная переписка.
    #
    # Активен standard,strict. Контракт Stop: decision:block / additionalContext (>=2.1.163).
    
    set -u
    DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
    . "$DIR/lib/hook-io.sh"
    export VIBE_HOOK_EVENT="Stop"   # для журнала срабатываний (v9 F0.1)
    
    hook_read_stdin
    CWD="$(hook_field '.cwd')"
    [ -z "$CWD" ] && CWD="$PWD"
    
    # Guard: только vibe-target-проекты.
    hook_is_vibe_project "$CWD" || hook_emit_pass
    
    PROFILE="$(hook_profile "$CWD")"
    ROOT="$(hook_plugin_root)"
    
    # Активен только в standard,strict.
    profile_in "standard,strict" "$PROFILE" || hook_emit_pass
    
    # --- Общий cap цепочки Stop-блоков ---
    CHAIN_FILE="$CWD/.harness/stop-chain-count"
    CHAIN=0
    if [ -f "$CHAIN_FILE" ]; then
      CHAIN="$(tr -dc '0-9' < "$CHAIN_FILE" 2>/dev/null)"
      CHAIN="${CHAIN:-0}"
    fi
    
    # emit_capped_block <reason> — block с учётом общего cap (3 на цепочку хода).
    emit_capped_block() {
      if [ "$CHAIN" -ge 3 ]; then
        mkdir -p "$CWD/.harness" 2>/dev/null
        printf '%s\tcap-pass\t%s\n' "$(date '+%Y-%m-%d %H:%M' 2>/dev/null || echo '?')" "$1" \
          >> "$CWD/.harness/stop-cap-log" 2>/dev/null
        hook_emit_pass
      fi
      mkdir -p "$CWD/.harness" 2>/dev/null
      printf '%s\n' "$((CHAIN + 1))" > "$CHAIN_FILE" 2>/dev/null
      hook_emit_stop_block "$1"
    }
    
    WARNS=""
    
    # --- Приоритет 1: stop-intent (H19) — обещание действия без tool_use ---
    # hook_run_check (fail-loud): краш проверки -> WARN-строка + crash-артефакт.
    VERDICT="$(HOOK_PAYLOAD="$HOOK_INPUT" hook_run_check "$CWD" "stop-intent" verdict "$ROOT/hooks/checks/stop-intent-without-action.sh" "$CWD")"
    BLOCK_MSG="$(printf '%s' "$VERDICT" | awk -F'\t' '$1=="BLOCK"{print $2; exit}')"
    if [ -n "$BLOCK_MSG" ]; then
      emit_capped_block "Vibe Dev (профиль ${PROFILE}): ${BLOCK_MSG}"
    fi
    W="$(printf '%s' "$VERDICT" | awk -F'\t' '$1=="WARN"{print $2; exit}')"
    [ -n "$W" ] && WARNS="$W"
    
    # --- Приоритет 2: clarity-gate (F4) — ясность финального сообщения ---
    # Боль №1 аудита: жаргон / развилки без рекомендации / человеко-дни доходили до пользователя.
    # BLOCK -> агент дописывает короткий аддендум (не переписывает всё). Tiered по precision.
    CL_VERDICT="$(HOOK_PAYLOAD="$HOOK_INPUT" hook_run_check "$CWD" "clarity-gate" verdict "$ROOT/hooks/checks/clarity-stop-gate.sh" "$CWD" "$PROFILE")"
    CL_BLOCK="$(printf '%s' "$CL_VERDICT" | awk -F'\t' '$1=="BLOCK"{print $2; exit}')"
    if [ -n "$CL_BLOCK" ]; then
      emit_capped_block "Vibe Dev (clarity-gate, профиль ${PROFILE}): ${CL_BLOCK}"
    fi
    CW="$(printf '%s' "$CL_VERDICT" | awk -F'\t' '$1=="WARN"{print $2; exit}')"
    if [ -n "$CW" ]; then
      if [ -n "$WARNS" ]; then WARNS="$WARNS
    $CW"; else WARNS="$CW"; fi
    fi
    
    # --- Приоритет 3 (v7 P6): wave-continue — в go-режиме ход кончился вопросом -> gentle inject. ---
    # Только warn/inject (не block, не capped-block): вопрос не давим (passthrough бизнес-развилок).
    # Не запускается, если приоритет 1/2 уже сделал block (emit_capped_block выходит из скрипта).
    WC_VERDICT="$(HOOK_PAYLOAD="$HOOK_INPUT" hook_run_check "$CWD" "wave-continue" verdict "$ROOT/hooks/checks/wave-continue.sh" "$CWD")"
    WCW="$(printf '%s' "$WC_VERDICT" | awk -F'\t' '$1=="WARN"{print $2; exit}')"
    if [ -n "$WCW" ]; then
      if [ -n "$WARNS" ]; then WARNS="$WARNS
    $WCW"; else WARNS="$WCW"; fi
    fi
    
    # WARN'ы (например, краш проверки) -> мягкий additionalContext (>=2.1.163; старые движки игнорируют).
    if [ -n "$WARNS" ]; then
      hook_emit_stop_context "Vibe Dev: ${WARNS}"
    fi
    hook_emit_pass
    
  • hooks/dispatch-user-prompt.shRunsGitHub
    Read the script
    #!/bin/bash
    # Vibe Dev v6 — UserPromptSubmit dispatcher (точка входа на событие UserPromptSubmit). H6.
    #
    # Поток: stdin JSON -> guard (vibe-проект?) -> профиль -> handoff-reminder
    #        -> inject additionalContext (cold-start чеклист) или тихий pass.
    #
    # handoff-reminder — warn-уровень (inject, НЕ block): промпт пользователя легитимен,
    # блокировать его нельзя; напоминание видно модели. Активен standard,strict; minimal — off.
    # Контракт UserPromptSubmit: docs/hooks-contract-verified-2026-06-03.md §4 (stdout как контекст).
    
    set -u
    DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
    . "$DIR/lib/hook-io.sh"
    export VIBE_HOOK_EVENT="UserPromptSubmit"   # для журнала срабатываний (v9 F0.1)
    
    hook_read_stdin
    CWD="$(hook_field '.cwd')"
    [ -z "$CWD" ] && CWD="$PWD"
    
    # Guard: только vibe-target-проекты.
    hook_is_vibe_project "$CWD" || hook_emit_pass
    
    # Активация (v6.2 F2): heartbeat «хуки живы» + перевод pending-профиля в боевой.
    # UserPromptSubmit срабатывает каждое сообщение -> bootstrap активируется без рестарта.
    hook_write_heartbeat "$CWD"
    ACTIVATED="$(hook_activate_pending_profile "$CWD")"
    
    # Новый промпт пользователя = новая цепочка хода: сброс общего cap Stop-блоков (F3)
    # и счётчика дописок clarity-gate (F4).
    rm -f "$CWD/.harness/stop-chain-count" "$CWD/.harness/clarity-stop-count" 2>/dev/null
    
    PROFILE="$(hook_profile "$CWD")"
    ROOT="$(hook_plugin_root)"
    
    # Активен только в standard,strict.
    if ! profile_in "standard,strict" "$PROFILE"; then
      [ -n "$ACTIVATED" ] && hook_emit_context "UserPromptSubmit" "✅ Vibe Dev: enforcement активирован живым хуком — профиль «${ACTIVATED}» подтверждён."
      hook_emit_pass
    fi
    
    PIECES=""
    [ -n "$ACTIVATED" ] && PIECES="✅ Vibe Dev: enforcement активирован живым хуком — профиль «${ACTIVATED}» подтверждён (скажи пользователю одной строкой)."
    
    # H6: сигнал завершения сессии -> cold-start чеклист + маркер handoff-pending.
    # hook_run_check (fail-loud): краш проверки -> абзац-предупреждение + crash-артефакт.
    HANDOFF="$(HOOK_PAYLOAD="$HOOK_INPUT" hook_run_check "$CWD" "handoff-reminder" text "$ROOT/hooks/checks/handoff-reminder.sh" "$CWD")"
    if [ -n "$(printf '%s' "$HANDOFF" | tr -d '[:space:]')" ]; then
      case "$HANDOFF" in
        "⚠️ сторож"*)
          # Проверка УПАЛА, не успев ничего решить — это НЕ сигнал завершения: маркеры не ставим,
          # но предупреждение о краше доносим.
          ;;
        *)
          # Маркер для SessionStart-probe (loop H6): следующий старт проверит, обновился ли
          # SESSION.md после этого сигнала. Если нет — handoff мог не записаться -> warn.
          mkdir -p "$CWD/.harness/locks" 2>/dev/null
          : > "$CWD/.harness/handoff-pending" 2>/dev/null
          # closing-mode (F7, П6): на время закрытия — деградация прав (PreToolUse-гейт:
          # запись только в state-файлы, Bash только git/read-only). Снимется следующим
          # промптом без сигнала завершения (ветка else ниже).
          : > "$CWD/.harness/locks/closing-mode" 2>/dev/null
          ;;
      esac
      if [ -n "$PIECES" ]; then
        PIECES="$PIECES
    —
    $HANDOFF"
      else
        PIECES="$HANDOFF"
      fi
    else
      # Промпт БЕЗ сигнала завершения: пользователь продолжает работу — режим закрытия снят
      # (его инструкция главнее; закрывает FP «на сегодня всё… хотя нет, поправь ещё кнопку»).
      rm -f "$CWD/.harness/locks/closing-mode" 2>/dev/null
    fi
    
    # interrupt-recovery (v6.2.1): хвост последнего хода оборван техническим прерыванием
    # (обрыв клиента/доставка сообщения), в новом промпте нет стоп-слов -> inject «продолжай,
    # это был обрыв, не запрет» (агент не стоит часами после лживого "user rejected").
    IRECOV="$(HOOK_PAYLOAD="$HOOK_INPUT" hook_run_check "$CWD" "interrupt-recovery" text "$ROOT/hooks/checks/interrupt-recovery.sh" "$CWD")"
    if [ -n "$(printf '%s' "$IRECOV" | tr -d '[:space:]')" ]; then
      if [ -n "$PIECES" ]; then
        PIECES="$PIECES
    —
    $IRECOV"
      else
        PIECES="$IRECOV"
      fi
    fi
    
    # Анти-залипание (прокси №1 tunnel-vision): стоп-сигнал / коррекция курса -> напоминание
    # (смена УРОВНЯ, не способа). Маркер handoff НЕ ставит — это не завершение сессии.
    STUCK="$(HOOK_PAYLOAD="$HOOK_INPUT" hook_run_check "$CWD" "stuck-signal" text "$ROOT/hooks/checks/stuck-signal-reminder.sh" "$CWD")"
    
    # research-skip (F6, lock-паттерн): явная фраза «пропусти рисёрч» -> хук (не агент!) пишет
    # маркер .harness/locks/research-skipped с цитатой; гейт архитектуры его уважает.
    RSKIP="$(HOOK_PAYLOAD="$HOOK_INPUT" hook_run_check "$CWD" "research-skip" text "$ROOT/hooks/checks/research-skip-listener.sh" "$CWD")"
    if [ -n "$(printf '%s' "$RSKIP" | tr -d '[:space:]')" ]; then
      if [ -n "$PIECES" ]; then
        PIECES="$PIECES
    —
    $RSKIP"
      else
        PIECES="$RSKIP"
      fi
    fi
    
    # secret-skip (v7 P14 escape, lock-паттерн): явная фраза «ключ тестовый / забей» -> хук (не агент!)
    # пишет одноразовый маркер .harness/locks/secret-scan-off; secret-scan-write снимает по нему блок.
    SSKIP="$(HOOK_PAYLOAD="$HOOK_INPUT" hook_run_check "$CWD" "secret-skip" text "$ROOT/hooks/checks/secret-skip-listener.sh" "$CWD")"
    if [ -n "$(printf '%s' "$SSKIP" | tr -d '[:space:]')" ]; then
      if [ -n "$PIECES" ]; then
        PIECES="$PIECES
    —
    $SSKIP"
      else
        PIECES="$SSKIP"
      fi
    fi
    
    # go-mode (v7 P6): явная фраза «не тормози / делай до конца» -> хук ставит .harness/locks/go-mode
    # (стоп-слово снимает). wave-continue на Stop читает маркер. Тихо (только маркер, без текста).
    HOOK_PAYLOAD="$HOOK_INPUT" hook_run_check "$CWD" "go-mode" text "$ROOT/hooks/checks/go-mode-listener.sh" "$CWD" >/dev/null 2>&1
    
    # secret-in-prompt (F8): живой ключ в сообщении пользователя -> предупреждение о ротации
    # (inject, не block: ключ уже в контексте — задача предупредить и направить в .env).
    SECR="$(HOOK_PAYLOAD="$HOOK_INPUT" hook_run_check "$CWD" "secret-in-prompt" text "$ROOT/hooks/checks/secret-in-prompt.sh" "$CWD")"
    if [ -n "$(printf '%s' "$SECR" | tr -d '[:space:]')" ]; then
      if [ -n "$PIECES" ]; then
        PIECES="$PIECES
    —
    $SECR"
      else
        PIECES="$SECR"
      fi
    fi
    if [ -n "$(printf '%s' "$STUCK" | tr -d '[:space:]')" ]; then
      if [ -n "$PIECES" ]; then
        PIECES="$PIECES
    —
    $STUCK"
      else
        PIECES="$STUC
  • hooks/pre-commit-scope.shGitHub
  • hooks/pre-compact.shRunsGitHub

All 8 scripts are listed above. The source is inlined for 6 of them, starting with whatever hooks.json actually runs. See all of them in the repo.

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 withvibe-dev

🌐 English: this file · Русский: README.ru.md A harness-first plugin that turns a business idea into a shipped product — for founders who build with Codex and Claude Code.

Get the whole plugin