Skip to content
Development
Hook

Hooks

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

From plugin
gsd-pro
8615 agents38 commands3 hooks

Where it lives

  • hooks/gsd-check-update.jsGitHub
    Read the script
    #!/usr/bin/env node
    // Check for GSD updates in background, write result to cache
    // Called by SessionStart hook - runs once per session
    
    const fs = require('fs');
    const path = require('path');
    const os = require('os');
    const { spawn } = require('child_process');
    
    const homeDir = os.homedir();
    const cwd = process.cwd();
    
    // Detect runtime config directory (supports Claude, OpenCode, Gemini)
    // Respects CLAUDE_CONFIG_DIR for custom config directory setups
    function detectConfigDir(baseDir) {
      // Check env override first (supports multi-account setups)
      const envDir = process.env.CLAUDE_CONFIG_DIR;
      if (envDir && fs.existsSync(path.join(envDir, 'get-shit-done', 'VERSION'))) {
        return envDir;
      }
      for (const dir of ['.config/opencode', '.opencode', '.gemini', '.claude']) {
        if (fs.existsSync(path.join(baseDir, dir, 'get-shit-done', 'VERSION'))) {
          return path.join(baseDir, dir);
        }
      }
      return envDir || path.join(baseDir, '.claude');
    }
    
    const globalConfigDir = detectConfigDir(homeDir);
    const projectConfigDir = detectConfigDir(cwd);
    const cacheDir = path.join(globalConfigDir, 'cache');
    const cacheFile = path.join(cacheDir, 'gsd-update-check.json');
    
    // VERSION file locations (check project first, then global)
    const projectVersionFile = path.join(projectConfigDir, 'get-shit-done', 'VERSION');
    const globalVersionFile = path.join(globalConfigDir, 'get-shit-done', 'VERSION');
    
    // Ensure cache directory exists
    if (!fs.existsSync(cacheDir)) {
      fs.mkdirSync(cacheDir, { recursive: true });
    }
    
    // Run check in background (spawn background process, windowsHide prevents console flash)
    const child = spawn(process.execPath, ['-e', `
      const fs = require('fs');
      const { execSync } = require('child_process');
    
      const cacheFile = ${JSON.stringify(cacheFile)};
      const projectVersionFile = ${JSON.stringify(projectVersionFile)};
      const globalVersionFile = ${JSON.stringify(globalVersionFile)};
    
      // Check project directory first (local install), then global
      let installed = '0.0.0';
      try {
        if (fs.existsSync(projectVersionFile)) {
          installed = fs.readFileSync(projectVersionFile, 'utf8').trim();
        } else if (fs.existsSync(globalVersionFile)) {
          installed = fs.readFileSync(globalVersionFile, 'utf8').trim();
        }
      } catch (e) {}
    
      let latest = null;
      try {
        latest = execSync('npm view get-shit-done-cc version', { encoding: 'utf8', timeout: 10000, windowsHide: true }).trim();
      } catch (e) {}
    
      const result = {
        update_available: latest && installed !== latest,
        installed,
        latest: latest || 'unknown',
        checked: Math.floor(Date.now() / 1000)
      };
    
      fs.writeFileSync(cacheFile, JSON.stringify(result));
    `], {
      stdio: 'ignore',
      windowsHide: true,
      detached: true  // Required on Windows for proper process detachment
    });
    
    child.unref();
    
  • hooks/gsd-context-monitor.jsGitHub
    Read the script
    #!/usr/bin/env node
    // Context Monitor - PostToolUse/AfterTool hook (Gemini uses AfterTool)
    // Reads context metrics from the statusline bridge file and injects
    // warnings when context usage is high. This makes the AGENT aware of
    // context limits (the statusline only shows the user).
    //
    // How it works:
    // 1. The statusline hook writes metrics to /tmp/claude-ctx-{session_id}.json
    // 2. This hook reads those metrics after each tool use
    // 3. When remaining context drops below thresholds, it injects a warning
    //    as additionalContext, which the agent sees in its conversation
    //
    // Thresholds:
    //   WARNING  (remaining <= 35%): Agent should wrap up current task
    //   CRITICAL (remaining <= 25%): Agent should stop immediately and save state
    //
    // Debounce: 5 tool uses between warnings to avoid spam
    // Severity escalation bypasses debounce (WARNING -> CRITICAL fires immediately)
    
    const fs = require('fs');
    const os = require('os');
    const path = require('path');
    
    const WARNING_THRESHOLD = 35;  // remaining_percentage <= 35%
    const CRITICAL_THRESHOLD = 25; // remaining_percentage <= 25%
    const STALE_SECONDS = 60;      // ignore metrics older than 60s
    const DEBOUNCE_CALLS = 5;      // min tool uses between warnings
    
    let input = '';
    // Timeout guard: if stdin doesn't close within 3s (e.g. pipe issues on
    // Windows/Git Bash), exit silently instead of hanging until Claude Code
    // kills the process and reports "hook error". See #775.
    const stdinTimeout = setTimeout(() => process.exit(0), 3000);
    process.stdin.setEncoding('utf8');
    process.stdin.on('data', chunk => input += chunk);
    process.stdin.on('end', () => {
      clearTimeout(stdinTimeout);
      try {
        const data = JSON.parse(input);
        const sessionId = data.session_id;
    
        if (!sessionId) {
          process.exit(0);
        }
    
        const tmpDir = os.tmpdir();
        const metricsPath = path.join(tmpDir, `claude-ctx-${sessionId}.json`);
    
        // If no metrics file, this is a subagent or fresh session -- exit silently
        if (!fs.existsSync(metricsPath)) {
          process.exit(0);
        }
    
        const metrics = JSON.parse(fs.readFileSync(metricsPath, 'utf8'));
        const now = Math.floor(Date.now() / 1000);
    
        // Ignore stale metrics
        if (metrics.timestamp && (now - metrics.timestamp) > STALE_SECONDS) {
          process.exit(0);
        }
    
        const remaining = metrics.remaining_percentage;
        const usedPct = metrics.used_pct;
    
        // No warning needed
        if (remaining > WARNING_THRESHOLD) {
          process.exit(0);
        }
    
        // Debounce: check if we warned recently
        const warnPath = path.join(tmpDir, `claude-ctx-${sessionId}-warned.json`);
        let warnData = { callsSinceWarn: 0, lastLevel: null };
        let firstWarn = true;
    
        if (fs.existsSync(warnPath)) {
          try {
            warnData = JSON.parse(fs.readFileSync(warnPath, 'utf8'));
            firstWarn = false;
          } catch (e) {
            // Corrupted file, reset
          }
        }
    
        warnData.callsSinceWarn = (warnData.callsSinceWarn || 0) + 1;
    
        const isCritical = remaining <= CRITICAL_THRESHOLD;
        const currentLevel = isCritical ? 'critical' : 'warning';
    
        // Emit immediately on first warning, then debounce subsequent ones
        // Severity escalation (WARNING -> CRITICAL) bypasses debounce
        const severityEscalated = currentLevel === 'critical' && warnData.lastLevel === 'warning';
        if (!firstWarn && warnData.callsSinceWarn < DEBOUNCE_CALLS && !severityEscalated) {
          // Update counter and exit without warning
          fs.writeFileSync(warnPath, JSON.stringify(warnData));
          process.exit(0);
        }
    
        // Reset debounce counter
        warnData.callsSinceWarn = 0;
        warnData.lastLevel = currentLevel;
        fs.writeFileSync(warnPath, JSON.stringify(warnData));
    
        // Detect if GSD is active (has .planning/STATE.md in working directory)
        const cwd = data.cwd || process.cwd();
        const isGsdActive = fs.existsSync(path.join(cwd, '.planning', 'STATE.md'));
    
        // Build advisory warning message (never use imperative commands that
        // override user preferences — see #884)
        let message;
        if (isCritical) {
          message = isGsdActive
            ? `CONTEXT CRITICAL: Usage at ${usedPct}%. Remaining: ${remaining}%. ` +
              'Context is nearly exhausted. Do NOT start new complex work or write handoff files — ' +
              'GSD state is already tracked in STATE.md. Inform the user so they can run ' +
              '/gsd:pause-work at the next natural stopping point.'
            : `CONTEXT CRITICAL: Usage at ${usedPct}%. Remaining: ${remaining}%. ` +
              'Context is nearly exhausted. Inform the user that context is low and ask how they ' +
              'want to proceed. Do NOT autonomously save state or write handoff files unless the user asks.';
        } else {
          message = isGsdActive
            ? `CONTEXT WARNING: Usage at ${usedPct}%. Remaining: ${remaining}%. ` +
              'Context is getting limited. Avoid starting new complex work. If not between ' +
              'defined plan steps, inform the user so they can prepare to pause.'
            : `CONTEXT WARNING: Usage at ${usedPct}%. Remaining: ${remaining}%. ` +
              'Be aware that context is getting limited. Avoid unnecessary exploration or ' +
              'starting new complex work.';
        }
    
        const output = {
          hookSpecificOutput: {
            hookEventName: process.env.GEMINI_API_KEY ? "AfterTool" : "PostToolUse",
            additionalContext: message
          }
        };
    
        process.stdout.write(JSON.stringify(output));
      } catch (e) {
        // Silent fail -- never block tool execution
        process.exit(0);
      }
    });
    
  • hooks/gsd-statusline.jsGitHub
    Read the script
    #!/usr/bin/env node
    // Claude Code Statusline - GSD Edition
    // Shows: model | current task | directory | context usage
    
    const fs = require('fs');
    const path = require('path');
    const os = require('os');
    
    // Read JSON from stdin
    let input = '';
    // Timeout guard: if stdin doesn't close within 3s (e.g. pipe issues on
    // Windows/Git Bash), exit silently instead of hanging. See #775.
    const stdinTimeout = setTimeout(() => process.exit(0), 3000);
    process.stdin.setEncoding('utf8');
    process.stdin.on('data', chunk => input += chunk);
    process.stdin.on('end', () => {
      clearTimeout(stdinTimeout);
      try {
        const data = JSON.parse(input);
        const model = data.model?.display_name || 'Claude';
        const dir = data.workspace?.current_dir || process.cwd();
        const session = data.session_id || '';
        const remaining = data.context_window?.remaining_percentage;
    
        // Context window display (shows USED percentage scaled to usable context)
        // Claude Code reserves ~16.5% for autocompact buffer, so usable context
        // is 83.5% of the total window. We normalize to show 100% at that point.
        const AUTO_COMPACT_BUFFER_PCT = 16.5;
        let ctx = '';
        if (remaining != null) {
          // Normalize: subtract buffer from remaining, scale to usable range
          const usableRemaining = Math.max(0, ((remaining - AUTO_COMPACT_BUFFER_PCT) / (100 - AUTO_COMPACT_BUFFER_PCT)) * 100);
          const used = Math.max(0, Math.min(100, Math.round(100 - usableRemaining)));
    
          // Write context metrics to bridge file for the context-monitor PostToolUse hook.
          // The monitor reads this file to inject agent-facing warnings when context is low.
          if (session) {
            try {
              const bridgePath = path.join(os.tmpdir(), `claude-ctx-${session}.json`);
              const bridgeData = JSON.stringify({
                session_id: session,
                remaining_percentage: remaining,
                used_pct: used,
                timestamp: Math.floor(Date.now() / 1000)
              });
              fs.writeFileSync(bridgePath, bridgeData);
            } catch (e) {
              // Silent fail -- bridge is best-effort, don't break statusline
            }
          }
    
          // Build progress bar (10 segments)
          const filled = Math.floor(used / 10);
          const bar = '█'.repeat(filled) + '░'.repeat(10 - filled);
    
          // Color based on usable context thresholds
          if (used < 50) {
            ctx = ` \x1b[32m${bar} ${used}%\x1b[0m`;
          } else if (used < 65) {
            ctx = ` \x1b[33m${bar} ${used}%\x1b[0m`;
          } else if (used < 80) {
            ctx = ` \x1b[38;5;208m${bar} ${used}%\x1b[0m`;
          } else {
            ctx = ` \x1b[5;31m💀 ${bar} ${used}%\x1b[0m`;
          }
        }
    
        // Current task from todos
        let task = '';
        const homeDir = os.homedir();
        // Respect CLAUDE_CONFIG_DIR for custom config directory setups (#870)
        const claudeDir = process.env.CLAUDE_CONFIG_DIR || path.join(homeDir, '.claude');
        const todosDir = path.join(claudeDir, 'todos');
        if (session && fs.existsSync(todosDir)) {
          try {
            const files = fs.readdirSync(todosDir)
              .filter(f => f.startsWith(session) && f.includes('-agent-') && f.endsWith('.json'))
              .map(f => ({ name: f, mtime: fs.statSync(path.join(todosDir, f)).mtime }))
              .sort((a, b) => b.mtime - a.mtime);
    
            if (files.length > 0) {
              try {
                const todos = JSON.parse(fs.readFileSync(path.join(todosDir, files[0].name), 'utf8'));
                const inProgress = todos.find(t => t.status === 'in_progress');
                if (inProgress) task = inProgress.activeForm || '';
              } catch (e) {}
            }
          } catch (e) {
            // Silently fail on file system errors - don't break statusline
          }
        }
    
        // GSD update available?
        let gsdUpdate = '';
        const cacheFile = path.join(claudeDir, 'cache', 'gsd-update-check.json');
        if (fs.existsSync(cacheFile)) {
          try {
            const cache = JSON.parse(fs.readFileSync(cacheFile, 'utf8'));
            if (cache.update_available) {
              gsdUpdate = '\x1b[33m⬆ /gsd:update\x1b[0m │ ';
            }
          } catch (e) {}
        }
    
        // Output
        const dirname = path.basename(dir);
        if (task) {
          process.stdout.write(`${gsdUpdate}\x1b[2m${model}\x1b[0m │ \x1b[1m${task}\x1b[0m │ \x1b[2m${dirname}\x1b[0m${ctx}`);
        } else {
          process.stdout.write(`${gsdUpdate}\x1b[2m${model}\x1b[0m │ \x1b[2m${dirname}\x1b[0m${ctx}`);
        }
      } catch (e) {
        // Silent fail - don't break statusline on parse errors
      }
    });
    

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.

@itsjwill
Ships withgsd-pro

GSD Pro — The most powerful AI coding workflow for Claude Code. Free alternative to Cursor Composer, Copilot Workspace, Devin & Bolt. Multi-model routing, rollback/recovery, adaptive context. Fork of 30K★ original. npx get-shit-done-cc

Stars on GitHub

Momentum

stars in total
86
Star history of itsjwill/gsd-pro 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 gsd-pro.

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