Skip to content
Development
Hook

Hooks

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

From plugin
vibecheck
41 skill1 hook

Claude Code

> /plugin marketplace add dalvgit/vibecheck
> /plugin install vibecheck@vibecheck

What fires, and when

PreToolUse

  • MatchesBash"${CLAUDE_PLUGIN_ROOT}/hooks/gate.sh"
Read hooks/hooks.json

Where it lives

  • hooks/gate.shRunsGitHub
    Read the script
    #!/bin/sh
    # vibecheck — the gate.
    #
    # Three modes:
    #   (no args)  PreToolUse hook. Reads the hook JSON on stdin. Exit 2 blocks the push.
    #   --diff     Print the outgoing diff. The exam reads this.
    #   --pass     Record a receipt for the current outgoing diff. The exam calls this after you pass.
    #
    # The receipt is a hash of the diff you were examined on. The gate lets a push
    # through only when that hash still matches what you're about to send. Add a
    # commit and the hash moves, so you sit the exam again.
    
    set -eu
    
    EMPTY_TREE=4b825dc642cb6eb9a060e54bf8d69288fbee4904
    RECEIPT=""
    
    # Our own absolute path — resolved before any cd, so we can hand it to the exam.
    SELF=$(cd "$(dirname "$0")" && pwd)/$(basename "$0")
    
    # Anchor to the repo root. Run from a subdirectory, a relative receipt path is
    # never found (you'd pass the exam and stay blocked forever) and `git diff -- .`
    # would only see the part of the push under your cwd. Returns 1 if not a repo.
    # `--absolute-git-dir`, not a literal .git/, because in a worktree or submodule
    # .git is a file pointing elsewhere.
    anchor() {
      root=$(git rev-parse --show-toplevel 2>/dev/null) || return 1
      cd "$root" || return 1
      gd="$(git rev-parse --absolute-git-dir)"
      RECEIPT="$gd/vibecheck-receipt"
      CHALLENGE="$gd/vibecheck-challenge"
    }
    
    # --- config (all optional; absent file means defaults) ------------------------
    # Only the two keys that decide *whether to examine at all* are read here. The
    # rest of .vibecheck.toml is read by the exam skill itself.
    config() { sed -n "s/^[[:space:]]*$1[[:space:]]*=[[:space:]]*//p" .vibecheck.toml 2>/dev/null | head -1; }
    
    # Defaults to 1: if it isn't an ignored file, you get examined on it.
    # Size is not a proxy for risk — `def is_admin(u): return True` is two lines. The
    # ignore globs are the right filter for "don't bother me", not a line threshold.
    min_lines() { l=$(config min_lines | tr -cd '0-9'); echo "${l:-1}"; }
    
    # Ignore globs become git pathspecs, so ignored files never reach the exam.
    excludes() {
      raw=$(config ignore | tr -d '[]"' )
      [ -n "${raw:-}" ] || raw='*.lock, dist/**, *.md, package-lock.json'
      echo "$raw" | tr ',' '\n' | while read -r g; do
        g=$(echo "$g" | xargs 2>/dev/null || true)
        [ -n "$g" ] && printf ':(exclude,glob)%s\n' "$g"
      done
    }
    
    # --- the outgoing diff --------------------------------------------------------
    # What `git push` would actually send: everything on HEAD since it forked from
    # the branch we're tracking. `A...HEAD` diffs from the merge-base, so a stale
    # upstream doesn't drag unrelated changes into the exam.
    base() {
      if up=$(git rev-parse --abbrev-ref --symbolic-full-name '@{u}' 2>/dev/null); then
        git merge-base "$up" HEAD 2>/dev/null && return 0
      fi
      # No upstream yet — this branch has never been pushed. Fork point from the
      # remote's default branch, falling back through the usual suspects. Skip the
      # branch we're standing on: merge-base(main, HEAD) while *on* main is HEAD
      # itself, which would diff HEAD against HEAD and wave the whole push through.
      here=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
      for ref in origin/HEAD origin/main origin/master main master; do
        [ "$ref" = "$here" ] && continue
        if git rev-parse --verify --quiet "$ref" >/dev/null 2>&1; then
          git merge-base "$ref" HEAD 2>/dev/null && return 0
        fi
      done
      echo "$EMPTY_TREE"  # never pushed anywhere: all of it is new
    }
    
    outgoing() {
      # shellcheck disable=SC2046
      git diff "$(base)" HEAD -- . $(excludes) 2>/dev/null || true
    }
    
    hash_outgoing() {
      outgoing | { shasum -a 256 2>/dev/null || sha256sum; } | cut -d' ' -f1
    }
    
    # The receipt: line 1 is the hash of the diff you were examined on, line 2 marks an
    # exam that was skipped rather than passed. It is valid only for the exact diff it
    # was issued against — add a commit and the hash moves, so you sit a new exam.
    #
    # This is a forcing function, not a security control. The hash is derivable from the
    # repo, so a model with a shell could forge the receipt and skip the exam. It doesn't,
    # because in a session you are watching, you would see it happen: no questions, just a
    # push. The human in the loop is the enforcement. See the README on what that means
    # for unattended runs.
    receipt_ok() {
      [ -f "$RECEIPT" ] || return 1
      [ "$(head -1 "$RECEIPT")" = "$(hash_outgoing)" ] || return 1
    }
    
    unlock() { # $1 = "SKIPPED" or empty
      d=$(outgoing)
      [ -n "$d" ] || { echo "vibecheck: nothing outgoing, no receipt issued." >&2; exit 1; }
      printf '%s\n%s\n' "$(hash_outgoing)" "${1:-}" > "$RECEIPT"
    }
    
    # --- modes --------------------------------------------------------------------
    case "${1:-}" in
      --guide)
        # Print the shared, tool-neutral exam guide. Ships next to this script; every
        # adapter (Claude skill, OpenCode plugin, Codex hook) points the model here so
        # they all get the same question-craft. No repo needed.
        d=$(dirname "$SELF")
        for g in "$d/EXAM-GUIDE.md" "$d/../EXAM-GUIDE.md"; do
          [ -f "$g" ] && { cat "$g"; exit 0; }
        done
        echo "vibecheck: EXAM-GUIDE.md not found next to the gate." >&2; exit 1 ;;
      --diff)
        anchor || { echo "vibecheck: not a git repository." >&2; exit 1; }
        outgoing; exit 0 ;;
      --pass)
        anchor || { echo "vibecheck: not a git repository." >&2; exit 1; }
        unlock ""
        echo "🧪 vibecheck passed. Push unlocked."
        exit 0 ;;
      --skip)
        anchor || { echo "vibecheck: not a git repository." >&2; exit 1; }
        unlock "SKIPPED"
        echo "🧪 vibecheck SKIPPED — no exam taken. Pushing code nobody has been checked on."
        exit 0 ;;
    esac
    
    # --- hook mode ----------------------------------------------------------------
    payload=$(cat)
    
    # Pull a string field out of the hook payload. jq if it's there, python3 if not,
    # and failing both, sed — because a gate that quietly stops gating on a machine
    # without jq installed is worse than no gate at all.
    #
    # The sed path: cut everything up to the key, protect escaped quotes behind a
    # sentinel, cut at the closing quot

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.

