Files
2026-07-13 12:40:22 +08:00

312 lines
14 KiB
YAML

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
});
}