hi
This commit is contained in:
592
scripts/pr-challenge.py
Executable file
592
scripts/pr-challenge.py
Executable file
@@ -0,0 +1,592 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
PR Challenge System - Random developer challenges and PR rejections
|
||||
This script adds gamification and humor to the pull request process
|
||||
"""
|
||||
|
||||
import random
|
||||
import json
|
||||
import time
|
||||
from datetime import datetime
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
class PRChallengeSystem:
|
||||
"""Professional PR review system with entertaining challenges"""
|
||||
|
||||
def __init__(self, challenge_frequency: float = 0.05):
|
||||
self.challenge_frequency = min(1.0, max(0.0, challenge_frequency))
|
||||
self.challenges_completed = []
|
||||
self.challenges_failed = []
|
||||
|
||||
# Developer challenges
|
||||
self.developer_challenges = [
|
||||
{
|
||||
'title': 'The Speed Code Challenge',
|
||||
'description': 'Complete this PR review in under 2 minutes',
|
||||
'time_limit': 120,
|
||||
'difficulty': 'medium',
|
||||
'reward': 'Speed Demon Badge',
|
||||
'category': 'performance'
|
||||
},
|
||||
{
|
||||
'title': 'The Perfect Review Challenge',
|
||||
'description': 'Find at least 3 meaningful improvements in this PR',
|
||||
'requirements': {'min_improvements': 3},
|
||||
'difficulty': 'hard',
|
||||
'reward': 'Eagle Eye Badge',
|
||||
'category': 'quality'
|
||||
},
|
||||
{
|
||||
'title': 'The Git Master Challenge',
|
||||
'description': 'Explain the difference between merge, rebase, and squash without looking it up',
|
||||
'validation_function': 'validate_git_knowledge',
|
||||
'difficulty': 'medium',
|
||||
'reward': 'Git Guru Badge',
|
||||
'category': 'knowledge'
|
||||
},
|
||||
{
|
||||
'title': 'The Documentation Detective',
|
||||
'description': 'Find and fix all typos in the PR description and comments',
|
||||
'difficulty': 'easy',
|
||||
'reward': 'Proofreader Badge',
|
||||
'category': 'detail'
|
||||
},
|
||||
{
|
||||
'title': 'The Code Archaeologist',
|
||||
'description': 'Identify the oldest file in this PR and explain its historical significance',
|
||||
'difficulty': 'hard',
|
||||
'reward': 'History Buff Badge',
|
||||
'category': 'investigation'
|
||||
},
|
||||
{
|
||||
'title': 'The Zen Master Challenge',
|
||||
'description': 'Review this PR with only constructive, positive feedback',
|
||||
'difficulty': 'medium',
|
||||
'reward': 'Zen Master Badge',
|
||||
'category': 'attitude'
|
||||
},
|
||||
{
|
||||
'title': 'The Efficiency Expert',
|
||||
'description': 'Suggest at least one optimization that would improve performance by 10% or more',
|
||||
'difficulty': 'hard',
|
||||
'reward': 'Performance Badge',
|
||||
'category': 'optimization'
|
||||
},
|
||||
{
|
||||
'title': 'The Security Sentinel',
|
||||
'description': 'Identify at least one potential security issue in the code changes',
|
||||
'difficulty': 'medium',
|
||||
'reward': 'Security Guardian Badge',
|
||||
'category': 'security'
|
||||
},
|
||||
{
|
||||
'title': 'The Testing Tyrant',
|
||||
'description': 'Suggest at least 2 test cases that should be added',
|
||||
'difficulty': 'medium',
|
||||
'reward': 'Test Master Badge',
|
||||
'category': 'testing'
|
||||
},
|
||||
{
|
||||
'title': 'The Naming Connoisseur',
|
||||
'description': 'Suggest better names for at least 2 variables or functions',
|
||||
'difficulty': 'easy',
|
||||
'reward': 'Naming Expert Badge',
|
||||
'category': 'style'
|
||||
}
|
||||
]
|
||||
|
||||
# PR rejection reasons (humorous but professional)
|
||||
self.rejection_reasons = [
|
||||
{
|
||||
'reason': 'The cosmic forces are not aligned for this merge',
|
||||
'explanation': 'Sometimes the universe sends us signals. Today it says "wait".',
|
||||
'suggestion': 'Try again tomorrow when Mercury is not in retrograde.',
|
||||
'severity': 'cosmic'
|
||||
},
|
||||
{
|
||||
'reason': 'This PR triggers my "too perfect" alarm',
|
||||
'explanation': 'The code is flawless, the documentation is complete, and the tests pass. This is suspicious.',
|
||||
'suggestion': 'Add a minor typo or a TODO comment to make it feel more authentic.',
|
||||
'severity': 'suspicious'
|
||||
},
|
||||
{
|
||||
'reason': 'Insufficient coffee was consumed during development',
|
||||
'explanation': 'Our coffee analysis shows this PR was created with suboptimal caffeine levels.',
|
||||
'suggestion': 'Drink at least 2 cups of coffee and try again.',
|
||||
'severity': 'biological'
|
||||
},
|
||||
{
|
||||
'reason': 'The PR violates the laws of physics',
|
||||
'explanation': 'This code claims to do the impossible. We admire the ambition.',
|
||||
'suggestion': 'Check if you\'ve accidentally invented a perpetual motion machine.',
|
||||
'severity': 'scientific'
|
||||
},
|
||||
{
|
||||
'reason': 'The Git commit graph forms a frowny face',
|
||||
'explanation': 'The visual representation of your commits creates a sad expression. This affects team morale.',
|
||||
'suggestion': 'Add an extra commit to turn that frown upside down!',
|
||||
'severity': 'emotional'
|
||||
},
|
||||
{
|
||||
'reason': 'This PR is too efficient',
|
||||
'explanation': 'You\'ve solved the problem too well. We need to maintain job security for maintenance developers.',
|
||||
'suggestion': 'Add a few unnecessary comments or a complex algorithm.',
|
||||
'severity': 'economic'
|
||||
},
|
||||
{
|
||||
'reason': 'The code lacks personality',
|
||||
'explanation': 'Your code is technically perfect but emotionally void. Code should have soul!',
|
||||
'suggestion': 'Add some ASCII art or a humorous comment to give it character.',
|
||||
'severity': 'artistic'
|
||||
},
|
||||
{
|
||||
'reason': 'This PR breaks the space-time continuum',
|
||||
'explanation': 'Your changes have created a temporal paradox. We can\'t merge this until we resolve it.',
|
||||
'suggestion': 'Check if you\'ve modified any time-related functions.',
|
||||
'severity': 'temporal'
|
||||
},
|
||||
{
|
||||
'reason': 'The PR lacks dramatic tension',
|
||||
'explanation': 'Every good story needs conflict. Your PR is too straightforward.',
|
||||
'suggestion': 'Add some edge cases or error handling to create narrative tension.',
|
||||
'severity': 'literary'
|
||||
},
|
||||
{
|
||||
'reason': 'This PR was created on a Tuesday',
|
||||
'explanation': 'Everyone knows Tuesday is the worst day for code quality. It\'s scientifically proven.',
|
||||
'suggestion': 'Wait until Wednesday when the code quality improves.',
|
||||
'severity': 'calendar'
|
||||
}
|
||||
]
|
||||
|
||||
# PR validation requirements
|
||||
self.validation_requirements = [
|
||||
{
|
||||
'name': 'Code Quality',
|
||||
'description': 'Code follows team standards and best practices',
|
||||
'weight': 25
|
||||
},
|
||||
{
|
||||
'name': 'Test Coverage',
|
||||
'description': 'Adequate test coverage for changes made',
|
||||
'weight': 20
|
||||
},
|
||||
{
|
||||
'name': 'Documentation',
|
||||
'description': 'Changes are properly documented',
|
||||
'weight': 15
|
||||
},
|
||||
{
|
||||
'name': 'Performance Impact',
|
||||
'description': 'Performance implications considered and addressed',
|
||||
'weight': 15
|
||||
},
|
||||
{
|
||||
'name': 'Security Review',
|
||||
'description': 'Security implications assessed',
|
||||
'weight': 15
|
||||
},
|
||||
{
|
||||
'name': 'Break Changes',
|
||||
'description': 'Breaking changes properly communicated',
|
||||
'weight': 10
|
||||
}
|
||||
]
|
||||
|
||||
def should_trigger_challenge(self) -> bool:
|
||||
"""Determine if a challenge should be triggered"""
|
||||
return random.random() < self.challenge_frequency
|
||||
|
||||
def should_reject_pr(self) -> bool:
|
||||
"""Determine if a PR should be randomly rejected"""
|
||||
# Lower chance than challenges
|
||||
return random.random() < (self.challenge_frequency * 0.3)
|
||||
|
||||
def get_random_challenge(self) -> Dict:
|
||||
"""Get a random developer challenge"""
|
||||
return random.choice(self.developer_challenges)
|
||||
|
||||
def get_random_rejection(self) -> Dict:
|
||||
"""Get a random PR rejection reason"""
|
||||
return random.choice(self.rejection_reasons)
|
||||
|
||||
def generate_challenge(self, pr_data: Dict) -> Dict:
|
||||
"""Generate a challenge for a specific PR"""
|
||||
if not self.should_trigger_challenge():
|
||||
return None
|
||||
|
||||
challenge = self.get_random_challenge()
|
||||
challenge_data = {
|
||||
'pr_id': pr_data.get('id', 'unknown'),
|
||||
'pr_title': pr_data.get('title', 'Unknown PR'),
|
||||
'challenge': challenge,
|
||||
'issued_at': datetime.now().isoformat(),
|
||||
'status': 'pending',
|
||||
'time_limit': challenge.get('time_limit', 300),
|
||||
'difficulty': challenge.get('difficulty', 'medium')
|
||||
}
|
||||
|
||||
return challenge_data
|
||||
|
||||
def generate_rejection(self, pr_data: Dict) -> Dict:
|
||||
"""Generate a humorous rejection reason"""
|
||||
if not self.should_reject_pr():
|
||||
return None
|
||||
|
||||
rejection = self.get_random_rejection()
|
||||
rejection_data = {
|
||||
'pr_id': pr_data.get('id', 'unknown'),
|
||||
'pr_title': pr_data.get('title', 'Unknown PR'),
|
||||
'rejection': rejection,
|
||||
'rejected_at': datetime.now().isoformat(),
|
||||
'appeal_instructions': 'You may appeal this rejection by completing a developer challenge',
|
||||
'suggested_challenge': self.get_random_challenge()
|
||||
}
|
||||
|
||||
return rejection_data
|
||||
|
||||
def validate_pr_requirements(self, pr_data: Dict) -> Dict:
|
||||
"""Validate PR against standard requirements"""
|
||||
validation_results = {}
|
||||
total_score = 0
|
||||
max_score = 0
|
||||
|
||||
for requirement in self.validation_requirements:
|
||||
# In a real implementation, this would do actual validation
|
||||
# For demonstration, we'll use random scores
|
||||
score = random.randint(requirement['weight'] // 2, requirement['weight'])
|
||||
max_score += requirement['weight']
|
||||
total_score += score
|
||||
|
||||
validation_results[requirement['name']] = {
|
||||
'score': score,
|
||||
'max_score': requirement['weight'],
|
||||
'percentage': (score / requirement['weight']) * 100,
|
||||
'notes': self._generate_validation_notes(requirement['name'], score)
|
||||
}
|
||||
|
||||
overall_score = (total_score / max_score) * 100
|
||||
status = 'approved' if overall_score >= 80 else 'needs_work' if overall_score >= 60 else 'rejected'
|
||||
|
||||
return {
|
||||
'overall_score': overall_score,
|
||||
'status': status,
|
||||
'validations': validation_results,
|
||||
'recommendations': self._generate_recommendations(overall_score)
|
||||
}
|
||||
|
||||
def _generate_validation_notes(self, requirement_name: str, score: int) -> str:
|
||||
"""Generate notes for a specific validation"""
|
||||
notes = {
|
||||
'Code Quality': [
|
||||
'Code follows team standards well',
|
||||
'Good variable naming and structure',
|
||||
'Could use some refactoring in places',
|
||||
'Consider adding more comments'
|
||||
],
|
||||
'Test Coverage': [
|
||||
'Comprehensive test coverage',
|
||||
'Good unit tests included',
|
||||
'Missing integration tests',
|
||||
'Test cases could be more thorough'
|
||||
],
|
||||
'Documentation': [
|
||||
'Excellent documentation provided',
|
||||
'Clear comments throughout code',
|
||||
'API documentation needs updating',
|
||||
'README changes documented'
|
||||
],
|
||||
'Performance Impact': [
|
||||
'Performance considerations addressed',
|
||||
'Efficient algorithms used',
|
||||
'Consider caching for better performance',
|
||||
'Memory usage could be optimized'
|
||||
],
|
||||
'Security Review': [
|
||||
'Security implications well-considered',
|
||||
'Input validation implemented',
|
||||
'Authentication/authorization checked',
|
||||
'Could use additional security measures'
|
||||
],
|
||||
'Break Changes': [
|
||||
'Breaking changes properly documented',
|
||||
'Migration path provided',
|
||||
'Deprecation notices included',
|
||||
'Backward compatibility maintained'
|
||||
]
|
||||
}
|
||||
|
||||
requirement_notes = notes.get(requirement_name, ['Standard validation completed'])
|
||||
return random.choice(requirement_notes)
|
||||
|
||||
def _generate_recommendations(self, overall_score: float) -> List[str]:
|
||||
"""Generate improvement recommendations"""
|
||||
if overall_score >= 90:
|
||||
return [
|
||||
'Excellent work! This PR is ready for merge.',
|
||||
'Consider sharing your approach with the team as a best practice example.',
|
||||
'Your attention to detail is commendable.'
|
||||
]
|
||||
elif overall_score >= 80:
|
||||
return [
|
||||
'Good work! Minor improvements suggested before merge.',
|
||||
'Consider addressing the areas with lower scores.',
|
||||
'Overall, this is a solid contribution.'
|
||||
]
|
||||
elif overall_score >= 70:
|
||||
return [
|
||||
'Decent work, but needs some improvements.',
|
||||
'Focus on the areas with the lowest scores.',
|
||||
'Additional testing and documentation recommended.'
|
||||
]
|
||||
else:
|
||||
return [
|
||||
'Significant improvements needed before this can be merged.',
|
||||
'Please address all major concerns raised.',
|
||||
'Consider pairing with a senior developer for guidance.'
|
||||
]
|
||||
|
||||
def complete_challenge(self, challenge_data: Dict, completion_time: int) -> Dict:
|
||||
"""Mark a challenge as completed"""
|
||||
challenge = challenge_data['challenge']
|
||||
|
||||
result = {
|
||||
'challenge_id': challenge_data.get('id', 'unknown'),
|
||||
'completed_at': datetime.now().isoformat(),
|
||||
'completion_time': completion_time,
|
||||
'success': True,
|
||||
'reward': challenge.get('reward', 'Challenge Completed'),
|
||||
'achievement': f"Completed {challenge.get('title', 'Challenge')}"
|
||||
}
|
||||
|
||||
self.challenges_completed.append(result)
|
||||
return result
|
||||
|
||||
def fail_challenge(self, challenge_data: Dict) -> Dict:
|
||||
"""Mark a challenge as failed"""
|
||||
challenge = challenge_data['challenge']
|
||||
|
||||
result = {
|
||||
'challenge_id': challenge_data.get('id', 'unknown'),
|
||||
'failed_at': datetime.now().isoformat(),
|
||||
'success': False,
|
||||
'penalty': 'Better luck next time!',
|
||||
'encouragement': 'Every failure is a learning opportunity'
|
||||
}
|
||||
|
||||
self.challenges_failed.append(result)
|
||||
return result
|
||||
|
||||
def generate_challenge_response(self, challenge_data: Dict) -> str:
|
||||
"""Generate a user-friendly challenge response"""
|
||||
challenge = challenge_data['challenge']
|
||||
pr_title = challenge_data['pr_title']
|
||||
|
||||
response_lines = [
|
||||
"🎪 DEVELOPER CHALLENGE ACTIVATED! 🎪",
|
||||
"=" * 50,
|
||||
f"🎯 PR: {pr_title}",
|
||||
f"🏆 Challenge: {challenge['title']}",
|
||||
f"📝 Description: {challenge['description']}",
|
||||
f"🎮 Difficulty: {challenge['difficulty'].title()}",
|
||||
f"⏰ Time Limit: {challenge.get('time_limit', 'No time limit')} seconds",
|
||||
f"🎁 Reward: {challenge['reward']}",
|
||||
"",
|
||||
"🎲 Accept this challenge to prove your developer skills!",
|
||||
"Complete the challenge to earn special recognition!",
|
||||
"",
|
||||
"Type 'accept' to begin the challenge, or 'skip' to continue normally."
|
||||
]
|
||||
|
||||
return "\n".join(response_lines)
|
||||
|
||||
def generate_rejection_response(self, rejection_data: Dict) -> str:
|
||||
"""Generate a humorous rejection response"""
|
||||
rejection = rejection_data['rejection']
|
||||
pr_title = rejection_data['pr_title']
|
||||
suggested_challenge = rejection_data['suggested_challenge']
|
||||
|
||||
response_lines = [
|
||||
"🚨 PR REJECTION NOTICE 🚨",
|
||||
"=" * 50,
|
||||
f"📋 PR: {pr_title}",
|
||||
f"❌ Reason: {rejection['reason']}",
|
||||
"",
|
||||
"📖 Explanation:",
|
||||
f" {rejection['explanation']}",
|
||||
"",
|
||||
"💡 Suggestion:",
|
||||
f" {rejection['suggestion']}",
|
||||
"",
|
||||
"🎮 Appeal Option:",
|
||||
f" Complete the '{suggested_challenge['title']}' challenge to override this rejection!",
|
||||
f" Challenge: {suggested_challenge['description']}",
|
||||
f" Reward: {suggested_challenge['reward']}",
|
||||
"",
|
||||
"🎪 Remember: This is all in good fun! Your PR will be processed normally.",
|
||||
"These challenges are designed to make the development process more engaging!"
|
||||
]
|
||||
|
||||
return "\n".join(response_lines)
|
||||
|
||||
def generate_pr_summary(self, pr_data: Dict, validation_result: Dict) -> str:
|
||||
"""Generate a comprehensive PR summary"""
|
||||
status = validation_result['status']
|
||||
score = validation_result['overall_score']
|
||||
|
||||
summary_lines = [
|
||||
"🎪 PR REVIEW SUMMARY 🎪",
|
||||
"=" * 50,
|
||||
f"📋 PR: {pr_data.get('title', 'Unknown PR')}",
|
||||
f"📊 Overall Score: {score:.1f}%",
|
||||
f"🎯 Status: {status.upper()}",
|
||||
"",
|
||||
"📋 Detailed Breakdown:"
|
||||
]
|
||||
|
||||
for name, result in validation_result['validations'].items():
|
||||
percentage = result['percentage']
|
||||
emoji = "🟢" if percentage >= 80 else "🟡" if percentage >= 60 else "🔴"
|
||||
summary_lines.append(f" {emoji} {name}: {percentage:.0f}% ({result['score']}/{result['max_score']})")
|
||||
|
||||
summary_lines.append("")
|
||||
|
||||
if validation_result['recommendations']:
|
||||
summary_lines.append("💡 Recommendations:")
|
||||
for rec in validation_result['recommendations']:
|
||||
summary_lines.append(f" • {rec}")
|
||||
|
||||
# Add challenge/rejection info if applicable
|
||||
if self.should_trigger_challenge():
|
||||
challenge = self.generate_challenge(pr_data)
|
||||
if challenge:
|
||||
summary_lines.append("")
|
||||
summary_lines.append("🎮 SPECIAL NOTICE:")
|
||||
summary_lines.append(" This PR has been selected for a developer challenge!")
|
||||
summary_lines.append(" Check the challenge system for details.")
|
||||
|
||||
if self.should_reject_pr():
|
||||
rejection = self.generate_rejection(pr_data)
|
||||
if rejection:
|
||||
summary_lines.append("")
|
||||
summary_lines.append("🚨 ATTENTION:")
|
||||
summary_lines.append(" This PR has encountered a... unique situation.")
|
||||
summary_lines.append(" Please check the rejection notice for details.")
|
||||
|
||||
summary_lines.append("")
|
||||
summary_lines.append("🚀 Thank you for your contribution!")
|
||||
|
||||
return "\n".join(summary_lines)
|
||||
|
||||
def get_challenge_statistics(self) -> Dict:
|
||||
"""Get statistics about challenges"""
|
||||
return {
|
||||
'total_challenges': len(self.challenges_completed) + len(self.challenges_failed),
|
||||
'completed_challenges': len(self.challenges_completed),
|
||||
'failed_challenges': len(self.challenges_failed),
|
||||
'success_rate': (len(self.challenges_completed) / max(1, len(self.challenges_completed) + len(self.challenges_failed))) * 100,
|
||||
'most_common_category': self._get_most_common_category(),
|
||||
'average_completion_time': self._get_average_completion_time()
|
||||
}
|
||||
|
||||
def _get_most_common_category(self) -> str:
|
||||
"""Get the most common challenge category completed"""
|
||||
if not self.challenges_completed:
|
||||
return 'none'
|
||||
|
||||
categories = {}
|
||||
for challenge in self.challenges_completed:
|
||||
# Extract category from achievement message
|
||||
for challenge_def in self.developer_challenges:
|
||||
if challenge_def['reward'] in challenge.get('achievement', ''):
|
||||
category = challenge_def.get('category', 'general')
|
||||
categories[category] = categories.get(category, 0) + 1
|
||||
break
|
||||
|
||||
return max(categories, key=categories.get) if categories else 'general'
|
||||
|
||||
def _get_average_completion_time(self) -> float:
|
||||
"""Get average challenge completion time"""
|
||||
if not self.challenges_completed:
|
||||
return 0.0
|
||||
|
||||
total_time = sum(c.get('completion_time', 0) for c in self.challenges_completed)
|
||||
return total_time / len(self.challenges_completed)
|
||||
|
||||
def main():
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description='PR Challenge System - Gamify your pull requests!')
|
||||
parser.add_argument('--simulate', '-s', action='store_true',
|
||||
help='Simulate PR review process')
|
||||
parser.add_argument('--challenge-frequency', '-f', type=float, default=0.05,
|
||||
help='Challenge frequency (0.0-1.0, default: 0.05)')
|
||||
parser.add_argument('--pr-title', '-p', help='Simulate review for specific PR title')
|
||||
parser.add_argument('--stats', action='store_true',
|
||||
help='Show challenge statistics')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
challenge_system = PRChallengeSystem(args.challenge_frequency)
|
||||
|
||||
if args.stats:
|
||||
stats = challenge_system.get_challenge_statistics()
|
||||
print("🎪 Challenge Statistics")
|
||||
print("=" * 30)
|
||||
print(f"Total Challenges: {stats['total_challenges']}")
|
||||
print(f"Completed: {stats['completed_challenges']}")
|
||||
print(f"Failed: {stats['failed_challenges']}")
|
||||
print(f"Success Rate: {stats['success_rate']:.1f}%")
|
||||
print(f"Most Common Category: {stats['most_common_category']}")
|
||||
print(f"Avg Completion Time: {stats['average_completion_time']:.1f}s")
|
||||
return
|
||||
|
||||
if args.simulate:
|
||||
# Mock PR data
|
||||
pr_title = args.pr_title or "feat: add user authentication system"
|
||||
pr_data = {
|
||||
'id': '123',
|
||||
'title': pr_title,
|
||||
'author': 'developer',
|
||||
'files_changed': 15,
|
||||
'additions': 500,
|
||||
'deletions': 100
|
||||
}
|
||||
|
||||
print("🎪 PR Review Simulation")
|
||||
print("=" * 40)
|
||||
|
||||
# Validate PR requirements
|
||||
validation = challenge_system.validate_pr_requirements(pr_data)
|
||||
print("📋 PR Validation:")
|
||||
print(f" Overall Score: {validation['overall_score']:.1f}%")
|
||||
print(f" Status: {validation['status']}")
|
||||
print()
|
||||
|
||||
# Check for challenges
|
||||
challenge = challenge_system.generate_challenge(pr_data)
|
||||
if challenge:
|
||||
print("🎮 Challenge Generated!")
|
||||
print(challenge_system.generate_challenge_response(challenge))
|
||||
print()
|
||||
|
||||
# Check for rejections
|
||||
rejection = challenge_system.generate_rejection(pr_data)
|
||||
if rejection:
|
||||
print("🚨 Rejection Generated!")
|
||||
print(challenge_system.generate_rejection_response(rejection))
|
||||
print()
|
||||
|
||||
# Show full summary
|
||||
print(challenge_system.generate_pr_summary(pr_data, validation))
|
||||
|
||||
else:
|
||||
print("🎪 PR Challenge System")
|
||||
print("=" * 40)
|
||||
print("Use --simulate to test the system")
|
||||
print("Use --stats to view statistics")
|
||||
print("This system adds gamification to your PR process!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
Reference in New Issue
Block a user