Skip to content
Development
Hook

Hooks

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

From plugin
ultraship
12445 skills13 agents16 commands3 hooks
+1

Claude Code

> /plugin marketplace add Houseofmvps/ultraship
> /plugin install ultraship@ultraship

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.

  • bash "${CLAUDE_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.

  • bash "${CLAUDE_PLUGIN_ROOT}/hooks/currency-guard.sh"

PostCompact

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

Where it lives

  • hooks/currency-guard.shRunsGitHub
    Read the script
    #!/bin/bash
    # Ultraship Currency Guard — UserPromptSubmit hook
    #
    # Models answer version-sensitive questions from training data that may be
    # months or years stale. This hook fires on every prompt, detects when the
    # prompt is about something whose correct answer changes over time (library
    # APIs, versions, pricing, model IDs, "latest" anything), and injects a
    # deterministic directive telling Claude to verify against current sources
    # (context7 for library docs, WebSearch/WebFetch for everything else) instead
    # of relying on training data.
    #
    # This is a reminder, not a block — it exits 0 and only adds context when a
    # currency-sensitive trigger is present, so normal prompts are untouched.
    
    # Read the hook payload from stdin (JSON with a `prompt` field).
    payload=$(cat)
    
    # Extract the prompt text. Prefer python3 for robust JSON parsing; fall back to
    # a sed extraction if python3 is unavailable.
    prompt=""
    if command -v python3 >/dev/null 2>&1; then
      prompt=$(printf '%s' "$payload" | python3 -c 'import sys,json
    try:
        print(json.load(sys.stdin).get("prompt",""))
    except Exception:
        pass' 2>/dev/null)
    fi
    if [ -z "$prompt" ]; then
      # Fallback: grab the value of the first "prompt": "..." occurrence.
      prompt=$(printf '%s' "$payload" | sed -n 's/.*"prompt"[[:space:]]*:[[:space:]]*"\(.*\)/\1/p' | head -c 4000)
    fi
    
    # Lowercase for matching.
    lc=$(printf '%s' "$prompt" | tr '[:upper:]' '[:lower:]')
    
    # If the prompt is empty, do nothing.
    if [ -z "$lc" ]; then
      exit 0
    fi
    
    # Currency-sensitive triggers. Two buckets:
    #   1. Generic recency/version words.
    #   2. Library/framework/tool/SDK/API discussion, which is version-sensitive.
    # Word-boundary-ish matching via grep -E. Keep this fast and conservative —
    # false positives just add a short reminder, false negatives miss enforcement.
    generic_re='(latest|newest|current|up[ -]?to[ -]?date|recently|this year|2025|2026|deprecat|breaking change|migrat|upgrade|release notes|changelog)'
    versionish_re='\bv?[0-9]+\.[0-9x]+'
    techterm_re='(api|sdk|cli|library|framework|package|npm|pypi|crate|gem|endpoint|model id|model name|pricing|price|cost per|rate limit|docs|documentation|install|config|version)'
    # Named ecosystems that move fast (high-signal). Extend freely.
    named_re='(next\.?js|react|vue|svelte|astro|hono|express|fastify|nest|drizzle|prisma|tailwind|shadcn|vite|bun|deno|node|typescript|stripe|polar|supabase|vercel|railway|cloudflare|openai|anthropic|claude|gpt|gemini|llama|langchain|playwright|puppeteer)'
    
    inject=false
    reason=""
    
    if printf '%s' "$lc" | grep -Eq "$generic_re"; then
      inject=true
      reason="recency/version language"
    elif printf '%s' "$lc" | grep -Eq "$versionish_re"; then
      inject=true
      reason="a specific version number"
    elif printf '%s' "$lc" | grep -Eq "$named_re" && printf '%s' "$lc" | grep -Eq "$techterm_re"; then
      inject=true
      reason="a fast-moving library/tool"
    fi
    
    if [ "$inject" != true ]; then
      exit 0
    fi
    
    # Build the directive. Single line, escaped for JSON.
    msg="Ultraship Currency Guard: this prompt touches ${reason}, where training data is often stale. BEFORE answering, verify against current sources — use the context7 MCP (resolve-library-id then query-docs) for any library/framework/SDK API, and WebSearch/WebFetch for versions, pricing, model IDs, release notes, or anything time-sensitive. Do not state version-specific facts, API signatures, or prices from memory. If you cannot verify, say so explicitly rather than guessing."
    
    # Escape backslashes and double quotes for valid JSON.
    esc=$(printf '%s' "$msg" | sed 's/\\/\\\\/g; s/"/\\"/g')
    
    printf '{"hookSpecificOutput":{"hookEventName":"UserPromptSubmit","additionalContext":"%s"}}' "$esc"
    exit 0
    
  • hooks/guard-bash.shGitHub
    Read the script
    #!/bin/bash
    # Ultraship Guard — PreToolUse hook for Bash commands
    # Blocks destructive commands and warns before execution
    # Reads the tool input from stdin (JSON with "input" field containing the command)
    
    INPUT=$(cat)
    # Extract the command value from JSON — handle escaped quotes
    # Use grep to get the "command":"value" pair, then strip the key prefix and trailing quote+brace
    COMMAND=$(printf '%s' "$INPUT" | tr -d '\n' | sed 's/.*"command"[[:space:]]*:[[:space:]]*"//;s/"[[:space:]]*[,}].*//' | sed 's/\\"/"/g')
    
    if [ -z "$COMMAND" ]; then
      # No command found — allow
      exit 0
    fi
    
    # Destructive patterns to block
    BLOCKED=false
    REASON=""
    
    # rm -rf anywhere in the command (catches piped, chained, and direct usage)
    if echo "$COMMAND" | grep -qE 'rm\s+(-[a-zA-Z]*r[a-zA-Z]*f|--recursive)'; then
      BLOCKED=true
      REASON="Destructive recursive removal (rm -rf)"
    fi
    
    # DROP TABLE / DROP DATABASE / TRUNCATE
    if echo "$COMMAND" | grep -qiE '(DROP\s+(TABLE|DATABASE)|TRUNCATE\s+TABLE)'; then
      BLOCKED=true
      REASON="SQL destructive operation — DROP/TRUNCATE"
    fi
    
    # git push --force to main/master
    if echo "$COMMAND" | grep -qE 'git\s+push\s+(-[a-zA-Z]*f|--force).*\s+(main|master)'; then
      BLOCKED=true
      REASON="Force-push to main/master branch"
    fi
    
    # git reset --hard
    if echo "$COMMAND" | grep -qE 'git\s+reset\s+--hard'; then
      BLOCKED=true
      REASON="git reset --hard — discards all uncommitted changes"
    fi
    
    # git checkout . (discard all changes)
    if echo "$COMMAND" | grep -qE 'git\s+checkout\s+\.$'; then
      BLOCKED=true
      REASON="git checkout . — discards all working directory changes"
    fi
    
    # git clean -f
    if echo "$COMMAND" | grep -qE 'git\s+clean\s+-[a-zA-Z]*f'; then
      BLOCKED=true
      REASON="git clean -f — permanently removes untracked files"
    fi
    
    # git branch -D (force delete)
    if echo "$COMMAND" | grep -qE 'git\s+branch\s+-D\s'; then
      BLOCKED=true
      REASON="git branch -D — force-deletes branch without merge check"
    fi
    
    # kubectl delete
    if echo "$COMMAND" | grep -qE 'kubectl\s+delete'; then
      BLOCKED=true
      REASON="kubectl delete — removes Kubernetes resources"
    fi
    
    # docker system prune / docker volume rm
    if echo "$COMMAND" | grep -qE 'docker\s+(system\s+prune|volume\s+rm)'; then
      BLOCKED=true
      REASON="Docker destructive operation"
    fi
    
    # git restore . (discard all working directory changes)
    if echo "$COMMAND" | grep -qE 'git\s+restore\s+\.$'; then
      BLOCKED=true
      REASON="git restore . — discards all working directory changes"
    fi
    
    # base64 decode piped to shell (encoded destructive commands)
    if echo "$COMMAND" | grep -qE 'base64\s+(-d|--decode).*\|\s*(ba)?sh'; then
      BLOCKED=true
      REASON="base64-encoded command piped to shell — potential destructive payload"
    fi
    
    # curl/wget piped to shell (remote code execution)
    if echo "$COMMAND" | grep -qE '(curl|wget)\s.*\|\s*(ba)?sh'; then
      BLOCKED=true
      REASON="Remote script piped to shell — potential code execution"
    fi
    
    # Python/Perl destructive one-liners
    if echo "$COMMAND" | grep -qE 'python[23]?\s+-c\s.*\b(rmtree|unlink|remove)\b'; then
      BLOCKED=true
      REASON="Python destructive filesystem operation"
    fi
    if echo "$COMMAND" | grep -qE 'perl\s+-e\s.*\b(rmtree|unlink)\b'; then
      BLOCKED=true
      REASON="Perl destructive filesystem operation"
    fi
    
    # xargs with destructive git/rm commands
    if echo "$COMMAND" | grep -qE 'xargs\s.*\b(rm\s+-rf|git\s+push\s+--force)'; then
      BLOCKED=true
      REASON="xargs chaining destructive command"
    fi
    
    if [ "$BLOCKED" = true ]; then
      echo "⚠️  GUARD BLOCKED: $REASON"
      echo "Command: $COMMAND"
      echo "To proceed, explicitly confirm this action."
      exit 2
    fi
    
    exit 0
    
  • hooks/guard-edit.shGitHub
    Read the script
    #!/bin/bash
    # Ultraship Guard — PreToolUse hook for Edit/Write commands
    # Blocks edits outside the frozen directory (if set)
    # Reads tool input from stdin
    
    FREEZE_FILE="${PWD}/.ultraship/guard-freeze.txt"
    
    # If no freeze file, allow all edits
    if [ ! -f "$FREEZE_FILE" ]; then
      exit 0
    fi
    
    FREEZE_DIR=$(cat "$FREEZE_FILE" 2>/dev/null | head -1 | tr -d '[:space:]')
    
    if [ -z "$FREEZE_DIR" ]; then
      exit 0
    fi
    
    # Extract file_path from the input JSON
    INPUT=$(cat)
    FILE_PATH=$(echo "$INPUT" | grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"file_path"[[:space:]]*:[[:space:]]*"//;s/"$//')
    
    if [ -z "$FILE_PATH" ]; then
      exit 0
    fi
    
    # Resolve to absolute path for comparison
    RESOLVED_FREEZE=$(cd "$PWD" && realpath "$FREEZE_DIR" 2>/dev/null || echo "$PWD/$FREEZE_DIR")
    RESOLVED_FILE=$(realpath "$FILE_PATH" 2>/dev/null || echo "$FILE_PATH")
    
    # Check if the file is within the frozen directory
    case "$RESOLVED_FILE" in
      "$RESOLVED_FREEZE"/*)
        # File is within the allowed directory
        exit 0
        ;;
      "$RESOLVED_FREEZE")
        # File is the directory itself (unlikely but safe)
        exit 0
        ;;
      *)
        echo "⚠️  GUARD BLOCKED: Edit outside frozen directory"
        echo "File: $FILE_PATH"
        echo "Allowed directory: $FREEZE_DIR"
        echo "To edit files outside this directory, run /unfreeze first."
        exit 2
        ;;
    esac
    
  • hooks/post-compact.shRunsGitHub
    Read the script
    #!/bin/bash
    # Ultraship PostCompact hook
    # Re-injects essential context after conversation compaction
    # This prevents ultraship state from being lost in long sessions
    
    CONTEXT=""
    
    # Check if guard is active
    FREEZE_FILE="${PWD}/.ultraship/guard-freeze.txt"
    if [ -f "$FREEZE_FILE" ]; then
      FREEZE_DIR=$(cat "$FREEZE_FILE" 2>/dev/null | head -1 | tr -d '[:space:]')
      if [ -n "$FREEZE_DIR" ]; then
        CONTEXT="Ultraship Guard is ACTIVE — edits restricted to: ${FREEZE_DIR}. Run /guard to manage."
      fi
    fi
    
    # Remind about available Ultraship commands
    CONTEXT="${CONTEXT}\\nUltraship plugin is active. Key commands: /ship (pre-deploy audit), /seo (SEO audit), /pentest (security test), /guard (safety), /sprint (workflow), /investigate (debug), /rescue (incidents), /compete (competitor analysis), /seo-strategy (elite SEO), /canary (post-deploy check), /retro (retrospective), /learn (project knowledge)."
    
    # Check CLAUDE.md freshness
    CLAUDE_MD="$PWD/CLAUDE.md"
    if [ -f "$CLAUDE_MD" ]; then
      if [ "$(uname)" = "Darwin" ]; then
        mod_epoch=$(stat -f %m "$CLAUDE_MD")
      else
        mod_epoch=$(stat -c %Y "$CLAUDE_MD")
      fi
      now_epoch=$(date +%s)
      age_days=$(( (now_epoch - mod_epoch) / 86400 ))
      if [ "$age_days" -ge 7 ]; then
        CONTEXT="${CONTEXT}\\nCLAUDE.md is ${age_days} days old — consider /revise-claude-md."
      fi
    fi
    
    # Output as hookSpecificOutput
    msg=$(printf '%s' "$CONTEXT" | sed 's/\\/\\\\/g; s/"/\\"/g; s/	/\\t/g')
    printf '{"hookSpecificOutput":{"hookEventName":"PostCompact","additionalContext":"%s"}}' "$msg"
    
  • hooks/session-start.shRunsGitHub
    Read the script
    #!/bin/bash
    # Ultraship SessionStart hook
    # Checks CLAUDE.md existence/freshness and enforces memory-first behavior
    
    CLAUDE_MD="$PWD/CLAUDE.md"
    CONTEXT=""
    
    # Helper: safely output JSON with escaped strings
    json_context() {
      local msg="$1"
      # Escape backslashes, double quotes, and control characters for valid JSON
      msg=$(printf '%s' "$msg" | sed 's/\\/\\\\/g; s/"/\\"/g; s/	/\\t/g')
      printf '{"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":"%s"}}' "$msg"
    }
    
    # --- Memory check ---
    # Look for MEMORY.md in common locations
    MEMORY_LOCATIONS=(
      "$HOME/.claude/projects/$(echo "$PWD" | tr '/' '-')/memory/MEMORY.md"
      "$PWD/.claude/memory/MEMORY.md"
      "$HOME/.claude/MEMORY.md"
    )
    
    MEMORY_FOUND=false
    for loc in "${MEMORY_LOCATIONS[@]}"; do
      if [ -f "$loc" ]; then
        MEMORY_FOUND=true
        break
      fi
    done
    
    if [ "$MEMORY_FOUND" = true ]; then
      CONTEXT="IMPORTANT: Read MEMORY.md and relevant memory files BEFORE performing any task. This ensures persistent context across sessions. Never skip this step."
    else
      CONTEXT="No memory files found. Consider setting up auto-memory (MEMORY.md) for persistent context across sessions."
    fi
    
    # --- CLAUDE.md check ---
    if [ ! -f "$CLAUDE_MD" ]; then
      CONTEXT="${CONTEXT}\\nNo CLAUDE.md found in this project directory (${PWD}). Offer to create one based on the project structure — check package.json, directory layout, and any existing README for context."
      json_context "$CONTEXT"
      exit 0
    fi
    
    if [ "$(uname)" = "Darwin" ]; then
      mod_epoch=$(stat -f %m "$CLAUDE_MD")
    else
      mod_epoch=$(stat -c %Y "$CLAUDE_MD")
    fi
    now_epoch=$(date +%s)
    age_days=$(( (now_epoch - mod_epoch) / 86400 ))
    
    if [ "$age_days" -ge 7 ]; then
      CONTEXT="${CONTEXT}\\nCLAUDE.md in this project is ${age_days} days old. Consider running /revise-claude-md to keep it current."
    fi
    
    json_context "$CONTEXT"
    

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.

@houseofmvps
Ships withultraship

"ULTRASHIP" Claude Code plugin — 39 skills, 33 tools, 11 agents for ship-ready workflows: planning, review, pentesting, safety guardrails, canary monitoring, SEO/AI-readiness check, penetration testing, code review, competitive analysis, incident response. 1 dependency. 180 tests. MIT.

12445 skills13 agents2 MCP
Get the whole plugin

Stars on GitHub

Momentum

stars in total
124
Star history of Houseofmvps/ultraship 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 hook311k
@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 hooks188k
@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+311k
@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 ultraship.

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