#!/usr/bin/env -S uv run --script # /// script # requires-python = ">=3.10" # dependencies = [] # /// """ Git Workflow Detector - PreToolUse Hook with Preference Management Detects when Claude is about to use inefficient multi-tool git workflows and suggests using optimized scripts instead. Features: - First detection: Uses AskUserQuestion for user choice - User can set preference: "Always use scripts" - Subsequent detections: Auto-use based on preference - Subagents: Always use scripts (no prompting) Triggers: - Multiple git commands in single Bash call - Sequential git operations (add, commit, push) - PR/merge workflows Does NOT block - provides suggestions only. """ import json import sys import re from pathlib import Path from datetime import datetime # Preference storage PREFERENCE_FILE = Path.home() / ".claude" / "plugins" / "contextune" / "data" / "git_workflow_preferences.json" # Script mappings SCRIPT_SUGGESTIONS = { 'commit_and_push': { 'patterns': [ r'git add.*git commit.*git push', r'git commit.*git push', ], 'script': './scripts/commit_and_push.sh', 'usage': './scripts/commit_and_push.sh "." "message" "branch"', 'savings': '90-97% tokens, $0.035-0.084 cost reduction' }, 'create_pr': { 'patterns': [ r'gh pr create', r'git push.*gh pr', ], 'script': './scripts/create_pr.sh', 'usage': './scripts/create_pr.sh "title" "body" "base" "head"', 'savings': '90-95% tokens, $0.030-0.080 cost reduction' }, 'merge_workflow': { 'patterns': [ r'git merge.*git push.*git branch -d', r'git merge.*git branch.*delete', ], 'script': './scripts/merge_and_cleanup.sh', 'usage': './scripts/merge_and_cleanup.sh "branch" "into_branch"', 'savings': '90-95% tokens, $0.030-0.080 cost reduction' } } def read_preference() -> dict: """ Read user's git workflow preference. Returns: dict with 'auto_use_scripts' (bool or None) and 'set_at' timestamp """ if not PREFERENCE_FILE.exists(): return {'auto_use_scripts': None, 'set_at': None} try: with open(PREFERENCE_FILE, 'r') as f: return json.load(f) except (json.JSONDecodeError, IOError): return {'auto_use_scripts': None, 'set_at': None} def write_preference(auto_use_scripts: bool): """ Write user's git workflow preference. Args: auto_use_scripts: Whether to automatically use scripts """ PREFERENCE_FILE.parent.mkdir(parents=True, exist_ok=True) data = { 'auto_use_scripts': auto_use_scripts, 'set_at': datetime.now().isoformat() } with open(PREFERENCE_FILE, 'w') as f: json.dump(data, f, indent=2) def detect_git_workflow(command: str) -> tuple[bool, dict]: """ Detect if command contains multi-step git workflow. Args: command: Bash command to analyze Returns: (is_workflow: bool, suggestion: dict) """ if 'git ' not in command: return False, {} # Check each workflow pattern for workflow_name, workflow_info in SCRIPT_SUGGESTIONS.items(): for pattern in workflow_info['patterns']: if re.search(pattern, command, re.IGNORECASE): return True, { 'workflow': workflow_name, 'script': workflow_info['script'], 'usage': workflow_info['usage'], 'savings': workflow_info['savings'] } # Check for multiple git commands (&&, ;, or newlines) git_command_count = len(re.findall(r'\bgit\s+\w+', command)) if git_command_count >= 3: return True, { 'workflow': 'multiple_git_commands', 'script': './scripts/smart_execute.sh', 'usage': 'Consider consolidating into a single script', 'savings': 'Reduces tool call overhead (~90% token reduction)' } return False, {} def format_auto_use_message(suggestion: dict) -> str: """Format message when auto-using scripts (preference set).""" return f""" ✨ AUTO-OPTIMIZATION ACTIVE Using optimized script: {suggestion['script']} (Your preference is set to auto-use scripts) Benefits: • {suggestion['savings']} • Automatic error recovery (Haiku/Copilot) • Minimal session context impact Change preference: Delete {PREFERENCE_FILE} """.strip() def format_ask_user_instruction(suggestion: dict) -> str: """Format instruction for Claude to use AskUserQuestion.""" return f""" 🎯 GIT WORKFLOW OPTIMIZATION DETECTED Use AskUserQuestion tool with these settings: Question: "Git workflow optimization available - how would you like to proceed?" Header: "Optimize" Options: 1. Use optimized script Description: "Use {suggestion['script']} - {suggestion['savings']}, automatic error recovery" 2. Use standard approach Description: "Proceed with multiple Bash tool calls (current method)" 3. Always use scripts Description: "Set preference to auto-use scripts for all git workflows this session" Based on user selection: - Option 1: Execute {suggestion['script']} (one-time) - Option 2: Continue with Bash as planned - Option 3: Save preference, then execute {suggestion['script']} To save preference (Option 3), create file at: {PREFERENCE_FILE} With content: {{"auto_use_scripts": true, "set_at": "{datetime.now().isoformat()}"}} """.strip() def format_suggestion_only(suggestion: dict) -> str: """Format simple suggestion when no preference system available.""" return f""" 💡 Git Workflow Optimization Available Detected: Multi-step git operation ({suggestion['workflow']}) Optimized alternative: {suggestion['script']} Usage: {suggestion['usage']} Benefits: • {suggestion['savings']} • Automatic error recovery (Haiku/Copilot cascade) • Minimal session context impact You can use the optimized script or proceed with current approach. """.strip() def main(): """PreToolUse hook entry point.""" try: hook_data = json.loads(sys.stdin.read()) tool = hook_data.get('tool', {}) tool_name = tool.get('name', '') tool_params = tool.get('parameters', {}) # Only check Bash tool if tool_name != 'Bash': output = {'continue': True} print(json.dumps(output)) sys.exit(0) command = tool_params.get('command', '') # Detect git workflows is_workflow, suggestion = detect_git_workflow(command) if not is_workflow or not suggestion: # Not a git workflow, continue normally output = {'continue': True} print(json.dumps(output)) sys.exit(0) # Workflow detected - check preference preference = read_preference() auto_use = preference.get('auto_use_scripts') if auto_use is True: # User prefers auto-use - suggest directly message = format_auto_use_message(suggestion) print(f"DEBUG: Auto-using scripts (preference set)", file=sys.stderr) elif auto_use is False: # User prefers Bash - don't suggest print(f"DEBUG: User prefers Bash approach (preference set)", file=sys.stderr) output = {'continue': True} print(json.dumps(output)) sys.exit(0) else: # No preference - ask user with AskUserQuestion message = format_ask_user_instruction(suggestion) print(f"DEBUG: First detection, will prompt user via AskUserQuestion", file=sys.stderr) # Inject suggestion/instruction output = { 'continue': True, 'hookSpecificOutput': { 'hookEventName': 'PreToolUse', 'additionalContext': message } } print(json.dumps(output)) except Exception as e: print(f"DEBUG: Git workflow detector error: {e}", file=sys.stderr) import traceback traceback.print_exc(file=sys.stderr) # Always continue (don't block tools) output = {'continue': True} print(json.dumps(output)) sys.exit(0) if __name__ == '__main__': main()