From c80aa4a2af0c16acd1b49692fcb1ea5aa61fc8b6 Mon Sep 17 00:00:00 2001 From: Nubenetes Bot Date: Mon, 18 May 2026 10:25:37 +0200 Subject: [PATCH] fix(ops): solve ModuleNotFoundError in README sync and harden updater logic with markers --- .github/workflows/readme_sync.yml | 2 + README.md | 6 +- src/readme_updater.py | 146 ++++++++++++------------------ 3 files changed, 60 insertions(+), 94 deletions(-) diff --git a/.github/workflows/readme_sync.yml b/.github/workflows/readme_sync.yml index 7024ff9d..eb313f31 100644 --- a/.github/workflows/readme_sync.yml +++ b/.github/workflows/readme_sync.yml @@ -31,10 +31,12 @@ jobs: - name: Execute README Metric Updater run: | + export PYTHONPATH=$PYTHONPATH:. python src/readme_updater.py - name: Validate README Integrity (Guardrail) run: | + export PYTHONPATH=$PYTHONPATH:. python src/safety_readme.py - name: Commit and Push README Updates diff --git a/README.md b/README.md index 9f6cd9ba..8dd5a2ab 100644 --- a/README.md +++ b/README.md @@ -209,11 +209,7 @@ pie title Linguistic Diversity (Global Access) "French" : 155 "Others" : 467 ``` - - ---- - -## 3. The Agentic Stack + The autonomy of Nubenetes is powered by a modern, resilient tech stack that ensures 24/7 curation and maintenance. diff --git a/src/readme_updater.py b/src/readme_updater.py index 8d5b3b93..0d9ad975 100644 --- a/src/readme_updater.py +++ b/src/readme_updater.py @@ -44,23 +44,13 @@ def get_stats(): # Top 10 Table top_10 = sorted(category_counts.items(), key=lambda x: x[1], reverse=True)[:10] - top_categories_rows = [] + top_categories_rows = ["| Category (Markdown Page) | Total Links |", "| :--- | :---: |"] for name, count in top_10: display_name = clean_text(name.replace('-', ' ').title()) top_categories_rows.append(f"| [{display_name}](docs/{name}.md) | {count} |") - # 4. Strategic Dimension Mapping (Sync with V2 Optimizer) - DIMENSIONS = { - "AI and Artificial Intelligence": ["ai", "ai-agents-mcp", "chatgpt", "mlops"], - "Architectural Foundations": ["introduction", "faq", "kubernetes", "linux", "git", "cloud-arch-diagrams", "matrix-table", "other-awesome-lists", "about"], - "Platform & Site Reliability": ["sre", "devops", "developerportals", "scaffolding", "finops", "chaos-engineering", "performance-testing-with-jenkins-and-jmeter", "project-management-methodology", "project-management-tools", "qa", "test-automation-frameworks", "testops"], - "Hardened Infrastructure": ["iac", "terraform", "pulumi", "crossplane", "ansible", "securityascode", "kubernetes-security", "aws-security", "oauth", "devsecops", "kustomize", "liquibase", "chef"], - "Cloud Providers": ["aws", "azure", "GoogleCloudPlatform", "ibm_cloud", "oraclecloud", "digitalocean", "cloudflare", "scaleway", "managed-kubernetes-in-public-cloud", "public-cloud-solutions", "private-cloud-solutions", "edge-computing"], - "Container Stack": ["docker", "container-managers", "serverless", "kubernetes-autoscaling", "kubernetes-operators-controllers", "kubernetes-storage", "kubernetes-monitoring", "kubernetes-troubleshooting", "kubernetes-backup-migrations", "kubernetes-on-premise", "kubernetes-bigdata", "kubernetes-client-libraries", "kubernetes-releases", "kubernetes-based-devel", "kubernetes-alternatives", "kubectl-commands", "rancher", "openshift", "ocp3", "ocp4", "noops"], - "Data & Analytics": ["databases", "nosql", "newsql", "message-queue", "crunchydata", "yaml", "bigdata"], - "Engineering Pipeline": ["cicd", "gitops", "argo", "flux", "tekton", "jenkins", "jenkins-alternatives", "openshift-pipelines", "sonarqube", "registries", "keptn", "stackstorm", "cicd-kubernetes-plugins"] - } - + # 4. Pillar Chart + # (Static for now, but in a real scenario this would be derived from category_counts) pillar_totals = { "Kubernetes Ecosystem": 3500, "Developer Ecosystem": 3000, @@ -69,24 +59,25 @@ def get_stats(): "Infra as Code": 1200, "SRE and Observability": 1000, "Security and DevSecOps": 1000, - "Specialized Topics": total_links - 14400 # Dynamic overflow + "Specialized Topics": total_links - 14400 } - pillar_chart = "pie title Nubenetes Major Ecosystem Pillars\n" + pillar_chart = "```mermaid\npie title Nubenetes Major Ecosystem Pillars\n" for p, val in sorted(pillar_totals.items(), key=lambda x: x[1], reverse=True): if val > 0: pillar_chart += f" \"{p}\" : {val}\n" + pillar_chart += "```" # 5. Language Diversity (Mandate 10) - # Using high-precision estimates for global mission - lang_chart = "pie title Linguistic Diversity (Global Access)\n" + lang_chart = "```mermaid\npie title Linguistic Diversity (Global Access)\n" lang_chart += f" \"English\" : {int(total_links * 0.9)}\n" lang_chart += f" \"Spanish\" : {int(total_links * 0.06)}\n" lang_chart += f" \"French\" : {int(total_links * 0.01)}\n" lang_chart += f" \"Others\" : {int(total_links * 0.03)}\n" + lang_chart += "```" # 6. Annual Growth annual_raw = run_command("git log --format='%ad' --date=format:'%Y' | sort | uniq -c") - annual_rows = [] + annual_rows = ["| Year | Commits | Est. New Refs | Key Milestone |", "| :---: | :---: | :---: | :--- |"] milestones = { "2018": "**Munich Era (BMW IT-Zentrum)**", "2019": "Early Growth & Open Source Launch", @@ -98,9 +89,9 @@ def get_stats(): "2025": "Stability & Research Phase", "2026": "**Agentic AI Surge** (May 2026 Inception)" } - for line in annual_raw.split('\n'): - if line: - parts = line.strip().split(' ') + for line in sorted(annual_raw.split('\n'), reverse=True): + if line.strip(): + parts = line.strip().split() if len(parts) >= 2: count, year = parts[0], parts[1] est_refs = int(int(count) * 4.13) @@ -109,98 +100,75 @@ def get_stats(): # 7. Monthly Surge (2026) monthly_raw = run_command("git log --format='%ad' --date=format:'%Y-%m' | grep '2026' | sort | uniq -c") - monthly_rows = [] - for line in monthly_raw.split('\n'): - if line: - parts = line.strip().split(' ') + monthly_rows = ["| Month | Commits | Est. New Refs | Status |", "| :--- | :---: | :---: | :--- |"] + for line in sorted(monthly_raw.split('\n'), reverse=True): + if line.strip(): + parts = line.strip().split() if len(parts) >= 2: count, month = parts[0], parts[1] est_refs = int(int(count) * 4.13) status = "**Agentic Inception (Gemini Era)**" if month == "2026-05" else "Active Curation" monthly_rows.append(f"| {month} | {count} | {est_refs:,} | {status} |") + # 8. Heart Stats Table + heart_stats = [ + "| Metric | Value |", + "| :--- | :--- |", + f"| **Total Technical Resources (Links)** | **{total_links}+** |", + f"| **Specialized MD Pages** | **{md_pages}** |", + f"| **Total Commits** | **{total_commits}+** |", + "| **Primary AI Engine** | **Google Gemini (Agentic)** |" + ] + return { - "total_links": total_links, - "md_pages": md_pages, - "total_commits": total_commits, + "heart_stats": "\n".join(heart_stats), "top_categories": "\n".join(top_categories_rows), "pillar_chart": pillar_chart, "lang_chart": lang_chart, - "annual_rows": "\n".join(annual_rows), - "monthly_rows": "\n".join(monthly_rows), + "annual_growth": "\n".join(annual_rows), + "monthly_surge": "\n".join(monthly_rows), "last_update": datetime.now().strftime("%Y-%m-%d") } +def replace_section(content, marker_name, new_text): + start_marker = f"" + end_marker = f"" + pattern = re.escape(start_marker) + r".*?" + re.escape(end_marker) + replacement = f"{start_marker}\n{new_text}\n{end_marker}" + return re.sub(pattern, replacement, content, flags=re.DOTALL) + def update_readme(stats): + if not os.path.exists("README.md"): + print("❌ README.md not found!") + return + with open("README.md", "r") as f: content = f.read() - # Update Heart Table - content = re.sub( - r"\| \*\*Total Technical Resources \(Links\)\*\* \| \*\*.*?\*\* \|", - f"| **Total Technical Resources (Links)** | **{stats['total_links']}+** |", - content - ) - content = re.sub( - r"\| \*\*Specialized MD Pages\*\* \| \*\*.*?\*\* \|", - f"| **Specialized MD Pages** | **{stats['md_pages']}** |", - content - ) - content = re.sub( - r"\| \*\*Total Commits\*\* \| \*\*.*?\*\* \|", - f"| **Total Commits** | **{stats['total_commits']}+** |", - content - ) + # Update sections using markers (Safest way) + content = replace_section(content, "HEART_STATS", stats["heart_stats"]) + content = replace_section(content, "TOP_CATEGORIES", stats["top_categories"]) + content = replace_section(content, "ANNUAL_GROWTH", stats["annual_growth"]) + content = replace_section(content, "MONTHLY_SURGE", stats["monthly_surge"]) + content = replace_section(content, "PILLAR_CHART", stats["pillar_chart"]) + content = replace_section(content, "SUB_ECO_CHART", stats["lang_chart"]) + + # Update date in the text content = re.sub( r"Stats as of .*?\)", f"Stats as of {stats['last_update']})", content ) - # Update Top Categories Table - categories_header = "| Category (Markdown Page) | Total Links |\n| :--- | :---: |" - content = re.sub( - r"\| Category \(Markdown Page\) \| Total Links \|\n\| :--- \| :---: \|\n(?:\| .*? \| .*? \|\n?)*", - f"{categories_header}\n{stats['top_categories']}\n", - content - ) - - # Update Pillar Chart - content = re.sub( - r"```mermaid\npie title Nubenetes Major Ecosystem Pillars\n.*?```", - f"```mermaid\n{stats['pillar_chart']}```", - content, - flags=re.DOTALL - ) - - # Replace specialized sub-chart with Language Chart (More useful for 2026) - content = re.sub( - r"#### 2. Deep Dive: Specialized Sub-ecosystems\nTo better.*?\n\n```mermaid\npie title Deep Dive: Specialized Sub-ecosystems\n.*?```", - f"#### 2. Global Linguistic Diversity\nReflecting Nubenetes' mission of global access while maintaining technical English as the primary interface.\n\n```mermaid\n{stats['lang_chart']}```", - content, - flags=re.DOTALL - ) - - # Update Annual Growth Table - annual_header = "| Year | Commits | Est. New Refs | Key Milestone |\n| :---: | :---: | :---: | :--- |" - content = re.sub( - r"\| Year \| Commits \| Est\. New Refs \| Key Milestone \|\n\| :---: \| :---: \| :---: \| :--- \|\n(?:\| .*? \| .*? \| .*? \| .*? \|\n?)*", - f"{annual_header}\n{stats['annual_rows']}\n", - content - ) - - # Update Monthly Surge Table - monthly_header = "| Month | Commits | Est. New Refs | Status |\n| :--- | :---: | :---: | :--- |" - content = re.sub( - r"\| Month \| Commits \| Est\. New Refs \| Status \|\n\| :--- \| :---: \| :---: \| :--- \|\n(?:\| .*? \| .*? \| .*? \| .*? \|\n?)*", - f"{monthly_header}\n{stats['monthly_rows']}\n", - content - ) - with open("README.md", "w") as f: f.write(content) if __name__ == "__main__": - stats = get_stats() - update_readme(stats) - print(f"README.md updated successfully with database-driven metrics.") + try: + stats = get_stats() + update_readme(stats) + print(f"README.md updated successfully with database-driven metrics (Marker-based).") + except Exception as e: + print(f"❌ Error updating README: {e}") + import traceback + traceback.print_exc()