#!/usr/bin/env python3 """ Auto-Fix Script Applies safe fixes to Claude Code configuration with backup. """ import json import shutil import sys from datetime import datetime from pathlib import Path # serverInstructions templates for common MCP servers SERVER_INSTRUCTIONS = { "playwright": "Browser automation for web interaction. Use for: SEO audits, page analysis, screenshots, form testing, Core Web Vitals. Keywords: browser, page, screenshot, click, navigate, DOM, selector", "puppeteer": "Chrome automation for web testing. Use for: SEO audits, page rendering, JavaScript site testing. Keywords: browser, chrome, headless, screenshot, page", "notion": "Notion workspace integration. Use for: saving research, documentation, project notes, knowledge base. Keywords: notion, page, database, wiki, notes, save", "github": "GitHub repository management. Use for: commits, PRs, issues, code review. Keywords: git, github, commit, pull request, issue, repository", "postgres": "PostgreSQL database queries. Use for: data analysis, SQL queries, analytics. Keywords: sql, query, database, table, select, analytics", "postgresql": "PostgreSQL database queries. Use for: data analysis, SQL queries, analytics. Keywords: sql, query, database, table, select, analytics", "bigquery": "Google BigQuery for large-scale analysis. Use for: analytics queries, data warehouse. Keywords: bigquery, sql, analytics, data warehouse", "firecrawl": "Web scraping and crawling. Use for: site crawling, content extraction, competitor analysis. Keywords: crawl, scrape, extract, spider, sitemap", "slack": "Slack workspace integration. Use for: messages, notifications, team communication. Keywords: slack, message, channel, notification", "linear": "Linear issue tracking. Use for: issue management, project tracking. Keywords: linear, issue, task, project, sprint", "memory": "Persistent memory across sessions. Use for: storing preferences, context recall. Keywords: remember, memory, store, recall", "filesystem": "Local file operations. Use for: file reading/writing, directory management. Keywords: file, directory, read, write, path", "chrome-devtools": "Chrome DevTools for debugging. Use for: GTM debugging, network analysis, console logs. Keywords: devtools, chrome, debug, network, console", } class AutoFixer: def __init__(self, dry_run: bool = True): self.dry_run = dry_run self.fixes = [] self.backup_dir = Path.home() / ".claude" / "backups" / datetime.now().strftime("%Y%m%d_%H%M%S") def backup_file(self, path: Path) -> bool: """Create backup before modifying.""" if not path.exists(): return True try: backup_path = self.backup_dir / path.relative_to(Path.home()) backup_path.parent.mkdir(parents=True, exist_ok=True) shutil.copy2(path, backup_path) return True except Exception as e: print(f"Warning: Could not backup {path}: {e}", file=sys.stderr) return False def fix_mcp_instructions(self, settings_path: Path) -> list: """Add serverInstructions to MCP servers.""" fixes = [] try: with open(settings_path) as f: settings = json.load(f) except (json.JSONDecodeError, IOError) as e: return [f"Error reading {settings_path}: {e}"] servers = settings.get("mcpServers", {}) modified = False for name, config in servers.items(): if not isinstance(config, dict): continue if "serverInstructions" in config: continue # Find matching template instructions = None name_lower = name.lower() for key, template in SERVER_INSTRUCTIONS.items(): if key in name_lower: instructions = template break if not instructions: instructions = f"External tool: {name}. Use when this functionality is needed." if self.dry_run: fixes.append(f"[DRY RUN] Would add serverInstructions to '{name}'") else: config["serverInstructions"] = instructions modified = True fixes.append(f"Added serverInstructions to '{name}'") if modified and not self.dry_run: self.backup_file(settings_path) with open(settings_path, 'w') as f: json.dump(settings, f, indent=2) return fixes def fix_command_frontmatter(self, cmd_path: Path) -> str | None: """Add frontmatter to command missing it.""" try: content = cmd_path.read_text() except IOError: return None if content.startswith('---'): return None new_content = f'''--- description: {cmd_path.stem.replace('-', ' ').title()} command --- {content}''' if self.dry_run: return f"[DRY RUN] Would add frontmatter to {cmd_path.name}" self.backup_file(cmd_path) cmd_path.write_text(new_content) return f"Added frontmatter to {cmd_path.name}" def run(self) -> dict: """Apply all fixes.""" results = {"applied": [], "skipped": [], "errors": []} # Fix MCP settings settings_paths = [ Path.home() / ".claude" / "settings.json", Path.cwd() / ".claude" / "settings.json" ] for path in settings_paths: if path.exists(): fixes = self.fix_mcp_instructions(path) results["applied"].extend(fixes) # Fix commands without frontmatter cmd_dirs = [ Path.home() / ".claude" / "commands", Path.cwd() / ".claude" / "commands" ] for cmd_dir in cmd_dirs: if cmd_dir.exists(): for cmd_file in cmd_dir.glob("*.md"): fix = self.fix_command_frontmatter(cmd_file) if fix: results["applied"].append(fix) return results def main(): import argparse parser = argparse.ArgumentParser(description="Auto-fix Claude Code settings") parser.add_argument("--apply", action="store_true", help="Apply fixes (default is dry-run)") args = parser.parse_args() fixer = AutoFixer(dry_run=not args.apply) results = fixer.run() print(json.dumps(results, indent=2)) if fixer.dry_run: print("\n[DRY RUN] No changes applied. Use --apply to apply fixes.", file=sys.stderr) else: print(f"\n[APPLIED] {len(results['applied'])} fixes.", file=sys.stderr) if fixer.backup_dir.exists(): print(f"Backups: {fixer.backup_dir}", file=sys.stderr) return 0 if __name__ == "__main__": sys.exit(main())