Stats
4
Stars
1
Forks
Maintained
Maintenance
Shell
Language
MIT
License
Last commit
Created

Repo: dalvgit/vibecheck

@dalvgit
Ships withvibecheck

Your AI wrote it. Your name's on it. Now prove you read it. vibecheck blocks git push until you pass a quick multiple-choice quiz on the diff your coding assistant is shipping. Pass and it goes through. Not in the mood? Skip — it's always offered, no nagging.

Stars on GitHub

Momentum

stars in total
4
Star history of dalvgit/vibecheck on GitHub, a chart by star-history.com

More plugins that ship hooks.

superpowers

superpowers

A development methodology for coding agents that makes the agent brainstorm before building, follow plans, run parallel work and finish branches in a disciplined way. You get fewer skipped steps and more consistent results from your coding assistant.

15 skills1 hook318k
@obra@obra
Install Plugin
ponytail

ponytail

A Claude Code plugin with skills for auditing a whole repo, reviewing a change, listing deferred shortcut comments, and showing measured savings and a quick command reference.

6 skills10 hooks191k
@dietrichgebert@dietrichgebert
Install Plugin
agent-skills

agent-skills

Walks a software change from a written spec through design, testing, code review, CI/CD, and release, applying the matching engineering skill at each stage. Useful for developers using Claude Code on full development workflows.

25 skills+3117k
@addyosmani@addyosmani
Install Plugin
planning-with-files

planning-with-files

Persistent file-based planning for AI coding agents on long-running, multi-step tasks. It keeps task_plan.md, findings.md and progress.md on disk, so work can resume after /clear or context compaction.

6 skills+228k
@othmanadi@othmanadi
Install Plugin
gsd-core

gsd-core

A spec-driven development workflow for Claude Code that guides a project through planning, phases, milestone audits and fixes. Includes skills to generate tests from UAT criteria and to audit completed work.

72 skills+312k
@open-gsd@open-gsd
Install Plugin
superpowers-zh

superpowers-zh

Chinese-language skill pack for Claude Code that adds workflow guidance for brainstorming, TDD, debugging, and code review, plus Chinese conventions for commits, documentation, and domestic Git platforms like Gitee and Coding.net.

21 skills1 hook8.5k
@jnmetacode@jnmetacode
Install Plugin
socraticode

socraticode

Gives Claude Code semantic search, dependency graph and architecture context over a codebase, so an AI assistant can explore and understand a project's code. Includes skills for indexing a project and managing its file watching.

2 skills+33.4k
@giancarloerra@giancarloerra
Install Plugin
cocoindex-code

cocoindex-code

A lightweight code search engine that uses AST-based semantic search with local embeddings and incremental indexing. It helps Claude Code users find code faster and more efficiently.

1 skill1 MCP+13k
@cocoindex-io@cocoindex-io
Install Plugin

Questions about the hooks in vibecheck.

Which coding agents can use the hooks in vibecheck?
The hooks in vibecheck install in Claude Code and Cursor: each runs Claude Code hooks from .claude/settings.json, so copy the hooks there.