name: README Quality Check on: pull_request: paths: - 'README*.md' - 'Docs/**/*.md' push: branches: [main, master, develop] workflow_dispatch: permissions: contents: read pull-requests: write issues: write jobs: readme-quality-check: name: Multi-language README Quality Assessment runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.11' - name: Install dependencies run: | python -m pip install --upgrade pip pip install requests beautifulsoup4 pyyaml - name: Create quality checker script run: | cat > readme_checker.py << 'EOF' #!/usr/bin/env python3 # -*- coding: utf-8 -*- """ SuperClaude Multi-language README Quality Checker Checks version sync, link validity, and structural consistency """ import os import re import requests import json from pathlib import Path from urllib.parse import urljoin class READMEQualityChecker: def __init__(self): self.readme_files = ['README.md', 'README-zh.md', 'README-ja.md', 'README-kr.md'] self.results = { 'structure_consistency': [], 'link_validation': [], 'translation_sync': [], 'overall_score': 0 } def check_structure_consistency(self): """Check structural consistency""" print("šŸ” Checking structural consistency...") structures = {} for file in self.readme_files: if os.path.exists(file): with open(file, 'r', encoding='utf-8') as f: content = f.read() # Extract heading structure headers = re.findall(r'^#{1,6}\s+(.+)$', content, re.MULTILINE) structures[file] = len(headers) # Compare structural differences line_counts = [structures.get(f, 0) for f in self.readme_files if f in structures] if line_counts: max_diff = max(line_counts) - min(line_counts) consistency_score = max(0, 100 - (max_diff * 5)) self.results['structure_consistency'] = { 'score': consistency_score, 'details': structures, 'status': 'PASS' if consistency_score >= 90 else 'WARN' } print(f"āœ… Structural consistency: {consistency_score}/100") for file, count in structures.items(): print(f" {file}: {count} headers") def check_link_validation(self): """Check link validity""" print("šŸ”— Checking link validity...") all_links = {} broken_links = [] for file in self.readme_files: if os.path.exists(file): with open(file, 'r', encoding='utf-8') as f: content = f.read() # Extract all links links = re.findall(r'\[([^\]]+)\]\(([^)]+)\)', content) all_links[file] = [] for text, url in links: link_info = {'text': text, 'url': url, 'status': 'unknown'} # Check local file links if not url.startswith(('http://', 'https://', '#')): if os.path.exists(url): link_info['status'] = 'valid' else: link_info['status'] = 'broken' broken_links.append(f"{file}: {url}") # HTTP link check (simplified) elif url.startswith(('http://', 'https://')): try: # Only check key links to avoid excessive requests if any(domain in url for domain in ['github.com', 'pypi.org', 'npmjs.com']): response = requests.head(url, timeout=10, allow_redirects=True) link_info['status'] = 'valid' if response.status_code < 400 else 'broken' else: link_info['status'] = 'skipped' except: link_info['status'] = 'error' else: link_info['status'] = 'anchor' all_links[file].append(link_info) # Calculate link health score total_links = sum(len(links) for links in all_links.values()) broken_count = len(broken_links) link_score = max(0, 100 - (broken_count * 10)) if total_links > 0 else 100 self.results['link_validation'] = { 'score': link_score, 'total_links': total_links, 'broken_links': broken_count, 'broken_list': broken_links[:10], # Show max 10 'status': 'PASS' if link_score >= 80 else 'FAIL' } print(f"āœ… Link validity: {link_score}/100") print(f" Total links: {total_links}") print(f" Broken links: {broken_count}") def check_translation_sync(self): """Check translation sync""" print("šŸŒ Checking translation sync...") if not all(os.path.exists(f) for f in self.readme_files): print("āš ļø Some README files are missing") self.results['translation_sync'] = { 'score': 60, 'status': 'WARN', 'message': 'Some README files are missing' } return # Check file modification times mod_times = {} for file in self.readme_files: mod_times[file] = os.path.getmtime(file) # Calculate time difference (seconds) times = list(mod_times.values()) time_diff = max(times) - min(times) # Score based on time diff (within 7 days = synced) sync_score = max(0, 100 - (time_diff / (7 * 24 * 3600) * 20)) self.results['translation_sync'] = { 'score': int(sync_score), 'time_diff_days': round(time_diff / (24 * 3600), 2), 'status': 'PASS' if sync_score >= 80 else 'WARN', 'mod_times': {f: f"{os.path.getmtime(f):.0f}" for f in self.readme_files} } print(f"āœ… Translation sync: {int(sync_score)}/100") print(f" Max time difference: {round(time_diff / (24 * 3600), 1)} days") def generate_report(self): """Generate quality report""" print("\nšŸ“Š Generating quality report...") # Calculate overall score scores = [ self.results['structure_consistency'].get('score', 0), self.results['link_validation'].get('score', 0), self.results['translation_sync'].get('score', 0) ] overall_score = sum(scores) // len(scores) self.results['overall_score'] = overall_score # Generate GitHub Actions summary pipe = "|" table_header = f"{pipe} Check {pipe} Score {pipe} Status {pipe} Details {pipe}" table_separator = f"{pipe}----------|------|------|------|" table_row1 = f"{pipe} šŸ“ Structure {pipe} {self.results['structure_consistency'].get('score', 0)}/100 {pipe} {self.results['structure_consistency'].get('status', 'N/A')} {pipe} {len(self.results['structure_consistency'].get('details', {}))} files {pipe}" table_row2 = f"{pipe} šŸ”— Links {pipe} {self.results['link_validation'].get('score', 0)}/100 {pipe} {self.results['link_validation'].get('status', 'N/A')} {pipe} {self.results['link_validation'].get('broken_links', 0)} broken {pipe}" table_row3 = f"{pipe} šŸŒ Translation {pipe} {self.results['translation_sync'].get('score', 0)}/100 {pipe} {self.results['translation_sync'].get('status', 'N/A')} {pipe} {self.results['translation_sync'].get('time_diff_days', 0)} days diff {pipe}" summary_parts = [ "## šŸ“Š README Quality Check Report", "", f"### šŸ† Overall Score: {overall_score}/100", "", table_header, table_separator, table_row1, table_row2, table_row3, "", "### šŸ“‹ Details", "", "**Structural consistency details:**" ] summary = "\n".join(summary_parts) for file, count in self.results['structure_consistency'].get('details', {}).items(): summary += f"\n- `{file}`: {count} headings" if self.results['link_validation'].get('broken_links'): summary += f"\n\n**Broken links:**\n" for link in self.results['link_validation']['broken_list']: summary += f"\n- āŒ {link}" summary += f"\n\n### šŸŽÆ Recommendations\n" if overall_score >= 90: summary += "āœ… Excellent quality! Keep it up." elif overall_score >= 70: summary += "āš ļø Good quality with room for improvement." else: summary += "🚨 Needs improvement! Please review the issues above." # Write GitHub Actions summary github_step_summary = os.environ.get('GITHUB_STEP_SUMMARY') if github_step_summary: with open(github_step_summary, 'w', encoding='utf-8') as f: f.write(summary) # Save detailed results with open('readme-quality-report.json', 'w', encoding='utf-8') as f: json.dump(self.results, f, indent=2, ensure_ascii=False) print("āœ… Report generated") # Determine exit code based on score return 0 if overall_score >= 70 else 1 def run_all_checks(self): """Run all checks""" print("šŸš€ Starting README quality check...\n") self.check_structure_consistency() self.check_link_validation() self.check_translation_sync() exit_code = self.generate_report() print(f"\nšŸŽÆ Check complete! Score: {self.results['overall_score']}/100") return exit_code if __name__ == "__main__": checker = READMEQualityChecker() exit_code = checker.run_all_checks() exit(exit_code) EOF - name: Run README quality check run: python readme_checker.py - name: Upload quality report if: always() uses: actions/upload-artifact@v4 with: name: readme-quality-report path: readme-quality-report.json retention-days: 30 - name: Comment PR (if applicable) if: github.event_name == 'pull_request' && always() && github.event.pull_request.head.repo.full_name == github.repository uses: actions/github-script@v7 with: script: | const fs = require('fs'); if (fs.existsSync('readme-quality-report.json')) { const report = JSON.parse(fs.readFileSync('readme-quality-report.json', 'utf8')); const score = report.overall_score; const emoji = score >= 90 ? 'šŸ†' : score >= 70 ? 'āœ…' : 'āš ļø'; const comment = `${emoji} **README Quality Check: ${score}/100**\n\n` + `šŸ“ Structural consistency: ${report.structure_consistency?.score || 0}/100\n` + `šŸ”— Link validity: ${report.link_validation?.score || 0}/100\n` + `šŸŒ Translation sync: ${report.translation_sync?.score || 0}/100\n\n` + `See the Actions tab for the detailed report.`; github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, body: comment }); }