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.
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.
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
Get the whole plugin
Stats
84
Stars
19
Forks
Quiet
Maintenance
JavaScript
Language
MIT
License
6mo ago
Last commit
8mo ago
Created
Repo: itsjwill/gsd-pro

