Skip to content
Development
Hook

Hooks

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

From plugin
devteam
1820 skills128 agents20 commands13 hooks
+1
Install
> /plugin marketplace add michael-harris/devteam
> /plugin install devteam@devteam-marketplace

Ships with devteam. Installing the plugin gets these hooks.

What fires, and when

PreToolUse

  • MatchesEdit|Writenode "${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.js" pre-tool-use-hook
  • MatchesBashnode "${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.js" pre-tool-use-hook

PostToolUse

  • MatchesBashnode "${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.js" post-tool-use-hook
  • MatchesEdit|Writenode "${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.js" post-tool-use-hook

Stop

  • node "${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.js" stop-hook

SubagentStart

  • node "${CLAUDE_PLUGIN_ROOT}/hooks/log-event.js" agent_start orchestration "Subagent started"

SubagentStop

  • node "${CLAUDE_PLUGIN_ROOT}/hooks/log-event.js" agent_stop orchestration "Subagent completed"

TaskCompleted

  • node "${CLAUDE_PLUGIN_ROOT}/hooks/log-event.js" task_completed orchestration "Task marked complete"

WorktreeCreate

  • node "${CLAUDE_PLUGIN_ROOT}/hooks/log-event.js" worktree_created infrastructure "Git worktree created"

WorktreeRemove

  • node "${CLAUDE_PLUGIN_ROOT}/hooks/log-event.js" worktree_removed infrastructure "Git worktree removed"

TeammateIdle

PreCompact

  • node "${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.js" pre-compact

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|resumenode "${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.js" session-start

SessionEnd

  • node "${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.js" session-end

Notification

  • Matchesidle_promptnode "${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.js" persistence-hook
Read hooks/hooks.json

In the plugin's words

How devteam describes its own hook set.

DevTeam declarative hooks configuration — includes command, prompt, and agent hook types

