Skip to content
Development
HotHook

Hooks

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

From plugin
addyosmani-agent-skills
98k25 skills4 agents9 commands7 hooks
Install
> /plugin marketplace add addyosmani/agent-skills
> /plugin install agent-skills@addy-agent-skills

Ships with addyosmani-agent-skills. Installing the plugin gets these hooks.

Where it lives

  • hooks/sdd-cache-post.shGitHub
    Read the script
    #!/bin/bash
    # sdd-cache-post.sh — PostToolUse hook for WebFetch.
    #
    # After WebFetch, stores the response body in .claude/sdd-cache/<sha>.json
    # with the current ETag / Last-Modified captured via a HEAD request so the
    # pre hook can revalidate on the next fetch.
    #
    # Keyed by URL. The caller's prompt is stored as metadata (not part of the
    # key) so a future cache hit can show what question produced the cached
    # reading. Entries without ETag or Last-Modified are not cached.
    #
    # Dependencies: jq, curl, shasum (or sha256sum).
    
    set -euo pipefail
    
    command -v jq   >/dev/null 2>&1 || exit 0
    command -v curl >/dev/null 2>&1 || exit 0
    command -v shasum >/dev/null 2>&1 || command -v sha256sum >/dev/null 2>&1 || exit 0
    
    if [ -t 0 ]; then INPUT="{}"; else INPUT=$(cat); fi
    
    # Debug logging: active when SDD_CACHE_DEBUG=1 is set, or when a sentinel
    # file exists at .claude/sdd-cache/.debug. Toggle with `touch` / `rm`.
    dbg() {
      local dir="${CLAUDE_PROJECT_DIR:-$PWD}/.claude/sdd-cache"
      [ "${SDD_CACHE_DEBUG:-0}" = "1" ] || [ -f "$dir/.debug" ] || return 0
      mkdir -p "$dir"
      printf '%s [post] %s\n' "$(date -u +%FT%TZ)" "$*" >> "$dir/.debug.log"
    }
    dbg "fired, input=$(printf '%s' "$INPUT" | head -c 400)"
    
    URL=$(printf '%s'    "$INPUT" | jq -r '.tool_input.url    // empty' 2>/dev/null || true)
    PROMPT=$(printf '%s' "$INPUT" | jq -r '.tool_input.prompt // empty' 2>/dev/null || true)
    if [ -z "$URL" ]; then dbg "no url in tool_input, exit"; exit 0; fi
    dbg "url=$URL prompt=$(printf '%s' "$PROMPT" | head -c 80)"
    
    # WebFetch tool_response shape (Claude Code as of 2026-04): an object with
    # keys bytes, code, codeText, durationMs, result, url — content lives at
    # .result. The other keys (.output / .text / .content / .body) are kept as
    # defensive fallbacks in case the shape changes; jq returns empty if none
    # match. The string branch handles older/custom integrations.
    TOOL_RESPONSE_TYPE=$(printf '%s' "$INPUT" | jq -r '.tool_response | type' 2>/dev/null || echo "unknown")
    dbg "tool_response type=$TOOL_RESPONSE_TYPE keys=$(printf '%s' "$INPUT" | jq -r 'try (.tool_response | keys | join(",")) catch "n/a"' 2>/dev/null)"
    
    CONTENT=$(printf '%s' "$INPUT" | jq -r '
      if (.tool_response | type) == "object" then
        (.tool_response.result
         // .tool_response.output
         // .tool_response.text
         // .tool_response.content
         // .tool_response.body
         // empty)
      elif (.tool_response | type) == "string" then
        .tool_response
      else
        empty
      end
    ' 2>/dev/null || true)
    
    if [ -z "$CONTENT" ]; then
      dbg "could not extract content from tool_response, exit (shape unknown)"
      exit 0
    fi
    dbg "extracted content bytes=${#CONTENT}"
    
    # Must match the pre hook: sha256(URL), first 32 hex chars.
    hash_key() {
      if command -v shasum >/dev/null 2>&1; then
        printf '%s' "$1" | shasum -a 256 | cut -c1-32
      else
        printf '%s' "$1" | sha256sum | cut -c1-32
      fi
    }
    
    CACHE_DIR="${CLAUDE_PROJECT_DIR:-$PWD}/.claude/sdd-cache"
    mkdir -p "$CACHE_DIR"
    CACHE_FILE="$CACHE_DIR/$(hash_key "$URL").json"
    
    # Capture validators from the origin. Follow redirects so they match the
    # URL the agent actually talked to. Strip CR so awk's paragraph mode
    # recognises blank separators between response blocks on a redirect chain.
    HEAD_OUT=$(curl -sI -L --max-time 5 "$URL" 2>/dev/null | tr -d '\r' || true)
    
    # Take only the final response's headers (last paragraph) to avoid picking
    # up validators from intermediate 301/302 hops.
    FINAL_HEADERS=$(printf '%s' "$HEAD_OUT" | awk '
      BEGIN { RS = ""; last = "" }
      { last = $0 }
      END { print last }
    ')
    
    extract_header() {
      local name="$1"
      printf '%s' "$FINAL_HEADERS" | awk -v h="$name" '
        BEGIN { FS = ":" }
        tolower($1) == tolower(h) {
          sub(/^[^:]*:[ \t]*/, "")
          sub(/[ \t]+$/, "")
          print
          exit
        }
      '
    }
    
    ETAG=$(extract_header "ETag")
    LAST_MOD=$(extract_header "Last-Modified")
    dbg "HEAD etag=$ETAG last_modified=$LAST_MOD"
    
    if [ -z "$ETAG" ] && [ -z "$LAST_MOD" ]; then
      dbg "no validator from origin, removing any stale entry and exit"
      rm -f "$CACHE_FILE"
      exit 0
    fi
    
    NOW=$(date +%s)
    
    TMP="${CACHE_FILE}.$$.tmp"
    if jq -n \
      --arg url           "$URL" \
      --arg prompt        "$PROMPT" \
      --arg etag          "$ETAG" \
      --arg last_modified "$LAST_MOD" \
      --arg content       "$CONTENT" \
      --argjson fetched_at "$NOW" \
      '{url: $url, prompt: $prompt, etag: $etag, last_modified: $last_modified, content: $content, fetched_at: $fetched_at}' \
      > "$TMP"
    then
      mv "$TMP" "$CACHE_FILE"
      dbg "wrote cache file $CACHE_FILE"
    else
      rm -f "$TMP"
      dbg "jq failed, temp cleaned"
    fi
    
    exit 0
    
  • hooks/sdd-cache-pre.shGitHub
    Read the script
    #!/bin/bash
    # sdd-cache-pre.sh — PreToolUse hook for WebFetch.
    #
    # HTTP resource cache keyed by URL. Freshness is delegated to the origin via
    # HTTP validators; 304 Not Modified is the only signal to serve from cache.
    # On hit, exits 2 and writes the cached body to stderr so Claude Code can
    # deliver it to the agent in place of the WebFetch result. Otherwise exits 0.
    #
    # No TTL: if validators don't catch a change, nothing will. Entries without
    # ETag or Last-Modified are never cached (can't revalidate).
    #
    # Cached bodies are prompt-shaped (WebFetch post-processes through a model),
    # so the key is URL-only and the original prompt is surfaced in the hit
    # message so the next agent can tell if the earlier reading still applies.
    #
    # Dependencies: jq, curl, shasum (or sha256sum).
    
    set -euo pipefail
    
    # Graceful degradation: if any dependency is missing, let the fetch through.
    command -v jq   >/dev/null 2>&1 || exit 0
    command -v curl >/dev/null 2>&1 || exit 0
    command -v shasum >/dev/null 2>&1 || command -v sha256sum >/dev/null 2>&1 || exit 0
    
    if [ -t 0 ]; then INPUT="{}"; else INPUT=$(cat); fi
    
    # Debug logging: active when SDD_CACHE_DEBUG=1 is set, or when a sentinel
    # file exists at .claude/sdd-cache/.debug. Toggle with `touch` / `rm`.
    dbg() {
      local dir="${CLAUDE_PROJECT_DIR:-$PWD}/.claude/sdd-cache"
      [ "${SDD_CACHE_DEBUG:-0}" = "1" ] || [ -f "$dir/.debug" ] || return 0
      mkdir -p "$dir"
      printf '%s [pre]  %s\n' "$(date -u +%FT%TZ)" "$*" >> "$dir/.debug.log"
    }
    dbg "fired"
    
    URL=$(printf '%s' "$INPUT" | jq -r '.tool_input.url // empty' 2>/dev/null || true)
    if [ -z "$URL" ]; then dbg "no url in tool_input, exit"; exit 0; fi
    dbg "url=$URL"
    
    # Cache key is sha256(URL), truncated to 128 bits.
    hash_key() {
      if command -v shasum >/dev/null 2>&1; then
        printf '%s' "$1" | shasum -a 256 | cut -c1-32
      else
        printf '%s' "$1" | sha256sum | cut -c1-32
      fi
    }
    
    CACHE_DIR="${CLAUDE_PROJECT_DIR:-$PWD}/.claude/sdd-cache"
    CACHE_FILE="$CACHE_DIR/$(hash_key "$URL").json"
    
    if [ ! -f "$CACHE_FILE" ]; then dbg "no cache file at $CACHE_FILE, exit"; exit 0; fi
    dbg "cache file exists: $CACHE_FILE"
    
    FETCHED_AT=$(jq -r '.fetched_at // 0' "$CACHE_FILE" 2>/dev/null || echo 0)
    ORIGINAL_PROMPT=$(jq -r '.prompt // empty' "$CACHE_FILE" 2>/dev/null || true)
    ETAG=$(jq -r '.etag // empty' "$CACHE_FILE" 2>/dev/null || true)
    LAST_MOD=$(jq -r '.last_modified // empty' "$CACHE_FILE" 2>/dev/null || true)
    
    # No validator means we cannot verify freshness — never serve from cache.
    if [ -z "$ETAG" ] && [ -z "$LAST_MOD" ]; then
      dbg "cached entry has no etag/last-modified, cannot revalidate, bypass"
      exit 0
    fi
    
    HEADERS=()
    [ -n "$ETAG" ]     && HEADERS+=(-H "If-None-Match: $ETAG")
    [ -n "$LAST_MOD" ] && HEADERS+=(-H "If-Modified-Since: $LAST_MOD")
    
    STATUS=$(curl -sI -o /dev/null -w "%{http_code}" \
      --max-time 5 -L \
      "${HEADERS[@]}" \
      "$URL" 2>/dev/null || echo "000")
    dbg "revalidation HEAD status=$STATUS"
    
    if [ "$STATUS" != "304" ]; then
      dbg "not 304, letting WebFetch proceed"
      exit 0
    fi
    
    # Server confirmed content unchanged. Serve cached copy to the agent.
    CONTENT=$(jq -r '.content // empty' "$CACHE_FILE" 2>/dev/null || true)
    if [ -z "$CONTENT" ]; then dbg "cache file has empty content field, bypass"; exit 0; fi
    dbg "cache HIT, blocking WebFetch with ${#CONTENT} bytes of cached content"
    
    VERIFIED_AT_ISO=$(date -u -r "$FETCHED_AT" +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null \
                  || date -u -d "@$FETCHED_AT" +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null \
                  || echo "unknown")
    
    # Emit the payload with printf so $CONTENT is never interpreted by the shell
    # (docs contain backticks, $vars, and backslashes in code examples; an
    # unquoted heredoc would treat them as command substitution).
    {
      printf '[sdd-cache] Cache hit for %s\n\n' "$URL"
      printf 'Revalidated via HTTP 304; unchanged since %s. Use the cached\n' "$VERIFIED_AT_ISO"
      printf 'content below as if WebFetch had just returned it.\n\n'
      if [ -n "$ORIGINAL_PROMPT" ]; then
        printf 'Original WebFetch prompt: "%s". If your angle differs, judge\n' "$ORIGINAL_PROMPT"
        printf 'whether this reading still covers it.\n\n'
      fi
      printf -- '----- BEGIN CACHED CONTENT -----\n'
      printf '%s\n' "$CONTENT"
      printf -- '----- END CACHED CONTENT -----\n'
    } >&2
    exit 2
    
  • hooks/sdd-cache-test.shGitHub
    Read the script
    #!/bin/bash
    # sdd-cache-test.sh — Tests for the sdd-cache pre/post WebFetch hooks
    #
    # No real network traffic: a deterministic curl stub is placed first on PATH.
    #   CURL_STUB_STATUS  — printed when the hook probes a status code (pre hook,
    #                       invoked with -w); defaults to 000
    #   CURL_STUB_HEADERS — printed for header captures (post hook, no -w)
    # Every test URL is a public https:// host and every seeded cache entry uses
    # a fresh fetched_at, so the suite passes both with and without the SSRF /
    # TTL hardening of the hooks (issue #295).
    #
    # Run: bash hooks/sdd-cache-test.sh
    
    set -euo pipefail
    
    PASS=0 FAIL=0
    TMPDIR=$(mktemp -d)
    trap 'rm -rf "$TMPDIR"' EXIT
    
    # The hooks require jq to do anything; without it they no-op by design.
    if ! command -v jq >/dev/null 2>&1; then
      printf 'SKIP: jq not available; sdd-cache hooks no-op without it\n'
      exit 0
    fi
    
    # ── curl stub ─────────────────────────────────────────────────────────────
    STUB_BIN="$TMPDIR/bin"
    mkdir -p "$STUB_BIN"
    cat > "$STUB_BIN/curl" <<'EOF'
    #!/bin/bash
    # Deterministic curl replacement. -w present => status probe (pre hook),
    # otherwise header capture (post hook).
    has_w=0
    for a in "$@"; do [ "$a" = "-w" ] && has_w=1; done
    if [ $has_w -eq 1 ]; then
      printf '%s' "${CURL_STUB_STATUS:-000}"
    else
      printf '%s\n' "${CURL_STUB_HEADERS:-}"
    fi
    exit 0
    EOF
    chmod +x "$STUB_BIN/curl"
    
    hash_key() {
      if command -v shasum >/dev/null 2>&1; then
        printf '%s' "$1" | shasum -a 256 | cut -c1-32
      else
        printf '%s' "$1" | sha256sum | cut -c1-32
      fi
    }
    
    assert_eq() {
      local label="$1" expected="$2" actual="$3"
      if [ "$expected" = "$actual" ]; then
        PASS=$((PASS + 1))
        printf '  PASS: %s\n' "$label"
      else
        FAIL=$((FAIL + 1))
        printf '  FAIL: %s\n' "$label" >&2
        printf '    expected: %s\n' "$(printf '%s' "$expected" | cat -v)" >&2
        printf '    actual:   %s\n' "$(printf '%s' "$actual" | cat -v)" >&2
      fi
    }
    
    # run_pre / run_post: invoke a hook with isolated project dir + stubbed curl.
    # Usage: run_pre <project_dir> <input_json> ; rc in $RC, stderr in $ERR.
    RC=0 ERR=""
    run_hook() {
      local script="$1" proj="$2" input="$3"
      RC=0
      ERR=$(printf '%s' "$input" | \
        CLAUDE_PROJECT_DIR="$proj" PATH="$STUB_BIN:$PATH" \
        CURL_STUB_STATUS="${CURL_STUB_STATUS:-000}" \
        CURL_STUB_HEADERS="${CURL_STUB_HEADERS:-}" \
        bash "hooks/$script" 2>&1 >/dev/null) || RC=$?
    }
    
    # seed_entry <proj> <url> <content> [etag] — write a valid, fresh cache entry.
    seed_entry() {
      # ${4-...} (not ${4:-...}): an explicitly empty etag must stay empty.
      local proj="$1" url="$2" content="$3" etag="${4-\"seed-etag\"}"
      local dir="$proj/.claude/sdd-cache"
      mkdir -p "$dir"
      jq -n \
        --arg url "$url" \
        --arg prompt "original prompt" \
        --arg etag "$etag" \
        --arg last_modified "" \
        --arg content "$content" \
        --argjson fetched_at "$(date +%s)" \
        '{url:$url, prompt:$prompt, etag:$etag, last_modified:$last_modified,
          content:$content, fetched_at:$fetched_at}' \
        > "$dir/$(hash_key "$url").json"
    }
    
    pre_input()  { jq -n --arg url "$1" '{tool_input:{url:$url}}'; }
    post_input() { jq -n --arg url "$1" --arg result "$2" \
      '{tool_input:{url:$url, prompt:"test prompt"}, tool_response:{result:$result, code:200}}'; }
    
    URL="https://docs.example.com/guide"
    
    # ── Test 1: pre — no url in input lets WebFetch proceed ───────────────────
    printf 'Test 1: pre — no url in tool_input\n'
    P="$TMPDIR/t1"; mkdir -p "$P"
    run_hook sdd-cache-pre.sh "$P" '{"tool_input":{}}'
    assert_eq "exit 0 without url" "0" "$RC"
    
    # ── Test 2: pre — malformed JSON input is not fatal ───────────────────────
    printf '\nTest 2: pre — malformed JSON input\n'
    P="$TMPDIR/t2"; mkdir -p "$P"
    run_hook sdd-cache-pre.sh "$P" 'NOT_JSON{{{'
    assert_eq "exit 0 on malformed JSON" "0" "$RC"
    
    # ── Test 3: pre — no cache entry lets WebFetch proceed ────────────────────
    printf '\nTest 3: pre — cache miss\n'
    P="$TMPDIR/t3"; mkdir -p "$P"
    run_hook sdd-cache-pre.sh "$P" "$(pre_input "$URL")"
    assert_eq "exit 0 on cache miss" "0" "$RC"
    
    # ── Test 4: pre — entry without validators is never served ────────────────
    printf '\nTest 4: pre — entry lacking ETag/Last-Modified bypasses cache\n'
    P="$TMPDIR/t4"; mkdir -p "$P"
    seed_entry "$P" "$URL" "cached body" ""
    CURL_STUB_STATUS=304 run_hook sdd-cache-pre.sh "$P" "$(pre_input "$URL")"
    assert_eq "exit 0 despite 304 when no validator stored" "0" "$RC"
    
    # ── Test 5: pre — non-304 status lets WebFetch proceed ────────────────────
    printf '\nTest 5: pre — origin answers 200\n'
    P="$TMPDIR/t5"; mkdir -p "$P"
    seed_entry "$P" "$URL" "cached body"
    CURL_STUB_STATUS=200 run_hook sdd-cache-pre.sh "$P" "$(pre_input "$URL")"
    assert_eq "exit 0 on 200 (content changed)" "0" "$RC"
    
    # ── Test 6: pre — network failure lets WebFetch proceed ───────────────────
    printf '\nTest 6: pre — revalidation request fails\n'
    P="$TMPDIR/t6"; mkdir -p "$P"
    seed_entry "$P" "$URL" "cached body"
    CURL_STUB_STATUS=000 run_hook sdd-cache-pre.sh "$P" "$(pre_input "$URL")"
    assert_eq "exit 0 on network failure" "0" "$RC"
    
    # ── Test 7: pre — 304 serves cached content byte-exactly ──────────────────
    printf '\nTest 7: pre — cache hit on 304\n'
    P="$TMPDIR/t7"; mkdir -p "$P"
    # Content with the shell-hostile shapes the hook promises to pass through:
    # backticks, $vars, backslashes, glob chars, multiple lines.
    TRICKY='Line with `backticks` and $HOME and \backslash
    second line: *glob* [brackets] and a "quote"
    $(this must not execute)'
    seed_entry "$P" "$URL" "$TRICKY"
    CURL_STUB_STATUS=304 run_hook sdd-cache-pre.sh "$P" "$(pre_input "$URL")"
    assert_eq "exit 2 on cache hit" "2" "$RC"
    assert_eq "hit message names the URL" "1" "$(printf '%s' "$ERR" | grep -cF "$URL")"
    assert_eq "hit message says revalidated" "1" "$(printf '%s' "$ERR" | grep -c 'Revalidated')"
    assert_eq "original prompt surfaced" "1" "$(printf '%s' "$ERR" | grep -cF 'original prompt')"
    served=$(printf '%s\n' "$ERR" | sed -n '/BEGIN CACHED CONTENT/,/END CACHED CONTENT/p' | sed '1d;$d')
    assert_eq "cached content served byte
  • hooks/session-start-test.shGitHub
    Read the script
    #!/bin/bash
    # session-start-test.sh - Tests for the SessionStart hook JSON payload
    
    set -euo pipefail
    
    tmp_payload="$(mktemp)"
    trap 'rm -f "$tmp_payload"' EXIT
    
    has_jq=0
    if command -v jq >/dev/null 2>&1; then
      has_jq=1
    fi
    
    payload="$(bash hooks/session-start.sh)"
    printf '%s' "$payload" > "$tmp_payload"
    
    HAS_JQ="$has_jq" PAYLOAD_PATH="$tmp_payload" node <<'NODE'
    const fs = require('fs');
    
    const payload = JSON.parse(fs.readFileSync(process.env.PAYLOAD_PATH, 'utf8'));
    const hasJq = process.env.HAS_JQ === '1';
    const out = payload.hookSpecificOutput;
    
    if (!out || typeof out !== 'object') {
      throw new Error('payload is missing hookSpecificOutput (hosts reject other shapes)');
    }
    if (out.hookEventName !== 'SessionStart') {
      throw new Error(`expected hookEventName SessionStart, got ${out.hookEventName}`);
    }
    if (typeof out.additionalContext !== 'string' || !out.additionalContext.trim()) {
      throw new Error('additionalContext must be a non-empty string');
    }
    
    const ctx = out.additionalContext;
    if (hasJq) {
      if (!ctx.includes('agent-skills loaded.')) {
        throw new Error('additionalContext is missing startup preface');
      }
      if (!ctx.includes('# Using Agent Skills')) {
        throw new Error('additionalContext is missing using-agent-skills content');
      }
    } else if (!ctx.includes('jq is required')) {
      throw new Error('additionalContext is missing jq fallback guidance');
    }
    
    console.log('session-start JSON payload OK');
    NODE
    
  • hooks/session-start.shGitHub
    Read the script
    #!/bin/bash
    # agent-skills session start hook
    # Injects the using-agent-skills meta-skill into a new session.
    #
    # Not wired by the plugin: hosts that already route skills from their
    # descriptions (Claude Code, Codex CLI) would run a second router on top of
    # the native one — see docs/getting-started.md. Wire this script into a
    # SessionStart hook only on hosts without native skill routing.
    #
    # Every output path must emit the standard SessionStart envelope
    #   {"hookSpecificOutput": {"hookEventName": "SessionStart", "additionalContext": "..."}}
    # Hosts that validate hook output (Codex CLI, Claude Code) reject other shapes.
    
    SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
    SKILLS_DIR="$(dirname "$SCRIPT_DIR")/skills"
    META_SKILL="$SKILLS_DIR/using-agent-skills/SKILL.md"
    
    if ! command -v jq >/dev/null 2>&1; then
      echo '{"hookSpecificOutput": {"hookEventName": "SessionStart", "additionalContext": "agent-skills: jq is required for the session-start hook but was not found on PATH. Install jq (e.g. `brew install jq` or `apt-get install jq`) to enable meta-skill injection. Skills remain available individually."}}'
      exit 0
    fi
    
    if [ -f "$META_SKILL" ]; then
      CONTENT=$(cat "$META_SKILL")
      # Use jq to properly escape and construct valid JSON
      jq -cn \
        --arg context "agent-skills loaded. Use the skill discovery flowchart to find the right skill for your task.
    
    $CONTENT" \
        '{hookSpecificOutput: {hookEventName: "SessionStart", additionalContext: $context}}'
    else
      echo '{"hookSpecificOutput": {"hookEventName": "SessionStart", "additionalContext": "agent-skills: using-agent-skills meta-skill not found. Skills may still be available individually."}}'
    fi
    
  • hooks/simplify-ignore-test.shGitHub
    Read the script
    #!/bin/bash
    # simplify-ignore-test.sh — Tests for the simplify-ignore hook
    #
    # Exercises filter_file by extracting function definitions from the hook.
    # Run: bash hooks/simplify-ignore-test.sh
    
    set -euo pipefail
    
    PASS=0 FAIL=0
    TMPDIR=$(mktemp -d)
    trap 'rm -rf "$TMPDIR"' EXIT
    
    export CACHE="$TMPDIR/cache"
    mkdir -p "$CACHE"
    
    # Extract function definitions we need
    hash_cmd() {
      if command -v shasum >/dev/null 2>&1; then shasum
      elif command -v sha1sum >/dev/null 2>&1; then sha1sum
      else printf '%s\n' "error: missing shasum or sha1sum" >&2; exit 1; fi
    }
    file_id() { printf '%s' "$1" | hash_cmd | cut -c1-16; }
    block_hash() { printf '%s' "$1" | hash_cmd | cut -c1-8; }
    escape_glob() {
      local s="$1"
      s="${s//\\/\\\\}"
      s="${s//\*/\\*}"
      s="${s//\?/\\?}"
      s="${s//\[/\\[}"
      printf '%s' "$s"
    }
    
    # Extract filter_file from the hook script (line 59 "filter_file()" to line 142 closing brace)
    eval "$(sed -n '/^filter_file()/,/^}/p' hooks/simplify-ignore.sh)"
    
    assert_eq() {
      local label="$1" expected="$2" actual="$3"
      if [ "$expected" = "$actual" ]; then
        PASS=$((PASS + 1))
        printf '  PASS: %s\n' "$label"
      else
        FAIL=$((FAIL + 1))
        printf '  FAIL: %s\n' "$label" >&2
        printf '    expected: %s\n' "$(printf '%s' "$expected" | cat -v)" >&2
        printf '    actual:   %s\n' "$(printf '%s' "$actual" | cat -v)" >&2
      fi
    }
    
    # ── Test 1: Single-line block produces exactly one placeholder ────────────
    printf 'Test 1: Single-line block (start+end on same line)\n'
    rm -f "$CACHE"/*
    
    SRC="$TMPDIR/single-line.js"
    DEST="$TMPDIR/single-line-filtered.js"
    cat > "$SRC" <<'EOF'
    const a = 1;
    /* simplify-ignore-start */ const secret = 42; /* simplify-ignore-end */
    const b = 2;
    EOF
    
    FID="test_single"
    filter_file "$SRC" "$DEST" "$FID"
    
    placeholder_count=$(grep -c 'BLOCK_' "$DEST")
    assert_eq "exactly one placeholder line" "1" "$placeholder_count"
    assert_eq "line before block preserved" "1" "$(grep -c 'const a = 1' "$DEST")"
    assert_eq "line after block preserved" "1" "$(grep -c 'const b = 2' "$DEST")"
    
    block_files=$(ls "$CACHE/${FID}".block.* 2>/dev/null | wc -l | tr -d ' ')
    assert_eq "one block file in cache" "1" "$block_files"
    
    block_content=$(cat "$CACHE/${FID}".block.*)
    assert_eq "block content matches" \
      "/* simplify-ignore-start */ const secret = 42; /* simplify-ignore-end */" \
      "$block_content"
    
    # ── Test 2: Multi-line block ─────────────────────────────────────────────
    printf '\nTest 2: Multi-line block\n'
    rm -f "$CACHE"/*
    
    SRC="$TMPDIR/multi-line.js"
    DEST="$TMPDIR/multi-line-filtered.js"
    cat > "$SRC" <<'EOF'
    const a = 1;
    // simplify-ignore-start
    const secret1 = 42;
    const secret2 = 99;
    // simplify-ignore-end
    const b = 2;
    EOF
    
    FID="test_multi"
    filter_file "$SRC" "$DEST" "$FID"
    
    placeholder_count=$(grep -c 'BLOCK_' "$DEST")
    assert_eq "exactly one placeholder for multi-line block" "1" "$placeholder_count"
    
    output_lines=$(wc -l < "$DEST" | tr -d ' ')
    assert_eq "output has 3 lines (before + placeholder + after)" "3" "$output_lines"
    
    # ── Test 3: Multiple blocks in one file ──────────────────────────────────
    printf '\nTest 3: Multiple blocks in one file\n'
    rm -f "$CACHE"/*
    
    SRC="$TMPDIR/multi-block.js"
    DEST="$TMPDIR/multi-block-filtered.js"
    cat > "$SRC" <<'EOF'
    line1
    // simplify-ignore-start
    blockA
    // simplify-ignore-end
    line2
    // simplify-ignore-start
    blockB
    // simplify-ignore-end
    line3
    EOF
    
    FID="test_multiblock"
    filter_file "$SRC" "$DEST" "$FID"
    
    placeholder_count=$(grep -c 'BLOCK_' "$DEST")
    assert_eq "two placeholders for two blocks" "2" "$placeholder_count"
    
    block_files=$(ls "$CACHE/${FID}".block.* 2>/dev/null | wc -l | tr -d ' ')
    assert_eq "two block files in cache" "2" "$block_files"
    
    # ── Test 4: Reason string preserved ──────────────────────────────────────
    printf '\nTest 4: Reason string in placeholder\n'
    rm -f "$CACHE"/*
    
    SRC="$TMPDIR/reason.js"
    DEST="$TMPDIR/reason-filtered.js"
    cat > "$SRC" <<'EOF'
    // simplify-ignore-start: perf-critical
    hot_loop();
    // simplify-ignore-end
    EOF
    
    FID="test_reason"
    filter_file "$SRC" "$DEST" "$FID"
    
    assert_eq "placeholder includes reason" "1" "$(grep -c 'perf-critical' "$DEST")"
    
    reason_files=$(ls "$CACHE/${FID}".reason.* 2>/dev/null | wc -l | tr -d ' ')
    assert_eq "reason file saved" "1" "$reason_files"
    assert_eq "reason content" "perf-critical" "$(cat "$CACHE/${FID}".reason.*)"
    
    # ── Test 5: Trailing newline preservation ────────────────────────────────
    printf '\nTest 5: Trailing newline preservation\n'
    rm -f "$CACHE"/*
    
    SRC="$TMPDIR/no-trailing-nl.js"
    DEST="$TMPDIR/no-trailing-nl-filtered.js"
    printf 'line1\n// simplify-ignore-start\nsecret\n// simplify-ignore-end' > "$SRC"
    
    FID="test_trail"
    filter_file "$SRC" "$DEST" "$FID"
    
    # Source has no trailing newline; dest should also have no trailing newline
    src_has_nl=$(tail -c 1 "$SRC" | wc -l | tr -d ' ')
    dest_has_nl=$(tail -c 1 "$DEST" | wc -l | tr -d ' ')
    assert_eq "dest preserves no-trailing-newline from source" "$src_has_nl" "$dest_has_nl"
    
    # ── Test 6: No blocks → return 1 ────────────────────────────────────────
    printf '\nTest 6: No blocks returns 1\n'
    rm -f "$CACHE"/*
    
    SRC="$TMPDIR/no-blocks.js"
    DEST="$TMPDIR/no-blocks-filtered.js"
    cat > "$SRC" <<'EOF'
    const a = 1;
    const b = 2;
    EOF
    
    FID="test_noblocks"
    rc=0
    filter_file "$SRC" "$DEST" "$FID" || rc=$?
    assert_eq "returns 1 when no blocks found" "1" "$rc"
    
    # ── Test 7: Unclosed block emits warning and flushes ─────────────────────
    printf '\nTest 7: Unclosed block\n'
    rm -f "$CACHE"/*
    
    SRC="$TMPDIR/unclosed.js"
    DEST="$TMPDIR/unclosed-filtered.js"
    cat > "$SRC" <<'EOF'
    line1
    // simplify-ignore-start
    orphan code
    EOF
    
    FID="test_unclosed"
    stderr_out=$(filter_file "$SRC" "$DEST" "$FID" 2>&1) || true
    assert_eq "warning emitted for unclosed block" "1" "$(printf '%s' "$stderr_out" | grep -c 'unclosed')"
    assert_eq "orphan code flushed to output" "1" "$(grep -c 'orphan code' "$DEST")"
    
    # ── Test 8: Single-line block with reason ────────────────────────────────
    printf '\nTest 8: Single-line block with reason\n'
    rm -f "$CACHE"/*
    
    SRC="$TMPDIR/single-reason.js"
    DEST="$TMPDIR/sin
  • hooks/simplify-ignore.shGitHub

All 7 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 withaddyosmani-agent-skills

Production-grade engineering skills for AI coding agents. Skills encode the workflows, quality gates, and best practices that senior engineers use when building software.

Get the whole plugin, auto-invoked