chore: import zh skill code-mentor

This commit is contained in:
wehub-skill-sync
2026-07-13 21:36:32 +08:00
commit 6eb137bec1
16 changed files with 6163 additions and 0 deletions
+379
View File
@@ -0,0 +1,379 @@
#!/usr/bin/env python3
"""
Code Analyzer - Static analysis tool for code review
Analyzes code for:
- Bugs and potential errors
- Style violations
- Complexity metrics
- Security issues
- Best practice violations
Supports: Python, JavaScript, Java, C++
Usage:
python analyze_code.py <file_path>
python analyze_code.py <file_path> --format json
"""
import argparse
import ast
import json
import os
import re
import sys
from pathlib import Path
from typing import List, Dict, Any
class CodeIssue:
"""Represents a code issue found during analysis."""
def __init__(self, category, severity, line, message, suggestion=None):
self.category = category # bug, style, performance, security
self.severity = severity # critical, warning, info
self.line = line
self.message = message
self.suggestion = suggestion
def to_dict(self):
return {
'category': self.category,
'severity': self.severity,
'line': self.line,
'message': self.message,
'suggestion': self.suggestion
}
class PythonAnalyzer:
"""Analyzer for Python code."""
def __init__(self, code, filename):
self.code = code
self.filename = filename
self.lines = code.split('\n')
self.issues = []
def analyze(self) -> List[CodeIssue]:
"""Run all analysis checks."""
try:
tree = ast.parse(self.code)
self._check_syntax(tree)
except SyntaxError as e:
self.issues.append(CodeIssue(
'bug', 'critical', e.lineno,
f"Syntax error: {e.msg}"
))
return self.issues
self._check_style()
self._check_complexity()
self._check_best_practices()
self._check_security()
return self.issues
def _check_syntax(self, tree):
"""Check for common syntax and logic issues."""
for node in ast.walk(tree):
# Check for bare except
if isinstance(node, ast.ExceptHandler):
if node.type is None:
self.issues.append(CodeIssue(
'style', 'warning', node.lineno,
"Bare except: clause catches all exceptions",
"Use specific exception types (e.g., except ValueError:)"
))
# Check for mutable default arguments
if isinstance(node, ast.FunctionDef):
for default in node.args.defaults:
if isinstance(default, (ast.List, ast.Dict, ast.Set)):
self.issues.append(CodeIssue(
'bug', 'warning', node.lineno,
f"Mutable default argument in function '{node.name}'",
"Use None as default and create mutable object inside function"
))
def _check_style(self):
"""Check PEP 8 style guidelines."""
for i, line in enumerate(self.lines, 1):
# Line too long
if len(line) > 100:
self.issues.append(CodeIssue(
'style', 'info', i,
f"Line too long ({len(line)} > 100 characters)"
))
# Multiple statements on one line
if ';' in line and not line.strip().startswith('#'):
self.issues.append(CodeIssue(
'style', 'info', i,
"Multiple statements on one line (use semicolon)",
"Place each statement on its own line"
))
# Trailing whitespace
if line.endswith(' ') or line.endswith('\t'):
self.issues.append(CodeIssue(
'style', 'info', i,
"Trailing whitespace"
))
def _check_complexity(self):
"""Check for complexity issues."""
try:
tree = ast.parse(self.code)
except:
return
for node in ast.walk(tree):
if isinstance(node, ast.FunctionDef):
# Count nested depth
max_depth = self._calculate_nesting_depth(node)
if max_depth > 4:
self.issues.append(CodeIssue(
'performance', 'warning', node.lineno,
f"Function '{node.name}' has deep nesting (depth {max_depth})",
"Consider extracting nested logic into separate functions"
))
# Count number of statements
statements = sum(1 for _ in ast.walk(node))
if statements > 50:
self.issues.append(CodeIssue(
'style', 'warning', node.lineno,
f"Function '{node.name}' is too long ({statements} statements)",
"Consider breaking into smaller functions"
))
def _calculate_nesting_depth(self, node, depth=0):
"""Calculate maximum nesting depth in a function."""
max_depth = depth
for child in ast.iter_child_nodes(node):
if isinstance(child, (ast.If, ast.For, ast.While, ast.With)):
child_depth = self._calculate_nesting_depth(child, depth + 1)
max_depth = max(max_depth, child_depth)
return max_depth
def _check_best_practices(self):
"""Check for violations of best practices."""
for i, line in enumerate(self.lines, 1):
# Check for print statements in production code
if re.search(r'\bprint\s*\(', line) and 'debug' not in line.lower():
self.issues.append(CodeIssue(
'style', 'info', i,
"Print statement found - consider using logging",
"Use logging module instead of print for production code"
))
# Check for == None instead of is None
if re.search(r'==\s*None|None\s*==', line):
self.issues.append(CodeIssue(
'style', 'info', i,
"Use 'is None' instead of '== None'"
))
# Check for != None instead of is not None
if re.search(r'!=\s*None|None\s*!=', line):
self.issues.append(CodeIssue(
'style', 'info', i,
"Use 'is not None' instead of '!= None'"
))
def _check_security(self):
"""Check for common security issues."""
for i, line in enumerate(self.lines, 1):
# SQL injection vulnerability
if 'execute' in line and ('+' in line or '%' in line or 'format' in line):
if 'SELECT' in line.upper() or 'INSERT' in line.upper():
self.issues.append(CodeIssue(
'security', 'critical', i,
"Potential SQL injection vulnerability",
"Use parameterized queries with placeholders"
))
# eval() usage
if re.search(r'\beval\s*\(', line):
self.issues.append(CodeIssue(
'security', 'critical', i,
"Use of eval() is dangerous",
"Avoid eval() - use ast.literal_eval() for safe evaluation"
))
# Hard-coded passwords/secrets
if re.search(r'password\s*=\s*["\']', line, re.IGNORECASE):
self.issues.append(CodeIssue(
'security', 'critical', i,
"Potential hard-coded password",
"Use environment variables or secure configuration"
))
class JavaScriptAnalyzer:
"""Basic analyzer for JavaScript code."""
def __init__(self, code, filename):
self.code = code
self.filename = filename
self.lines = code.split('\n')
self.issues = []
def analyze(self) -> List[CodeIssue]:
"""Run all analysis checks."""
self._check_style()
self._check_best_practices()
return self.issues
def _check_style(self):
"""Check style guidelines."""
for i, line in enumerate(self.lines, 1):
# var instead of let/const
if re.search(r'\bvar\s+', line):
self.issues.append(CodeIssue(
'style', 'warning', i,
"Use 'let' or 'const' instead of 'var'",
"ES6+ recommends let/const for better scoping"
))
# == instead of ===
if '==' in line and '===' not in line and '!==' not in line:
self.issues.append(CodeIssue(
'style', 'info', i,
"Use '===' instead of '==' for strict equality"
))
def _check_best_practices(self):
"""Check JavaScript best practices."""
for i, line in enumerate(self.lines, 1):
# console.log in production
if 'console.log' in line:
self.issues.append(CodeIssue(
'style', 'info', i,
"console.log found - remove before production"
))
class CodeMetrics:
"""Calculate code metrics."""
def __init__(self, code):
self.code = code
self.lines = code.split('\n')
def calculate(self) -> Dict[str, Any]:
"""Calculate various metrics."""
total_lines = len(self.lines)
code_lines = sum(1 for line in self.lines if line.strip() and not line.strip().startswith('#'))
comment_lines = sum(1 for line in self.lines if line.strip().startswith('#'))
blank_lines = total_lines - code_lines - comment_lines
return {
'total_lines': total_lines,
'code_lines': code_lines,
'comment_lines': comment_lines,
'blank_lines': blank_lines,
'comment_ratio': round(comment_lines / max(code_lines, 1), 2)
}
def detect_language(filename):
"""Detect programming language from file extension."""
ext = Path(filename).suffix.lower()
language_map = {
'.py': 'python',
'.js': 'javascript',
'.jsx': 'javascript',
'.ts': 'javascript',
'.tsx': 'javascript',
'.java': 'java',
'.cpp': 'cpp',
'.cc': 'cpp',
'.cxx': 'cpp',
'.c': 'c'
}
return language_map.get(ext, 'unknown')
def analyze_file(filepath, output_format='text'):
"""Analyze a code file."""
if not os.path.exists(filepath):
print(f"Error: File '{filepath}' not found", file=sys.stderr)
sys.exit(1)
with open(filepath, 'r', encoding='utf-8') as f:
code = f.read()
language = detect_language(filepath)
# Choose analyzer based on language
if language == 'python':
analyzer = PythonAnalyzer(code, filepath)
elif language == 'javascript':
analyzer = JavaScriptAnalyzer(code, filepath)
else:
print(f"Error: Unsupported language '{language}'", file=sys.stderr)
sys.exit(1)
# Run analysis
issues = analyzer.analyze()
# Calculate metrics
metrics = CodeMetrics(code).calculate()
# Output results
if output_format == 'json':
result = {
'file': filepath,
'language': language,
'metrics': metrics,
'issues': [issue.to_dict() for issue in issues]
}
print(json.dumps(result, indent=2))
else:
print(f"\n{'=' * 60}")
print(f"Code Analysis: {filepath}")
print(f"Language: {language}")
print(f"{'=' * 60}\n")
print("METRICS:")
print(f" Total lines: {metrics['total_lines']}")
print(f" Code lines: {metrics['code_lines']}")
print(f" Comment lines: {metrics['comment_lines']}")
print(f" Blank lines: {metrics['blank_lines']}")
print(f" Comment ratio: {metrics['comment_ratio']:.2%}\n")
if issues:
print(f"ISSUES FOUND: {len(issues)}\n")
# Group by severity
critical = [i for i in issues if i.severity == 'critical']
warnings = [i for i in issues if i.severity == 'warning']
info = [i for i in issues if i.severity == 'info']
for severity, items in [('CRITICAL', critical), ('WARNING', warnings), ('INFO', info)]:
if items:
print(f"{severity}:")
for issue in items:
print(f" Line {issue.line}: [{issue.category}] {issue.message}")
if issue.suggestion:
print(f"{issue.suggestion}")
print()
else:
print("✓ No issues found!\n")
def main():
parser = argparse.ArgumentParser(description='Analyze code for issues and metrics')
parser.add_argument('file', help='Path to code file to analyze')
parser.add_argument('--format', choices=['text', 'json'], default='text',
help='Output format (default: text)')
args = parser.parse_args()
analyze_file(args.file, args.format)
if __name__ == '__main__':
main()
+291
View File
@@ -0,0 +1,291 @@
#!/usr/bin/env python3
"""
Complexity Analyzer - Analyze time and space complexity of algorithms
Features:
- Parse code using AST
- Detect loops (nested, sequential)
- Identify recursion
- Analyze data structure operations
- Estimate Big-O complexity
- Suggest optimizations
Usage:
python complexity_analyzer.py <file_path> [--function <function_name>]
"""
import argparse
import ast
import json
import os
import sys
from typing import Dict, List, Tuple
class ComplexityAnalyzer(ast.NodeVisitor):
"""Analyze time and space complexity of Python code."""
def __init__(self, function_name=None):
self.function_name = function_name
self.results = {}
self.current_function = None
def visit_FunctionDef(self, node):
"""Analyze a function definition."""
# Only analyze specific function if requested
if self.function_name and node.name != self.function_name:
return
self.current_function = node.name
analysis = {
'name': node.name,
'line': node.lineno,
'time_complexity': 'O(1)',
'space_complexity': 'O(1)',
'loops': [],
'recursion': False,
'operations': [],
'suggestions': []
}
# Analyze the function body
loop_depth = self._analyze_loops(node)
has_recursion = self._check_recursion(node)
data_structure_ops = self._analyze_data_structures(node)
# Determine time complexity
if has_recursion:
analysis['recursion'] = True
recursion_type = self._classify_recursion(node)
analysis['time_complexity'] = recursion_type
analysis['suggestions'].append(
"Recursive function - consider memoization or iterative approach"
)
elif loop_depth >= 3:
analysis['time_complexity'] = f'O(n^{loop_depth})'
analysis['suggestions'].append(
f"Deep nesting ({loop_depth} levels) - consider optimization"
)
elif loop_depth == 2:
analysis['time_complexity'] = 'O(n²)'
analysis['suggestions'].append(
"Nested loop detected - can this be optimized with hash map?"
)
elif loop_depth == 1:
analysis['time_complexity'] = 'O(n)'
# Adjust for data structure operations
for op in data_structure_ops:
if op['type'] == 'sort':
if '' not in analysis['time_complexity']:
analysis['time_complexity'] = 'O(n log n)'
elif op['type'] == 'dict_lookup':
analysis['operations'].append(op)
elif op['type'] == 'list_search':
if loop_depth == 0:
analysis['time_complexity'] = 'O(n)'
# Analyze space complexity
space = self._analyze_space_complexity(node)
analysis['space_complexity'] = space
self.results[node.name] = analysis
self.generic_visit(node)
def _analyze_loops(self, node, depth=0) -> int:
"""Calculate maximum loop nesting depth."""
max_depth = depth
for child in ast.walk(node):
if isinstance(child, (ast.For, ast.While)):
# Check if this is a direct child, not in a nested function
if self._is_direct_child(node, child):
child_depth = self._analyze_loops(child, depth + 1)
max_depth = max(max_depth, child_depth)
return max_depth
def _is_direct_child(self, parent, child):
"""Check if child is a direct descendant (not in nested function)."""
for node in ast.walk(parent):
if node == child:
return True
if isinstance(node, ast.FunctionDef) and node != parent:
# Stop if we hit another function definition
return False
return False
def _check_recursion(self, node) -> bool:
"""Check if function is recursive."""
function_name = node.name
for child in ast.walk(node):
if isinstance(child, ast.Call):
if isinstance(child.func, ast.Name) and child.func.id == function_name:
return True
# Check for indirect recursion via attribute
if isinstance(child.func, ast.Attribute):
if child.func.attr == function_name:
return True
return False
def _classify_recursion(self, node) -> str:
"""Classify type of recursion for complexity estimation."""
# Count recursive calls
recursive_calls = 0
function_name = node.name
for child in ast.walk(node):
if isinstance(child, ast.Call):
if isinstance(child.func, ast.Name) and child.func.id == function_name:
recursive_calls += 1
if recursive_calls == 1:
# Linear recursion (e.g., factorial)
return 'O(n)'
elif recursive_calls == 2:
# Binary recursion (e.g., fibonacci)
return 'O(2^n)'
else:
return 'O(recursive)'
def _analyze_data_structures(self, node) -> List[Dict]:
"""Analyze data structure operations."""
operations = []
for child in ast.walk(node):
# Sorting
if isinstance(child, ast.Call):
if isinstance(child.func, ast.Attribute):
if child.func.attr == 'sort':
operations.append({'type': 'sort', 'line': child.lineno})
elif isinstance(child.func, ast.Name):
if child.func.id == 'sorted':
operations.append({'type': 'sort', 'line': child.lineno})
# Dictionary/set operations (O(1) average)
if isinstance(child, ast.Subscript):
if isinstance(child.value, (ast.Dict, ast.Set)):
operations.append({'type': 'dict_lookup', 'line': child.lineno})
# List search operations (O(n))
if isinstance(child, ast.Compare):
if any(isinstance(op, ast.In) for op in child.ops):
operations.append({'type': 'list_search', 'line': child.lineno})
return operations
def _analyze_space_complexity(self, node) -> str:
"""Estimate space complexity."""
# Check for list comprehensions, array creation
has_array_creation = False
has_recursion = self._check_recursion(node)
for child in ast.walk(node):
# List comprehension or list creation
if isinstance(child, (ast.ListComp, ast.List)):
has_array_creation = True
# Dictionary comprehension
if isinstance(child, (ast.DictComp, ast.Dict)):
has_array_creation = True
if has_recursion:
# Recursion uses call stack
return 'O(n) - call stack'
elif has_array_creation:
return 'O(n) - auxiliary space'
else:
return 'O(1)'
def format_output(results, output_format='text'):
"""Format analysis results."""
if output_format == 'json':
print(json.dumps(results, indent=2))
else:
print("\n" + "=" * 60)
print("COMPLEXITY ANALYSIS")
print("=" * 60 + "\n")
for func_name, analysis in results.items():
print(f"Function: {func_name} (line {analysis['line']})")
print(f" Time Complexity: {analysis['time_complexity']}")
print(f" Space Complexity: {analysis['space_complexity']}")
if analysis['recursion']:
print(f" Recursion: Yes")
if analysis['operations']:
print(f" Operations:")
for op in analysis['operations']:
print(f" - {op['type']} at line {op['line']}")
if analysis['suggestions']:
print(f" Suggestions:")
for suggestion in analysis['suggestions']:
print(f"{suggestion}")
print()
def analyze_file(filepath, function_name=None, output_format='text'):
"""Analyze a Python file."""
if not os.path.exists(filepath):
print(f"Error: File '{filepath}' not found", file=sys.stderr)
sys.exit(1)
with open(filepath, 'r', encoding='utf-8') as f:
code = f.read()
try:
tree = ast.parse(code)
except SyntaxError as e:
print(f"Syntax error in file: {e}", file=sys.stderr)
sys.exit(1)
analyzer = ComplexityAnalyzer(function_name)
analyzer.visit(tree)
if not analyzer.results:
if function_name:
print(f"Error: Function '{function_name}' not found", file=sys.stderr)
else:
print("No functions found in file", file=sys.stderr)
sys.exit(1)
format_output(analyzer.results, output_format)
def analyze_code_snippet(code, output_format='text'):
"""Analyze a code snippet."""
try:
tree = ast.parse(code)
except SyntaxError as e:
print(f"Syntax error: {e}", file=sys.stderr)
sys.exit(1)
analyzer = ComplexityAnalyzer()
analyzer.visit(tree)
format_output(analyzer.results, output_format)
def main():
parser = argparse.ArgumentParser(
description='Analyze time and space complexity of code'
)
parser.add_argument('file', help='Python file to analyze')
parser.add_argument('--function', help='Specific function to analyze')
parser.add_argument('--format', choices=['text', 'json'], default='text',
help='Output format (default: text)')
args = parser.parse_args()
analyze_file(args.file, args.function, args.format)
if __name__ == '__main__':
main()
+334
View File
@@ -0,0 +1,334 @@
#!/usr/bin/env python3
"""
Test Runner - Execute and format test results
Supports:
- pytest (Python)
- unittest (Python)
- jest (JavaScript)
- JUnit (Java)
Usage:
python run_tests.py <test_file>
python run_tests.py <test_directory>
python run_tests.py --framework pytest
"""
import argparse
import json
import os
import subprocess
import sys
from pathlib import Path
class TestResult:
"""Represents test execution results."""
def __init__(self):
self.passed = 0
self.failed = 0
self.errors = 0
self.skipped = 0
self.total = 0
self.duration = 0.0
self.failures = []
def to_dict(self):
return {
'passed': self.passed,
'failed': self.failed,
'errors': self.errors,
'skipped': self.skipped,
'total': self.total,
'duration': self.duration,
'failures': self.failures
}
class TestRunner:
"""Base class for test runners."""
def __init__(self, target):
self.target = target
def run(self) -> TestResult:
raise NotImplementedError
class PytestRunner(TestRunner):
"""Run pytest tests."""
def run(self) -> TestResult:
result = TestResult()
try:
# Run pytest with verbose output and JSON report
cmd = [
'python', '-m', 'pytest',
self.target,
'-v',
'--tb=short'
]
process = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=60
)
# Parse output
output = process.stdout + process.stderr
lines = output.split('\n')
for line in lines:
if ' PASSED' in line:
result.passed += 1
elif ' FAILED' in line:
result.failed += 1
# Extract test name and failure info
test_name = line.split('::')[1].split(' ')[0] if '::' in line else 'unknown'
result.failures.append({
'test': test_name,
'message': 'See output for details'
})
elif ' ERROR' in line:
result.errors += 1
elif ' SKIPPED' in line:
result.skipped += 1
# Extract duration
if 'passed in' in line or 'failed in' in line:
try:
duration_str = line.split(' in ')[1].split('s')[0]
result.duration = float(duration_str)
except:
pass
result.total = result.passed + result.failed + result.errors + result.skipped
return result
except FileNotFoundError:
print("Error: pytest not found. Install with: pip install pytest", file=sys.stderr)
sys.exit(1)
except subprocess.TimeoutExpired:
print("Error: Tests timed out after 60 seconds", file=sys.stderr)
sys.exit(1)
except Exception as e:
print(f"Error running tests: {e}", file=sys.stderr)
sys.exit(1)
class UnittestRunner(TestRunner):
"""Run unittest tests."""
def run(self) -> TestResult:
result = TestResult()
try:
cmd = [
'python', '-m', 'unittest',
'discover',
'-s', self.target,
'-v'
]
process = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=60
)
output = process.stdout + process.stderr
lines = output.split('\n')
for line in lines:
if ' ... ok' in line:
result.passed += 1
elif 'FAIL:' in line:
result.failed += 1
test_name = line.replace('FAIL:', '').strip()
result.failures.append({
'test': test_name,
'message': 'See output for details'
})
elif 'ERROR:' in line:
result.errors += 1
# Parse summary line
for line in reversed(lines):
if 'Ran ' in line and ' test' in line:
try:
result.total = int(line.split('Ran ')[1].split(' test')[0])
except:
pass
break
return result
except FileNotFoundError:
print("Error: unittest module not found", file=sys.stderr)
sys.exit(1)
except Exception as e:
print(f"Error running tests: {e}", file=sys.stderr)
sys.exit(1)
class JestRunner(TestRunner):
"""Run Jest tests."""
def run(self) -> TestResult:
result = TestResult()
try:
cmd = ['npx', 'jest', self.target, '--verbose']
process = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=60
)
output = process.stdout + process.stderr
lines = output.split('\n')
for line in lines:
if '' in line or 'PASS' in line:
result.passed += 1
elif '' in line or 'FAIL' in line:
result.failed += 1
# Parse summary
for line in lines:
if 'Tests:' in line:
parts = line.split(',')
for part in parts:
if 'passed' in part:
try:
result.passed = int(part.split()[0])
except:
pass
elif 'failed' in part:
try:
result.failed = int(part.split()[0])
except:
pass
result.total = result.passed + result.failed
return result
except FileNotFoundError:
print("Error: Jest not found. Install with: npm install --save-dev jest", file=sys.stderr)
sys.exit(1)
except Exception as e:
print(f"Error running tests: {e}", file=sys.stderr)
sys.exit(1)
def detect_framework(target):
"""Detect testing framework to use."""
# Check if it's a Python file
if target.endswith('.py') or os.path.isdir(target):
# Check for pytest markers
if os.path.isfile(target):
with open(target, 'r') as f:
content = f.read()
if 'import pytest' in content or '@pytest' in content:
return 'pytest'
elif 'import unittest' in content or 'class Test' in content:
return 'unittest'
else:
# Check for pytest.ini or setup.cfg
if os.path.exists('pytest.ini') or os.path.exists('setup.cfg'):
return 'pytest'
return 'unittest'
# Check if it's JavaScript
elif target.endswith('.js') or target.endswith('.test.js'):
return 'jest'
return 'pytest' # Default
def run_tests(target, framework=None, output_format='text'):
"""Run tests and format output."""
if not os.path.exists(target):
print(f"Error: Target '{target}' not found", file=sys.stderr)
sys.exit(1)
# Detect framework if not specified
if framework is None:
framework = detect_framework(target)
# Create appropriate runner
if framework == 'pytest':
runner = PytestRunner(target)
elif framework == 'unittest':
runner = UnittestRunner(target)
elif framework == 'jest':
runner = JestRunner(target)
else:
print(f"Error: Unsupported framework '{framework}'", file=sys.stderr)
sys.exit(1)
print(f"\nRunning tests with {framework}...")
print(f"Target: {target}\n")
# Run tests
result = runner.run()
# Output results
if output_format == 'json':
print(json.dumps(result.to_dict(), indent=2))
else:
print("=" * 60)
print("TEST RESULTS")
print("=" * 60)
print(f"Total: {result.total}")
print(f"Passed: {result.passed}")
print(f"Failed: {result.failed}")
if result.errors > 0:
print(f"Errors: {result.errors}")
if result.skipped > 0:
print(f"Skipped: {result.skipped}")
if result.duration > 0:
print(f"Duration: {result.duration:.2f}s")
print()
if result.failures:
print("FAILURES:")
for failure in result.failures:
print(f" - {failure['test']}")
if failure.get('message'):
print(f" {failure['message']}")
print()
# Summary
if result.failed == 0 and result.errors == 0:
print("✓ All tests passed!")
else:
print(f"{result.failed + result.errors} test(s) failed")
print()
def main():
parser = argparse.ArgumentParser(description='Run and format test results')
parser.add_argument('target', help='Test file or directory to run')
parser.add_argument('--framework', choices=['pytest', 'unittest', 'jest'],
help='Testing framework to use (auto-detected if not specified)')
parser.add_argument('--format', choices=['text', 'json'], default='text',
help='Output format (default: text)')
args = parser.parse_args()
run_tests(args.target, args.framework, args.format)
if __name__ == '__main__':
main()