Python Refactor Transform complex Python into clear, maintainable code while preserving correctness. Phased workflow with safety-by-design and continuous validation. For deep references (anti-patterns, OOP principles, cognitive complexity, regression prevention), see the directory. When to invoke - Explicit "human", "readable", "maintainable", "clean", or "refactor" request - Code review flags comprehension or maintainability issues - Legacy code modernization - Onboarding / educational contexts - Complexity metrics exceed thresholds - Red flags : file 500 lines with scattered functions and g…

, line)\n if match:\n file_path, row, col, code, message = match.groups()\n issues.append({\n 'file': file_path,\n 'line': int(row),\n 'column': int(col),\n 'code': code,\n 'message': message.strip(),\n 'severity': categorize_issue_severity(code),\n 'category': categorize_issue(code)\n })\n elif ':' in line and line.split(':')[0].strip().isdigit():\n # Statistics line: \"123 E501 line too long\"\n parts = line.split(None, 2)\n if len(parts) >= 2:\n count = parts[0]\n code = parts[1]\n if count.isdigit():\n statistics[code] = int(count)\n\n # Group issues by category\n by_category = {}\n by_severity = {}\n by_file = {}\n\n for issue in issues:\n category = issue['category']\n severity = issue['severity']\n file_path = issue['file']\n\n by_category[category] = by_category.get(category, 0) + 1\n by_severity[severity] = by_severity.get(severity, 0) + 1\n by_file[file_path] = by_file.get(file_path, 0) + 1\n\n return {\n 'target': str(target_path),\n 'timestamp': datetime.now().isoformat(),\n 'total_issues': len(issues),\n 'exit_code': returncode,\n 'passed': returncode == 0,\n 'issues': issues,\n 'statistics': statistics,\n 'by_category': by_category,\n 'by_severity': by_severity,\n 'by_file': by_file,\n 'installed_plugins': installed_plugins,\n 'errors': stderr if stderr else None\n }\n\n\ndef categorize_issue(code: str) -> str:\n \"\"\"Categorize flake8 issue by code prefix.\n\n Args:\n code: Flake8 error/warning code (e.g., E501, W503)\n\n Returns:\n Category name\n \"\"\"\n prefix = code[0] if code else 'U'\n\n categories = {\n 'E': 'Style Error (PEP 8)',\n 'W': 'Style Warning (PEP 8)',\n 'F': 'PyFlakes Error',\n 'C': 'Complexity',\n 'B': 'Bugbear (Likely Bug)',\n 'N': 'Naming Convention',\n 'D': 'Docstring',\n 'A': 'Annotations',\n 'S': 'Simplification',\n 'T': 'Type Checking',\n }\n\n return categories.get(prefix, 'Other')\n\n\ndef categorize_issue_severity(code: str) -> str:\n \"\"\"Determine severity level of issue.\n\n Args:\n code: Flake8 error/warning code\n\n Returns:\n Severity level (high/medium/low)\n \"\"\"\n # High severity: Likely bugs, security issues\n high_severity = [\n 'F', 'B', # PyFlakes errors, Bugbear\n 'E9', # Runtime errors\n ]\n\n # Medium severity: Complexity, bad practices\n medium_severity = [\n 'C', # Complexity\n 'N', # Naming\n 'A', # Annotations\n ]\n\n # Check code prefix\n for prefix in high_severity:\n if code.startswith(prefix):\n return 'high'\n\n for prefix in medium_severity:\n if code.startswith(prefix):\n return 'medium'\n\n return 'low'\n\n\ndef generate_summary_report(analysis: Dict[str, Any]) -> str:\n \"\"\"Generate human-readable summary report.\n\n Args:\n analysis: Parsed analysis results\n\n Returns:\n Formatted text report\n \"\"\"\n lines = []\n\n lines.append(\"=\" * 70)\n lines.append(\"Flake8 Code Quality Analysis Report\")\n lines.append(\"=\" * 70)\n lines.append(\"\")\n\n lines.append(f\"Target: {analysis['target']}\")\n lines.append(f\"Timestamp: {analysis['timestamp']}\")\n lines.append(f\"Result: {'PASSED' if analysis['passed'] else 'FAILED'}\")\n lines.append(f\"Total Issues: {analysis['total_issues']}\")\n lines.append(\"\")\n\n # Installed plugins organized by priority\n lines.append(\"Installed Plugins:\")\n lines.append(\"-\" * 70)\n installed_plugins = analysis['installed_plugins']\n\n lines.append(\"ESSENTIAL (Must-Have):\")\n for plugin, description in ESSENTIAL_PLUGINS.items():\n status = \"✓\" if installed_plugins.get(plugin) else \"✗\"\n lines.append(f\" {status} {plugin}: {description}\")\n\n lines.append(\"\\nRECOMMENDED (Strong Impact):\")\n for plugin, description in RECOMMENDED_PLUGINS.items():\n if plugin not in ESSENTIAL_PLUGINS:\n status = \"✓\" if installed_plugins.get(plugin) else \"✗\"\n lines.append(f\" {status} {plugin}: {description}\")\n\n lines.append(\"\\nOPTIONAL (Nice to Have):\")\n for plugin, description in OPTIONAL_PLUGINS.items():\n status = \"✓\" if installed_plugins.get(plugin) else \"✗\"\n lines.append(f\" {status} {plugin}: {description}\")\n lines.append(\"\")\n\n # Missing plugins warning\n missing_essential = [p for p in ESSENTIAL_PLUGINS if not installed_plugins.get(p)]\n missing_recommended = [p for p in RECOMMENDED_PLUGINS if p not in ESSENTIAL_PLUGINS and not installed_plugins.get(p)]\n missing_optional = [p for p in OPTIONAL_PLUGINS if not installed_plugins.get(p)]\n\n if missing_essential:\n lines.append(\"⚠ Missing ESSENTIAL Plugins (install these first):\")\n for plugin in missing_essential:\n lines.append(f\" - {plugin}\")\n lines.append(f\"\\nInstall: pip install {' '.join(missing_essential)}\")\n lines.append(\"\")\n\n if missing_recommended:\n lines.append(\"⚠ Missing RECOMMENDED Plugins:\")\n for plugin in missing_recommended:\n lines.append(f\" - {plugin}\")\n lines.append(f\"\\nInstall: pip install {' '.join(missing_recommended)}\")\n lines.append(\"\")\n\n # Issues by severity\n if analysis['by_severity']:\n lines.append(\"Issues by Severity:\")\n lines.append(\"-\" * 70)\n for severity in ['high', 'medium', 'low']:\n count = analysis['by_severity'].get(severity, 0)\n if count > 0:\n symbol = \"✗\" if severity == 'high' else \"⚠\" if severity == 'medium' else \"•\"\n lines.append(f\" {symbol} {severity.upper()}: {count}\")\n lines.append(\"\")\n\n # Issues by category\n if analysis['by_category']:\n lines.append(\"Issues by Category:\")\n lines.append(\"-\" * 70)\n for category, count in sorted(\n analysis['by_category'].items(),\n key=lambda x: x[1],\n reverse=True\n ):\n lines.append(f\" {category}: {count}\")\n lines.append(\"\")\n\n # Top issues\n if analysis['statistics']:\n lines.append(\"Top Issue Types:\")\n lines.append(\"-\" * 70)\n sorted_stats = sorted(\n analysis['statistics'].items(),\n key=lambda x: x[1],\n reverse=True\n )\n for code, count in sorted_stats[:10]:\n lines.append(f\" {code}: {count} occurrence(s)\")\n lines.append(\"\")\n\n # Files with most issues\n if analysis['by_file']:\n lines.append(\"Files with Most Issues:\")\n lines.append(\"-\" * 70)\n sorted_files = sorted(\n analysis['by_file'].items(),\n key=lambda x: x[1],\n reverse=True\n )\n for file_path, count in sorted_files[:10]:\n lines.append(f\" {file_path}: {count} issue(s)\")\n lines.append(\"\")\n\n # Detailed issues\n if analysis['issues']:\n lines.append(\"Detailed Issues:\")\n lines.append(\"-\" * 70)\n\n # Group by severity and show high severity first\n high_issues = [i for i in analysis['issues'] if i['severity'] == 'high']\n medium_issues = [i for i in analysis['issues'] if i['severity'] == 'medium']\n low_issues = [i for i in analysis['issues'] if i['severity'] == 'low']\n\n for severity_label, issues in [\n ('HIGH SEVERITY', high_issues),\n ('MEDIUM SEVERITY', medium_issues),\n ('LOW SEVERITY (First 20)', low_issues[:20])\n ]:\n if issues:\n lines.append(f\"\\n{severity_label}:\")\n for issue in issues:\n lines.append(\n f\" {issue['file']}:{issue['line']}:{issue['column']} \"\n f\"{issue['code']} {issue['message']}\"\n )\n\n lines.append(\"\")\n lines.append(\"=\" * 70)\n\n return '\\n'.join(lines)\n\n\ndef generate_html_report(analysis: Dict[str, Any]) -> str:\n \"\"\"Generate HTML report.\n\n Args:\n analysis: Parsed analysis results\n\n Returns:\n HTML report string\n \"\"\"\n severity_colors = {\n 'high': '#e74c3c',\n 'medium': '#f39c12',\n 'low': '#3498db'\n }\n\n html = f\"\"\"\u003c!DOCTYPE html>\n\u003chtml>\n\u003chead>\n \u003cmeta charset=\"UTF-8\">\n \u003ctitle>Flake8 Analysis Report\u003c/title>\n \u003cstyle>\n body {{\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif;\n margin: 0;\n padding: 20px;\n background: #f5f5f5;\n }}\n .container {{\n max-width: 1200px;\n margin: 0 auto;\n background: white;\n padding: 30px;\n border-radius: 8px;\n box-shadow: 0 2px 4px rgba(0,0,0,0.1);\n }}\n h1 {{\n color: #2c3e50;\n border-bottom: 3px solid #3498db;\n padding-bottom: 10px;\n }}\n h2 {{\n color: #34495e;\n margin-top: 30px;\n }}\n .summary {{\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n gap: 15px;\n margin: 20px 0;\n }}\n .summary-card {{\n padding: 20px;\n border-radius: 6px;\n background: #ecf0f1;\n }}\n .summary-card.passed {{ background: #d5f4e6; }}\n .summary-card.failed {{ background: #fadbd8; }}\n .summary-card h3 {{\n margin: 0 0 10px 0;\n color: #7f8c8d;\n font-size: 14px;\n text-transform: uppercase;\n }}\n .summary-card .value {{\n font-size: 32px;\n font-weight: bold;\n color: #2c3e50;\n }}\n .severity-badge {{\n display: inline-block;\n padding: 4px 8px;\n border-radius: 4px;\n font-size: 12px;\n font-weight: bold;\n color: white;\n margin-right: 5px;\n }}\n .severity-high {{ background: {severity_colors['high']}; }}\n .severity-medium {{ background: {severity_colors['medium']}; }}\n .severity-low {{ background: {severity_colors['low']}; }}\n .issue-list {{\n margin: 20px 0;\n }}\n .issue {{\n padding: 15px;\n margin: 10px 0;\n border-left: 4px solid #bdc3c7;\n background: #f8f9fa;\n border-radius: 4px;\n }}\n .issue.high {{ border-left-color: {severity_colors['high']}; }}\n .issue.medium {{ border-left-color: {severity_colors['medium']}; }}\n .issue.low {{ border-left-color: {severity_colors['low']}; }}\n .issue-code {{\n font-family: monospace;\n background: #2c3e50;\n color: white;\n padding: 2px 6px;\n border-radius: 3px;\n font-size: 12px;\n }}\n .issue-location {{\n color: #7f8c8d;\n font-size: 14px;\n }}\n .plugin-list {{\n list-style: none;\n padding: 0;\n }}\n .plugin-list li {{\n padding: 8px 0;\n border-bottom: 1px solid #ecf0f1;\n }}\n .plugin-installed {{ color: #27ae60; }}\n .plugin-missing {{ color: #e74c3c; }}\n table {{\n width: 100%;\n border-collapse: collapse;\n margin: 20px 0;\n }}\n th, td {{\n padding: 12px;\n text-align: left;\n border-bottom: 1px solid #ecf0f1;\n }}\n th {{\n background: #34495e;\n color: white;\n }}\n tr:hover {{ background: #f8f9fa; }}\n \u003c/style>\n\u003c/head>\n\u003cbody>\n \u003cdiv class=\"container\">\n \u003ch1>Flake8 Code Quality Analysis Report\u003c/h1>\n\n \u003cdiv class=\"summary\">\n \u003cdiv class=\"summary-card {'passed' if analysis['passed'] else 'failed'}\">\n \u003ch3>Status\u003c/h3>\n \u003cdiv class=\"value\">{'PASSED' if analysis['passed'] else 'FAILED'}\u003c/div>\n \u003c/div>\n \u003cdiv class=\"summary-card\">\n \u003ch3>Total Issues\u003c/h3>\n \u003cdiv class=\"value\">{analysis['total_issues']}\u003c/div>\n \u003c/div>\n \u003cdiv class=\"summary-card\">\n \u003ch3>Target\u003c/h3>\n \u003cdiv style=\"font-size: 14px; word-break: break-all;\">{analysis['target']}\u003c/div>\n \u003c/div>\n \u003cdiv class=\"summary-card\">\n \u003ch3>Timestamp\u003c/h3>\n \u003cdiv style=\"font-size: 14px;\">{analysis['timestamp']}\u003c/div>\n \u003c/div>\n \u003c/div>\n\"\"\"\n\n # Issues by severity\n if analysis['by_severity']:\n html += \"\u003ch2>Issues by Severity\u003c/h2>\"\n html += '\u003cdiv style=\"margin: 20px 0;\">'\n for severity in ['high', 'medium', 'low']:\n count = analysis['by_severity'].get(severity, 0)\n if count > 0:\n html += f'\u003cspan class=\"severity-badge severity-{severity}\">{severity.upper()}: {count}\u003c/span>'\n html += '\u003c/div>'\n\n # Installed plugins organized by priority\n html += \"\u003ch2>Installed Plugins\u003c/h2>\"\n\n html += \"\u003ch3>Essential (Must-Have)\u003c/h3>\"\n html += '\u003cul class=\"plugin-list\">'\n for plugin, description in ESSENTIAL_PLUGINS.items():\n installed = analysis['installed_plugins'].get(plugin, False)\n status_class = 'plugin-installed' if installed else 'plugin-missing'\n status_symbol = '✓' if installed else '✗'\n html += f'\u003cli class=\"{status_class}\">\u003cstrong>{status_symbol} {plugin}\u003c/strong>: {description}\u003c/li>'\n html += '\u003c/ul>'\n\n html += \"\u003ch3>Recommended (Strong Impact)\u003c/h3>\"\n html += '\u003cul class=\"plugin-list\">'\n for plugin, description in RECOMMENDED_PLUGINS.items():\n if plugin not in ESSENTIAL_PLUGINS:\n installed = analysis['installed_plugins'].get(plugin, False)\n status_class = 'plugin-installed' if installed else 'plugin-missing'\n status_symbol = '✓' if installed else '✗'\n html += f'\u003cli class=\"{status_class}\">\u003cstrong>{status_symbol} {plugin}\u003c/strong>: {description}\u003c/li>'\n html += '\u003c/ul>'\n\n html += \"\u003ch3>Optional (Nice to Have)\u003c/h3>\"\n html += '\u003cul class=\"plugin-list\">'\n for plugin, description in OPTIONAL_PLUGINS.items():\n installed = analysis['installed_plugins'].get(plugin, False)\n status_class = 'plugin-installed' if installed else 'plugin-missing'\n status_symbol = '✓' if installed else '✗'\n html += f'\u003cli class=\"{status_class}\">\u003cstrong>{status_symbol} {plugin}\u003c/strong>: {description}\u003c/li>'\n html += '\u003c/ul>'\n\n # Top issues table\n if analysis['statistics']:\n html += \"\u003ch2>Top Issue Types\u003c/h2>\"\n html += \"\u003ctable>\"\n html += \"\u003ctr>\u003cth>Code\u003c/th>\u003cth>Occurrences\u003c/th>\u003cth>Category\u003c/th>\u003c/tr>\"\n sorted_stats = sorted(analysis['statistics'].items(), key=lambda x: x[1], reverse=True)\n for code, count in sorted_stats[:15]:\n category = categorize_issue(code)\n html += f\"\u003ctr>\u003ctd>\u003cspan class='issue-code'>{code}\u003c/span>\u003c/td>\u003ctd>{count}\u003c/td>\u003ctd>{category}\u003c/td>\u003c/tr>\"\n html += \"\u003c/table>\"\n\n # Detailed issues\n if analysis['issues']:\n html += \"\u003ch2>Detailed Issues\u003c/h2>\"\n\n # Group by severity\n for severity in ['high', 'medium', 'low']:\n severity_issues = [i for i in analysis['issues'] if i['severity'] == severity]\n if severity_issues:\n limit = 50 if severity == 'high' else 30 if severity == 'medium' else 20\n html += f\"\u003ch3>{severity.upper()} Severity Issues\u003c/h3>\"\n html += '\u003cdiv class=\"issue-list\">'\n for issue in severity_issues[:limit]:\n html += f'''\n \u003cdiv class=\"issue {severity}\">\n \u003cdiv>\n \u003cspan class=\"issue-code\">{issue['code']}\u003c/span>\n \u003cspan class=\"severity-badge severity-{severity}\">{severity.upper()}\u003c/span>\n \u003cspan class=\"issue-location\">{issue['file']}:{issue['line']}:{issue['column']}\u003c/span>\n \u003c/div>\n \u003cdiv style=\"margin-top: 8px;\">{issue['message']}\u003c/div>\n \u003cdiv style=\"margin-top: 4px; color: #7f8c8d; font-size: 13px;\">Category: {issue['category']}\u003c/div>\n \u003c/div>\n '''\n if len(severity_issues) > limit:\n html += f'\u003cdiv style=\"padding: 10px; color: #7f8c8d;\">... and {len(severity_issues) - limit} more {severity} severity issues\u003c/div>'\n html += '\u003c/div>'\n\n html += \"\"\"\n \u003c/div>\n\u003c/body>\n\u003c/html>\n\"\"\"\n\n return html\n\n\ndef main():\n parser = argparse.ArgumentParser(\n description=\"Comprehensive code analysis using flake8 with plugins\"\n )\n parser.add_argument(\n \"target\",\n type=Path,\n help=\"File or directory to analyze\"\n )\n parser.add_argument(\n \"--output\",\n type=Path,\n help=\"Output file for JSON report\"\n )\n parser.add_argument(\n \"--html\",\n type=Path,\n help=\"Output file for HTML report\"\n )\n parser.add_argument(\n \"--max-complexity\",\n type=int,\n default=10,\n help=\"Maximum cyclomatic complexity (default: 10)\"\n )\n parser.add_argument(\n \"--max-cognitive-complexity\",\n type=int,\n default=10,\n help=\"Maximum cognitive complexity (default: 10)\"\n )\n parser.add_argument(\n \"--ignore\",\n type=str,\n help=\"Comma-separated list of error codes to ignore\"\n )\n parser.add_argument(\n \"--quiet\",\n action=\"store_true\",\n help=\"Minimal console output\"\n )\n\n args = parser.parse_args()\n\n if not args.target.exists():\n print(f\"Error: Target not found: {args.target}\", file=sys.stderr)\n sys.exit(1)\n\n # Parse ignore codes\n ignore_codes = args.ignore.split(',') if args.ignore else None\n\n # Run analysis\n analysis = run_flake8_analysis(\n args.target,\n max_complexity=args.max_complexity,\n max_cognitive_complexity=args.max_cognitive_complexity,\n ignore_codes=ignore_codes\n )\n\n # Check for errors\n if 'error' in analysis:\n print(f\"Error: {analysis['error']}\", file=sys.stderr)\n if 'install_command' in analysis:\n print(f\"Install with: {analysis['install_command']}\", file=sys.stderr)\n sys.exit(1)\n\n # Save JSON report\n if args.output:\n with open(args.output, 'w') as f:\n json.dump(analysis, f, indent=2)\n if not args.quiet:\n print(f\"JSON report saved to: {args.output}\")\n\n # Save HTML report\n if args.html:\n html_content = generate_html_report(analysis)\n with open(args.html, 'w', encoding='utf-8') as f:\n f.write(html_content)\n if not args.quiet:\n print(f\"HTML report saved to: {args.html}\")\n\n # Print summary to console\n if not args.quiet:\n summary = generate_summary_report(analysis)\n print(summary)\n\n # Exit with appropriate code\n sys.exit(0 if analysis['passed'] else 1)\n\n\nif __name__ == \"__main__\":\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":25925,"content_sha256":"8ee83a219b6ea76e2d20a9fefa5fe50d92bbcda8dd958d0c0b9e942d7a43fb5c"},{"filename":"scripts/benchmark_changes.py","content":"#!/usr/bin/env python3\n\"\"\"Benchmark performance changes between code versions.\n\nThis script runs performance benchmarks on before/after versions of code\nto ensure refactoring doesn't introduce significant performance regression.\n\nUsage:\n python benchmark_changes.py \u003cbefore_file> \u003cafter_file> \u003ctest_module> [--threshold 10]\n\"\"\"\n\nimport argparse\nimport importlib.util\nimport sys\nimport timeit\nfrom pathlib import Path\nfrom typing import Dict, Any, Callable, List\nimport json\n\n\ndef load_module_from_file(file_path: Path, module_name: str):\n \"\"\"Dynamically load a Python module from a file path.\"\"\"\n spec = importlib.util.spec_from_file_location(module_name, file_path)\n if spec is None or spec.loader is None:\n raise ImportError(f\"Could not load module from {file_path}\")\n\n module = importlib.util.module_from_spec(spec)\n sys.modules[module_name] = module\n spec.loader.exec_module(module)\n return module\n\n\ndef discover_benchmarkable_functions(module) -> List[tuple[str, Callable]]:\n \"\"\"Discover public functions in a module that can be benchmarked.\n\n Returns list of (name, function) tuples.\n \"\"\"\n functions = []\n\n for name in dir(module):\n if name.startswith('_'):\n continue\n\n obj = getattr(module, name)\n if callable(obj) and hasattr(obj, '__module__') and obj.__module__ == module.__name__:\n functions.append((name, obj))\n\n return functions\n\n\ndef create_benchmark_wrapper(func: Callable, test_module) -> Callable:\n \"\"\"Create a benchmark wrapper that provides test data from test module.\n\n The test module should define benchmark_data_\u003cfunction_name> or\n benchmark_setup_\u003cfunction_name> functions.\n \"\"\"\n func_name = func.__name__\n\n # Try to get benchmark data from test module\n data_provider_name = f\"benchmark_data_{func_name}\"\n setup_provider_name = f\"benchmark_setup_{func_name}\"\n\n if hasattr(test_module, data_provider_name):\n data_provider = getattr(test_module, data_provider_name)\n return lambda: func(*data_provider())\n\n elif hasattr(test_module, setup_provider_name):\n setup_provider = getattr(test_module, setup_provider_name)\n setup_data = setup_provider()\n\n if isinstance(setup_data, dict):\n return lambda: func(**setup_data)\n elif isinstance(setup_data, (list, tuple)):\n return lambda: func(*setup_data)\n else:\n return lambda: func(setup_data)\n\n else:\n # Try calling with no arguments\n return lambda: func()\n\n\ndef benchmark_function(func: Callable, number: int = 1000, repeat: int = 5) -> Dict[str, float]:\n \"\"\"Benchmark a function and return timing statistics.\n\n Args:\n func: Function to benchmark\n number: Number of executions per timing\n repeat: Number of times to repeat the timing\n\n Returns:\n Dict with 'min', 'max', 'mean', 'median' times in seconds\n \"\"\"\n try:\n # Warm up\n func()\n\n # Run benchmark\n times = timeit.repeat(func, number=number, repeat=repeat)\n\n # Convert to per-execution times\n times = [t / number for t in times]\n\n return {\n 'min': min(times),\n 'max': max(times),\n 'mean': sum(times) / len(times),\n 'median': sorted(times)[len(times) // 2]\n }\n\n except Exception as e:\n print(f\" Error benchmarking function: {e}\", file=sys.stderr)\n return None\n\n\ndef compare_benchmarks(\n before_results: Dict[str, float],\n after_results: Dict[str, float],\n threshold_pct: float = 10.0\n) -> Dict[str, Any]:\n \"\"\"Compare benchmark results and determine if there's significant regression.\n\n Args:\n before_results: Timing results from before version\n after_results: Timing results from after version\n threshold_pct: Acceptable performance regression threshold (%)\n\n Returns:\n Dict with comparison data and regression status\n \"\"\"\n if before_results is None or after_results is None:\n return {\n 'regression': None,\n 'error': 'Benchmark failed'\n }\n\n # Use median time for comparison (more stable than mean)\n before_time = before_results['median']\n after_time = after_results['median']\n\n # Calculate percentage change\n if before_time > 0:\n pct_change = ((after_time - before_time) / before_time) * 100\n else:\n pct_change = 0.0\n\n # Determine if there's significant regression\n has_regression = pct_change > threshold_pct\n\n return {\n 'before_median': before_time,\n 'after_median': after_time,\n 'pct_change': round(pct_change, 2),\n 'threshold_pct': threshold_pct,\n 'regression': has_regression,\n 'faster': pct_change \u003c 0\n }\n\n\ndef print_benchmark_results(\n func_name: str,\n before_results: Dict[str, float],\n after_results: Dict[str, float],\n comparison: Dict[str, Any]\n):\n \"\"\"Print benchmark results for a single function.\"\"\"\n print(f\"\\n Function: {func_name}\")\n print(f\" {'─'*66}\")\n\n if comparison.get('error'):\n print(f\" ✗ {comparison['error']}\")\n return\n\n before_time = comparison['before_median']\n after_time = comparison['after_median']\n pct_change = comparison['pct_change']\n threshold = comparison['threshold_pct']\n\n # Format times nicely\n def format_time(t):\n if t \u003c 1e-6:\n return f\"{t*1e9:.2f} ns\"\n elif t \u003c 1e-3:\n return f\"{t*1e6:.2f} µs\"\n elif t \u003c 1:\n return f\"{t*1e3:.2f} ms\"\n else:\n return f\"{t:.2f} s\"\n\n print(f\" Before: {format_time(before_time)} (median)\")\n print(f\" After: {format_time(after_time)} (median)\")\n\n if comparison['faster']:\n print(f\" Change: {pct_change:+.1f}% ✓ FASTER\")\n elif comparison['regression']:\n print(f\" Change: {pct_change:+.1f}% ✗ REGRESSION (threshold: {threshold}%)\")\n else:\n print(f\" Change: {pct_change:+.1f}% ✓ Within threshold\")\n\n\ndef main():\n parser = argparse.ArgumentParser(\n description=\"Benchmark performance changes between code versions\"\n )\n parser.add_argument(\"before_file\", type=Path, help=\"Path to file before refactoring\")\n parser.add_argument(\"after_file\", type=Path, help=\"Path to file after refactoring\")\n parser.add_argument(\"test_module\", type=Path, help=\"Path to test/benchmark data module\")\n parser.add_argument(\n \"--threshold\",\n type=float,\n default=10.0,\n help=\"Performance regression threshold in percent (default: 10)\"\n )\n parser.add_argument(\n \"--number\",\n type=int,\n default=1000,\n help=\"Number of executions per timing (default: 1000)\"\n )\n parser.add_argument(\n \"--repeat\",\n type=int,\n default=5,\n help=\"Number of times to repeat timing (default: 5)\"\n )\n parser.add_argument(\"--json\", action=\"store_true\", help=\"Output JSON format\")\n\n args = parser.parse_args()\n\n # Validate files\n for file_path in [args.before_file, args.after_file, args.test_module]:\n if not file_path.exists():\n print(f\"Error: File not found: {file_path}\", file=sys.stderr)\n sys.exit(1)\n if not file_path.suffix == '.py':\n print(f\"Error: File must be a Python file (.py): {file_path}\", file=sys.stderr)\n sys.exit(1)\n\n # Load modules\n try:\n before_module = load_module_from_file(args.before_file, \"before_module\")\n after_module = load_module_from_file(args.after_file, \"after_module\")\n test_module = load_module_from_file(args.test_module, \"test_module\")\n except Exception as e:\n print(f\"Error loading modules: {e}\", file=sys.stderr)\n sys.exit(1)\n\n # Discover functions to benchmark\n before_functions = discover_benchmarkable_functions(before_module)\n after_functions = discover_benchmarkable_functions(after_module)\n\n # Find common functions\n before_names = {name for name, _ in before_functions}\n after_names = {name for name, _ in after_functions}\n common_names = before_names & after_names\n\n if not common_names:\n print(\"Error: No common functions found between before and after versions\", file=sys.stderr)\n sys.exit(1)\n\n # Run benchmarks\n results = {}\n regressions_found = False\n\n if not args.json:\n print(f\"\\n{'='*70}\")\n print(f\"Performance Benchmark Comparison\")\n print(f\"{'='*70}\")\n print(f\"\\nBenchmarking {len(common_names)} function(s)...\")\n\n for func_name in sorted(common_names):\n # Get functions\n before_func = next(f for name, f in before_functions if name == func_name)\n after_func = next(f for name, f in after_functions if name == func_name)\n\n # Create benchmark wrappers\n try:\n before_wrapper = create_benchmark_wrapper(before_func, test_module)\n after_wrapper = create_benchmark_wrapper(after_func, test_module)\n except Exception as e:\n print(f\"\\n Error creating benchmark for {func_name}: {e}\", file=sys.stderr)\n continue\n\n # Run benchmarks\n before_results = benchmark_function(before_wrapper, args.number, args.repeat)\n after_results = benchmark_function(after_wrapper, args.number, args.repeat)\n\n # Compare\n comparison = compare_benchmarks(before_results, after_results, args.threshold)\n\n results[func_name] = {\n 'before': before_results,\n 'after': after_results,\n 'comparison': comparison\n }\n\n if not args.json:\n print_benchmark_results(func_name, before_results, after_results, comparison)\n\n if comparison.get('regression'):\n regressions_found = True\n\n # Summary\n if not args.json:\n print(f\"\\n{'='*70}\")\n print(f\"Summary:\")\n print(f\"{'='*70}\")\n\n total = len(results)\n faster = sum(1 for r in results.values() if r['comparison'].get('faster'))\n regressed = sum(1 for r in results.values() if r['comparison'].get('regression'))\n within_threshold = total - faster - regressed\n\n print(f\" Total functions: {total}\")\n print(f\" Faster: {faster}\")\n print(f\" Within threshold: {within_threshold}\")\n print(f\" Regressions: {regressed}\")\n\n if regressions_found:\n print(f\"\\n✗ Performance regressions detected!\")\n sys.exit(1)\n else:\n print(f\"\\n✓ No significant performance regressions\")\n\n else:\n print(json.dumps(results, indent=2))\n\n sys.exit(0)\n\n\nif __name__ == \"__main__\":\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":10661,"content_sha256":"e9fb5976d413a7b61babbe99b43f270e2f90600be05ef19edff1547ac245ed6d"},{"filename":"scripts/check_documentation.py","content":"#!/usr/bin/env python3\n\"\"\"Check documentation coverage for Python code.\n\nThis script analyzes:\n- Docstring coverage for modules, classes, and functions\n- Type hint coverage for function parameters and returns\n- Documentation quality metrics\n\nUsage:\n python check_documentation.py \u003cfile_path> [--json]\n\"\"\"\n\nimport argparse\nimport ast\nimport json\nimport sys\nfrom dataclasses import dataclass, asdict\nfrom pathlib import Path\nfrom typing import List, Dict, Any\n\n\n@dataclass\nclass FunctionDocMetrics:\n \"\"\"Documentation metrics for a single function.\"\"\"\n name: str\n line_number: int\n has_docstring: bool\n docstring_length: int\n has_return_type: bool\n num_params: int\n num_params_with_types: int\n is_public: bool\n\n\n@dataclass\nclass ClassDocMetrics:\n \"\"\"Documentation metrics for a class.\"\"\"\n name: str\n line_number: int\n has_docstring: bool\n docstring_length: int\n num_methods: int\n num_methods_documented: int\n is_public: bool\n\n\n@dataclass\nclass FileDocMetrics:\n \"\"\"Overall documentation metrics for a file.\"\"\"\n file_path: str\n has_module_docstring: bool\n module_docstring_length: int\n\n total_functions: int\n public_functions: int\n functions_with_docstrings: int\n public_functions_with_docstrings: int\n\n total_classes: int\n public_classes: int\n classes_with_docstrings: int\n public_classes_with_docstrings: int\n\n total_params: int\n params_with_types: int\n\n total_returns: int\n returns_with_types: int\n\n docstring_coverage_pct: float # Public items with docstrings\n type_hint_coverage_pct: float # Params with type hints\n\n functions: List[FunctionDocMetrics]\n classes: List[ClassDocMetrics]\n\n def to_dict(self) -> Dict[str, Any]:\n \"\"\"Convert to dictionary for JSON serialization.\"\"\"\n result = asdict(self)\n result['functions'] = [asdict(f) for f in self.functions]\n result['classes'] = [asdict(c) for c in self.classes]\n return result\n\n\ndef is_public(name: str) -> bool:\n \"\"\"Check if a name is public (doesn't start with underscore).\"\"\"\n return not name.startswith('_')\n\n\ndef get_docstring_length(node: ast.AST) -> int:\n \"\"\"Get length of docstring in lines, or 0 if no docstring.\"\"\"\n docstring = ast.get_docstring(node)\n if docstring:\n return len(docstring.splitlines())\n return 0\n\n\ndef has_type_hints(node: ast.FunctionDef) -> tuple[bool, int, int]:\n \"\"\"Check if function has type hints.\n\n Returns:\n (has_return_type, num_params_with_types, total_params)\n \"\"\"\n has_return_type = node.returns is not None\n\n # Count parameters with type annotations\n num_params_with_types = sum(\n 1 for arg in node.args.args if arg.annotation is not None\n )\n\n total_params = len(node.args.args)\n\n return has_return_type, num_params_with_types, total_params\n\n\ndef analyze_function(node: ast.FunctionDef) -> FunctionDocMetrics:\n \"\"\"Analyze a function definition for documentation metrics.\"\"\"\n has_return_type, num_params_with_types, total_params = has_type_hints(node)\n\n return FunctionDocMetrics(\n name=node.name,\n line_number=node.lineno,\n has_docstring=ast.get_docstring(node) is not None,\n docstring_length=get_docstring_length(node),\n has_return_type=has_return_type,\n num_params=total_params,\n num_params_with_types=num_params_with_types,\n is_public=is_public(node.name)\n )\n\n\ndef analyze_class(node: ast.ClassDef) -> ClassDocMetrics:\n \"\"\"Analyze a class definition for documentation metrics.\"\"\"\n # Count methods with docstrings\n methods = [n for n in node.body if isinstance(n, ast.FunctionDef)]\n methods_documented = sum(\n 1 for m in methods if ast.get_docstring(m) is not None\n )\n\n return ClassDocMetrics(\n name=node.name,\n line_number=node.lineno,\n has_docstring=ast.get_docstring(node) is not None,\n docstring_length=get_docstring_length(node),\n num_methods=len(methods),\n num_methods_documented=methods_documented,\n is_public=is_public(node.name)\n )\n\n\ndef analyze_file(file_path: Path) -> FileDocMetrics:\n \"\"\"Analyze a Python file for documentation coverage.\"\"\"\n with open(file_path, 'r', encoding='utf-8') as f:\n source = f.read()\n\n try:\n tree = ast.parse(source, filename=str(file_path))\n except SyntaxError as e:\n print(f\"Error parsing {file_path}: {e}\", file=sys.stderr)\n sys.exit(1)\n\n # Module-level docstring\n module_docstring = ast.get_docstring(tree)\n has_module_docstring = module_docstring is not None\n module_docstring_length = len(module_docstring.splitlines()) if module_docstring else 0\n\n # Analyze top-level functions and classes\n functions: List[FunctionDocMetrics] = []\n classes: List[ClassDocMetrics] = []\n\n total_params = 0\n params_with_types = 0\n total_returns = 0\n returns_with_types = 0\n\n for node in tree.body:\n if isinstance(node, ast.FunctionDef):\n func_metrics = analyze_function(node)\n functions.append(func_metrics)\n\n total_params += func_metrics.num_params\n params_with_types += func_metrics.num_params_with_types\n total_returns += 1\n if func_metrics.has_return_type:\n returns_with_types += 1\n\n elif isinstance(node, ast.ClassDef):\n class_metrics = analyze_class(node)\n classes.append(class_metrics)\n\n # Also analyze class methods\n for method_node in node.body:\n if isinstance(method_node, ast.FunctionDef):\n method_metrics = analyze_function(method_node)\n functions.append(method_metrics)\n\n total_params += method_metrics.num_params\n params_with_types += method_metrics.num_params_with_types\n total_returns += 1\n if method_metrics.has_return_type:\n returns_with_types += 1\n\n # Calculate statistics\n total_functions = len(functions)\n public_functions = sum(1 for f in functions if f.is_public)\n functions_with_docstrings = sum(1 for f in functions if f.has_docstring)\n public_functions_with_docstrings = sum(\n 1 for f in functions if f.is_public and f.has_docstring\n )\n\n total_classes = len(classes)\n public_classes = sum(1 for c in classes if c.is_public)\n classes_with_docstrings = sum(1 for c in classes if c.has_docstring)\n public_classes_with_docstrings = sum(\n 1 for c in classes if c.is_public and c.has_docstring\n )\n\n # Calculate coverage percentages\n public_items = public_functions + public_classes\n documented_public_items = public_functions_with_docstrings + public_classes_with_docstrings\n\n if public_items > 0:\n docstring_coverage_pct = round((documented_public_items / public_items) * 100, 1)\n else:\n docstring_coverage_pct = 0.0\n\n if total_params > 0:\n type_hint_coverage_pct = round((params_with_types / total_params) * 100, 1)\n else:\n type_hint_coverage_pct = 0.0\n\n return FileDocMetrics(\n file_path=str(file_path),\n has_module_docstring=has_module_docstring,\n module_docstring_length=module_docstring_length,\n total_functions=total_functions,\n public_functions=public_functions,\n functions_with_docstrings=functions_with_docstrings,\n public_functions_with_docstrings=public_functions_with_docstrings,\n total_classes=total_classes,\n public_classes=public_classes,\n classes_with_docstrings=classes_with_docstrings,\n public_classes_with_docstrings=public_classes_with_docstrings,\n total_params=total_params,\n params_with_types=params_with_types,\n total_returns=total_returns,\n returns_with_types=returns_with_types,\n docstring_coverage_pct=docstring_coverage_pct,\n type_hint_coverage_pct=type_hint_coverage_pct,\n functions=functions,\n classes=classes\n )\n\n\ndef print_metrics(metrics: FileDocMetrics, verbose: bool = True):\n \"\"\"Print documentation metrics in human-readable format.\"\"\"\n print(f\"\\n{'='*70}\")\n print(f\"Documentation Coverage: {metrics.file_path}\")\n print(f\"{'='*70}\\n\")\n\n # Module-level documentation\n if metrics.has_module_docstring:\n print(f\"✓ Module docstring present ({metrics.module_docstring_length} lines)\")\n else:\n print(f\"✗ Module docstring missing\")\n\n print(f\"\\nOverall Statistics:\")\n print(f\" Public Functions: {metrics.public_functions}\")\n print(f\" Functions with Docstrings: {metrics.public_functions_with_docstrings}/{metrics.public_functions}\")\n print(f\" Public Classes: {metrics.public_classes}\")\n print(f\" Classes with Docstrings: {metrics.public_classes_with_docstrings}/{metrics.public_classes}\")\n print(f\"\\n Docstring Coverage: {metrics.docstring_coverage_pct}% (target: >80%)\")\n print(f\" Type Hint Coverage: {metrics.type_hint_coverage_pct}% (target: >90%)\")\n\n # Flag issues\n issues = []\n if not metrics.has_module_docstring:\n issues.append(\" ⚠ Module docstring is missing\")\n if metrics.docstring_coverage_pct \u003c 80:\n issues.append(f\" ⚠ Docstring coverage is {metrics.docstring_coverage_pct}% (target: >80%)\")\n if metrics.type_hint_coverage_pct \u003c 90:\n issues.append(f\" ⚠ Type hint coverage is {metrics.type_hint_coverage_pct}% (target: >90%)\")\n\n if issues:\n print(f\"\\nIssues Found:\")\n for issue in issues:\n print(issue)\n else:\n print(f\"\\n✓ All documentation targets met\")\n\n if verbose:\n # Show undocumented public items\n undocumented_funcs = [\n f for f in metrics.functions\n if f.is_public and not f.has_docstring\n ]\n undocumented_classes = [\n c for c in metrics.classes\n if c.is_public and not c.has_docstring\n ]\n\n if undocumented_funcs:\n print(f\"\\nUndocumented Public Functions:\")\n for func in undocumented_funcs[:10]: # Show first 10\n print(f\" {func.name}:{func.line_number} - Missing docstring\")\n if len(undocumented_funcs) > 10:\n print(f\" ... and {len(undocumented_funcs) - 10} more\")\n\n if undocumented_classes:\n print(f\"\\nUndocumented Public Classes:\")\n for cls in undocumented_classes:\n print(f\" {cls.name}:{cls.line_number} - Missing docstring\")\n\n # Show functions with missing type hints\n missing_types = [\n f for f in metrics.functions\n if f.is_public and (\n not f.has_return_type or\n f.num_params_with_types \u003c f.num_params\n )\n ]\n\n if missing_types:\n print(f\"\\nPublic Functions with Missing Type Hints:\")\n for func in missing_types[:10]: # Show first 10\n hints = []\n if not func.has_return_type:\n hints.append(\"missing return type\")\n if func.num_params_with_types \u003c func.num_params:\n hints.append(f\"{func.num_params - func.num_params_with_types} params without types\")\n print(f\" {func.name}:{func.line_number} - {', '.join(hints)}\")\n if len(missing_types) > 10:\n print(f\" ... and {len(missing_types) - 10} more\")\n\n\ndef main():\n parser = argparse.ArgumentParser(\n description=\"Check documentation coverage for Python code\"\n )\n parser.add_argument(\"file_path\", type=Path, help=\"Path to Python file to analyze\")\n parser.add_argument(\"--json\", action=\"store_true\", help=\"Output JSON format\")\n parser.add_argument(\"--quiet\", action=\"store_true\", help=\"Minimal output\")\n\n args = parser.parse_args()\n\n if not args.file_path.exists():\n print(f\"Error: File not found: {args.file_path}\", file=sys.stderr)\n sys.exit(1)\n\n if not args.file_path.suffix == '.py':\n print(f\"Error: File must be a Python file (.py)\", file=sys.stderr)\n sys.exit(1)\n\n metrics = analyze_file(args.file_path)\n\n if args.json:\n print(json.dumps(metrics.to_dict(), indent=2))\n else:\n print_metrics(metrics, verbose=not args.quiet)\n\n\nif __name__ == \"__main__\":\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":12342,"content_sha256":"eb30d3322c9823362019a90ef9c3c4cbf148821efd69f5aefc2bd557290a7d7a"},{"filename":"scripts/compare_flake8_reports.py","content":"#!/usr/bin/env python3\n\"\"\"Compare flake8 reports before and after refactoring.\n\nThis script compares two flake8 analysis reports (JSON format) to show\nimprovements or regressions in code quality.\n\nUsage:\n python compare_flake8_reports.py before.json after.json [--html output.html]\n\"\"\"\n\nimport argparse\nimport json\nimport sys\nfrom pathlib import Path\nfrom typing import Dict, Any\n\n\ndef load_report(file_path: Path) -> Dict[str, Any]:\n \"\"\"Load flake8 report from JSON file.\n\n Args:\n file_path: Path to JSON report file\n\n Returns:\n Parsed report dictionary\n \"\"\"\n try:\n with open(file_path, 'r') as f:\n return json.load(f)\n except Exception as e:\n print(f\"Error loading report {file_path}: {e}\", file=sys.stderr)\n sys.exit(1)\n\n\ndef compare_reports(before: Dict[str, Any], after: Dict[str, Any]) -> Dict[str, Any]:\n \"\"\"Compare two flake8 reports and calculate improvements.\n\n Args:\n before: Report from before refactoring\n after: Report from after refactoring\n\n Returns:\n Comparison results with improvements/regressions\n \"\"\"\n # Overall metrics\n total_before = before['total_issues']\n total_after = after['total_issues']\n total_change = total_after - total_before\n total_pct = ((total_before - total_after) / total_before * 100) if total_before > 0 else 0\n\n # By severity\n severity_comparison = {}\n for severity in ['high', 'medium', 'low']:\n before_count = before['by_severity'].get(severity, 0)\n after_count = after['by_severity'].get(severity, 0)\n change = after_count - before_count\n pct = ((before_count - after_count) / before_count * 100) if before_count > 0 else 0\n\n severity_comparison[severity] = {\n 'before': before_count,\n 'after': after_count,\n 'change': change,\n 'pct_improvement': pct,\n 'improved': change \u003c= 0\n }\n\n # By category\n all_categories = set(before['by_category'].keys()) | set(after['by_category'].keys())\n category_comparison = {}\n\n for category in all_categories:\n before_count = before['by_category'].get(category, 0)\n after_count = after['by_category'].get(category, 0)\n change = after_count - before_count\n pct = ((before_count - after_count) / before_count * 100) if before_count > 0 else 0\n\n category_comparison[category] = {\n 'before': before_count,\n 'after': after_count,\n 'change': change,\n 'pct_improvement': pct,\n 'improved': change \u003c= 0\n }\n\n # By error code\n all_codes = set(before['statistics'].keys()) | set(after['statistics'].keys())\n code_comparison = {}\n\n for code in all_codes:\n before_count = before['statistics'].get(code, 0)\n after_count = after['statistics'].get(code, 0)\n change = after_count - before_count\n pct = ((before_count - after_count) / before_count * 100) if before_count > 0 else 0\n\n code_comparison[code] = {\n 'before': before_count,\n 'after': after_count,\n 'change': change,\n 'pct_improvement': pct,\n 'improved': change \u003c= 0\n }\n\n # Issues fixed vs new issues\n fixed_issues = []\n new_issues = []\n\n # Create signature for each issue\n before_sigs = {\n f\"{i['file']}:{i['line']}:{i['code']}\"\n for i in before.get('issues', [])\n }\n after_sigs = {\n f\"{i['file']}:{i['line']}:{i['code']}\"\n for i in after.get('issues', [])\n }\n\n # Find fixed issues\n for issue in before.get('issues', []):\n sig = f\"{issue['file']}:{issue['line']}:{issue['code']}\"\n if sig not in after_sigs:\n fixed_issues.append(issue)\n\n # Find new issues\n for issue in after.get('issues', []):\n sig = f\"{issue['file']}:{issue['line']}:{issue['code']}\"\n if sig not in before_sigs:\n new_issues.append(issue)\n\n # Status assessment\n passed_before = before.get('passed', False)\n passed_after = after.get('passed', False)\n\n if not passed_before and passed_after:\n status = 'IMPROVED - Now Passing'\n elif passed_before and not passed_after:\n status = 'REGRESSED - Now Failing'\n elif passed_after:\n status = 'PASSING - Maintained'\n else:\n status = 'FAILING - Needs Work'\n\n return {\n 'status': status,\n 'passed_before': passed_before,\n 'passed_after': passed_after,\n 'overall': {\n 'before': total_before,\n 'after': total_after,\n 'change': total_change,\n 'pct_improvement': total_pct,\n 'improved': total_change \u003c= 0\n },\n 'by_severity': severity_comparison,\n 'by_category': category_comparison,\n 'by_code': code_comparison,\n 'fixed_issues': fixed_issues,\n 'new_issues': new_issues,\n 'fixed_count': len(fixed_issues),\n 'new_count': len(new_issues),\n 'net_improvement': len(fixed_issues) - len(new_issues)\n }\n\n\ndef generate_text_report(comparison: Dict[str, Any]) -> str:\n \"\"\"Generate human-readable comparison report.\n\n Args:\n comparison: Comparison results\n\n Returns:\n Formatted text report\n \"\"\"\n lines = []\n\n lines.append(\"=\" * 70)\n lines.append(\"Flake8 Comparison Report: Before vs After Refactoring\")\n lines.append(\"=\" * 70)\n lines.append(\"\")\n\n # Overall status\n lines.append(f\"Status: {comparison['status']}\")\n lines.append(\"\")\n\n # Overall metrics\n overall = comparison['overall']\n symbol = \"✓\" if overall['improved'] else \"✗\"\n lines.append(\"Overall Metrics:\")\n lines.append(\"-\" * 70)\n lines.append(f\" Total Issues Before: {overall['before']}\")\n lines.append(f\" Total Issues After: {overall['after']}\")\n lines.append(f\" Change: {overall['change']:+d} ({overall['pct_improvement']:+.1f}%) {symbol}\")\n lines.append(\"\")\n\n # Issues fixed vs new\n lines.append(\"Issue Changes:\")\n lines.append(\"-\" * 70)\n lines.append(f\" Issues Fixed: {comparison['fixed_count']}\")\n lines.append(f\" New Issues: {comparison['new_count']}\")\n lines.append(f\" Net Improvement: {comparison['net_improvement']:+d}\")\n lines.append(\"\")\n\n # By severity\n lines.append(\"Issues by Severity:\")\n lines.append(\"-\" * 70)\n for severity in ['high', 'medium', 'low']:\n data = comparison['by_severity'][severity]\n symbol = \"✓\" if data['improved'] else \"✗\"\n lines.append(\n f\" {severity.upper()}: {data['before']} → {data['after']} \"\n f\"({data['change']:+d}, {data['pct_improvement']:+.1f}%) {symbol}\"\n )\n lines.append(\"\")\n\n # By category (show biggest improvements/regressions)\n lines.append(\"Biggest Improvements by Category:\")\n lines.append(\"-\" * 70)\n\n improvements = [\n (cat, data)\n for cat, data in comparison['by_category'].items()\n if data['improved'] and data['change'] \u003c 0\n ]\n improvements.sort(key=lambda x: x[1]['pct_improvement'], reverse=True)\n\n if improvements:\n for category, data in improvements[:10]:\n lines.append(\n f\" {category}: {data['before']} → {data['after']} \"\n f\"({data['pct_improvement']:+.1f}% improvement) ✓\"\n )\n else:\n lines.append(\" No improvements\")\n lines.append(\"\")\n\n # Regressions\n regressions = [\n (cat, data)\n for cat, data in comparison['by_category'].items()\n if not data['improved']\n ]\n regressions.sort(key=lambda x: x[1]['change'], reverse=True)\n\n if regressions:\n lines.append(\"Regressions by Category:\")\n lines.append(\"-\" * 70)\n for category, data in regressions:\n lines.append(\n f\" {category}: {data['before']} → {data['after']} \"\n f\"({data['change']:+d}) ✗\"\n )\n lines.append(\"\")\n\n # Top code improvements\n lines.append(\"Top Error Code Improvements:\")\n lines.append(\"-\" * 70)\n\n code_improvements = [\n (code, data)\n for code, data in comparison['by_code'].items()\n if data['improved'] and data['change'] \u003c 0\n ]\n code_improvements.sort(key=lambda x: abs(x[1]['change']), reverse=True)\n\n if code_improvements:\n for code, data in code_improvements[:10]:\n lines.append(\n f\" {code}: {data['before']} → {data['after']} \"\n f\"({data['change']:+d}) ✓\"\n )\n else:\n lines.append(\" No improvements\")\n lines.append(\"\")\n\n # Sample fixed issues\n if comparison['fixed_issues']:\n lines.append(\"Sample Fixed Issues:\")\n lines.append(\"-\" * 70)\n for issue in comparison['fixed_issues'][:10]:\n lines.append(\n f\" ✓ {issue['file']}:{issue['line']} {issue['code']} \"\n f\"[{issue['severity']}] - {issue['message']}\"\n )\n if len(comparison['fixed_issues']) > 10:\n lines.append(f\" ... and {len(comparison['fixed_issues']) - 10} more\")\n lines.append(\"\")\n\n # New issues (warnings)\n if comparison['new_issues']:\n lines.append(\"New Issues Introduced:\")\n lines.append(\"-\" * 70)\n for issue in comparison['new_issues'][:10]:\n lines.append(\n f\" ✗ {issue['file']}:{issue['line']} {issue['code']} \"\n f\"[{issue['severity']}] - {issue['message']}\"\n )\n if len(comparison['new_issues']) > 10:\n lines.append(f\" ... and {len(comparison['new_issues']) - 10} more\")\n lines.append(\"\")\n\n # Summary\n lines.append(\"=\" * 70)\n lines.append(\"Summary:\")\n lines.append(\"-\" * 70)\n\n if comparison['net_improvement'] > 0:\n lines.append(f\"✓ Net improvement of {comparison['net_improvement']} issues\")\n elif comparison['net_improvement'] \u003c 0:\n lines.append(f\"✗ Net regression of {abs(comparison['net_improvement'])} issues\")\n else:\n lines.append(\"= No net change in issue count\")\n\n if comparison['overall']['improved']:\n lines.append(f\"✓ Total issues reduced by {comparison['overall']['pct_improvement']:.1f}%\")\n else:\n lines.append(\"✗ Total issues increased\")\n\n lines.append(\"\")\n lines.append(\"=\" * 70)\n\n return '\\n'.join(lines)\n\n\ndef generate_html_report(comparison: Dict[str, Any]) -> str:\n \"\"\"Generate HTML comparison report.\n\n Args:\n comparison: Comparison results\n\n Returns:\n HTML report string\n \"\"\"\n improved_color = '#27ae60'\n regressed_color = '#e74c3c'\n neutral_color = '#95a5a6'\n\n overall = comparison['overall']\n status_color = improved_color if overall['improved'] else regressed_color\n\n html = f\"\"\"\u003c!DOCTYPE html>\n\u003chtml>\n\u003chead>\n \u003cmeta charset=\"UTF-8\">\n \u003ctitle>Flake8 Comparison Report\u003c/title>\n \u003cstyle>\n body {{\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif;\n margin: 0;\n padding: 20px;\n background: #f5f5f5;\n }}\n .container {{\n max-width: 1200px;\n margin: 0 auto;\n background: white;\n padding: 30px;\n border-radius: 8px;\n box-shadow: 0 2px 4px rgba(0,0,0,0.1);\n }}\n h1 {{\n color: #2c3e50;\n border-bottom: 3px solid #3498db;\n padding-bottom: 10px;\n }}\n h2 {{\n color: #34495e;\n margin-top: 30px;\n }}\n .status {{\n font-size: 24px;\n font-weight: bold;\n color: {status_color};\n margin: 20px 0;\n padding: 20px;\n background: #ecf0f1;\n border-radius: 6px;\n text-align: center;\n }}\n .metrics {{\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));\n gap: 20px;\n margin: 20px 0;\n }}\n .metric-card {{\n padding: 20px;\n border-radius: 6px;\n background: #ecf0f1;\n border-left: 4px solid {neutral_color};\n }}\n .metric-card.improved {{ border-left-color: {improved_color}; }}\n .metric-card.regressed {{ border-left-color: {regressed_color}; }}\n .metric-card h3 {{\n margin: 0 0 10px 0;\n color: #7f8c8d;\n font-size: 14px;\n text-transform: uppercase;\n }}\n .metric-value {{\n font-size: 24px;\n font-weight: bold;\n color: #2c3e50;\n }}\n .metric-change {{\n margin-top: 5px;\n font-size: 16px;\n }}\n .metric-change.positive {{ color: {improved_color}; }}\n .metric-change.negative {{ color: {regressed_color}; }}\n table {{\n width: 100%;\n border-collapse: collapse;\n margin: 20px 0;\n }}\n th, td {{\n padding: 12px;\n text-align: left;\n border-bottom: 1px solid #ecf0f1;\n }}\n th {{\n background: #34495e;\n color: white;\n }}\n tr:hover {{ background: #f8f9fa; }}\n .improved-row {{ background: #d5f4e6; }}\n .regressed-row {{ background: #fadbd8; }}\n .issue-list {{\n margin: 20px 0;\n }}\n .issue {{\n padding: 15px;\n margin: 10px 0;\n border-left: 4px solid #bdc3c7;\n background: #f8f9fa;\n border-radius: 4px;\n font-size: 14px;\n }}\n .issue.fixed {{ border-left-color: {improved_color}; }}\n .issue.new {{ border-left-color: {regressed_color}; }}\n .severity-badge {{\n display: inline-block;\n padding: 2px 6px;\n border-radius: 3px;\n font-size: 11px;\n font-weight: bold;\n color: white;\n margin-left: 5px;\n }}\n .severity-high {{ background: #e74c3c; }}\n .severity-medium {{ background: #f39c12; }}\n .severity-low {{ background: #3498db; }}\n \u003c/style>\n\u003c/head>\n\u003cbody>\n \u003cdiv class=\"container\">\n \u003ch1>Flake8 Comparison: Before vs After Refactoring\u003c/h1>\n\n \u003cdiv class=\"status\">{comparison['status']}\u003c/div>\n\n \u003cdiv class=\"metrics\">\n \u003cdiv class=\"metric-card {'improved' if overall['improved'] else 'regressed'}\">\n \u003ch3>Total Issues\u003c/h3>\n \u003cdiv class=\"metric-value\">{overall['before']} → {overall['after']}\u003c/div>\n \u003cdiv class=\"metric-change {'positive' if overall['improved'] else 'negative'}\">\n {overall['change']:+d} ({overall['pct_improvement']:+.1f}%)\n \u003c/div>\n \u003c/div>\n\n \u003cdiv class=\"metric-card {'improved' if comparison['net_improvement'] > 0 else 'regressed' if comparison['net_improvement'] \u003c 0 else ''}\">\n \u003ch3>Net Improvement\u003c/h3>\n \u003cdiv class=\"metric-value\">{comparison['net_improvement']:+d}\u003c/div>\n \u003cdiv style=\"margin-top: 5px; font-size: 14px;\">\n Fixed: {comparison['fixed_count']} | New: {comparison['new_count']}\n \u003c/div>\n \u003c/div>\n \u003c/div>\n\n \u003ch2>Issues by Severity\u003c/h2>\n \u003ctable>\n \u003ctr>\n \u003cth>Severity\u003c/th>\n \u003cth>Before\u003c/th>\n \u003cth>After\u003c/th>\n \u003cth>Change\u003c/th>\n \u003cth>Improvement\u003c/th>\n \u003c/tr>\n\"\"\"\n\n for severity in ['high', 'medium', 'low']:\n data = comparison['by_severity'][severity]\n row_class = 'improved-row' if data['improved'] else 'regressed-row' if data['change'] > 0 else ''\n symbol = '✓' if data['improved'] else '✗'\n html += f\"\"\"\n \u003ctr class=\"{row_class}\">\n \u003ctd>\u003cspan class=\"severity-badge severity-{severity}\">{severity.upper()}\u003c/span>\u003c/td>\n \u003ctd>{data['before']}\u003c/td>\n \u003ctd>{data['after']}\u003c/td>\n \u003ctd>{data['change']:+d}\u003c/td>\n \u003ctd>{data['pct_improvement']:+.1f}% {symbol}\u003c/td>\n \u003c/tr>\n\"\"\"\n\n html += \"\u003c/table>\"\n\n # Category improvements\n improvements = [(cat, data) for cat, data in comparison['by_category'].items() if data['improved'] and data['change'] \u003c 0]\n improvements.sort(key=lambda x: abs(x[1]['change']), reverse=True)\n\n if improvements:\n html += \"\u003ch2>Top Category Improvements\u003c/h2>\u003ctable>\"\n html += \"\u003ctr>\u003cth>Category\u003c/th>\u003cth>Before\u003c/th>\u003cth>After\u003c/th>\u003cth>Change\u003c/th>\u003cth>Improvement\u003c/th>\u003c/tr>\"\n for category, data in improvements[:10]:\n html += f\"\"\"\n \u003ctr class=\"improved-row\">\n \u003ctd>{category}\u003c/td>\n \u003ctd>{data['before']}\u003c/td>\n \u003ctd>{data['after']}\u003c/td>\n \u003ctd>{data['change']:+d}\u003c/td>\n \u003ctd>{data['pct_improvement']:+.1f}% ✓\u003c/td>\n \u003c/tr>\n \"\"\"\n html += \"\u003c/table>\"\n\n # Fixed issues\n if comparison['fixed_issues']:\n html += f\"\u003ch2>Fixed Issues ({len(comparison['fixed_issues'])})\u003c/h2>\"\n html += '\u003cdiv class=\"issue-list\">'\n for issue in comparison['fixed_issues'][:30]:\n html += f\"\"\"\n \u003cdiv class=\"issue fixed\">\n ✓ \u003cstrong>{issue['code']}\u003c/strong>\n \u003cspan class=\"severity-badge severity-{issue['severity']}\">{issue['severity']}\u003c/span>\n {issue['file']}:{issue['line']} - {issue['message']}\n \u003c/div>\n \"\"\"\n if len(comparison['fixed_issues']) > 30:\n html += f'\u003cp>... and {len(comparison[\"fixed_issues\"]) - 30} more fixed issues\u003c/p>'\n html += '\u003c/div>'\n\n # New issues\n if comparison['new_issues']:\n html += f\"\u003ch2>New Issues ({len(comparison['new_issues'])})\u003c/h2>\"\n html += '\u003cdiv class=\"issue-list\">'\n for issue in comparison['new_issues'][:30]:\n html += f\"\"\"\n \u003cdiv class=\"issue new\">\n ✗ \u003cstrong>{issue['code']}\u003c/strong>\n \u003cspan class=\"severity-badge severity-{issue['severity']}\">{issue['severity']}\u003c/span>\n {issue['file']}:{issue['line']} - {issue['message']}\n \u003c/div>\n \"\"\"\n if len(comparison['new_issues']) > 30:\n html += f'\u003cp>... and {len(comparison[\"new_issues\"]) - 30} more new issues\u003c/p>'\n html += '\u003c/div>'\n\n html += \"\"\"\n \u003c/div>\n\u003c/body>\n\u003c/html>\n\"\"\"\n\n return html\n\n\ndef main():\n parser = argparse.ArgumentParser(\n description=\"Compare flake8 reports before and after refactoring\"\n )\n parser.add_argument(\n \"before_report\",\n type=Path,\n help=\"JSON report from before refactoring\"\n )\n parser.add_argument(\n \"after_report\",\n type=Path,\n help=\"JSON report from after refactoring\"\n )\n parser.add_argument(\n \"--html\",\n type=Path,\n help=\"Output HTML comparison report\"\n )\n parser.add_argument(\n \"--json\",\n type=Path,\n help=\"Output JSON comparison data\"\n )\n\n args = parser.parse_args()\n\n # Load reports\n before = load_report(args.before_report)\n after = load_report(args.after_report)\n\n # Compare\n comparison = compare_reports(before, after)\n\n # Generate text report\n text_report = generate_text_report(comparison)\n print(text_report)\n\n # Save HTML if requested\n if args.html:\n html_report = generate_html_report(comparison)\n with open(args.html, 'w', encoding='utf-8') as f:\n f.write(html_report)\n print(f\"\\nHTML report saved to: {args.html}\")\n\n # Save JSON if requested\n if args.json:\n with open(args.json, 'w') as f:\n json.dump(comparison, f, indent=2)\n print(f\"JSON comparison saved to: {args.json}\")\n\n # Exit code based on whether we improved\n sys.exit(0 if comparison['overall']['improved'] else 1)\n\n\nif __name__ == \"__main__\":\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":20061,"content_sha256":"f059bdd6ecb81b152d1fd1acdd72c532460f0599cb4be4f34362d6cf84ee6d67"},{"filename":"scripts/compare_metrics.py","content":"#!/usr/bin/env python3\n\"\"\"Compare code metrics before and after refactoring.\n\nThis script compares complexity and documentation metrics between two\nversions of a file to quantify refactoring improvements.\n\nUsage:\n python compare_metrics.py \u003cbefore_file> \u003cafter_file> [--json]\n\"\"\"\n\nimport argparse\nimport json\nimport sys\nfrom pathlib import Path\nfrom typing import Dict, Any\n\n# Import our other metric scripts\nimport measure_complexity\nimport check_documentation\n\n\ndef calculate_percentage_change(before: float, after: float) -> float:\n \"\"\"Calculate percentage change (positive = improvement for metrics we want to decrease).\"\"\"\n if before == 0:\n return 0.0\n # For metrics we want to decrease (complexity, length), negative change is good\n return round(((before - after) / before) * 100, 1)\n\n\ndef calculate_percentage_increase(before: float, after: float) -> float:\n \"\"\"Calculate percentage increase (positive = improvement for metrics we want to increase).\"\"\"\n if before == 0:\n if after > 0:\n return 100.0\n return 0.0\n # For metrics we want to increase (coverage), positive change is good\n return round(((after - before) / before) * 100, 1)\n\n\ndef compare_complexity(before_file: Path, after_file: Path) -> Dict[str, Any]:\n \"\"\"Compare complexity metrics between two files.\"\"\"\n before_metrics = measure_complexity.analyze_file(before_file)\n after_metrics = measure_complexity.analyze_file(after_file)\n\n comparison = {\n 'avg_complexity': {\n 'before': before_metrics.avg_complexity,\n 'after': after_metrics.avg_complexity,\n 'change': calculate_percentage_change(\n before_metrics.avg_complexity,\n after_metrics.avg_complexity\n ),\n 'improved': after_metrics.avg_complexity \u003c= before_metrics.avg_complexity\n },\n 'max_complexity': {\n 'before': before_metrics.max_complexity,\n 'after': after_metrics.max_complexity,\n 'change': calculate_percentage_change(\n before_metrics.max_complexity,\n after_metrics.max_complexity\n ),\n 'improved': after_metrics.max_complexity \u003c= before_metrics.max_complexity\n },\n 'avg_length': {\n 'before': before_metrics.avg_length,\n 'after': after_metrics.avg_length,\n 'change': calculate_percentage_change(\n before_metrics.avg_length,\n after_metrics.avg_length\n ),\n 'improved': after_metrics.avg_length \u003c= before_metrics.avg_length\n },\n 'max_length': {\n 'before': before_metrics.max_length,\n 'after': after_metrics.max_length,\n 'change': calculate_percentage_change(\n before_metrics.max_length,\n after_metrics.max_length\n ),\n 'improved': after_metrics.max_length \u003c= before_metrics.max_length\n },\n 'avg_nesting': {\n 'before': before_metrics.avg_nesting,\n 'after': after_metrics.avg_nesting,\n 'change': calculate_percentage_change(\n before_metrics.avg_nesting,\n after_metrics.avg_nesting\n ),\n 'improved': after_metrics.avg_nesting \u003c= before_metrics.avg_nesting\n },\n 'max_nesting': {\n 'before': before_metrics.max_nesting,\n 'after': after_metrics.max_nesting,\n 'change': calculate_percentage_change(\n before_metrics.max_nesting,\n after_metrics.max_nesting\n ),\n 'improved': after_metrics.max_nesting \u003c= before_metrics.max_nesting\n }\n }\n\n return comparison\n\n\ndef compare_documentation(before_file: Path, after_file: Path) -> Dict[str, Any]:\n \"\"\"Compare documentation metrics between two files.\"\"\"\n before_metrics = check_documentation.analyze_file(before_file)\n after_metrics = check_documentation.analyze_file(after_file)\n\n comparison = {\n 'module_docstring': {\n 'before': before_metrics.has_module_docstring,\n 'after': after_metrics.has_module_docstring,\n 'improved': after_metrics.has_module_docstring and not before_metrics.has_module_docstring\n },\n 'docstring_coverage': {\n 'before': before_metrics.docstring_coverage_pct,\n 'after': after_metrics.docstring_coverage_pct,\n 'change': calculate_percentage_increase(\n before_metrics.docstring_coverage_pct,\n after_metrics.docstring_coverage_pct\n ),\n 'improved': after_metrics.docstring_coverage_pct >= before_metrics.docstring_coverage_pct\n },\n 'type_hint_coverage': {\n 'before': before_metrics.type_hint_coverage_pct,\n 'after': after_metrics.type_hint_coverage_pct,\n 'change': calculate_percentage_increase(\n before_metrics.type_hint_coverage_pct,\n after_metrics.type_hint_coverage_pct\n ),\n 'improved': after_metrics.type_hint_coverage_pct >= before_metrics.type_hint_coverage_pct\n }\n }\n\n return comparison\n\n\ndef print_comparison(complexity: Dict[str, Any], documentation: Dict[str, Any]):\n \"\"\"Print comparison results in human-readable format.\"\"\"\n print(f\"\\n{'='*70}\")\n print(f\"Refactoring Metrics Comparison\")\n print(f\"{'='*70}\\n\")\n\n print(\"Complexity Metrics:\")\n print(f\"{'─'*70}\")\n\n for metric_name, data in complexity.items():\n metric_label = metric_name.replace('_', ' ').title()\n before = data['before']\n after = data['after']\n change = data['change']\n improved = data['improved']\n\n symbol = '✓' if improved else '✗'\n change_str = f\"{change:+.1f}%\" if change != 0 else \"no change\"\n\n print(f\" {metric_label}:\")\n print(f\" Before: {before}, After: {after}, Change: {change_str} {symbol}\")\n\n print(f\"\\nDocumentation Metrics:\")\n print(f\"{'─'*70}\")\n\n # Module docstring\n mod_doc = documentation['module_docstring']\n if mod_doc['after'] and not mod_doc['before']:\n print(f\" Module Docstring: Added ✓\")\n elif mod_doc['after']:\n print(f\" Module Docstring: Present ✓\")\n else:\n print(f\" Module Docstring: Missing ✗\")\n\n # Coverage metrics\n for metric_name, data in documentation.items():\n if metric_name == 'module_docstring':\n continue\n\n metric_label = metric_name.replace('_', ' ').title()\n before = data['before']\n after = data['after']\n change = data['change']\n improved = data['improved']\n\n symbol = '✓' if improved else '✗'\n change_str = f\"{change:+.1f}%\" if change != 0 else \"no change\"\n\n print(f\" {metric_label}:\")\n print(f\" Before: {before}%, After: {after}%, Change: {change_str} {symbol}\")\n\n # Overall assessment\n print(f\"\\nOverall Assessment:\")\n print(f\"{'─'*70}\")\n\n complexity_improvements = sum(1 for data in complexity.values() if data['improved'])\n complexity_total = len(complexity)\n\n doc_improvements = sum(1 for data in documentation.values() if data['improved'])\n doc_total = len(documentation)\n\n print(f\" Complexity: {complexity_improvements}/{complexity_total} metrics improved\")\n print(f\" Documentation: {doc_improvements}/{doc_total} metrics improved\")\n\n if complexity_improvements == complexity_total and doc_improvements == doc_total:\n print(f\"\\n✓ All metrics improved or maintained!\")\n elif complexity_improvements + doc_improvements > 0:\n print(f\"\\n⚠ Some metrics improved\")\n else:\n print(f\"\\n✗ No improvements detected\")\n\n\ndef main():\n parser = argparse.ArgumentParser(\n description=\"Compare code metrics before and after refactoring\"\n )\n parser.add_argument(\"before_file\", type=Path, help=\"Path to file before refactoring\")\n parser.add_argument(\"after_file\", type=Path, help=\"Path to file after refactoring\")\n parser.add_argument(\"--json\", action=\"store_true\", help=\"Output JSON format\")\n\n args = parser.parse_args()\n\n # Validate files\n for file_path in [args.before_file, args.after_file]:\n if not file_path.exists():\n print(f\"Error: File not found: {file_path}\", file=sys.stderr)\n sys.exit(1)\n if not file_path.suffix == '.py':\n print(f\"Error: File must be a Python file (.py): {file_path}\", file=sys.stderr)\n sys.exit(1)\n\n # Compare metrics\n complexity_comparison = compare_complexity(args.before_file, args.after_file)\n documentation_comparison = compare_documentation(args.before_file, args.after_file)\n\n if args.json:\n output = {\n 'complexity': complexity_comparison,\n 'documentation': documentation_comparison\n }\n print(json.dumps(output, indent=2))\n else:\n print_comparison(complexity_comparison, documentation_comparison)\n\n\nif __name__ == \"__main__\":\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":9056,"content_sha256":"f9438182fc365b754911c6a48eed1ac8558979d648708522fb63674f5a1bf7a5"},{"filename":"scripts/measure_complexity.py","content":"#!/usr/bin/env python3\n\"\"\"Measure code complexity metrics for refactoring validation.\n\nThis script analyzes source code files to measure:\n- Cyclomatic complexity per function\n- Function length (lines of code)\n- Nesting depth\n- Overall file statistics\n\nUsage:\n python measure_complexity.py \u003cfile_path> [--json]\n\"\"\"\n\nimport argparse\nimport ast\nimport json\nimport sys\nfrom dataclasses import dataclass, asdict\nfrom pathlib import Path\nfrom typing import List, Dict, Any\n\n\n@dataclass\nclass FunctionMetrics:\n \"\"\"Metrics for a single function.\"\"\"\n name: str\n line_number: int\n complexity: int\n length: int # lines of code\n max_nesting: int\n num_parameters: int\n\n\n@dataclass\nclass FileMetrics:\n \"\"\"Overall metrics for a file.\"\"\"\n file_path: str\n total_functions: int\n avg_complexity: float\n max_complexity: int\n avg_length: float\n max_length: int\n avg_nesting: float\n max_nesting: int\n functions: List[FunctionMetrics]\n\n def to_dict(self) -> Dict[str, Any]:\n \"\"\"Convert to dictionary for JSON serialization.\"\"\"\n result = asdict(self)\n result['functions'] = [asdict(f) for f in self.functions]\n return result\n\n\nclass ComplexityAnalyzer(ast.NodeVisitor):\n \"\"\"AST visitor to calculate cyclomatic complexity.\"\"\"\n\n def __init__(self):\n self.complexity = 1 # Base complexity is 1\n\n def visit_If(self, node):\n self.complexity += 1\n self.generic_visit(node)\n\n def visit_While(self, node):\n self.complexity += 1\n self.generic_visit(node)\n\n def visit_For(self, node):\n self.complexity += 1\n self.generic_visit(node)\n\n def visit_ExceptHandler(self, node):\n self.complexity += 1\n self.generic_visit(node)\n\n def visit_With(self, node):\n self.complexity += 1\n self.generic_visit(node)\n\n def visit_Assert(self, node):\n self.complexity += 1\n self.generic_visit(node)\n\n def visit_BoolOp(self, node):\n # Each 'and'/'or' adds a decision point\n self.complexity += len(node.values) - 1\n self.generic_visit(node)\n\n def visit_comprehension(self, node):\n self.complexity += 1\n if node.ifs:\n self.complexity += len(node.ifs)\n self.generic_visit(node)\n\n\nclass NestingAnalyzer(ast.NodeVisitor):\n \"\"\"AST visitor to calculate maximum nesting depth.\"\"\"\n\n def __init__(self):\n self.max_depth = 0\n self.current_depth = 0\n\n def _visit_nested(self, node):\n self.current_depth += 1\n self.max_depth = max(self.max_depth, self.current_depth)\n self.generic_visit(node)\n self.current_depth -= 1\n\n visit_If = _visit_nested\n visit_While = _visit_nested\n visit_For = _visit_nested\n visit_With = _visit_nested\n visit_Try = _visit_nested\n\n\ndef calculate_complexity(func_node: ast.FunctionDef) -> int:\n \"\"\"Calculate cyclomatic complexity for a function.\"\"\"\n analyzer = ComplexityAnalyzer()\n analyzer.visit(func_node)\n return analyzer.complexity\n\n\ndef calculate_max_nesting(func_node: ast.FunctionDef) -> int:\n \"\"\"Calculate maximum nesting depth for a function.\"\"\"\n analyzer = NestingAnalyzer()\n analyzer.visit(func_node)\n return analyzer.max_depth\n\n\ndef calculate_function_length(func_node: ast.FunctionDef) -> int:\n \"\"\"Calculate lines of code for a function (excluding docstring).\"\"\"\n # Get total lines\n if hasattr(func_node, 'end_lineno') and func_node.end_lineno:\n total_lines = func_node.end_lineno - func_node.lineno + 1\n else:\n # Fallback for older Python versions\n total_lines = 1\n\n # Subtract docstring lines if present\n if (ast.get_docstring(func_node) and\n func_node.body and\n isinstance(func_node.body[0], ast.Expr) and\n isinstance(func_node.body[0].value, (ast.Str, ast.Constant))):\n docstring_node = func_node.body[0]\n if hasattr(docstring_node, 'end_lineno'):\n docstring_lines = docstring_node.end_lineno - docstring_node.lineno + 1\n total_lines -= docstring_lines\n\n return max(1, total_lines)\n\n\ndef analyze_file(file_path: Path) -> FileMetrics:\n \"\"\"Analyze a Python file and return metrics.\"\"\"\n with open(file_path, 'r', encoding='utf-8') as f:\n source = f.read()\n\n try:\n tree = ast.parse(source, filename=str(file_path))\n except SyntaxError as e:\n print(f\"Error parsing {file_path}: {e}\", file=sys.stderr)\n sys.exit(1)\n\n functions: List[FunctionMetrics] = []\n\n # Find all function definitions\n for node in ast.walk(tree):\n if isinstance(node, ast.FunctionDef):\n metrics = FunctionMetrics(\n name=node.name,\n line_number=node.lineno,\n complexity=calculate_complexity(node),\n length=calculate_function_length(node),\n max_nesting=calculate_max_nesting(node),\n num_parameters=len(node.args.args)\n )\n functions.append(metrics)\n\n # Calculate overall statistics\n if functions:\n avg_complexity = sum(f.complexity for f in functions) / len(functions)\n max_complexity = max(f.complexity for f in functions)\n avg_length = sum(f.length for f in functions) / len(functions)\n max_length = max(f.length for f in functions)\n avg_nesting = sum(f.max_nesting for f in functions) / len(functions)\n max_nesting = max(f.max_nesting for f in functions)\n else:\n avg_complexity = max_complexity = 0\n avg_length = max_length = 0\n avg_nesting = max_nesting = 0\n\n return FileMetrics(\n file_path=str(file_path),\n total_functions=len(functions),\n avg_complexity=round(avg_complexity, 2),\n max_complexity=max_complexity,\n avg_length=round(avg_length, 2),\n max_length=max_length,\n avg_nesting=round(avg_nesting, 2),\n max_nesting=max_nesting,\n functions=functions\n )\n\n\ndef print_metrics(metrics: FileMetrics, verbose: bool = True):\n \"\"\"Print metrics in human-readable format.\"\"\"\n print(f\"\\n{'='*70}\")\n print(f\"Complexity Metrics: {metrics.file_path}\")\n print(f\"{'='*70}\\n\")\n\n print(f\"Overall Statistics:\")\n print(f\" Total Functions: {metrics.total_functions}\")\n print(f\" Avg Complexity: {metrics.avg_complexity} (target: \u003c10, warning: 15+)\")\n print(f\" Max Complexity: {metrics.max_complexity}\")\n print(f\" Avg Length: {metrics.avg_length} lines (target: \u003c30, warning: 50+)\")\n print(f\" Max Length: {metrics.max_length} lines\")\n print(f\" Avg Nesting: {metrics.avg_nesting} levels (target: ≤3)\")\n print(f\" Max Nesting: {metrics.max_nesting} levels\")\n\n # Flag problematic metrics\n issues = []\n if metrics.avg_complexity > 10:\n issues.append(f\" ⚠ Average complexity is {metrics.avg_complexity} (target: \u003c10)\")\n if metrics.max_complexity > 15:\n issues.append(f\" ⚠ Maximum complexity is {metrics.max_complexity} (warning: 15+)\")\n if metrics.avg_length > 30:\n issues.append(f\" ⚠ Average function length is {metrics.avg_length} lines (target: \u003c30)\")\n if metrics.max_nesting > 3:\n issues.append(f\" ⚠ Maximum nesting is {metrics.max_nesting} levels (target: ≤3)\")\n\n if issues:\n print(f\"\\nIssues Found:\")\n for issue in issues:\n print(issue)\n else:\n print(f\"\\n✓ All metrics within target ranges\")\n\n if verbose and metrics.functions:\n print(f\"\\nPer-Function Breakdown:\")\n print(f\"{'─'*70}\")\n\n # Sort by complexity (descending) to show worst offenders first\n sorted_funcs = sorted(metrics.functions, key=lambda f: f.complexity, reverse=True)\n\n for func in sorted_funcs[:10]: # Show top 10 most complex\n warnings = []\n if func.complexity > 15:\n warnings.append(\"HIGH COMPLEXITY\")\n elif func.complexity > 10:\n warnings.append(\"complexity warning\")\n if func.length > 50:\n warnings.append(\"VERY LONG\")\n elif func.length > 30:\n warnings.append(\"long\")\n if func.max_nesting > 3:\n warnings.append(f\"nesting: {func.max_nesting}\")\n\n warning_str = f\" [{', '.join(warnings)}]\" if warnings else \"\"\n\n print(f\" {func.name}:{func.line_number}\")\n print(f\" Complexity: {func.complexity}, Length: {func.length} lines, \"\n f\"Nesting: {func.max_nesting}, Params: {func.num_parameters}{warning_str}\")\n\n if len(sorted_funcs) > 10:\n print(f\" ... and {len(sorted_funcs) - 10} more functions\")\n\n\ndef main():\n parser = argparse.ArgumentParser(\n description=\"Measure code complexity metrics for refactoring validation\"\n )\n parser.add_argument(\"file_path\", type=Path, help=\"Path to Python file to analyze\")\n parser.add_argument(\"--json\", action=\"store_true\", help=\"Output JSON format\")\n parser.add_argument(\"--quiet\", action=\"store_true\", help=\"Minimal output\")\n\n args = parser.parse_args()\n\n if not args.file_path.exists():\n print(f\"Error: File not found: {args.file_path}\", file=sys.stderr)\n sys.exit(1)\n\n if not args.file_path.suffix == '.py':\n print(f\"Error: File must be a Python file (.py)\", file=sys.stderr)\n sys.exit(1)\n\n metrics = analyze_file(args.file_path)\n\n if args.json:\n print(json.dumps(metrics.to_dict(), indent=2))\n else:\n print_metrics(metrics, verbose=not args.quiet)\n\n\nif __name__ == \"__main__\":\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":9566,"content_sha256":"7aa0f67ac67d115547906cae8bd1f67df4ab4913d5d3a6f4e126e8c6560e9a46"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"Python Refactor","type":"text"}]},{"type":"paragraph","content":[{"text":"Transform complex Python into clear, maintainable code while preserving correctness. Phased workflow with safety-by-design and continuous validation. For deep references (anti-patterns, OOP principles, cognitive complexity, regression prevention), see the ","type":"text"},{"text":"references/","type":"text","marks":[{"type":"code_inline"}]},{"text":" directory.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"When to invoke","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Explicit \"human\", \"readable\", \"maintainable\", \"clean\", or \"refactor\" request","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Code review flags comprehension or maintainability issues","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Legacy code modernization","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Onboarding / educational contexts","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Complexity metrics exceed thresholds","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Red flags","type":"text","marks":[{"type":"strong"}]},{"text":": file > 500 lines with scattered functions and global state, multiple ","type":"text"},{"text":"global","type":"text","marks":[{"type":"code_inline"}]},{"text":" statements, no clear module/class organization, configuration mixed with business logic","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Do NOT invoke when","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Code is performance-critical and profiling shows perf optimization is needed first","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Code is scheduled for deletion or replacement","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"External dependencies require upstream contributions instead","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"User explicitly requested perf optimization over readability","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Core principles (priority order)","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Prefer structured OOP for complex code","type":"text","marks":[{"type":"strong"}]},{"text":" -- shared state, multiple concerns, scattered globals = restructure into classes/modules. (But: simple modules with pure functions, click/argparse CLIs, and functional pipelines DON'T need to be forced into classes.)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Clarity over cleverness","type":"text","marks":[{"type":"strong"}]},{"text":" -- explicit beats implicit","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Preserve correctness","type":"text","marks":[{"type":"strong"}]},{"text":" -- all tests pass, behavior identical","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Single Responsibility","type":"text","marks":[{"type":"strong"}]},{"text":" -- one thing per class/function (SOLID)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Self-documenting structure","type":"text","marks":[{"type":"strong"}]},{"text":" -- code = what, comments = why","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Progressive disclosure","type":"text","marks":[{"type":"strong"}]},{"text":" -- reveal complexity in layers","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Reasonable performance","type":"text","marks":[{"type":"strong"}]},{"text":" -- never sacrifice >2× without explicit approval","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Hard constraints","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"SAFETY BY DESIGN","type":"text","marks":[{"type":"strong"}]},{"text":" -- mandatory migration checklists for destructive changes. CREATE → SEARCH → MIGRATE → VERIFY → only then REMOVE. NEVER remove before 100% migration verified.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"STATIC ANALYSIS FIRST","type":"text","marks":[{"type":"strong"}]},{"text":" -- ","type":"text"},{"text":"flake8 --select=F821,E0602","type":"text","marks":[{"type":"code_inline"}]},{"text":" (or ","type":"text"},{"text":"ruff check --select=F821","type":"text","marks":[{"type":"code_inline"}]},{"text":") BEFORE tests. Catches NameErrors immediately.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"PRESERVE BEHAVIOR","type":"text","marks":[{"type":"strong"}]},{"text":" -- all existing tests pass after.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"NO PERF REGRESSION","type":"text","marks":[{"type":"strong"}]},{"text":" -- never degrade > 2× without explicit approval.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"NO API CHANGES","type":"text","marks":[{"type":"strong"}]},{"text":" -- public APIs unchanged unless explicitly requested + documented.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"NO OVER-ENGINEERING","type":"text","marks":[{"type":"strong"}]},{"text":" -- simple stays simple.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"NO MAGIC","type":"text","marks":[{"type":"strong"}]},{"text":" -- no framework magic, no metaprogramming unless absolutely necessary.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"VALIDATE CONTINUOUSLY","type":"text","marks":[{"type":"strong"}]},{"text":" -- static analysis + tests after each logical change.","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Regression prevention (MANDATORY)","type":"text"}]},{"type":"paragraph","content":[{"text":"Refactoring must NEVER introduce regressions.","type":"text","marks":[{"type":"strong"}]},{"text":" Read ","type":"text"},{"text":"references/REGRESSION_PREVENTION.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" before any session.","type":"text"}]},{"type":"paragraph","content":[{"text":"Before each session:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Test suite passes 100%","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Coverage ≥ 80% on target code (write tests FIRST if not)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Golden outputs captured for critical edge cases","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Static analysis baseline saved","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"After EACH micro-change (not at the end -- every single one):","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"flake8 --select=F821,E999","type":"text","marks":[{"type":"code_inline"}]},{"text":" → 0 errors","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"pytest -x","type":"text","marks":[{"type":"code_inline"}]},{"text":" → all passing","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Spot check 1 edge case for unchanged behavior","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"If ANY check fails: ","type":"text"},{"text":"STOP → REVERT → ANALYZE → FIX APPROACH → RETRY","type":"text","marks":[{"type":"strong"}]},{"text":".","type":"text"}]},{"type":"paragraph","content":[{"text":"ANY REGRESSION = TOTAL FAILURE.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Workflow (4 phases)","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Phase 1: Analysis","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Read the entire codebase section.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Identify readability issues using ","type":"text"},{"text":"references/anti-patterns.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" (script-like/global-state, God Objects, nested conditionals, long functions, magic numbers, cryptic names).","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Assess architecture against ","type":"text"},{"text":"references/oop_principles.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" (proper classes/modules, encapsulated state, separated responsibilities, SOLID, DI vs hard-coded deps).","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Measure current metrics with ","type":"text"},{"text":"scripts/measure_complexity.py","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"scripts/analyze_multi_metrics.py","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Run linting analysis (see Tooling below).","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Check test coverage; identify gaps to fill BEFORE refactoring.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Document with ","type":"text"},{"text":"assets/templates/analysis_template.md","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Output","type":"text","marks":[{"type":"strong"}]},{"text":": prioritized list of issues by impact and risk.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Phase 2: Planning","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Classify each change","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Non-destructive","type":"text","marks":[{"type":"strong"}]},{"text":" (rename, docs, type hints) → low risk","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Destructive","type":"text","marks":[{"type":"strong"}]},{"text":" (remove globals, delete functions, replace APIs) → high risk","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"For DESTRUCTIVE changes -- migration plan is MANDATORY","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Search ALL usages of each element to be removed","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Document every usage (file, line, type)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"No complete migration plan = cannot proceed with the destructive change","type":"text","marks":[{"type":"strong"}]}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Risk assessment per change (Low/Medium/High)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Dependency map -- what depends on this code?","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Test strategy -- what tests are needed? what might break?","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Order changes safest → riskiest","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Document expected metric improvements","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Output","type":"text","marks":[{"type":"strong"}]},{"text":": refactoring plan, sequenced changes, migration plans, test strategy, rollback plan.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Phase 3: Execution","type":"text"}]},{"type":"heading","attrs":{"level":4},"content":[{"text":"Non-destructive (safe anytime)","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Rename for clarity","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Extract magic numbers/strings to named constants","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Add/improve docs and type hints","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Add guard clauses to reduce nesting","type":"text"}]}]}]},{"type":"heading","attrs":{"level":4},"content":[{"text":"Destructive (STRICT PROTOCOL)","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"CREATE","type":"text","marks":[{"type":"strong"}]},{"text":" new structure (no removal) -- write new classes/functions + tests","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"SEARCH","type":"text","marks":[{"type":"strong"}]},{"text":" for ALL usages of the element being removed","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"CREATE","type":"text","marks":[{"type":"strong"}]},{"text":" migration checklist documenting every found usage","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"MIGRATE","type":"text","marks":[{"type":"strong"}]},{"text":" one usage at a time, checking off the list, running static analysis + tests after each","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"VERIFY","type":"text","marks":[{"type":"strong"}]},{"text":" complete migration -- re-run searches, should find zero old references","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"REMOVE","type":"text","marks":[{"type":"strong"}]},{"text":" old code only after 100% migration verified","type":"text"}]}]}]},{"type":"heading","attrs":{"level":4},"content":[{"text":"Execution rules","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"NEVER skip the migration checklist for destructive changes","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Run static analysis BEFORE tests","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"One pattern at a time -- never mix multiple refactoring patterns in a single change","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Atomic commits -- each migration step gets its own commit","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Stop on ANY error (static analysis OR test failure) → immediate fix/revert","type":"text"}]}]}]},{"type":"heading","attrs":{"level":4},"content":[{"text":"Recommended order","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Transform script-like code to proper architecture (","type":"text"},{"text":"references/examples/script_to_oop_transformation.md","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Rename for clarity","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Extract magic numbers/strings to constants/enums","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Improve docs + type hints","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Extract methods to reduce function length","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Simplify conditionals with guard clauses","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Reduce nesting depth","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Final review: separation of concerns","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Phase 4: Validation","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Static analysis FIRST","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"flake8 \u003cfile> --select=F821,E0602 # undefined names/variables -- MUST be 0\nflake8 \u003cfile> --select=F401 # unused imports\nflake8 \u003cfile> # full quality check","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Full test suite","type":"text","marks":[{"type":"strong"}]},{"text":" → 100% pass required.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Architecture validation","type":"text","marks":[{"type":"strong"}]},{"text":": global state eliminated/encapsulated, proper modules/classes, separated responsibilities, SOLID compliance.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Before/after metrics","type":"text","marks":[{"type":"strong"}]},{"text":" with ","type":"text"},{"text":"scripts/measure_complexity.py","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"scripts/analyze_multi_metrics.py","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Performance regression check","type":"text","marks":[{"type":"strong"}]},{"text":" with ","type":"text"},{"text":"scripts/benchmark_changes.py","type":"text","marks":[{"type":"code_inline"}]},{"text":" for hot paths.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Summary report","type":"text","marks":[{"type":"strong"}]},{"text":" using ","type":"text"},{"text":"assets/templates/summary_template.md","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Flag for human review","type":"text","marks":[{"type":"strong"}]},{"text":" if: perf degraded > 10%, public API signatures changed, test coverage decreased, significant architectural changes.","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Refactoring patterns (catalog summary)","type":"text"}]},{"type":"paragraph","content":[{"text":"Full catalog with examples in ","type":"text"},{"text":"references/patterns.md","type":"text","marks":[{"type":"code_inline"}]},{"text":". Key patterns:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Guard Clauses","type":"text","marks":[{"type":"strong"}]},{"text":" -- early returns instead of nested conditionals","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Extract Method","type":"text","marks":[{"type":"strong"}]},{"text":" -- split large functions into focused units (resets the nesting counter -- most powerful for cognitive complexity)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Dictionary Dispatch","type":"text","marks":[{"type":"strong"}]},{"text":" -- replace if-elif chains with lookup tables","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Match Statement","type":"text","marks":[{"type":"strong"}]},{"text":" (Py 3.10+) -- counts as +1 total, not per branch","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Named Boolean Conditions","type":"text","marks":[{"type":"strong"}]},{"text":" -- extract complex booleans into named variables","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Encapsulate Global State","type":"text","marks":[{"type":"strong"}]},{"text":" -- move globals into classes with proper encapsulation","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Group Related Functions","type":"text","marks":[{"type":"strong"}]},{"text":" -- organize scattered functions into classes by responsibility","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Create Domain Models","type":"text","marks":[{"type":"strong"}]},{"text":" -- replace primitive dicts with dataclasses + enums","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Apply Dependency Injection","type":"text","marks":[{"type":"strong"}]},{"text":" -- replace hard-coded deps with injected ones","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"For cognitive complexity calculation rules and reduction strategies, see ","type":"text"},{"text":"references/cognitive_complexity_guide.md","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Naming conventions","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Variables","type":"text","marks":[{"type":"strong"}]},{"text":": descriptive, booleans as ","type":"text"},{"text":"is_active","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"has_permission","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"can_edit","type":"text","marks":[{"type":"code_inline"}]},{"text":", collections as plurals","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Functions","type":"text","marks":[{"type":"strong"}]},{"text":": verb + object (","type":"text"},{"text":"calculate_total","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"validate_email","type":"text","marks":[{"type":"code_inline"}]},{"text":"); boolean queries as ","type":"text"},{"text":"is_valid()","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"has_items()","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Constants","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"UPPERCASE_WITH_UNDERSCORES","type":"text","marks":[{"type":"code_inline"}]},{"text":"; replace magic numbers/strings","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Classes","type":"text","marks":[{"type":"strong"}]},{"text":": PascalCase nouns (","type":"text"},{"text":"UserAccount","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"PaymentProcessor","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Anti-patterns to fix (priority order)","type":"text"}]},{"type":"paragraph","content":[{"text":"Full catalog: ","type":"text"},{"text":"references/anti-patterns.md","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Critical","type":"text","marks":[{"type":"strong"}]},{"text":": script-like / procedural code with global state; God Object / God Class","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"High","type":"text","marks":[{"type":"strong"}]},{"text":": complex nested conditionals (> 3 levels), long functions (> 30 lines), magic numbers, cryptic names, missing type hints, missing docstrings","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Medium","type":"text","marks":[{"type":"strong"}]},{"text":": duplicate code, primitive obsession, long parameter lists (> 5)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Low","type":"text","marks":[{"type":"strong"}]},{"text":": inconsistent naming, redundant comments, unused imports","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Tooling","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Primary stack: Ruff + Complexipy (recommended for new projects)","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"uv tool install ruff complexipy radon wily\n\nruff check src/ # fast linting (Rust, replaces flake8+plugins)\ncomplexipy src/ --max-complexity-allowed 15 # cognitive complexity (Rust)\nradon mi src/ -s # maintainability index","type":"text"}]},{"type":"paragraph","content":[{"text":"Full configuration (pyproject.toml, pre-commit, GitHub Actions): ","type":"text"},{"text":"references/cognitive_complexity_guide.md","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Alternative: flake8 + curated plugins","type":"text"}]},{"type":"paragraph","content":[{"text":"For projects already on flake8, see ","type":"text"},{"text":"references/flake8_plugins_guide.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" (curated 16-plugin selector list).","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Multi-metric analysis","type":"text"}]},{"type":"paragraph","content":[{"text":"scripts/analyze_multi_metrics.py","type":"text","marks":[{"type":"code_inline"}]},{"text":" combines complexipy + radon + maintainability index in a single report.","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Metric","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Tool","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Use","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Cognitive complexity","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"complexipy","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Human comprehension","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Cyclomatic complexity","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"ruff","type":"text","marks":[{"type":"strong"}]},{"text":" (C901), radon","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Test planning","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Maintainability index","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"radon","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Overall code health","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Metric targets","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Cyclomatic complexity: \u003c 10 per function (warning 15, error 20)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Cognitive complexity: \u003c 15 per function (SonarQube default; warning 20)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Function length: \u003c 30 lines (warning 50)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Nesting depth: ≤ 3 levels","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Docstring coverage: > 80% for public functions","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Type-hint coverage: > 90% for public APIs","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Historical tracking with Wily","type":"text"}]},{"type":"paragraph","content":[{"text":"Trends matter, not just thresholds. Setup + CI integration: ","type":"text"},{"text":"references/cognitive_complexity_guide.md","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Common refactoring mistakes","type":"text"}]},{"type":"paragraph","content":[{"text":"Full guide: ","type":"text"},{"text":"references/REGRESSION_PREVENTION.md","type":"text","marks":[{"type":"code_inline"}]},{"text":". Key traps:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Incomplete migration","type":"text","marks":[{"type":"strong"}]},{"text":" -- removing old code before ALL usages migrated (causes NameErrors).","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Partial pattern application","type":"text","marks":[{"type":"strong"}]},{"text":" -- applying refactoring to some functions but not others.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Breaking public APIs","type":"text","marks":[{"type":"strong"}]},{"text":" -- changing signatures used by external code.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Assuming tests cover everything","type":"text","marks":[{"type":"strong"}]},{"text":" -- tests pass but runtime errors occur (run static analysis!).","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"When to reach for which tool","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"clean-code","type":"text","marks":[{"type":"strong"}]},{"text":" (cross-language plugin) -- multi-language cosmetic cleanup; renames local vars, improves comments, simplifies structure. Lowest regression risk. Use for \"make this readable\", \"clean up naming.\"","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"python-refactor","type":"text","marks":[{"type":"strong"}]},{"text":" (this skill) -- Python-only deep restructuring. OOP transformation, SOLID, complexity metrics, migration checklists, benchmark validation. Use for \"refactor this module\", \"reduce complexity\", \"transform to OOP.\"","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Escalation path","type":"text","marks":[{"type":"strong"}]},{"text":": clean-code → python-refactor (safest to most thorough).","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Integration","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"python-tdd","type":"text","marks":[{"type":"strong"}]},{"text":" -- set up tests before refactoring, validate coverage after","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"python-performance-optimization","type":"text","marks":[{"type":"strong"}]},{"text":" -- deep profiling before/after","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"python-packaging","type":"text","marks":[{"type":"strong"}]},{"text":" -- handle pyproject.toml + distribution if refactoring a library","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"uv-package-manager","type":"text","marks":[{"type":"strong"}]},{"text":" -- ","type":"text"},{"text":"uv run ruff","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"uv run complexipy","type":"text","marks":[{"type":"code_inline"}]},{"text":" for tool execution","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"async-python-patterns","type":"text","marks":[{"type":"strong"}]},{"text":" -- reference async patterns when refactoring async code","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"When NOT to refactor","type":"text"}]},{"type":"paragraph","content":[{"text":"Perf-critical optimized code (profile first), code scheduled for deletion, external deps (contribute upstream), stable legacy code nobody needs to modify.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Limitations","type":"text"}]},{"type":"paragraph","content":[{"text":"Cannot improve algorithmic complexity (that's an algorithm change). Cannot add domain knowledge not in code/comments. Cannot guarantee correctness without tests. Style preferences vary -- adjust to team conventions.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Examples","type":"text"}]},{"type":"paragraph","content":[{"text":"references/examples/","type":"text","marks":[{"type":"code_inline"}]},{"text":":","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"script_to_oop_transformation.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" -- script → clean OOP architecture (flagship case study)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"python_complexity_reduction.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" -- nested conditionals and long functions","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"typescript_naming_improvements.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" -- naming patterns (cross-language reference)","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Success criteria","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Zero regressions","type":"text","marks":[{"type":"strong"}]},{"text":" -- all tests pass, behavior unchanged","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Golden master match for documented critical cases","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Complexity metrics improved (documented in summary)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"No perf regression > 10% (or explicit approval)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Documentation coverage improved","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Code easier for humans to understand","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"No new security vulnerabilities","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Atomic, well-documented git history","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Wily trend -- complexity not increased vs previous commit","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Static analysis shows improvement","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}}]},"metadata":{"date":"2026-06-05","name":"python-refactor","author":"@skillopedia","source":{"stars":4,"repo_name":"alfio-claude-plugins","origin_url":"https://github.com/acaprino/alfio-claude-plugins/blob/HEAD/plugins/python-development/skills/python-refactor/SKILL.md","repo_owner":"acaprino","body_sha256":"619368e921fdbdd54d1a6bbac71737c43b52e89dd375dfc1bca9a3f971b094b6","cluster_key":"0158650ae1a75ce083397cb6d111dd220fbaf10fcad7e85a925f638bc040c149","clean_bundle":{"format":"clean-skill-bundle-v1","source":"acaprino/alfio-claude-plugins/plugins/python-development/skills/python-refactor/SKILL.md","attachments":[{"id":"842b7203-2ea1-5fc5-b001-7354849a06d4","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/842b7203-2ea1-5fc5-b001-7354849a06d4/attachment.toml","path":"assets/pyproject.toml","size":3984,"sha256":"bca95a9b2ce4f5f6c9ee358e2100862823f30892acb12ab5477c4db5710ee1cf","contentType":"text/plain; charset=utf-8"},{"id":"27df3d08-4199-58ea-8a5e-d57353c6ab13","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/27df3d08-4199-58ea-8a5e-d57353c6ab13/attachment.md","path":"assets/templates/analysis_template.md","size":5831,"sha256":"d1f92067dfcae6f5fe33c82c5f57921e06749c57716ee59c21f0ca8dcff054d9","contentType":"text/markdown; charset=utf-8"},{"id":"ea1b1a58-fcee-5d1c-b456-f6cc83131f9a","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/ea1b1a58-fcee-5d1c-b456-f6cc83131f9a/attachment.md","path":"assets/templates/flake8_report_template.md","size":10976,"sha256":"2d2a567d8c719ff6780d867a90d347fcc74e3157f7e1334c2dbb19788620f02d","contentType":"text/markdown; charset=utf-8"},{"id":"91855880-2284-5a60-aec2-bd7a722adfe6","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/91855880-2284-5a60-aec2-bd7a722adfe6/attachment.md","path":"assets/templates/summary_template.md","size":6004,"sha256":"10a858f67d75d5fc27e48ceca6ea4755b095f80b13d693d3591208a4b3580016","contentType":"text/markdown; charset=utf-8"},{"id":"95ae0d77-f3dc-56e3-a2a4-4795267e2465","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/95ae0d77-f3dc-56e3-a2a4-4795267e2465/attachment.md","path":"references/REGRESSION_PREVENTION.md","size":14891,"sha256":"25e9c7b3dae8d4d6a000deb465a8c015f4a5a5b54f32c8997693bcfde8f2a208","contentType":"text/markdown; charset=utf-8"},{"id":"84bbbe39-6cb4-548c-bc17-a37125a9d6e7","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/84bbbe39-6cb4-548c-bc17-a37125a9d6e7/attachment.md","path":"references/anti-patterns.md","size":20820,"sha256":"a9c000ca0a9a84bd8fc6f09812f7768d87488873b2dbd55bfa49da2cfe9409d8","contentType":"text/markdown; charset=utf-8"},{"id":"cb6185df-abc7-578b-b777-772dddb15cec","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/cb6185df-abc7-578b-b777-772dddb15cec/attachment.md","path":"references/cognitive_complexity_guide.md","size":16753,"sha256":"ff2555830b4776dc78015104aa12c531aaad2a922ab458341050f909a6a53d11","contentType":"text/markdown; charset=utf-8"},{"id":"336641f2-f253-5231-adaa-b81b22a4f25d","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/336641f2-f253-5231-adaa-b81b22a4f25d/attachment.md","path":"references/examples/python_complexity_reduction.md","size":12007,"sha256":"43791d3f3f41722a631abb8763f7cf7255d2fccf3ef3d836045c566efc9511b8","contentType":"text/markdown; charset=utf-8"},{"id":"646fff33-210a-55cb-80ca-9e565bef00b8","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/646fff33-210a-55cb-80ca-9e565bef00b8/attachment.md","path":"references/examples/script_to_oop_transformation.md","size":32992,"sha256":"2d8c29b828e381771734d7bb083235e2cf2da009d185bef510bb15a373cc566a","contentType":"text/markdown; charset=utf-8"},{"id":"f2d5790e-db9a-521d-8ace-383cb9bd0824","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/f2d5790e-db9a-521d-8ace-383cb9bd0824/attachment.md","path":"references/examples/typescript_naming_improvements.md","size":11722,"sha256":"fa8a8f880006564dbd4c1717216f68107a391f20b5e7e627422cfbd09cb06d18","contentType":"text/markdown; charset=utf-8"},{"id":"99fdc82b-8a06-5bcd-93b3-d9675130dce2","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/99fdc82b-8a06-5bcd-93b3-d9675130dce2/attachment.md","path":"references/flake8_plugins_guide.md","size":7664,"sha256":"359c558f83860e9da1f479ca1787d54832cb15edd31eab73fdb6853e4c99c568","contentType":"text/markdown; charset=utf-8"},{"id":"cdd8d989-f3f1-5a32-b6bf-68030eac8e87","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/cdd8d989-f3f1-5a32-b6bf-68030eac8e87/attachment.md","path":"references/oop_principles.md","size":21394,"sha256":"d0b54b4c8e11d7ffad884718c791cef1cb41a8e2d309c2b5a0e20fba48a93403","contentType":"text/markdown; charset=utf-8"},{"id":"01340e81-00de-5d25-a83f-2cec0c8de518","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/01340e81-00de-5d25-a83f-2cec0c8de518/attachment.md","path":"references/patterns.md","size":25984,"sha256":"c5ec2582e874117d979dbfe71031564fff16befdc76f6f518dd6d5686894953a","contentType":"text/markdown; charset=utf-8"},{"id":"f4f22216-2835-5d52-aa48-24a5eafea8f0","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/f4f22216-2835-5d52-aa48-24a5eafea8f0/attachment.py","path":"scripts/analyze_multi_metrics.py","size":16879,"sha256":"494aec33fd7799b952175d68b5e1d088db8f495683244140720b3678328ccc5d","contentType":"text/x-python; charset=utf-8"},{"id":"ceafb63c-8efc-5bbb-af6d-ffacc785c5ec","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/ceafb63c-8efc-5bbb-af6d-ffacc785c5ec/attachment.py","path":"scripts/analyze_with_flake8.py","size":25925,"sha256":"8ee83a219b6ea76e2d20a9fefa5fe50d92bbcda8dd958d0c0b9e942d7a43fb5c","contentType":"text/x-python; charset=utf-8"},{"id":"7752bc10-661d-568b-84a2-3514f39cc911","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/7752bc10-661d-568b-84a2-3514f39cc911/attachment.py","path":"scripts/benchmark_changes.py","size":10661,"sha256":"e9fb5976d413a7b61babbe99b43f270e2f90600be05ef19edff1547ac245ed6d","contentType":"text/x-python; charset=utf-8"},{"id":"1c3c786a-61ba-5df7-b511-a0b86bb64718","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/1c3c786a-61ba-5df7-b511-a0b86bb64718/attachment.py","path":"scripts/check_documentation.py","size":12342,"sha256":"eb30d3322c9823362019a90ef9c3c4cbf148821efd69f5aefc2bd557290a7d7a","contentType":"text/x-python; charset=utf-8"},{"id":"90c4a58c-b3ad-54fe-b7f4-38d2c29e3880","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/90c4a58c-b3ad-54fe-b7f4-38d2c29e3880/attachment.py","path":"scripts/compare_flake8_reports.py","size":20061,"sha256":"f059bdd6ecb81b152d1fd1acdd72c532460f0599cb4be4f34362d6cf84ee6d67","contentType":"text/x-python; charset=utf-8"},{"id":"865bd46a-7dde-50e2-93bb-f1ccab7ef887","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/865bd46a-7dde-50e2-93bb-f1ccab7ef887/attachment.py","path":"scripts/compare_metrics.py","size":9056,"sha256":"f9438182fc365b754911c6a48eed1ac8558979d648708522fb63674f5a1bf7a5","contentType":"text/x-python; charset=utf-8"},{"id":"b5c4b732-80c6-597f-a64c-a57e71355c74","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/b5c4b732-80c6-597f-a64c-a57e71355c74/attachment.py","path":"scripts/measure_complexity.py","size":9566,"sha256":"7aa0f67ac67d115547906cae8bd1f67df4ab4913d5d3a6f4e126e8c6560e9a46","contentType":"text/x-python; charset=utf-8"}],"bundle_sha256":"6247d3f5bf78596a9a0c05135517ca0c80d0a336642803dfac7c66c75e82f6f9","attachment_count":20,"text_attachments":20,"attachment_storage":"skillopedia-attachments-v1","binary_attachments":0,"excluded_attachments":[]},"cluster_size":1,"skill_md_path":"plugins/python-development/skills/python-refactor/SKILL.md","import_metadata":{"date":"2026-06-05","author":"@skillopedia","version":"v1","category":"web-development","category_label":"Web"},"exact_dupes_collapsed_into_this":0},"version":"v1","category":"web-development","import_tag":"clean-skills-v1","description":"Systematic code refactoring skill that transforms complex, hard-to-understand code into clear, well-documented, maintainable code while preserving correctness. Applies structured refactoring patterns with validation. TRIGGER WHEN: users request \"readable\", \"maintainable\", or \"clean\" code, during code reviews flagging comprehension issues, for legacy code modernization, or in educational/onboarding contexts DO NOT TRIGGER WHEN: the task is outside the specific scope of this component.\n"}},"renderedAt":1782979246945}

Python Refactor Transform complex Python into clear, maintainable code while preserving correctness. Phased workflow with safety-by-design and continuous validation. For deep references (anti-patterns, OOP principles, cognitive complexity, regression prevention), see the directory. When to invoke - Explicit "human", "readable", "maintainable", "clean", or "refactor" request - Code review flags comprehension or maintainability issues - Legacy code modernization - Onboarding / educational contexts - Complexity metrics exceed thresholds - Red flags : file 500 lines with scattered functions and g…