Files
awesome-kubernetes/src/readme_updater.py
Nubenetes Bot 4e87c248fd feat: implement AI-powered news digest engine, MkDocs UX overhaul, pipeline hardening, and stub page merges
- Add 26-category news digest engine (src/news_digest.py) with Gemini AI ranking
  for 3/6/12 month temporal panels across tech, cloud, and geo categories
- Add discovered_at, company, geo_region fields to inventory schema with backfill
  script populating 18K+ existing entries
- Fix critical v2-mkdocs.yml bug: plugins were nested under theme (silently disabled)
- Add MkDocs Material features: instant nav, breadcrumbs, footer, announce bar
- Add trending cards CSS grid and replace Agentic Pulse with dynamic Trending Now
- Generate tech-digest.md and industry-digest.md with tabbed 3/6/12 month views
- Merge 12 stub pages (<40 lines each) into parent categories with redirects
- Replace 50 bare except:pass patterns with contextual logging across all pipeline files
- Expand autonomous discovery from 6 to 14 GitHub search queries
- Add stale health re-check for online entries older than 30 days
- Track addition_method by source type (rss, twitter, github_trending)
- Add digest generation step to CI publish workflow

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 00:38:56 +02:00

215 lines
8.9 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import os
import re
import subprocess
import yaml
from datetime import datetime
from src.inventory_manager import load_inventory
# Unified Path Config
V1_DIR = "docs"
V2_DIR = "v2-docs"
def run_command(cmd):
try:
return subprocess.check_output(cmd, shell=True).decode('utf-8').strip()
except Exception as e:
print(f"[WARN] Command '{cmd[:60]}' failed: {str(e)[:100]}")
return "0"
def clean_text(text: str) -> str:
"""Strips emojis and ampersands for README compatibility."""
if not text: return ""
text = text.replace("&", "and")
text = re.sub(r'[\U00010000-\U0010ffff]', '', text) # Strip emojis
return text.strip()
def get_stats():
# 1. Load Inventory (The Source of Truth)
inventory = {}
try:
inventory = load_inventory()
except Exception as e:
print(f"[WARN] Failed to load inventory: {str(e)[:100]}")
# 2. Basic Metrics
total_links = len([u for u in inventory.keys() if not u.startswith("INTRO:")])
md_pages = len([f for f in os.listdir(V1_DIR) if f.endswith(".md")])
total_commits = run_command("git rev-list --count HEAD")
# 3. Density Map (Links per category)
category_counts = {}
for url, meta in inventory.items():
if url.startswith("INTRO:"): continue
locations = meta.get("v1_locations", [])
for loc in locations:
# Extract category name from docs/category.md
cat = loc.replace("docs/", "").replace(".md", "")
category_counts[cat] = category_counts.get(cat, 0) + 1
# Top 10 Table
top_10 = sorted(category_counts.items(), key=lambda x: x[1], reverse=True)[:10]
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. 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,
"Public/Private Cloud": 2500,
"CI/CD and GitOps": 2200,
"Infra as Code": 1200,
"SRE and Observability": 1000,
"Security and DevSecOps": 1000,
"Specialized Topics": total_links - 14400
}
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)
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 = ["| # | Year | Commits | Est. New Refs | Key Milestone |", "| :---: | :---: | :---: | :---: | :--- |"]
milestones = {
"2018": "**Munich Era (BMW IT-Zentrum)**",
"2019": "Early Growth and Open Source Launch",
"2020": "**The Great Expansion** (Global Pandemic/Remote Era)",
"2021": "Maturity and Standardization",
"2022": "Cloud Native Hardening",
"2023": "Maintenance & Refinement",
"2024": "Curation Strategy Pivot",
"2025": "Stability & Research Phase",
"2026": "**Agentic AI Surge** (May 2026 Inception)"
}
# Parse and sort chronologically (ascending)
growth_data = []
for line in annual_raw.split('\n'):
if line.strip():
parts = line.strip().split()
if len(parts) >= 2:
growth_data.append({"count": parts[0], "year": parts[1]})
growth_data.sort(key=lambda x: x["year"])
# Generate Bar Chart (Mandate 3: Metric Comparison)
max_val = max([int(int(item["count"]) * 4.13) for item in growth_data] + [int(item["count"]) for item in growth_data])
y_max = ((max_val // 1000) + 1) * 1000
annual_chart = "```mermaid\n---\nconfig:\n themeVariables:\n xyChart:\n plotColorPalette: '#3b82f6, #fb923c'\n theme: mc\n---\nxychart-beta\n title \"Nubenetes Annual Growth Metrics (20182026)\"\n"
years = [f'"{item["year"]}"' for item in growth_data]
commits = [item["count"] for item in growth_data]
refs = [str(int(int(item["count"]) * 4.13)) for item in growth_data]
annual_chart += f" x-axis [{', '.join(years)}]\n"
annual_chart += f" y-axis \"Volume (Commits / Estimated New Refs)\" 0 --> {y_max}\n"
annual_chart += f" bar [{', '.join(refs)}]\n"
annual_chart += f" bar [{', '.join(commits)}]\n"
annual_chart += "```"
for idx, item in enumerate(growth_data, 1):
year = item["year"]
count = item["count"]
est_refs = int(int(count) * 4.13)
milestone = milestones.get(year, "Continuing Evolution")
annual_rows.append(f"| {idx} | {year} | {count} | {est_refs:,} | {milestone} |")
# 7. Monthly Surge (2026)
monthly_raw = run_command("git log --format='%ad' --date=format:'%Y-%m' | grep '2026' | sort | uniq -c")
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} |")
# 7. Efficiency Chart (Section 7.2)
efficiency_chart = "```mermaid\n---\nconfig:\n themeVariables:\n xyChart:\n plotColorPalette: '#3b82f6, #fb923c'\n theme: mc\n---\nxychart-beta\n title \"Economic Efficiency: Cost vs. Volume Share (%)\"\n"
efficiency_chart += " x-axis [\"Elite / New AI\", \"Bulk / Cached\", \"Infra / Local\"]\n"
efficiency_chart += " y-axis \"Share (%)\" 0 --> 100\n"
efficiency_chart += " bar [75, 15, 10]\n"
efficiency_chart += " bar [10, 25, 65]\n"
efficiency_chart += "```"
# 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 {
"heart_stats": "\n".join(heart_stats),
"top_categories": "\n".join(top_categories_rows),
"pillar_chart": pillar_chart,
"lang_chart": lang_chart,
"annual_growth": "\n".join(annual_rows),
"annual_chart": annual_chart,
"monthly_surge": "\n".join(monthly_rows),
"efficiency_chart": efficiency_chart,
"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 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, "ANNUAL_CHART", stats["annual_chart"])
content = replace_section(content, "MONTHLY_SURGE", stats["monthly_surge"])
content = replace_section(content, "EFFICIENCY_CHART", stats["efficiency_chart"])
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
)
with open("README.md", "w") as f:
f.write(content)
if __name__ == "__main__":
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()