Where it lives

  • hooks/install.ps1GitHub
    Read the script
    # DevTeam Hooks Installer (PowerShell)
    # Installs hooks into Claude Code configuration and git
    #
    # Usage: .\install.ps1 [-Auto]
    
    param(
        [switch]$Auto
    )
    
    $ErrorActionPreference = "Stop"
    
    $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
    $ProjectRoot = Split-Path -Parent $ScriptDir
    
    Write-Host ""
    Write-Host "================================================================" -ForegroundColor Blue
    Write-Host "             DevTeam Hooks Installer" -ForegroundColor Blue
    Write-Host "================================================================" -ForegroundColor Blue
    Write-Host ""
    
    # ============================================================================
    # DETECT CLAUDE CODE CONFIG
    # ============================================================================
    
    function Get-ClaudeConfigPath {
        $locations = @(
            "$env:USERPROFILE\.claude\settings.json",
            "$env:APPDATA\Claude\settings.json",
            "$env:LOCALAPPDATA\Claude\settings.json"
        )
    
        foreach ($loc in $locations) {
            $dir = Split-Path $loc -Parent
            if (Test-Path $dir) {
                return $loc
            }
        }
    
        return $null
    }
    
    $ClaudeConfigFile = Get-ClaudeConfigPath
    
    if (-not $ClaudeConfigFile) {
        Write-Host "! Could not auto-detect Claude Code configuration directory." -ForegroundColor Yellow
        Write-Host ""
        Write-Host "  Please manually add hooks to your Claude Code settings."
        Write-Host "  See hooks\README.md for configuration details."
        Write-Host ""
    } else {
        Write-Host "ok Found Claude Code config: $ClaudeConfigFile" -ForegroundColor Green
    }
    Write-Host ""
    
    # ============================================================================
    # INSTALL GIT HOOKS
    # ============================================================================
    
    $gitDir = Join-Path $ProjectRoot ".git"
    if (Test-Path $gitDir) {
        Write-Host "Installing git hooks..."
    
        $gitHooksDir = Join-Path $gitDir "hooks"
        if (-not (Test-Path $gitHooksDir)) {
            New-Item -ItemType Directory -Path $gitHooksDir -Force | Out-Null
        }
    
        # Pre-commit hook
        $preCommitPath = Join-Path $gitHooksDir "pre-commit"
        @"
    #!/bin/bash
    exec "$ScriptDir/scope-check.sh"
    "@ | Set-Content $preCommitPath -Encoding UTF8
    
        Write-Host "ok Git pre-commit hook installed" -ForegroundColor Green
    } else {
        Write-Host "! Not a git repository - skipping git hooks" -ForegroundColor Yellow
    }
    Write-Host ""
    
    # ============================================================================
    # GENERATE CLAUDE CODE CONFIG
    # ============================================================================
    
    Write-Host "Generating Claude Code hook configuration..."
    Write-Host ""
    
    $HooksConfig = @"
    {
      "hooks": {
        "PreToolUse": [
          {
            "matcher": ".*",
            "hooks": ["powershell -ExecutionPolicy Bypass -File $ScriptDir\pre-tool-use-hook.ps1"]
          }
        ],
        "PostToolUse": [
          {
            "matcher": ".*",
            "hooks": ["powershell -ExecutionPolicy Bypass -File $ScriptDir\post-tool-use-hook.ps1"]
          }
        ],
        "Stop": [
          {
            "matcher": ".*",
            "hooks": ["powershell -ExecutionPolicy Bypass -File $ScriptDir\stop-hook.ps1"]
          }
        ],
        "PostMessage": [
          {
            "matcher": ".*",
            "hooks": ["powershell -ExecutionPolicy Bypass -File $ScriptDir\persistence-hook.ps1"]
          }
        ],
        "SessionStart": [
          {
            "matcher": ".*",
            "hooks": ["powershell -ExecutionPolicy Bypass -File $ScriptDir\session-start.ps1"]
          }
        ],
        "SessionEnd": [
          {
            "matcher": ".*",
            "hooks": ["powershell -ExecutionPolicy Bypass -File $ScriptDir\session-end.ps1"]
          }
        ],
        "PreCompact": [
          {
            "matcher": ".*",
            "hooks": ["powershell -ExecutionPolicy Bypass -File $ScriptDir\pre-compact.ps1"]
          }
        ]
      }
    }
    "@
    
    Write-Host "Add the following to your Claude Code settings:"
    Write-Host ""
    Write-Host "----------------------------------------------------------------" -ForegroundColor Blue
    Write-Host $HooksConfig
    Write-Host "----------------------------------------------------------------" -ForegroundColor Blue
    Write-Host ""
    
    # ============================================================================
    # AUTO-INSTALL TO SETTINGS
    # ============================================================================
    
    function Install-ToSettings {
        param([string]$ConfigFile)
    
        if (-not (Test-Path $ConfigFile)) {
            # Create new config
            $configDir = Split-Path $ConfigFile -Parent
            if (-not (Test-Path $configDir)) {
                New-Item -ItemType Directory -Path $configDir -Force | Out-Null
            }
            $HooksConfig | Set-Content $ConfigFile -Encoding UTF8
            Write-Host "ok Config file created: $ConfigFile" -ForegroundColor Green
            return $true
        }
    
        # Backup existing config
        Copy-Item $ConfigFile "$ConfigFile.backup" -Force
        Write-Host "ok Backed up existing config to $ConfigFile.backup" -ForegroundColor Green
    
        # Try to merge configs
        try {
            $existing = Get-Content $ConfigFile -Raw | ConvertFrom-Json
            $new = $HooksConfig | ConvertFrom-Json
    
            # Merge hooks
            if (-not $existing.hooks) {
                $existing | Add-Member -NotePropertyName "hooks" -NotePropertyValue @{}
            }
    
            foreach ($prop in $new.hooks.PSObject.Properties) {
                $existing.hooks | Add-Member -NotePropertyName $prop.Name -NotePropertyValue $prop.Value -Force
            }
    
            $existing | ConvertTo-Json -Depth 10 | Set-Content $ConfigFile -Encoding UTF8
            Write-Host "ok Hooks merged into config" -ForegroundColor Green
            return $true
        } catch {
            Write-Host "! Could not merge configs: $_" -ForegroundColor Yellow
            Write-Host "  Please manually merge the configuration." -ForegroundColor Yellow
            return $false
        }
    }
    
    if ($ClaudeConfigFile) {
        if ($Auto) {
            Install-ToSettings $ClaudeConfigFile | Out-Null
        } else {
            $
  • hooks/install.shGitHub
    Read the script
    #!/bin/bash
    # DevTeam Hooks Installer
    # Installs hooks into Claude Code configuration and git
    #
    # Usage: ./install.sh [--auto]
    #   --auto: Skip interactive prompts, auto-install everything
    
    set -euo pipefail
    
    SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
    PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
    
    # Colors
    RED='\033[0;31m'
    GREEN='\033[0;32m'
    YELLOW='\033[1;33m'
    BLUE='\033[0;34m'
    NC='\033[0m'
    
    # Options
    AUTO_MODE=false
    [[ "${1:-}" == "--auto" ]] && AUTO_MODE=true
    
    echo ""
    echo -e "${BLUE}================================================================${NC}"
    echo -e "${BLUE}             DevTeam Hooks Installer${NC}"
    echo -e "${BLUE}================================================================${NC}"
    echo ""
    
    # ============================================================================
    # DETECT CLAUDE CODE CONFIG
    # ============================================================================
    
    detect_claude_config() {
        local config_dir=""
        local config_file=""
    
        # Check common locations
        if [[ -d "$HOME/.claude" ]]; then
            config_dir="$HOME/.claude"
        elif [[ -d "$HOME/Library/Application Support/Claude" ]]; then
            config_dir="$HOME/Library/Application Support/Claude"
        elif [[ -n "${APPDATA:-}" ]] && [[ -d "$APPDATA/Claude" ]]; then
            config_dir="$APPDATA/Claude"
        elif [[ -n "${XDG_CONFIG_HOME:-}" ]] && [[ -d "$XDG_CONFIG_HOME/claude" ]]; then
            config_dir="$XDG_CONFIG_HOME/claude"
        fi
    
        if [[ -n "$config_dir" ]]; then
            config_file="$config_dir/settings.json"
            echo "$config_file"
        fi
    }
    
    CLAUDE_CONFIG_FILE=$(detect_claude_config)
    
    if [[ -z "$CLAUDE_CONFIG_FILE" ]]; then
        echo -e "${YELLOW}! Could not auto-detect Claude Code configuration directory.${NC}"
        echo ""
        echo "  Please manually add hooks to your Claude Code settings."
        echo "  See hooks/README.md for configuration details."
        echo ""
    else
        echo -e "${GREEN}ok${NC} Found Claude Code config: $CLAUDE_CONFIG_FILE"
    fi
    echo ""
    
    # ============================================================================
    # MAKE HOOKS EXECUTABLE
    # ============================================================================
    
    echo "Making hooks executable..."
    
    chmod +x "$SCRIPT_DIR"/*.sh 2>/dev/null || true
    chmod +x "$SCRIPT_DIR"/lib/*.sh 2>/dev/null || true
    
    echo -e "${GREEN}ok${NC} Hooks are executable"
    echo ""
    
    # ============================================================================
    # INSTALL GIT HOOKS
    # ============================================================================
    
    if [[ -d "$PROJECT_ROOT/.git" ]]; then
        echo "Installing git hooks..."
    
        GIT_HOOKS_DIR="$PROJECT_ROOT/.git/hooks"
        mkdir -p "$GIT_HOOKS_DIR"
    
        # Pre-commit hook for scope checking
        cat > "$GIT_HOOKS_DIR/pre-commit" << EOF
    #!/bin/bash
    # DevTeam scope check hook
    exec "$SCRIPT_DIR/scope-check.sh"
    EOF
        chmod +x "$GIT_HOOKS_DIR/pre-commit"
    
        echo -e "${GREEN}ok${NC} Git pre-commit hook installed"
    else
        echo -e "${YELLOW}!${NC} Not a git repository - skipping git hooks"
    fi
    echo ""
    
    # ============================================================================
    # GENERATE CLAUDE CODE CONFIG
    # ============================================================================
    
    echo "Generating Claude Code hook configuration..."
    echo ""
    
    # Determine script extension based on platform
    if [[ "$OSTYPE" == "msys" ]] || [[ "$OSTYPE" == "cygwin" ]] || [[ -n "${WINDIR:-}" ]]; then
        HOOK_EXT=".ps1"
        SHELL_CMD="powershell -ExecutionPolicy Bypass -File"
    else
        HOOK_EXT=".sh"
        SHELL_CMD=""
    fi
    
    HOOKS_CONFIG=$(cat << EOF
    {
      "hooks": {
        "PreToolUse": [
          {
            "matcher": ".*",
            "hooks": ["$SHELL_CMD$SCRIPT_DIR/pre-tool-use-hook$HOOK_EXT"]
          }
        ],
        "PostToolUse": [
          {
            "matcher": ".*",
            "hooks": ["$SHELL_CMD$SCRIPT_DIR/post-tool-use-hook$HOOK_EXT"]
          }
        ],
        "Stop": [
          {
            "matcher": ".*",
            "hooks": ["$SHELL_CMD$SCRIPT_DIR/stop-hook$HOOK_EXT"]
          }
        ],
        "PostMessage": [
          {
            "matcher": ".*",
            "hooks": ["$SHELL_CMD$SCRIPT_DIR/persistence-hook$HOOK_EXT"]
          }
        ],
        "SessionStart": [
          {
            "matcher": ".*",
            "hooks": ["$SHELL_CMD$SCRIPT_DIR/session-start$HOOK_EXT"]
          }
        ],
        "SessionEnd": [
          {
            "matcher": ".*",
            "hooks": ["$SHELL_CMD$SCRIPT_DIR/session-end$HOOK_EXT"]
          }
        ],
        "PreCompact": [
          {
            "matcher": ".*",
            "hooks": ["$SHELL_CMD$SCRIPT_DIR/pre-compact$HOOK_EXT"]
          }
        ]
      }
    }
    EOF
    )
    
    echo "Add the following to your Claude Code settings:"
    echo ""
    echo -e "${BLUE}----------------------------------------------------------------${NC}"
    echo "$HOOKS_CONFIG"
    echo -e "${BLUE}----------------------------------------------------------------${NC}"
    echo ""
    
    # ============================================================================
    # AUTO-INSTALL TO SETTINGS
    # ============================================================================
    
    install_to_settings() {
        local config_file="$1"
    
        if [[ ! -f "$config_file" ]]; then
            # Create new config
            mkdir -p "$(dirname "$config_file")"
            echo "$HOOKS_CONFIG" > "$config_file"
            echo -e "${GREEN}ok${NC} Config file created: $config_file"
            return 0
        fi
    
        # Backup existing config
        cp "$config_file" "${config_file}.backup"
        echo -e "${GREEN}ok${NC} Backed up existing config to ${config_file}.backup"
    
        # Merge hooks into existing config
        if command -v jq &> /dev/null; then
            # Use jq for proper JSON merge
            local temp_file="${config_file}.tmp"
            jq -s '.[0] * .[1]' "$config_file" <(echo "$HOOKS_CONFIG") > "$temp_file" \
                || { mv "${config_file}.backup" "$config_file"; echo "jq merge failed"; return 1; }
            mv "$temp_file" "$config_file"
            echo -e "${GREEN}ok${NC} Hooks merged into config"
        else
            echo -e "${YELLOW}!${NC} jq not installed. Please manually m
  • hooks/log-event.jsRunsGitHub
    Read the script
    #!/usr/bin/env node
    // Cross-platform event logger for DevTeam hooks
    // Logs events to .devteam/devteam.db via sqlite3 CLI.
    // Best-effort — silently no-ops if sqlite3 or DB is unavailable.
    
    'use strict';
    
    const { execSync } = require('child_process');
    const { existsSync } = require('fs');
    
    var eventType = process.argv[2];
    var category = process.argv[3];
    var message = process.argv[4];
    var data = process.argv[5] || '{}';
    var dbFile = '.devteam/devteam.db';
    
    if (!eventType || !existsSync(dbFile)) {
      process.exit(0);
    }
    
    // Escape single quotes for SQL
    function esc(s) {
      return (s || '').replace(/'/g, "''");
    }
    
    var sql = "INSERT INTO events (session_id, event_type, event_category, message, data, timestamp) " +
      "VALUES (" +
      "(SELECT id FROM sessions WHERE status = 'running' ORDER BY started_at DESC LIMIT 1), " +
      "'" + esc(eventType) + "', " +
      "'" + esc(category) + "', " +
      "'" + esc(message) + "', " +
      "'" + esc(data) + "', " +
      "datetime('now'));";
    
    try {
      execSync('sqlite3 "' + dbFile + '"', {
        input: sql,
        stdio: ['pipe', 'ignore', 'ignore'],
        timeout: 5000,
      });
    } catch (e) {
      // Best-effort logging — don't block on failure
    }
    
  • hooks/persistence-hook.ps1GitHub
    Read the script
    # DevTeam Persistence Hook (PowerShell)
    # Detects and prevents premature task abandonment
    #
    # Exit codes:
    #   0 = Allow (output is acceptable)
    #   2 = Block and re-engage (detected abandonment attempt)
    
    $ErrorActionPreference = "Stop"
    
    # Source common library
    . "$PSScriptRoot\lib\hook-common.ps1"
    
    Initialize-Hook "persistence"
    
    # ============================================================================
    # CONFIGURATION
    # ============================================================================
    
    $MESSAGE = $env:CLAUDE_OUTPUT
    $ABANDONMENT_LOG = Join-Path $script:DEVTEAM_DIR "abandonment-attempts.log"
    
    # If no message, allow
    if ([string]::IsNullOrEmpty($MESSAGE)) {
        exit 0
    }
    
    # ============================================================================
    # ABANDONMENT DETECTION PATTERNS
    # ============================================================================
    
    # Direct abandonment phrases
    $GIVE_UP_PATTERNS = @(
        # Direct abandonment
        "I cannot complete this",
        "I'm unable to",
        "I can't figure out",
        "I don't know how to",
        "I'm not sure how to proceed",
        "I give up",
        "I'm stuck",
        "This is beyond my",
        "I cannot determine",
        "I'm at a loss",
        "I have no idea",
    
        # Premature completion claims
        "I've done what I can",
        "That's all I can do",
        "I've tried everything",
        "Nothing else I can try",
        "I'm out of ideas",
        "I've exhausted",
        "No other options",
    
        # Deflection to user
        "You should try",
        "You might want to",
        "You'll need to manually",
        "This requires human",
        "A human needs to",
        "You could try",
        "Perhaps you could",
        "Maybe you should",
    
        # False completion
        "I'll stop here",
        "Let me stop",
        "I think we should stop",
        "We can stop here",
        "I'm going to stop",
        "That should be enough",
        "I'll leave it here",
    
        # Excuse patterns
        "This is too complex",
        "This would take too long",
        "I don't have access",
        "I can't access",
        "Outside my capabilities",
        "Beyond my ability",
        "Not possible for me",
        "I lack the ability"
    )
    
    # Passive abandonment patterns
    $PASSIVE_ABANDONMENT_PATTERNS = @(
        "Let me know if you need",
        "Let me know if you want",
        "Let me know if you'd like",
        "Feel free to",
        "You can try",
        "You might try",
        "would you like me to",
        "should I",
        "I can stop here",
        "we could stop",
        "that should work",
        "should be working",
        "I hope this helps",
        "Hope that helps",
        "Let me know if",
        "If you need anything else",
        "I'm here if you need"
    )
    
    # Permission-seeking patterns
    $PERMISSION_SEEKING_PATTERNS = @(
        "Should I proceed",
        "Do you want me to",
        "Would you like me to",
        "Shall I",
        "Want me to",
        "Can I",
        "May I",
        "Is it okay if",
        "Would it be okay",
        "Do you mind if"
    )
    
    # Legitimate completion patterns
    $LEGITIMATE_STOP_PATTERNS = @(
        "EXIT_SIGNAL: true",
        "EXIT_SIGNAL:true",
        "All tests passing",
        "All quality gates passed",
        "Task completed successfully",
        "Implementation complete",
        "Ready for review",
        "Committed and pushed",
        "All acceptance criteria met",
        "Successfully completed",
        "/devteam:end"
    )
    
    # ============================================================================
    # DETECTION LOGIC
    # ============================================================================
    
    # Check for legitimate completion first
    foreach ($pattern in $LEGITIMATE_STOP_PATTERNS) {
        if ($MESSAGE -match [regex]::Escape($pattern)) {
            Write-HookInfo "persistence" "Legitimate completion detected: $pattern"
            exit 0
        }
    }
    
    $DETECTED_PATTERN = $null
    $DETECTION_TYPE = $null
    
    # Check for direct abandonment
    foreach ($pattern in $GIVE_UP_PATTERNS) {
        if ($MESSAGE -match [regex]::Escape($pattern)) {
            $DETECTED_PATTERN = $pattern
            $DETECTION_TYPE = "direct_abandonment"
            break
        }
    }
    
    # Check for passive abandonment
    if (-not $DETECTED_PATTERN) {
        foreach ($pattern in $PASSIVE_ABANDONMENT_PATTERNS) {
            if ($MESSAGE -match [regex]::Escape($pattern)) {
                $DETECTED_PATTERN = $pattern
                $DETECTION_TYPE = "passive_abandonment"
                break
            }
        }
    }
    
    # Check for permission-seeking (only when there's an active task)
    if (-not $DETECTED_PATTERN) {
        $activeSession = Get-CurrentSession
        $activeTask = Get-CurrentTask
    
        if ($activeSession -and $activeTask) {
            foreach ($pattern in $PERMISSION_SEEKING_PATTERNS) {
                if ($MESSAGE -match [regex]::Escape($pattern)) {
                    $DETECTED_PATTERN = $pattern
                    $DETECTION_TYPE = "permission_seeking"
                    break
                }
            }
        }
    }
    
    # If no abandonment detected, allow
    if (-not $DETECTED_PATTERN) {
        exit 0
    }
    
    # ============================================================================
    # ABANDONMENT RESPONSE
    # ============================================================================
    
    Write-HookWarn "persistence" "Abandonment attempt detected ($DETECTION_TYPE): '$DETECTED_PATTERN'"
    
    # Get current task info
    $TASK_ID = Get-CurrentTask
    if (-not $TASK_ID) { $TASK_ID = "unknown" }
    
    # Log to abandonment file
    if (-not (Test-Path (Split-Path $ABANDONMENT_LOG -Parent))) {
        New-Item -ItemType Directory -Path (Split-Path $ABANDONMENT_LOG -Parent) -Force | Out-Null
    }
    $timestamp = Get-Date -Format "o"
    "[$timestamp] $DETECTION_TYPE`: '$DETECTED_PATTERN' (task: $TASK_ID)" | Add-Content $ABANDONMENT_LOG
    
    # Count abandonment attempts
    $ATTEMPT_COUNT = 0
    if (Test-Path $ABANDONMENT_LOG) {
        $ATTEMPT_COUNT = (Get-Content $ABANDONMENT_LOG | Measure-Object -Line).Lines
    }
    
    Write-HookInfo "persistence" "Abandonment attempt #$ATTEMPT_COUNT for task: $TASK_ID"
    
    # Escape values for safe JSON embedding
    $SafePattern = ConvertTo-SafeJsonString $DETECTED_PATTERN
    $SafeType = ConvertTo-SafeJsonString $DETECTION_TYPE
    $SafeTaskId = ConvertTo-SafeJsonString $TASK_ID
    
    # Log to databas
  • hooks/persistence-hook.shGitHub
    Read the script
    #!/bin/bash
    # DevTeam Persistence Hook
    # Detects and prevents premature task abandonment
    #
    # This hook runs on PostMessage and analyzes Claude's output for
    # "give up" signals, blocking them and forcing continued effort.
    #
    # Exit codes:
    #   0 = Allow (output is acceptable)
    #   2 = Block and re-engage (detected abandonment attempt)
    #
    # Environment variables expected:
    #   CLAUDE_OUTPUT - Claude's text output to analyze
    
    set -euo pipefail
    
    SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
    
    # Source common library with fallback paths (H6)
    if [[ -f "$SCRIPT_DIR/lib/hook-common.sh" ]]; then
        source "$SCRIPT_DIR/lib/hook-common.sh"
    elif [[ -f "$SCRIPT_DIR/../lib/hook-common.sh" ]]; then
        source "$SCRIPT_DIR/../lib/hook-common.sh"
    else
        echo "[persistence] Warning: hook-common.sh not found" >&2
        exit 0
    fi
    
    init_hook "persistence"
    
    # ============================================================================
    # CONFIGURATION
    # ============================================================================
    
    MESSAGE="${CLAUDE_OUTPUT:-}"
    ABANDONMENT_LOG="${DEVTEAM_DIR}/abandonment-attempts.log"
    
    # If no message, allow
    if [[ -z "$MESSAGE" ]]; then
        exit 0
    fi
    
    # ============================================================================
    # ABANDONMENT DETECTION PATTERNS
    # ============================================================================
    
    # Direct abandonment phrases
    GIVE_UP_PATTERNS=(
        # Direct abandonment
        "I cannot complete this"
        "I'm unable to"
        "I can't figure out"
        "I don't know how to"
        "I'm not sure how to proceed"
        "I give up"
        "I'm stuck"
        "This is beyond my"
        "I cannot determine"
        "I'm at a loss"
        "I have no idea"
    
        # Premature completion claims
        "I've done what I can"
        "That's all I can do"
        "I've tried everything"
        "Nothing else I can try"
        "I'm out of ideas"
        "I've exhausted"
        "No other options"
    
        # Deflection to user
        "You should try"
        "You might want to"
        "You'll need to manually"
        "This requires human"
        "A human needs to"
        "You could try"
        "Perhaps you could"
        "Maybe you should"
    
        # False completion
        "I'll stop here"
        "Let me stop"
        "I think we should stop"
        "We can stop here"
        "I'm going to stop"
        "That should be enough"
        "I'll leave it here"
    
        # Excuse patterns
        "This is too complex"
        "This would take too long"
        "I don't have access"
        "I can't access"
        "Outside my capabilities"
        "Beyond my ability"
        "Not possible for me"
        "I lack the ability"
    )
    
    # Passive abandonment (suggests user action instead of completing)
    PASSIVE_ABANDONMENT_PATTERNS=(
        "Let me know if you'd like"
        "You can try"
        "You might try"
        "would you like me to"
        "should I"
        "I can stop here"
        "we could stop"
        "that should work"
        "should be working"
        "Let me know if"
        "If you need anything else"
        "I'm here if you need"
    )
    
    # Permission-seeking (asking when should be acting)
    PERMISSION_SEEKING_PATTERNS=(
        "Should I proceed"
        "Do you want me to"
        "Would you like me to"
        "Shall I"
        "Want me to"
        "Can I"
        "May I"
        "Is it okay if"
        "Would it be okay"
        "Do you mind if"
    )
    
    # Legitimate completion patterns (allow these)
    LEGITIMATE_STOP_PATTERNS=(
        "EXIT_SIGNAL: true"
        "EXIT_SIGNAL:true"
        "All tests passing"
        "All quality gates passed"
        "Task completed successfully"
        "Implementation complete"
        "Ready for review"
        "Committed and pushed"
        "All acceptance criteria met"
        "Successfully completed"
        "/devteam:end"
    )
    
    # ============================================================================
    # DETECTION LOGIC
    # ============================================================================
    
    # Check for legitimate completion first
    for pattern in "${LEGITIMATE_STOP_PATTERNS[@]}"; do
        if echo "$MESSAGE" | grep -qi "$pattern"; then
            log_info "persistence" "Legitimate completion detected: $pattern"
            exit 0
        fi
    done
    
    # Track detected patterns
    DETECTED_PATTERN=""
    DETECTION_TYPE=""
    
    # Check for direct abandonment
    for pattern in "${GIVE_UP_PATTERNS[@]}"; do
        if echo "$MESSAGE" | grep -qi "$pattern"; then
            DETECTED_PATTERN="$pattern"
            DETECTION_TYPE="direct_abandonment"
            break
        fi
    done
    
    # Check for passive abandonment
    if [[ -z "$DETECTED_PATTERN" ]]; then
        for pattern in "${PASSIVE_ABANDONMENT_PATTERNS[@]}"; do
            if echo "$MESSAGE" | grep -qi "$pattern"; then
                DETECTED_PATTERN="$pattern"
                DETECTION_TYPE="passive_abandonment"
                break
            fi
        done
    fi
    
    # Check for permission-seeking (only when there's an active task)
    if [[ -z "$DETECTED_PATTERN" ]]; then
        active_session=$(get_current_session)
        active_task=$(get_current_task)
    
        if [[ -n "$active_session" ]] && [[ -n "$active_task" ]]; then
            for pattern in "${PERMISSION_SEEKING_PATTERNS[@]}"; do
                if echo "$MESSAGE" | grep -qi "$pattern"; then
                    DETECTED_PATTERN="$pattern"
                    DETECTION_TYPE="permission_seeking"
                    break
                fi
            done
        fi
    fi
    
    # If no abandonment detected, allow
    if [[ -z "$DETECTED_PATTERN" ]]; then
        exit 0
    fi
    
    # ============================================================================
    # ABANDONMENT RESPONSE
    # ============================================================================
    
    log_warn "persistence" "Abandonment attempt detected ($DETECTION_TYPE): '$DETECTED_PATTERN'"
    
    # Get current task info
    TASK_ID=$(get_current_task)
    [[ -z "$TASK_ID" ]] && TASK_ID="unknown"
    
    # Log to abandonment file
    mkdir -p "$(dirname "$ABANDONMENT_LOG")"
    echo "[$(date -Iseconds)] $DETECTION_TYPE: '$(printf '%s' "$DETECTED_PATTERN")' (task: $TASK_ID)" >> "$ABANDONMENT_LOG"
    
    # Count abandonment attempts for this session
    ATTEMPT_COUNT=$(wc -l < "$ABANDONMENT_LOG" 2>/dev/null || echo "0")
    
    log_info "persistence" "Abandonment attempt #$ATTEMPT_COUNT for task: $TASK
  • hooks/post-tool-use-hook.ps1GitHub
  • hooks/post-tool-use-hook.shGitHub
  • hooks/pre-compact.ps1GitHub
  • hooks/pre-compact.shGitHub
  • hooks/pre-tool-use-hook.ps1GitHub
  • hooks/pre-tool-use-hook.shGitHub
  • hooks/run-hook.jsRunsGitHub
    Read the script
    #!/usr/bin/env node
    // Cross-platform hook runner for DevTeam
    // Detects OS and runs the appropriate .sh (Unix) or .ps1 (Windows) hook script.
    // Always exits 0 on error — hooks must never block Claude Code.
    
    'use strict';
    
    const { execSync } = require('child_process');
    const { existsSync } = require('fs');
    const { resolve } = require('path');
    
    const hookName = process.argv[2];
    
    if (!hookName) {
      process.exit(0);
    }
    
    const isWindows = process.platform === 'win32';
    const shScript = resolve(__dirname, hookName + '.sh');
    const ps1Script = resolve(__dirname, hookName + '.ps1');
    
    try {
      if (isWindows) {
        if (existsSync(ps1Script)) {
          execSync(
            'powershell -ExecutionPolicy Bypass -NoProfile -File "' + ps1Script + '"',
            { stdio: 'inherit', timeout: 30000 }
          );
        }
      } else {
        if (existsSync(shScript)) {
          execSync('bash "' + shScript + '"', { stdio: 'inherit', timeout: 30000 });
        }
      }
    } catch (e) {
      // Hooks degrade gracefully — never block Claude Code
      process.exit(0);
    }
    
  • hooks/scope-check.ps1GitHub
  • hooks/scope-check.shGitHub
  • hooks/session-end.ps1GitHub
  • hooks/session-end.shGitHub
  • hooks/session-start.ps1GitHub
  • hooks/session-start.shGitHub
  • hooks/stop-hook.ps1GitHub
  • hooks/stop-hook.shGitHub

All 20 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 withdevteam

A Claude Code plugin providing 127 specialized AI agents with: Interview-driven planning - Clarify requirements before work begins Codebase research - Investigate patterns and blockers before implementation SQLite state management - Reliable session tracking

Get the whole plugin