diff --git a/get-shit-done/workflows/settings.md b/get-shit-done/workflows/settings.md index b540cff61..760056918 100644 --- a/get-shit-done/workflows/settings.md +++ b/get-shit-done/workflows/settings.md @@ -160,7 +160,8 @@ Merge new settings into existing config.json: "branching_strategy": "none" | "phase" | "milestone" }, "hooks": { - "context_warnings": true/false + "context_warnings": true/false, + "workflow_guard": true/false } } ``` diff --git a/hooks/gsd-workflow-guard.js b/hooks/gsd-workflow-guard.js new file mode 100644 index 000000000..d8075aaf6 --- /dev/null +++ b/hooks/gsd-workflow-guard.js @@ -0,0 +1,93 @@ +#!/usr/bin/env node +// GSD Workflow Guard — PreToolUse hook +// Detects when Claude attempts file edits outside a GSD workflow context +// (no active /gsd: command or Task subagent) and injects an advisory warning. +// +// This is a SOFT guard — it advises, not blocks. The edit still proceeds. +// The warning nudges Claude to use /gsd:quick or /gsd:fast instead of +// making direct edits that bypass state tracking. +// +// Enable via config: hooks.workflow_guard: true (default: false) +// Only triggers on Write/Edit tool calls to non-.planning/ files. + +const fs = require('fs'); +const path = require('path'); + +let input = ''; +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 toolName = data.tool_name; + + // Only guard Write and Edit tool calls + if (toolName !== 'Write' && toolName !== 'Edit') { + process.exit(0); + } + + // Check if we're inside a GSD workflow (Task subagent or /gsd: command) + // Subagents have a session_id that differs from the parent + // and typically have a description field set by the orchestrator + if (data.tool_input?.is_subagent || data.session_type === 'task') { + process.exit(0); + } + + // Check the file being edited + const filePath = data.tool_input?.file_path || data.tool_input?.path || ''; + + // Allow edits to .planning/ files (GSD state management) + if (filePath.includes('.planning/') || filePath.includes('.planning\\')) { + process.exit(0); + } + + // Allow edits to common config/docs files that don't need GSD tracking + const allowedPatterns = [ + /\.gitignore$/, + /\.env/, + /CLAUDE\.md$/, + /AGENTS\.md$/, + /GEMINI\.md$/, + /settings\.json$/, + ]; + if (allowedPatterns.some(p => p.test(filePath))) { + process.exit(0); + } + + // Check if workflow guard is enabled + const cwd = data.cwd || process.cwd(); + const configPath = path.join(cwd, '.planning', 'config.json'); + if (fs.existsSync(configPath)) { + try { + const config = JSON.parse(fs.readFileSync(configPath, 'utf8')); + if (!config.hooks?.workflow_guard) { + process.exit(0); // Guard disabled (default) + } + } catch (e) { + process.exit(0); + } + } else { + process.exit(0); // No GSD project — don't guard + } + + // If we get here: GSD project, guard enabled, file edit outside .planning/, + // not in a subagent context. Inject advisory warning. + const output = { + hookSpecificOutput: { + hookEventName: "PreToolUse", + additionalContext: `⚠️ WORKFLOW ADVISORY: You're editing ${path.basename(filePath)} directly without a GSD command. ` + + 'This edit will not be tracked in STATE.md or produce a SUMMARY.md. ' + + 'Consider using /gsd:fast for trivial fixes or /gsd:quick for larger changes ' + + 'to maintain project state tracking. ' + + 'If this is intentional (e.g., user explicitly asked for a direct edit), proceed normally.' + } + }; + + process.stdout.write(JSON.stringify(output)); + } catch (e) { + // Silent fail — never block tool execution + process.exit(0); + } +}); diff --git a/scripts/build-hooks.js b/scripts/build-hooks.js index dda263e4e..b1b8fa416 100644 --- a/scripts/build-hooks.js +++ b/scripts/build-hooks.js @@ -17,7 +17,8 @@ const DIST_DIR = path.join(HOOKS_DIR, 'dist'); const HOOKS_TO_COPY = [ 'gsd-check-update.js', 'gsd-context-monitor.js', - 'gsd-statusline.js' + 'gsd-statusline.js', + 'gsd-workflow-guard.js' ]; /**