Weekly README Sync Check #41
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Weekly README Sync Check | |
| on: | |
| schedule: | |
| - cron: '0 6 * * 0' # Every Sunday 06:00 UTC (09:00 Riyadh) | |
| workflow_dispatch: | |
| jobs: | |
| check-readme-sync: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| issues: write | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Count files and compare with README | |
| id: sync_check | |
| run: | | |
| python3 - <<'PYEOF' | |
| import os, re, json, sys | |
| from pathlib import Path | |
| # ── 1. Actual file counts ───────────────────────────────────────────── | |
| def count_files(directory, extensions): | |
| d = Path(directory) | |
| if not d.exists(): | |
| return 0 | |
| return sum( | |
| 1 for f in d.iterdir() | |
| if f.is_file() and f.suffix in extensions | |
| ) | |
| actual = { | |
| "skills": count_files("skills", {".md"}), | |
| "sources": count_files("sources", {".md"}), | |
| "datasets": count_files("datasets", {".md", ".csv"}), | |
| "examples": count_files("examples", {".md"}), | |
| "prompts": count_files("prompts", {".md"}), | |
| "docs": count_files("docs", {".md"}), | |
| "scripts": count_files("scripts", {".py", ".sql"}), | |
| } | |
| # ── 2. Extract counts mentioned in README ───────────────────────────── | |
| readme = Path("README.md").read_text(encoding="utf-8") | |
| # Patterns: "skills/ 7 ملفات", "7 skill files", "skills/ 7 files", | |
| # "(skills/ 8 files)", "8 skill", etc. | |
| # Strategy: for each dir name search for the closest integer neighbour. | |
| def extract_readme_count(text, dir_name): | |
| # Look for dir_name followed shortly by a number, or number before dir_name | |
| patterns = [ | |
| rf"{re.escape(dir_name)}[^\n]{{0,40}}?(\d+)\s*(?:ملفات?|files?)", | |
| rf"(\d+)\s*(?:ملفات?|files?)[^\n]{{0,40}}?{re.escape(dir_name)}", | |
| rf"{re.escape(dir_name)}[/\s]{{0,5}}(\d+)", | |
| rf"\({re.escape(dir_name)}[/\s]{{0,5}}(\d+)", | |
| ] | |
| for pat in patterns: | |
| m = re.search(pat, text, re.IGNORECASE) | |
| if m: | |
| return int(m.group(1)) | |
| return None | |
| mentioned = {k: extract_readme_count(readme, k) for k in actual} | |
| # ── 3. Build comparison table ───────────────────────────────────────── | |
| rows = [] | |
| any_mismatch = False | |
| for key in actual: | |
| act = actual[key] | |
| exp = mentioned[key] | |
| if exp is None: | |
| status = "❓ غير مذكور" | |
| elif act == exp: | |
| status = "✅ متطابق" | |
| else: | |
| status = "⚠️ يحتاج تحديث" | |
| any_mismatch = True | |
| rows.append((key + "/", act, exp if exp is not None else "—", status)) | |
| # ── 4. Write outputs ────────────────────────────────────────────────── | |
| table_lines = [] | |
| for dir_, act, exp, status in rows: | |
| table_lines.append(f"| `{dir_}` | {act} | {exp} | {status} |") | |
| table_md = "\n".join(table_lines) | |
| counts_summary = ", ".join( | |
| f"{k}={actual[k]}" for k in actual | |
| ) | |
| # ── 5. Write GITHUB_OUTPUT ──────────────────────────────────────────── | |
| with open(os.environ["GITHUB_OUTPUT"], "a") as fh: | |
| fh.write(f"any_mismatch={'true' if any_mismatch else 'false'}\n") | |
| # ── 6. Write issue body to file (avoids shell interpolation of ── | |
| # Unicode, pipe chars, and backticks in the table rows) ── | |
| # Note: code_fence built at runtime so no unindented ``` lines appear | |
| # in the YAML literal block scalar (which would terminate it early). | |
| import datetime | |
| date_str = datetime.datetime.utcnow().strftime("%Y-%m-%d") | |
| code_fence = "`" * 3 | |
| body = ( | |
| f"## README Sync Check — {date_str}\n\n" | |
| "الملفات الفعلية مقارنة بما هو مذكور في README:\n\n" | |
| "| المجلد | الملفات الفعلية | ما يقوله README | الحالة |\n" | |
| "|--------|----------------|-----------------|--------|\n" | |
| f"{table_md}\n\n" | |
| "### الإجراء المطلوب\n\n" | |
| "لتحديث README، افتح Claude Code في مجلد الريبو واكتب:\n\n" | |
| f"{code_fence}\n" | |
| "Read the current file counts and update README.md to reflect:\n" | |
| f"{counts_summary}\n" | |
| f"{code_fence}\n\n" | |
| "---\n" | |
| "*Generated by Weekly README Sync Check workflow*" | |
| ) | |
| with open("/tmp/issue_body.md", "w", encoding="utf-8") as f: | |
| f.write(body) | |
| # Also write date for use in shell steps | |
| with open(os.environ["GITHUB_OUTPUT"], "a") as fh: | |
| fh.write(f"date_str={date_str}\n") | |
| # Print to log regardless | |
| print("=== README Sync Check ===") | |
| print(f"{'Directory':<12} {'Actual':>8} {'README':>8} Status") | |
| print("-" * 50) | |
| for dir_, act, exp, status in rows: | |
| print(f"{dir_:<12} {act:>8} {str(exp):>8} {status}") | |
| print() | |
| if any_mismatch: | |
| print("RESULT: README is OUT OF SYNC — issue will be created.") | |
| else: | |
| print("RESULT: README is in sync — no issue needed.") | |
| PYEOF | |
| - name: README is in sync | |
| if: steps.sync_check.outputs.any_mismatch == 'false' | |
| run: echo "README is in sync with the repository. No issue created." | |
| - name: Ensure 'documentation' label exists | |
| if: steps.sync_check.outputs.any_mismatch == 'true' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| gh label create "documentation" \ | |
| --description "Documentation updates" \ | |
| --color "0075ca" \ | |
| --repo "${{ github.repository }}" 2>/dev/null || true | |
| - name: Check for existing open sync issue | |
| if: steps.sync_check.outputs.any_mismatch == 'true' | |
| id: existing_issue | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| existing=$(gh issue list \ | |
| --repo "${{ github.repository }}" \ | |
| --state open \ | |
| --label documentation \ | |
| --search "تحديث README الأسبوعي in:title" \ | |
| --json number \ | |
| --jq '.[0].number // ""') | |
| echo "issue_number=${existing}" >> "$GITHUB_OUTPUT" | |
| - name: Create sync issue | |
| if: > | |
| steps.sync_check.outputs.any_mismatch == 'true' && | |
| steps.existing_issue.outputs.issue_number == '' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| DATE_STR: ${{ steps.sync_check.outputs.date_str }} | |
| run: | | |
| TITLE="📋 تحديث README الأسبوعي — ${DATE_STR}" | |
| gh issue create \ | |
| --repo "${{ github.repository }}" \ | |
| --title "${TITLE}" \ | |
| --body-file /tmp/issue_body.md \ | |
| --label "documentation" \ | |
| --assignee "Samix2026" | |
| - name: Log skipped (duplicate issue exists) | |
| if: > | |
| steps.sync_check.outputs.any_mismatch == 'true' && | |
| steps.existing_issue.outputs.issue_number != '' | |
| env: | |
| EXISTING_ISSUE: ${{ steps.existing_issue.outputs.issue_number }} | |
| run: | | |
| echo "README is out of sync but issue #${EXISTING_ISSUE} is already open. Skipping duplicate." |