fix(ops): solve ModuleNotFoundError in README sync and harden updater logic with markers

This commit is contained in:
Nubenetes Bot
2026-05-18 10:25:37 +02:00
parent 989c866681
commit c80aa4a2af
3 changed files with 60 additions and 94 deletions

View File

@@ -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

View File

@@ -209,11 +209,7 @@ pie title Linguistic Diversity (Global Access)
"French" : 155
"Others" : 467
```
<!-- PILLAR_CHART_END -->
---
## 3. The Agentic Stack
<!-- SUB_ECO_CHART_END -->
The autonomy of Nubenetes is powered by a modern, resilient tech stack that ensures 24/7 curation and maintenance.

View File

@@ -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"<!-- {marker_name}_START -->"
end_marker = f"<!-- {marker_name}_END -->"
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()