mirror of
https://github.com/nubenetes/awesome-kubernetes.git
synced 2026-08-25 07:57:25 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ae118d85b | ||
|
|
9c4adeb5ac | ||
|
|
1dd0dab308 | ||
|
|
e599793928 | ||
|
|
7f5fbe34ee | ||
|
|
119bd4158f | ||
|
|
a355889969 | ||
|
|
cf717c67ce | ||
|
|
512b37287e | ||
|
|
23256352a7 | ||
|
|
101b341200 | ||
|
|
1b56b77cbf | ||
|
|
655ad7d5ed | ||
|
|
29e2593e42 | ||
|
|
1e89f8af00 | ||
|
|
1a092a5af2 | ||
|
|
f4208bca4d | ||
|
|
90d04e6d6a | ||
|
|
7d845fab3e | ||
|
|
fbfa85c2d3 | ||
|
|
dc4591fb3e | ||
|
|
96706aff90 | ||
|
|
f3cb3a1ff3 | ||
|
|
cc6633dcdd | ||
|
|
759c10ad2c | ||
|
|
58601de338 | ||
|
|
11ebda6187 | ||
|
|
227dc23933 | ||
|
|
56db4d5e53 | ||
|
|
e332af2ea8 | ||
|
|
9d3321d0f0 | ||
|
|
9ed1318a9c | ||
|
|
91da9c8495 | ||
|
|
c9a7b2a8cb | ||
|
|
ad59cb75f0 | ||
|
|
88f891fd4e | ||
|
|
61733937be | ||
|
|
10c70025a2 | ||
|
|
cff4903166 | ||
|
|
16ad26f427 | ||
|
|
3f3a86b62c | ||
|
|
239dd05e6f | ||
|
|
932770b89b | ||
|
|
c8ca878f66 | ||
|
|
64c6e9ea18 | ||
|
|
53b19f1b09 | ||
|
|
f6e60a6487 | ||
|
|
5bfcaf1b03 | ||
|
|
b8144d9253 | ||
|
|
b321169459 | ||
|
|
34610f4671 | ||
|
|
f50fdffd64 | ||
|
|
f5fae99a38 | ||
|
|
ebae3c6e27 | ||
|
|
a8941afd24 | ||
|
|
f4fb5fe19b | ||
|
|
f46886abf3 | ||
|
|
6ccda021e2 | ||
|
|
3f44948119 | ||
|
|
fe024b4fd7 | ||
|
|
bdcedd6950 | ||
|
|
6769fff528 | ||
|
|
5b00ed4824 | ||
|
|
7e85ec6bdd | ||
|
|
dc0e090318 | ||
|
|
6f34ac8569 | ||
|
|
5d7f58c7fa | ||
|
|
52e2560fa0 | ||
|
|
41207ca0f0 | ||
|
|
b5b3dc3ce2 | ||
|
|
f221647646 | ||
|
|
099a40d6b1 | ||
|
|
74cbc47e2e | ||
|
|
f90d66478f | ||
|
|
d846c6f336 | ||
|
|
01634bceb2 | ||
|
|
d456128366 | ||
|
|
13d51e8fa9 | ||
|
|
87809870c5 | ||
|
|
e1b49ab4b5 | ||
|
|
57f9c618f1 | ||
|
|
65ce209183 | ||
|
|
fdd9eac809 | ||
|
|
059b4de3e8 | ||
|
|
a11bfe4ced | ||
|
|
9cb73cadcc | ||
|
|
05b66d006f | ||
|
|
3dc52af391 | ||
|
|
8b04851097 | ||
|
|
782dc1bd06 | ||
|
|
1149a9c8f7 | ||
|
|
4e87c248fd | ||
|
|
51e8ec8cfa | ||
|
|
138b63c69f | ||
|
|
95c7078e6a | ||
|
|
cba67c689c | ||
|
|
e527be5189 | ||
|
|
1cd243ee58 | ||
|
|
0c3ae9661c | ||
|
|
0f53474aa9 | ||
|
|
12402e089d | ||
|
|
871bd30e75 | ||
|
|
78f5600e75 | ||
|
|
d7c9162cb8 | ||
|
|
24b5bbc6a2 | ||
|
|
42442a1a6c | ||
|
|
5fe5431c53 |
@@ -178,6 +178,21 @@ jobs:
|
||||
gh workflow run 01.1.agentic_cron.yml -f historical_mode=true -f historical_chunked=true -f historical_until_date=$NEXT_DATE
|
||||
fi
|
||||
|
||||
- name: Run Deduplication Scan
|
||||
if: success()
|
||||
env:
|
||||
PYTHONPATH: .
|
||||
run: |
|
||||
python -u -c "import asyncio; from src.dedup import run_dedup; asyncio.run(run_dedup(dry_run=False))" || echo "Dedup scan skipped"
|
||||
|
||||
- name: Run Enrichment Pipeline
|
||||
if: success()
|
||||
env:
|
||||
PYTHONPATH: .
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
python -u -m src.enrichment || echo "Enrichment pipeline skipped"
|
||||
|
||||
- name: Upload Visual Dashboard Artifact
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v7
|
||||
|
||||
@@ -61,9 +61,9 @@ jobs:
|
||||
run: |
|
||||
git config --global user.name "Nubenetes Bot"
|
||||
git config --global user.email "bot@nubenetes.com"
|
||||
git add data/inventory.yaml
|
||||
git add data/inventory.yaml data/inventory.sql
|
||||
if git diff --staged --quiet; then
|
||||
echo "No changes in inventory.yaml to commit."
|
||||
echo "No changes in inventory to commit."
|
||||
else
|
||||
git commit -m "chore: update inventory stars and licenses [skip ci]"
|
||||
git pull origin develop --rebase
|
||||
|
||||
@@ -75,7 +75,7 @@ jobs:
|
||||
run: |
|
||||
git config --global user.name "Nubenetes Bot"
|
||||
git config --global user.email "bot@nubenetes.com"
|
||||
git add data/inventory.yaml
|
||||
git add data/inventory.yaml data/inventory.sql
|
||||
if git diff --staged --quiet; then
|
||||
echo "No changes in AI analysis to commit."
|
||||
else
|
||||
|
||||
@@ -85,7 +85,7 @@ jobs:
|
||||
run: |
|
||||
git config --global user.name "Nubenetes Bot"
|
||||
git config --global user.email "bot@nubenetes.com"
|
||||
git add data/inventory.yaml v2-docs/videos/
|
||||
git add data/inventory.yaml data/inventory.sql v2-docs/videos/
|
||||
if git diff --staged --quiet; then
|
||||
echo "No automated changes to commit."
|
||||
else
|
||||
|
||||
@@ -60,7 +60,20 @@ jobs:
|
||||
- name: Installation of Dependencies
|
||||
run: |
|
||||
pip install --no-cache-dir pydantic PyGithub httpx fake-useragent pytz python-dotenv pyyaml tenacity
|
||||
|
||||
|
||||
- name: Get current date for digest cache key
|
||||
id: digest-date
|
||||
run: echo "date=$(date -u +%Y-%m-%d)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Restore News Digest Cache
|
||||
id: cache-digest
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
path: data/news_digest.json
|
||||
key: news-digest-${{ steps.digest-date.outputs.date }}
|
||||
restore-keys: |
|
||||
news-digest-
|
||||
|
||||
- name: Execute Video Portal Generator
|
||||
env:
|
||||
PYTHONPATH: ${{ github.workspace }}
|
||||
@@ -73,6 +86,41 @@ jobs:
|
||||
run: |
|
||||
python src/reorganize_mosaic.py
|
||||
|
||||
- name: Run Deduplication Scan
|
||||
env:
|
||||
PYTHONPATH: ${{ github.workspace }}
|
||||
run: |
|
||||
python -u -c "import asyncio; from src.dedup import run_dedup; asyncio.run(run_dedup(dry_run=False))" || echo "Dedup scan skipped"
|
||||
|
||||
- name: Run Enrichment Pipeline
|
||||
env:
|
||||
PYTHONPATH: ${{ github.workspace }}
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
python -u -m src.enrichment || echo "Enrichment pipeline skipped (no token or error)"
|
||||
|
||||
- name: Generate News Digest
|
||||
if: steps.cache-digest.outputs.cache-hit != 'true'
|
||||
env:
|
||||
PYTHONPATH: ${{ github.workspace }}
|
||||
GEMINI_API_KEY_1: ${{ secrets.GEMINI_API_KEY_1 }}
|
||||
GEMINI_API_KEY_2: ${{ secrets.GEMINI_API_KEY_2 }}
|
||||
run: |
|
||||
python -u -m src.news_digest || echo "News digest generation skipped (no API key or error)"
|
||||
|
||||
- name: Generate RSS Feed
|
||||
env:
|
||||
PYTHONPATH: ${{ github.workspace }}
|
||||
run: |
|
||||
python -u -m src.rss_generator || echo "RSS generation skipped"
|
||||
|
||||
- name: Save News Digest Cache
|
||||
if: steps.cache-digest.outputs.cache-hit != 'true' && hashFiles('data/news_digest.json') != ''
|
||||
uses: actions/cache/save@v5
|
||||
with:
|
||||
path: data/news_digest.json
|
||||
key: news-digest-${{ steps.digest-date.outputs.date }}
|
||||
|
||||
- name: Run V2 Publisher (Render-Only)
|
||||
env:
|
||||
PYTHONPATH: ${{ github.workspace }}
|
||||
|
||||
@@ -49,6 +49,19 @@ jobs:
|
||||
- name: Assemble Final Site
|
||||
run: |
|
||||
mkdir -p site/
|
||||
|
||||
# Safety check: V2 must have a valid index.html and at least 50 pages
|
||||
V2_PAGE_COUNT=$(find site_v2/ -name "*.html" | wc -l)
|
||||
if [ "$V2_PAGE_COUNT" -lt 50 ] || [ ! -f site_v2/index.html ]; then
|
||||
echo "❌ V2 build looks broken (only $V2_PAGE_COUNT HTML pages). Deploying V1 only to protect the site."
|
||||
cp -r site_v1/* site/
|
||||
mkdir -p site/v1/
|
||||
cp -r site_v1/* site/v1/
|
||||
rm -rf site_v1/ site_v2/
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "✅ V2 build OK ($V2_PAGE_COUNT pages). Assembling dual-version site."
|
||||
# Copy V1 to root as fallback for any pages not present in V2
|
||||
cp -r site_v1/* site/
|
||||
# Overwrite root with V2 Elite Portal (matching pages will be V2 versions)
|
||||
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
run: |
|
||||
git config --global user.name "nubenetes-bot"
|
||||
git config --global user.email "bot@nubenetes.com"
|
||||
git add docs/ v2-docs/ README.md data/inventory.yaml src/memory/
|
||||
git add docs/ v2-docs/ README.md data/inventory.yaml data/inventory.sql src/memory/
|
||||
if ! git diff --cached --quiet; then
|
||||
git commit -m "style(cleaner): auto-correcting formatting & URL normalization"
|
||||
git push origin HEAD:${{ github.event.pull_request.head.ref }} || echo "⚠️ Push failed (likely fork permission limit)."
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
name: 09. Weekly Intelligence Digest
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Every Monday at 06:00 UTC (08:00 Madrid time)
|
||||
- cron: '0 6 * * 1'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
concurrency:
|
||||
group: develop-git-write-lock
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
weekly-digest:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Repository Synchronization
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: develop
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Python 3.11 Environment Provisioning
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.11'
|
||||
cache: 'pip'
|
||||
|
||||
- name: Install Dependencies
|
||||
run: pip install -r requirements.txt
|
||||
|
||||
- name: Get current date for digest cache key
|
||||
id: digest-date
|
||||
run: echo "date=$(date -u +%Y-%m-%d)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Restore News Digest Cache
|
||||
id: cache-digest
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
path: data/news_digest.json
|
||||
key: news-digest-${{ steps.digest-date.outputs.date }}
|
||||
restore-keys: |
|
||||
news-digest-
|
||||
|
||||
- name: Generate News Digest (Gemini)
|
||||
if: steps.cache-digest.outputs.cache-hit != 'true'
|
||||
env:
|
||||
PYTHONPATH: ${{ github.workspace }}
|
||||
GEMINI_API_KEY_1: ${{ secrets.GEMINI_API_KEY_1 }}
|
||||
GEMINI_API_KEY_2: ${{ secrets.GEMINI_API_KEY_2 }}
|
||||
run: |
|
||||
python -u -m src.news_digest
|
||||
|
||||
- name: Save News Digest Cache
|
||||
if: steps.cache-digest.outputs.cache-hit != 'true' && hashFiles('data/news_digest.json') != ''
|
||||
uses: actions/cache/save@v5
|
||||
with:
|
||||
path: data/news_digest.json
|
||||
key: news-digest-${{ steps.digest-date.outputs.date }}
|
||||
|
||||
- name: Generate RSS Feed
|
||||
env:
|
||||
PYTHONPATH: ${{ github.workspace }}
|
||||
run: |
|
||||
python -u -m src.rss_generator || echo "RSS generation skipped"
|
||||
|
||||
- name: Render V2 Portal (Digest Pages Only)
|
||||
env:
|
||||
PYTHONPATH: ${{ github.workspace }}
|
||||
PYTHONUNBUFFERED: "1"
|
||||
run: |
|
||||
python -u -m src.v2_optimizer --render-only
|
||||
|
||||
- name: Commit and Push Weekly Digest
|
||||
run: |
|
||||
git config --global user.name "Nubenetes Bot"
|
||||
git config --global user.email "bot@nubenetes.com"
|
||||
git add data/news_digest.json v2-docs/tech-digest.md v2-docs/industry-digest.md v2-docs/feed.xml || true
|
||||
if git diff --staged --quiet; then
|
||||
echo "No digest changes to commit."
|
||||
else
|
||||
git commit -m "feat: weekly intelligence digest update [skip ci]"
|
||||
for i in {1..3}; do
|
||||
git pull origin develop --rebase && git push origin develop && break || sleep 10
|
||||
done
|
||||
fi
|
||||
|
||||
- name: Trigger V2 Publisher
|
||||
uses: peter-evans/repository-dispatch@v3
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
event-type: weekly-digest-ready
|
||||
@@ -351,6 +351,7 @@ MigrationBackup/
|
||||
# Automatización Nubenetes
|
||||
src/__pycache__/
|
||||
*.json
|
||||
!data/news_digest.json
|
||||
.env
|
||||
nubenetes_agent_env/
|
||||
.venv/
|
||||
|
||||
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [[2.5.8]](https://github.com/nubenetes/awesome-kubernetes/releases/tag/v2.5.8) - 2026-06-18
|
||||
|
||||
### Added
|
||||
- **Debate & Ingestion Documentation**: Documented the Fast-Pass screening evaluator, Twikit toggle, failure-aware domain timeouts, mobile identity rotation, and tag pagination limits in `README.md`.
|
||||
- **Consensus Flow Visualization**: Redesigned the multi-agent consensus protocol Mermaid diagram in `README.md` to map the Fast-Pass screening logic.
|
||||
|
||||
## [[2.5.7]](https://github.com/nubenetes/awesome-kubernetes/releases/tag/v2.5.7) - 2026-06-18
|
||||
|
||||
### Added
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
# CLAUDE.md — Nubenetes Project Instructions for Claude Code
|
||||
|
||||
## Git Workflow: Gitflow
|
||||
|
||||
This repository uses **Gitflow**. All agents MUST follow this branching model:
|
||||
|
||||
### Branches
|
||||
- **`master`** — Production. Only receives merges from release branches. Every merge to master gets a **tag** and a **GitHub Release**.
|
||||
- **`develop`** — Integration branch. All feature branches merge here first.
|
||||
- **`feat/*`** — Feature branches. Created from `develop`, merged back to `develop` via PR.
|
||||
- **`release/vX.Y.Z`** — Release branches. Created from `develop` when ready to release, merged to both `master` AND back to `develop`.
|
||||
- **`gh-pages`** — Deployment. Never touch directly.
|
||||
|
||||
### Release Process (mandatory for all releases)
|
||||
1. Create feature branch from `develop`: `git checkout -b feat/description develop`
|
||||
2. Implement changes, commit, push feature branch
|
||||
3. Create PR: `gh pr create --base develop --head feat/description`
|
||||
4. Merge PR to develop: `gh pr merge N --merge`
|
||||
5. Create release branch: `git checkout -b release/vX.Y.Z develop`
|
||||
6. Merge release to master: `git checkout master && git merge release/vX.Y.Z --no-ff`
|
||||
7. Tag: `git tag -a vX.Y.Z -m "description"`
|
||||
8. Push master + tag: `git push origin master && git push origin vX.Y.Z`
|
||||
9. Back-merge master to develop: `git checkout develop && git merge master --no-ff && git push origin develop`
|
||||
10. Create GitHub Release: `gh release create vX.Y.Z --title "..." --notes "..."`
|
||||
|
||||
### Versioning
|
||||
- Current: `v2.6.0`
|
||||
- Format: `v{major}.{minor}.{patch}`
|
||||
- Major: breaking changes or architectural shifts
|
||||
- Minor: new features (like the digest engine, new modules)
|
||||
- Patch: bug fixes, config tweaks
|
||||
|
||||
### Protected Branches
|
||||
`master`, `develop`, and `gh-pages` are NEVER deleted. Branch cleanup runs bi-monthly for merged feature branches.
|
||||
|
||||
## Repository Structure
|
||||
|
||||
### Key Directories
|
||||
- `docs/` — V1 source (exhaustive archive, source of truth)
|
||||
- `v2-docs/` — V2 source (AI-curated elite portal, derived from V1)
|
||||
- `src/` — Python pipeline source code
|
||||
- `data/` — Inventory (YAML + SQL), config files, digest JSON
|
||||
- `scripts/` — Utility scripts (backfill, etc.)
|
||||
- `.github/workflows/` — CI/CD (15 workflows)
|
||||
|
||||
### Key Config Files
|
||||
- `v2-mkdocs.yml` — V2 MkDocs Material configuration
|
||||
- `mkdocs.yml` — V1 MkDocs configuration
|
||||
- `data/inventory.yaml` / `data/inventory.sql` — Unified inventory (18K+ entries)
|
||||
- `data/curation_sources.yaml` — RSS feeds and X/Twitter accounts
|
||||
- `data/link_rules.yaml` — Curation policies
|
||||
- `GEMINI.md` — AI mandates and learning roadmap (read by Gemini agents)
|
||||
|
||||
### Pipeline Modules
|
||||
- `src/v2_optimizer.py` — Main rendering engine (V2VisionEngine class)
|
||||
- `src/news_digest.py` — 26-category temporal digest with Gemini ranking
|
||||
- `src/enrichment.py` — CNCF Landscape + GitHub activity + license detection
|
||||
- `src/dedup.py` — URL/hash/title deduplication engine
|
||||
- `src/agentic_curator.py` — Ingestion pipeline with AI evaluation
|
||||
- `src/autonomous_discovery.py` — GitHub trending discovery (14 queries)
|
||||
- `src/gemini_utils.py` — Gemini API wrapper with key rotation
|
||||
- `src/inventory_manager.py` — Dual YAML+SQL inventory management
|
||||
|
||||
## Build and Run
|
||||
|
||||
### Local testing (no API keys needed)
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
mkdocs serve -f v2-mkdocs.yml # Preview V2 portal
|
||||
mkdocs serve -f mkdocs.yml # Preview V1 portal
|
||||
```
|
||||
|
||||
### Pipeline commands
|
||||
```bash
|
||||
python3 -m scripts.backfill_discovered_at # Backfill discovered_at field
|
||||
python3 -m src.news_digest # Generate digest (needs Gemini API key)
|
||||
python3 -m src.enrichment # CNCF + GitHub enrichment (needs GH_TOKEN)
|
||||
python3 -m src.dedup # Dedup scan (dry-run by default)
|
||||
python3 -m src.v2_optimizer --render-only # Render V2 portal (no AI calls)
|
||||
```
|
||||
|
||||
## Coding Conventions
|
||||
- All Python exceptions must use `except Exception as e: log_event(f"[WARN] context: {str(e)[:100]}")` — never bare `except: pass`
|
||||
- Use `from src.logger import log_event` for logging
|
||||
- Use `from src.config import MADRID_TZ` for timezone-aware timestamps
|
||||
- Inventory fields: `discovered_at` (ISO), `last_ai_eval` (ISO), `company`, `geo_region` must be preserved during merges
|
||||
- The `update_inventory_entry()` function preserves `discovered_at` — never overwrite it with new data
|
||||
|
||||
## URL Policy: Clean URLs, No .html Suffix
|
||||
Both V1 and V2 MUST use `use_directory_urls: true` in their mkdocs.yml. This produces clean URLs like `/kubernetes/` instead of `/kubernetes.html`. **NEVER** enable the `offline` plugin — it forces `.html` suffixes on all URLs, breaking SEO and existing deep-links. This is a hard rule.
|
||||
|
||||
## RSS/Twitter Sources
|
||||
RSS feeds are limited to those that actually work (many block bots). Don't add new RSS feeds without testing. Current working feeds are defined in `data/curation_sources.yaml`.
|
||||
@@ -376,4 +376,31 @@ The bot must rotate between profiles to avoid detection:
|
||||
- **V2 Index Metrics Protocol**: The "Knowledge Architecture and AI Coverage Status" report in the V2 index MUST include a direct comparison between V1 and V2 inventory. This report MUST display: 1. **V1 Base Inventory** (Total resources in the master archive), 2. **V2 Elite Selection** (Count of candidates and the resulting density ratio), 3. **AI Enrichment Coverage**, and 4. **GitHub Metadata Coverage**. This ensures transparency in the knowledge distillation process.
|
||||
- **Redundancy-Free Branding**: To ensure professional UI density, the V2 Portal header MUST NOT repeat the "Nubenetes" brand. The title MUST follow the pattern: "Nubenetes Elite Portal (V2) | Awesome Kubernetes and Cloud".
|
||||
- **Decoupled Workflow Architecture**: The Agentic V2 ecosystem MUST utilize a decoupled micro-workflow structure (Health Monitor, Metadata Engine, AI Curator, and Publisher) to optimize compute quotas and minimize Gemini token consumption. Any update to the V2 rendering logic MUST use the `--render-only` flag in the Publisher pipeline to maintain execution speed.
|
||||
to maintain execution speed.
|
||||
|
||||
## Git Workflow: Gitflow (Mandatory)
|
||||
|
||||
This repository uses **Gitflow**. All agents and automated processes MUST follow this branching model:
|
||||
|
||||
- **`master`**: Production branch. Only receives merges from `release/*` branches. Every merge to master gets a **semantic version tag** (`vX.Y.Z`) and a **GitHub Release** with detailed release notes.
|
||||
- **`develop`**: Integration branch. All feature branches merge here via PR. Back-merged from master after each release.
|
||||
- **`feat/*`**: Feature branches. Created from `develop`, merged back to `develop` via PR.
|
||||
- **`release/vX.Y.Z`**: Release branches. Created from `develop`, merged to `master` with `--no-ff`, then back-merged to `develop`.
|
||||
- **`gh-pages`**: Deployment. NEVER modified directly.
|
||||
- **Protected branches**: `master`, `develop`, `gh-pages` — NEVER deleted.
|
||||
|
||||
### Release Sequence
|
||||
1. Feature branch → PR to `develop` → merge
|
||||
2. Create `release/vX.Y.Z` from `develop`
|
||||
3. Merge release to `master` (`--no-ff`)
|
||||
4. Create annotated tag: `git tag -a vX.Y.Z -m "..."`
|
||||
5. Push master + tag
|
||||
6. Back-merge master to develop (`--no-ff`)
|
||||
7. Create GitHub Release with `gh release create`
|
||||
|
||||
### Versioning: `v{major}.{minor}.{patch}`
|
||||
- **Major**: breaking changes or architectural shifts
|
||||
- **Minor**: new features (modules, digest categories, new pipelines)
|
||||
- **Patch**: bug fixes, config tweaks, content updates
|
||||
|
||||
## URL Policy: Clean URLs (Mandatory)
|
||||
Both V1 (`mkdocs.yml`) and V2 (`v2-mkdocs.yml`) MUST use `use_directory_urls: true` to produce clean URLs like `/kubernetes/` instead of `/kubernetes.html`. **NEVER** enable the MkDocs `offline` plugin — it forces `.html` suffixes on all URLs, breaking SEO authority and thousands of existing deep-links. This is a hard, non-negotiable rule.
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
* [5.4. The Incremental Elite Engine](#54-the-incremental-elite-engine)
|
||||
* [5.5. Decoupled Knowledge Lifecycle (V2 Architecture)](#55-decoupled-knowledge-lifecycle-v2-architecture)
|
||||
* [5.6. Multi-Language Support Policy](#56-multi-language-support-policy)
|
||||
6. [6. The Unified Agentic Database (Knowledge Graph)](#6-the-unified-agentic-database-knowledge-graph)
|
||||
6. [6. The Unified Agentic Database (Coexistence Knowledge Graph)](#6-the-unified-agentic-database-coexistence-knowledge-graph)
|
||||
* [6.1. Database Components](#61-database-components)
|
||||
* [6.2. The 'Database-First' Reasoning Protocol (Zero-Redundancy)](#62-the-database-first-reasoning-protocol-zero-redundancy)
|
||||
* [6.3. Database Lifecycle and Hygiene](#63-database-lifecycle-and-hygiene)
|
||||
@@ -135,14 +135,14 @@ Additionally, as of May 2026, Nubenetes has reached the **Platinum Operational T
|
||||
## 2. Repository Metrics and Evolution
|
||||
|
||||
### 2.1. The "Heart" of Nubenetes
|
||||
(Stats as of 2026-06-18)
|
||||
(Stats as of 2026-06-19)
|
||||
|
||||
<!-- HEART_STATS_START -->
|
||||
| Metric | Value |
|
||||
| :--- | :--- |
|
||||
| **Total Technical Resources (Links)** | **18647+** |
|
||||
| **Specialized MD Pages** | **162** |
|
||||
| **Total Commits** | **5990+** |
|
||||
| **Total Commits** | **6082+** |
|
||||
| **Primary AI Engine** | **Google Gemini (Agentic)** |
|
||||
<!-- HEART_STATS_END -->
|
||||
|
||||
@@ -180,7 +180,7 @@ The growth of Nubenetes reflects the acceleration of the Cloud Native ecosystem.
|
||||
| 6 | 2023 | 30 | 123 | Maintenance & Refinement |
|
||||
| 7 | 2024 | 53 | 218 | Curation Strategy Pivot |
|
||||
| 8 | 2025 | 5 | 20 | Stability & Research Phase |
|
||||
| 9 | 2026 | 2431 | 10,040 | **Agentic AI Surge** (May 2026 Inception) |
|
||||
| 9 | 2026 | 2523 | 10,419 | **Agentic AI Surge** (May 2026 Inception) |
|
||||
<!-- ANNUAL_GROWTH_END -->
|
||||
|
||||
<!-- ANNUAL_CHART_START -->
|
||||
@@ -196,8 +196,8 @@ xychart-beta
|
||||
title "Nubenetes Annual Growth Metrics (2018–2026)"
|
||||
x-axis ["2018", "2019", "2020", "2021", "2022", "2023", "2024", "2025", "2026"]
|
||||
y-axis "Volume (Commits / Estimated New Refs)" 0 --> 11000
|
||||
bar [1445, 586, 8449, 2193, 1660, 123, 218, 20, 10040]
|
||||
bar [350, 142, 2046, 531, 402, 30, 53, 5, 2431]
|
||||
bar [1445, 586, 8449, 2193, 1660, 123, 218, 20, 10419]
|
||||
bar [350, 142, 2046, 531, 402, 30, 53, 5, 2523]
|
||||
```
|
||||
<!-- ANNUAL_CHART_END -->
|
||||
|
||||
@@ -207,7 +207,7 @@ xychart-beta
|
||||
| :--- | :---: | :---: | :--- |
|
||||
| 2026-04 | 25 | 103 | Active Curation |
|
||||
| 2026-05 | 2101 | 8,677 | **Agentic Inception (Gemini Era)** |
|
||||
| 2026-06 | 305 | 1,259 | Active Curation |
|
||||
| 2026-06 | 397 | 1,639 | Active Curation |
|
||||
<!-- MONTHLY_SURGE_END -->
|
||||
|
||||
### 2.4. Content Distribution and Semantic Clustering
|
||||
@@ -268,6 +268,10 @@ The autonomy of Nubenetes is powered by a modern, resilient tech stack that ensu
|
||||
| **Discovery** | Twikit and Playwright | Autonomous scraping and account rotation. |
|
||||
| **Resilience** | Identity Rotation | Evasion of anti-bot blocks using multiple profiles. |
|
||||
| **Deployment** | MkDocs Material & Native GH Pages | High-performance static site generation via native artifact deployment. |
|
||||
| **Intelligence** | News Digest Engine | AI-powered temporal digest across 26 categories (3/6/12 months). |
|
||||
| **Enrichment** | CNCF + GitHub Activity | Landscape graduation status, issue/PR velocity, license change detection. |
|
||||
| **Dedup** | Similarity Engine | URL, content-hash, and title-similarity deduplication (85% threshold). |
|
||||
| **Offline** | PWA Support | Service Worker caching for offline reading of the portal. |
|
||||
|
||||
---
|
||||
|
||||
@@ -356,7 +360,7 @@ graph TD
|
||||
style C fill:#f9f,stroke:#333,stroke-width:2px
|
||||
```
|
||||
|
||||
#### Shared Curation & Data Policies (`GEMINI.md`)
|
||||
#### Shared Curation and Data Policies (`GEMINI.md`)
|
||||
* **Target**: Ephemeral CI/CD runners (GitHub Actions) and local coding assistants.
|
||||
* **Purpose**: Dictates *what* the repository structure, link formatting, language metadata tagging, and minimum quality levels must look like.
|
||||
* **Automation integration**: Ingested by the build scripts to programmatically construct system prompts for API LLM completions.
|
||||
@@ -394,11 +398,51 @@ Nubenetes operates with two distinct editions to serve different engineering nee
|
||||
- **No stars**: Standard reference documentation and technical resources.
|
||||
- **Multi-Dimensional Tagging (1:N):** Every resource is classified with multiple semantic tags (e.g., `[DE FACTO STANDARD]`, `[GUIDE]`, `[CASE STUDY]`, `[EMERGING]`) providing deep technical context and maturity status.
|
||||
- **Minimalist Inline Summaries**: Resources feature a **"Deep-Dive"** inline tag (using native HTML5 `<details>`) that expands into a rich technical summary without consuming space when collapsed. These summaries use the **Double-Evidence Synthesis** protocol to provide verified architectural insights.
|
||||
- **Semantic Cross-Linking:** The portal autonomously identifies and links related categories within the same strategic dimension (e.g., suggesting `Flux` when reading about `Argo`), creating a cohesive **Industrial Knowledge Graph**.
|
||||
- **Semantic Cross-Linking:** The portal autonomously identifies and links related categories within the same strategic dimension (e.g., suggesting `Flux` when reading about `Argo`), creating a cohesive **Industrial Knowledge Graph**. Additionally, **cross-dimension "See Also" links** connect pages that share technical tags across different dimensions.
|
||||
- **Executive Context**: Every strategic dimension features an AI-generated **State-of-the-Art Introduction** providing high-level architectural context and industry direction before the link listings.
|
||||
- **Source of Truth:** The `v2-docs/` directory (Derived from V1).
|
||||
- **Deployment:** [nubenetes.com/v2/](https://nubenetes.com/v2)
|
||||
|
||||
#### V2 Intelligence Digest (June 2026)
|
||||
The V2 portal includes an **AI-powered Intelligence Digest** system that surfaces the most relevant resources from the last 3, 6, and 12 months across **26 curated categories**:
|
||||
|
||||
| Category Group | Categories |
|
||||
| :--- | :--- |
|
||||
| **Tech Core (9)** | Kubernetes & Orchestration, Containers & Runtime, Networking & Service Mesh, Architecture & Microservices, Data/Messaging/Storage, AI & Agents, MLOps & Data Science, Python/Java/Dev Ecosystem, Linux & System Foundations |
|
||||
| **Platform & Ops (8)** | Security & Compliance, Infrastructure as Code, CI/CD & GitOps, Observability/SRE/Testing, DevOps & Culture, Platform Engineering & DevEx, FinOps & Cloud Cost, Certification & Training |
|
||||
| **Cloud & Enterprise (5)** | AWS, Azure, GCP/OCI/Others, OpenShift/Red Hat, Virtualization & Private Cloud (VMware/Broadcom, Proxmox, Nutanix, KubeVirt) |
|
||||
| **Industry / Geo (4)** | Americas, Europe, Spain, Asia-Pacific |
|
||||
|
||||
**Key features:**
|
||||
- **Trending Now** cards on the index page with the top cross-category items ranked by Gemini AI
|
||||
- **Dedicated digest pages** (`tech-digest.md`, `industry-digest.md`) with tabbed 3/6/12 month views
|
||||
- **Temporal tracking** via `discovered_at` field on all 18,000+ inventory entries
|
||||
- **Company & geo-region classification** extracted by Gemini during ingestion for industry digest
|
||||
- **Automatic staleness detection**: entries enriched >6 months ago are re-evaluated by AI (`last_ai_eval`)
|
||||
|
||||
#### V2 Data Quality and Pipeline Hardening (June 2026)
|
||||
- **CNCF Landscape Integration** (`src/enrichment.py`): Auto-fetches graduation status (Sandbox/Incubating/Graduated/Archived) for CNCF projects to power maturity tags.
|
||||
- **GitHub Activity Enrichment**: Fetches issue/PR velocity and assigns community health scores (active/healthy/low/dormant).
|
||||
- **License Change Detection**: Compares stored licenses with current GitHub data, flagging high-impact changes (e.g., BSL, SSPL switches).
|
||||
- **Deduplication Engine** (`src/dedup.py`): URL normalization, content-hash matching, and title-similarity detection (85% threshold) to eliminate duplicate entries.
|
||||
- **Exception Observability**: All 50+ bare `except: pass` patterns across the pipeline replaced with contextual logging.
|
||||
- **Expanded Discovery**: Autonomous GitHub trending discovery expanded from 6 to 14 search queries covering DevOps, observability, security, IaC, databases, CI/CD, service mesh, and platform engineering.
|
||||
- **Stale Health Re-check**: Online entries older than 30 days are automatically re-validated instead of being skipped.
|
||||
|
||||
#### V2 MkDocs Material Enhancements (June 2026)
|
||||
- **Instant Navigation** with prefetch for SPA-like experience across 140+ pages
|
||||
- **Breadcrumbs** (`navigation.path`) for orientation in deep category hierarchies
|
||||
- **Announcement Bar** promoting the Intelligence Digest
|
||||
- **Tags Plugin** for native clickable cross-page tag navigation
|
||||
- **RSS Feed** for digest page subscription
|
||||
- **PWA/Offline Support** for cached offline reading
|
||||
- **Minify Plugin** for production HTML optimization
|
||||
- **12 Stub Pages Merged** into parent categories with automatic redirects (e.g., `react.md` → `javascript.md`, `chef.md` → `ansible.md`, `oauth.md` → `securityascode.md`)
|
||||
|
||||
#### V2 URL Policy (June 2026)
|
||||
- **Clean URLs enforced**: Both V1 and V2 use `use_directory_urls: true` producing SEO-friendly URLs (e.g., `/kubernetes/` not `/kubernetes.html`).
|
||||
- **Offline plugin permanently removed**: The MkDocs `offline` plugin forces `.html` suffixes on all URLs, breaking thousands of existing deep-links and SEO authority. It is explicitly forbidden in both `CLAUDE.md` and `GEMINI.md` mandates.
|
||||
|
||||
### 5.3. Architecture Comparison Matrix: V1 vs. V2
|
||||
To better understand the dual-nature of the project, the following matrix details the technical and philosophical differences between the two editions:
|
||||
|
||||
@@ -490,24 +534,31 @@ To embrace the diverse global Cloud Native community while maintaining internati
|
||||
|
||||
---
|
||||
|
||||
## 6. The Unified Agentic Database (Knowledge Graph)
|
||||
## 6. The Unified Agentic Database (Coexistence Knowledge Graph)
|
||||
|
||||
Nubenetes now utilizes a **Unified Metadata Architecture** to maintain consistency across V1 and V2 while optimizing AI performance. All links are indexed in a local YAML database that serves as the **Persistent Memory** for our autonomous agents.
|
||||
Nubenetes now utilizes a **Unified SQL & YAML Database Architecture** to maintain consistency across V1 and V2 while optimizing agentic operations and repository efficiency. All curated links and metadata are managed via a coexisting local database engine.
|
||||
|
||||
### 6.1. Database Components
|
||||
1. **Central Inventory ([`data/inventory.yaml`](data/inventory.yaml))**: The universal single source of truth for technical metadata and resource lifecycle.
|
||||
* **Core Data**: `title`, `year`, `stars` (0-5), `description` (V1 Native), `ai_summary` (V2 English), `category`.
|
||||
* **Structural Intelligence**: `hierarchy` (Recursive list up to 10 levels), `v1_locations`, `v2_locations`.
|
||||
* **Platinum Lifecycle**: `content_hash` (SHA256), `health_score` (0-100), `source_provenance`, `social_preview_url`, `mentions_count`.
|
||||
### 6.1. Database Components and SQLite Engine (Option 3 Coexistence)
|
||||
|
||||
To guarantee backward compatibility and Git efficiency, the system operates on a dual-save database coexistence model:
|
||||
1. **SQLite Database & SQL Text ([`data/inventory.sql`](data/inventory.sql))**: The Git source-of-truth. During execution, the SQL script compiles into a temporary in-memory SQLite database, enabling full relational schema access and SQL query optimization. On save, SQLite's native `iterdump()` decompiles it back into a flat SQL text database file where each resource insert occupies a single line for perfect git diff readability.
|
||||
2. **Central Backup Inventory ([`data/inventory.yaml`](data/inventory.yaml))**: Automatically synchronized during database saves. Serves as a backward-compatible interface for legacy markdown parsing scripts.
|
||||
3. **High-Speed Parsing (C-Loader Integration)**: Direct YAML parsing utilizes high-speed native C-extensions (`yaml.CSafeLoader` and `yaml.CSafeDumper`) across all Python scripts (e.g. `v2_optimizer.py`, `reorganize_mosaic.py`, `safety_guard.py`) for a 10x-20x speedup in parsing operations.
|
||||
|
||||
#### 6.1.2. Platinum Lifecycle Schema
|
||||
* **Core Data**: `url` (Primary Key), `title`, `year`, `stars` (0-5), `description` (V1 Native), `ai_summary` (V2 English), `category`.
|
||||
* **Structural Intelligence**: `hierarchy` (Recursive JSON list), `tags` (JSON list), `v1_locations`, `v2_locations`, `youtube_mosaic` (JSON dict).
|
||||
* **Platinum Lifecycle**: `content_hash` (SHA256 fingerprint), `health_score` (0-100), `source_provenance`, `social_preview_url`, `mentions_count`, `addition_method`.
|
||||
|
||||
### 6.2. The 'Database-First' Reasoning Protocol (Zero-Redundancy)
|
||||
To maximize economic efficiency and maintain the **30-minute execution standard**, all AI agents follow a **Database-First** and **Zero-Redundancy** protocol:
|
||||
1. **Local Lookup**: Before initiating any Gemini call, the agent checks if the URL is already indexed in [`data/inventory.yaml`](data/inventory.yaml).
|
||||
2. **Zero-Redundancy Pipeline**: The V2 Optimizer leverages health and metadata (`gh_stars`, `gh_license`) already validated by the `IntelligentLinkCleaner`. If a resource is marked as `status: online` and has recent metadata, V2 bypasses redundant network checks.
|
||||
3. **Smart Grounding (Search Retrieval)**: AI agents only activate grounding-heavy calls (Google Search) for resources that are new, missing metadata, or flagged for `needs_ai_refresh`. This reduces latencia by >80% for 15k+ link archives.
|
||||
4. **Insight Reuse**: If the resource exists with valid metadata, the agent **reuses existing insights**, reducing API traffic to zero.
|
||||
5. **Memory Efficiency Tracking**: The system tracks **Cache Hit Ratios** and **Estimated Token Savings** in every Intelligence Report.
|
||||
6. **Mandatory Persistence**: Modified YAML files are automatically injected into Pull Requests, ensuring that "System Memory" is version-controlled and shared across all workflows.
|
||||
1. **Local Lookup**: Before initiating any Gemini call, the agent queries the compiled SQLite/SQL database to see if the URL is already indexed.
|
||||
2. **Domain Reputation Registry**: In `main.py`, scraping/health-check success rates are recorded under `domain_reputation` inside `health_learning.json` for adaptive timeout and scraping rotation.
|
||||
3. **Stateful Debate Caching**: In `v2_debate.py`, consensus evaluations for borderline resources are cached based on the SHA256 hash of their combined metadata (`title`, `description`, `tags`). On cache hits, the agent skips redundant LLM calls and retrieves the score directly.
|
||||
4. **Pre-Commit Markdown Lint Hook**: In `src/pre_commit_schema_check.py`, a local Git pre-commit hook automatically runs on developer changes to enforce heading rules (no emojis/ampersands in titles), protocol integrity, link bracket spacing, and duplicate checks in docs markdown.
|
||||
5. **Insight Reuse**: If the resource exists with valid metadata, the agent **uses existing insights**, reducing API traffic to zero.
|
||||
6. **Memory Efficiency Tracking**: The system tracks **Cache Hit Ratios** and **Estimated Token Savings** in every Intelligence Report.
|
||||
7. **Mandatory Persistence**: Modified databases are automatically injected into Pull Requests, ensuring that "System Memory" is version-controlled and shared across all workflows.
|
||||
|
||||
### 6.3. Database Lifecycle and Hygiene
|
||||
To maintain a high-performance "Single Source of Truth", Nubenetes implements automated hygiene protocols:
|
||||
@@ -774,6 +825,7 @@ The following matrix defines our strategic model tiering across all workflows:
|
||||
| **PR Guardian** | PR Presubmit | **Gemini Flash/Lite** | Tier 1 | Rapid syntax and mandate format linting. | **Medium** |
|
||||
| **Curator (X/RSS)** | Agentic Curator | **Gemini Pro** | Tier 2 | Deep reasoning for human/social context. | **Low (Burst)** |
|
||||
| **Auditor** | V2 Elite Builder | **Gemini Pro** | Tier 2 | High-fidelity verification of [ELITE] resources. | **Medium** |
|
||||
| **Fast-Pass Evaluator** | Curation / V2 Builder | **Gemini Flash/Lite** | Tier 1 | Rapid single-call screening for obvious consensus/non-consensus. | **High** |
|
||||
| **Debater Personas** | Curation / V2 Builder | **Gemini Flash/Lite** | Tier 1 | Independent multi-perspective evaluations and rebuttals. | **High** |
|
||||
| **Debate Synthesis** | Curation / V2 Builder | **Gemini Pro** | Tier 2 | High-fidelity final consensus and summary synthesis. | **Medium** |
|
||||
|
||||
@@ -793,17 +845,22 @@ The heart of the new Nubenetes is a suite of AI Agents that operate on our `deve
|
||||
- **Elite Selection:** Scans the massive V1 archive to select the "Elite" top-tier resources.
|
||||
- **2026 Taxonomy:** Reorganizes content into high-density dimensions using **relevance-first sorting**.
|
||||
- **MVQ Hardening:** Automatically identifies stale repositories to exclude them from the Elite portal.
|
||||
- **Tags Page Cap (Recommendation #5)**: Caps the technical tag listings in `tags.md` to 100 entries per tag block (sorted by stars/year) to prevent DOM bloat, providing fallback links to the V1 Historical Archive.
|
||||
3. **IntelligentHealthChecker ([`src/intelligent_health_checker.py`](src/intelligent_health_checker.py))**:
|
||||
- **Resilience:** asynchronous health checks with 3x retry and identity rotation.
|
||||
- **V1 Integrity:** Focuses on link validity (removing 404s) to ensure the exhaustive V1 archive remains accessible.
|
||||
- **Domain failure audits (Recommendation #2)**: Automatically logs consecutive connection failures per domain in `health_learning.json` and drops check timeouts from 12s to 3s when consecutive failures $\ge 3$ to avoid hanging.
|
||||
- **Transparency:** Provides detailed, real-time unbuffered logging of all cleaning operations.
|
||||
4. **DebatePanelEngine ([`src/v2_debate.py`](src/v2_debate.py))**:
|
||||
- **Persona-based Evaluation**: Coordinates specialized opinions across Security Architect, SRE, and AI Engineer personas.
|
||||
- **Fast-Pass screening (Recommendation #3)**: Runs a single Flash model call at start; clear-cut cases bypass the full panel debate immediately.
|
||||
- **Persona-based Evaluation**: Coordinates specialized expert opinions (Security Architect, SRE, and AI Engineer personas) for borderline cases (initial scores in `[60, 75]`).
|
||||
- **Consensus Resolution**: Resolves high score-divergences (>= 15 points) using a round-robin debate structure.
|
||||
- **Auto-Corrective Memory**: Appends resolution logs to `src/memory/health_learning.json` for persistent, few-shot alignment.
|
||||
5. **Resilient Architecture Core**:
|
||||
- **Exponential Backoff**: Intelligent `tenacity`-based retry logic in `gemini_utils.py` gracefully handles 429 Rate Limits before triggering the Circuit Breaker.
|
||||
- **Flash-First Architecture**: Prioritizes Gemini Flash/Lite models for high-density Analyst tasks, enabling processing of 10,000+ resources within the 6-hour GitHub Actions limit through 100-item batching and 2-second safety delays.
|
||||
- **Curation Ingestion Toggle**: Supports `ENABLE_TWITTER_CURATION` environment flag to dynamically toggle Playwright-based Twikit extraction when remote scraping blocks.
|
||||
- **Adaptive Timeout & UA Rotation**: Adapts request headers and reduces health check timeouts dynamically under network throttle/block conditions.
|
||||
- **Programmatic Smart Injection (Option B)**: The system extracts document headers and has Gemini Flash choose the target header, performing the actual line insertion using Python. This bypasses the need for Gemini Pro to rewrite entire documents, slashing API usage and preventing 429 errors.
|
||||
- **Incremental Persistence (Mandate 22)**: Implements a dual-phase auto-save mechanism that flushes the `inventory.yaml` database to disk periodically **without waiting for the workflow to finish**:
|
||||
* **Metadata Phase**: Saves every **500 GitHub repositories** processed.
|
||||
@@ -819,17 +876,20 @@ To eliminate individual LLM rating bias, resolve borderline cases, and prevent a
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A["New Resource Found"] --> B["Persona 1: Security Architect"]
|
||||
A --> C["Persona 2: Cloud Native SRE"]
|
||||
A --> D["Persona 3: AI Platform Engineer"]
|
||||
B --> E["Independent Evaluations"]
|
||||
A["New Resource Found"] --> FP["Fast-Pass Evaluator (Flash)"]
|
||||
FP -->|Confident Score Outside 60-75| G["Accept / Reject Directly"]
|
||||
FP -->|Borderline Score 60-75| B["Persona 1: Security Architect"]
|
||||
FP -->|Borderline Score 60-75| C["Persona 2: Cloud Native SRE"]
|
||||
FP -->|Borderline Score 60-75| D["Persona 3: AI Platform Engineer"]
|
||||
B --> E["Independent expert Evaluations"]
|
||||
C --> E
|
||||
D --> E
|
||||
E -->|Scores Diverge >= 15 points| F["Trigger Debate Round"]
|
||||
E -->|Scores Converge| G["Accept / Reject Directly"]
|
||||
E -->|Expert Scores Diverge >= 15 points| F["Trigger Debate Round"]
|
||||
E -->|Expert Scores Converge| G
|
||||
F --> H["Round-Robin Discussion: Argue Pros and Cons"]
|
||||
H --> I["Consensus Reached and Final Score Assigned"]
|
||||
I --> J["Save Decision to Persistent Memory JSON"]
|
||||
G --> J
|
||||
```
|
||||
|
||||
#### 8.3.1. Panel of Expert Personas
|
||||
@@ -839,14 +899,15 @@ The panel consists of three distinct virtual expert roles, each prompting Gemini
|
||||
* **AI Platform Engineer**: Judges developer productivity, ease of integration with the modern AI stack (e.g., Model Context Protocol (MCP) tools), and overall 2026 Cloud Native architectural relevance.
|
||||
|
||||
#### 8.3.2. Protocol Execution Flow
|
||||
The debate protocol executes asynchronously in three distinct phases:
|
||||
1. **Phase 1: Independent Evaluation**: Each expert persona independently evaluates the resource (using Google Search Grounding to check the live state of the project). They assign an architectural impact score (0–100) and write a 1–2 sentence justification.
|
||||
2. **Phase 2: Divergence Assessment and Rebuttal**: If the difference between the highest and lowest assigned scores is **$\ge 15$ points**, a debate round is triggered. Each expert receives the scores and justifications of their peers and is asked to defend or revise their score in a rebuttal round.
|
||||
3. **Phase 3: Consensus and Synthesis**: The final consensus score is the average of the revised scores of the three personas. A fourth agent (Curation Synthesis Agent) compiles the justifications and rebuttals, generating a refined, high-density technical summary (2–5 sentences) and selecting precise ecosystem tags (e.g., `[DE FACTO STANDARD]`, `[ENTERPRISE-STABLE]`, `[EMERGING]`).
|
||||
The debate protocol executes in the following phases:
|
||||
1. **Fast-Pass Screening**: A single-call evaluator rates the resource. If the rating is highly confident (score $\le 59$ or $\ge 76$), it bypasses the expert panel entirely.
|
||||
2. **Expert Evaluation (Borderline Cases)**: If the screening rating is borderline (`[60, 75]`), the three expert personas (Security, SRE, and AI/Developer DX) independently evaluate the resource (using Google Search Grounding to check the live state of the project). They assign an architectural impact score (0–100) and write a 1–2 sentence justification.
|
||||
3. **Divergence Assessment and Rebuttal**: If the difference between the highest and lowest assigned scores is **$\ge 15$ points**, a debate round is triggered. Each expert receives the scores and justifications of their peers and is asked to defend or revise their score in a rebuttal round.
|
||||
4. **Consensus and Synthesis**: The final consensus score is the average of the revised scores of the three personas. A curation synthesis agent compiles the justifications and rebuttals, generating a refined, high-density technical summary (2–5 sentences) and selecting precise ecosystem tags (e.g., `[DE FACTO STANDARD]`, `[ENTERPRISE-STABLE]`, `[EMERGING]`).
|
||||
|
||||
#### 8.3.3. Integration Points
|
||||
* **Discovery Ingestion**: Hooked into [`src/agentic_curator.py`](src/agentic_curator.py) for new links with borderline initial scores between `70` and `85`.
|
||||
* **V2 Portal Auditing**: Hooked into [`src/v2_optimizer.py`](src/v2_optimizer.py) during builds for high-impact candidates (`[DE FACTO STANDARD]`, `[ENTERPRISE-STABLE]`) or borderline candidates (3–4 stars).
|
||||
* **Discovery Ingestion**: Hooked into [`src/agentic_curator.py`](src/agentic_curator.py) for new links with borderline initial scores.
|
||||
* **V2 Portal Auditing**: Hooked into [`src/v2_optimizer.py`](src/v2_optimizer.py) during builds for high-impact candidates or borderline candidates.
|
||||
* **Persistent Memory Log**: The final consensus score, justifications, rebuttals, and metadata are saved to `src/memory/health_learning.json` under `resolved_debates` to serve as few-shot training examples for future curation runs.
|
||||
|
||||
---
|
||||
@@ -1173,7 +1234,11 @@ To maintain transparency and ease of navigation, all key configuration, database
|
||||
- **Health Check Logic:** [`src/intelligent_health_checker.py`](src/intelligent_health_checker.py) - Link rot prevention and canonical updates.
|
||||
- **Twikit Ingestion:** [`src/ingestion_twikit.py`](src/ingestion_twikit.py) - X.com scraping and account rotation logic.
|
||||
- **Backup Ingestion:** [`src/ingestion_backup.py`](src/ingestion_backup.py) - Manual and historical JSON data processing.
|
||||
- **Discovery Engine:** [`src/autonomous_discovery.py`](src/autonomous_discovery.py) - Multi-source technical news extraction.
|
||||
- **Discovery Engine:** [`src/autonomous_discovery.py`](src/autonomous_discovery.py) - Multi-source technical news extraction (14 GitHub search queries).
|
||||
- **News Digest Engine:** [`src/news_digest.py`](src/news_digest.py) - AI-powered temporal digest across 26 categories with Gemini ranking (3/6/12 months).
|
||||
- **Enrichment Pipeline:** [`src/enrichment.py`](src/enrichment.py) - CNCF Landscape integration, GitHub activity enrichment, and license change detection.
|
||||
- **Deduplication Engine:** [`src/dedup.py`](src/dedup.py) - URL normalization, content-hash, and title-similarity dedup (85% threshold).
|
||||
- **Backfill Utility:** [`scripts/backfill_discovered_at.py`](scripts/backfill_discovered_at.py) - One-shot `discovered_at` population for existing entries.
|
||||
- **Gemini Utils:** [`src/gemini_utils.py`](src/gemini_utils.py) - AI model discovery, rate limiting, and session tracking.
|
||||
- **Markdown Logic:** [`src/markdown_ast.py`](src/markdown_ast.py) - Sophisticated parsing of repository content.
|
||||
- **Observability:** [`src/logger.py`](src/logger.py) | [`src/report_generator.py`](src/report_generator.py) - Execution transparency and visual reporting.
|
||||
|
||||
+19615
File diff suppressed because it is too large
Load Diff
+282319
-151867
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block announce %}
|
||||
<strong>New:</strong> <a href="./tech-digest/">Intelligence Digest</a> — AI-curated trending resources across 26 categories
|
||||
{% endblock %}
|
||||
Vendored
+271
-2
@@ -3,6 +3,15 @@
|
||||
* Color Palette: Deep Space Black & Neon Cyan
|
||||
*/
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
.md-content {
|
||||
max-width: 1600px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
:root {
|
||||
/* LIGHT MODE - Modern, Crisp, High Contrast */
|
||||
--md-primary-fg-color: #09090b; /* Zinc 950 */
|
||||
@@ -343,6 +352,22 @@ a {
|
||||
border-color: #2dd4bf !important;
|
||||
}
|
||||
|
||||
.hero-badge-card--amber {
|
||||
border-color: rgba(245, 158, 11, 0.2);
|
||||
background: rgba(245, 158, 11, 0.02);
|
||||
}
|
||||
.hero-badge-card--amber:hover {
|
||||
background: rgba(245, 158, 11, 0.06) !important;
|
||||
box-shadow: 0 8px 24px rgba(245, 158, 11, 0.18);
|
||||
border-color: #f59e0b !important;
|
||||
}
|
||||
|
||||
.hero-badge-icon {
|
||||
font-size: 2rem;
|
||||
line-height: 1;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.hero-badge-title {
|
||||
font-weight: bold;
|
||||
font-size: 0.95rem;
|
||||
@@ -355,6 +380,56 @@ a {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
/* Intelligence Digest mini-preview on index */
|
||||
.digest-preview {
|
||||
background: rgba(245, 158, 11, 0.04);
|
||||
border: 1px solid rgba(245, 158, 11, 0.15);
|
||||
border-radius: 12px;
|
||||
padding: 20px 24px;
|
||||
margin: 24px 0;
|
||||
}
|
||||
.digest-preview-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.digest-preview-title {
|
||||
font-weight: 700;
|
||||
font-size: 1rem;
|
||||
color: var(--md-primary-fg-color);
|
||||
}
|
||||
.digest-preview-link {
|
||||
font-size: 0.8rem;
|
||||
color: #f59e0b;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
.digest-preview-link:hover { text-decoration: underline; }
|
||||
.digest-preview-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.digest-preview-list li {
|
||||
padding: 5px 0;
|
||||
border-bottom: 1px solid rgba(245, 158, 11, 0.08);
|
||||
font-size: 0.875rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.digest-preview-list li:last-child { border-bottom: none; }
|
||||
.digest-preview-cat {
|
||||
font-size: 0.7rem;
|
||||
color: #f59e0b;
|
||||
background: rgba(245, 158, 11, 0.1);
|
||||
border-radius: 4px;
|
||||
padding: 1px 6px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Hero Showcase Image wrapper (4 cars in a container) */
|
||||
.hero-showcase-wrapper {
|
||||
margin: 24px auto;
|
||||
@@ -807,6 +882,13 @@ a {
|
||||
contain-intrinsic-size: auto 500px;
|
||||
}
|
||||
|
||||
/* Defer rendering of large resource lists in category pages */
|
||||
.md-typeset > ul,
|
||||
.md-typeset > ol {
|
||||
content-visibility: auto;
|
||||
contain-intrinsic-size: auto 200px;
|
||||
}
|
||||
|
||||
/* Collapsible tag lists on the tags index page */
|
||||
.v2-tag-section details {
|
||||
border: 1px solid rgba(0, 0, 0, 0.08);
|
||||
@@ -908,13 +990,200 @@ input[type="text"] {
|
||||
|
||||
/* Coarse touch pointer optimizations (Mobile AA targets) */
|
||||
@media (pointer: coarse) {
|
||||
.md-tag,
|
||||
.md-button,
|
||||
.md-tag,
|
||||
.md-button,
|
||||
.v2-tag-section summary {
|
||||
min-block-size: 44px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------- */
|
||||
/* ANNOUNCEMENT BAR */
|
||||
/* ---------------------------------------------------- */
|
||||
|
||||
.md-banner {
|
||||
background: linear-gradient(135deg, #0ea5e9, #22d3ee);
|
||||
color: #ffffff;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
padding: 8px 16px;
|
||||
}
|
||||
|
||||
.md-banner a {
|
||||
color: #ffffff;
|
||||
text-decoration: underline;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
[data-md-color-scheme="slate"] .md-banner {
|
||||
background: linear-gradient(135deg, #0284c7, #06b6d4);
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------- */
|
||||
/* TRENDING NOW — NEWS DIGEST CARDS */
|
||||
/* ---------------------------------------------------- */
|
||||
|
||||
.trending-section {
|
||||
margin: 32px 0;
|
||||
padding: 24px;
|
||||
border-radius: 16px;
|
||||
border: 1px solid rgba(14, 165, 233, 0.15);
|
||||
background: linear-gradient(180deg, rgba(14, 165, 233, 0.03) 0%, transparent 100%);
|
||||
}
|
||||
|
||||
[data-md-color-scheme="slate"] .trending-section {
|
||||
border-color: rgba(34, 211, 238, 0.15);
|
||||
background: linear-gradient(180deg, rgba(34, 211, 238, 0.05) 0%, transparent 100%);
|
||||
}
|
||||
|
||||
.trending-section__title {
|
||||
font-size: 1.4em;
|
||||
font-weight: 700;
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.trending-section__updated {
|
||||
font-size: 0.55em;
|
||||
font-weight: 500;
|
||||
padding: 2px 10px;
|
||||
border-radius: 20px;
|
||||
background: rgba(14, 165, 233, 0.12);
|
||||
color: var(--md-accent-fg-color);
|
||||
border: 1px solid rgba(14, 165, 233, 0.25);
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
.trending-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 16px;
|
||||
margin: 16px 0;
|
||||
}
|
||||
|
||||
.trending-card {
|
||||
border: 1px solid rgba(0, 0, 0, 0.08);
|
||||
border-radius: 12px;
|
||||
padding: 16px 20px;
|
||||
background: var(--md-primary-bg-color--light);
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.trending-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 24px rgba(14, 165, 233, 0.12);
|
||||
}
|
||||
|
||||
[data-md-color-scheme="slate"] .trending-card {
|
||||
border-color: rgba(255, 255, 255, 0.08);
|
||||
background: rgba(24, 24, 27, 0.4);
|
||||
}
|
||||
|
||||
[data-md-color-scheme="slate"] .trending-card:hover {
|
||||
box-shadow: 0 8px 24px rgba(34, 211, 238, 0.12);
|
||||
}
|
||||
|
||||
.trending-card__category {
|
||||
font-size: 0.75em;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: var(--md-accent-fg-color);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.trending-card__title {
|
||||
font-size: 0.95em;
|
||||
font-weight: 600;
|
||||
margin-bottom: 6px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.trending-card__title a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.trending-card__title a:hover {
|
||||
color: var(--md-accent-fg-color);
|
||||
}
|
||||
|
||||
.trending-card__meta {
|
||||
font-size: 0.8em;
|
||||
color: var(--md-primary-fg-color--dark);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.trending-card__why {
|
||||
font-size: 0.82em;
|
||||
line-height: 1.45;
|
||||
color: var(--md-primary-fg-color--light);
|
||||
}
|
||||
|
||||
.trending-card__impact {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: 12px;
|
||||
font-size: 0.75em;
|
||||
font-weight: 700;
|
||||
padding: 2px 8px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.trending-card__impact--critical {
|
||||
background: rgba(239, 68, 68, 0.15);
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
.trending-card__impact--high {
|
||||
background: rgba(245, 158, 11, 0.15);
|
||||
color: #f59e0b;
|
||||
}
|
||||
|
||||
.trending-card__impact--medium {
|
||||
background: rgba(14, 165, 233, 0.15);
|
||||
color: #0ea5e9;
|
||||
}
|
||||
|
||||
/* Digest link cards (CTA to full digest pages) */
|
||||
.digest-links {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
margin-top: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.digest-link-card {
|
||||
flex: 1;
|
||||
min-width: 240px;
|
||||
padding: 16px 24px;
|
||||
border-radius: 12px;
|
||||
border: 2px solid var(--md-accent-fg-color);
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
font-weight: 600;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.digest-link-card:hover {
|
||||
background: var(--md-accent-fg-color);
|
||||
color: #ffffff;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
[data-md-color-scheme="slate"] .digest-link-card:hover {
|
||||
color: #09090b;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Vendored
+4
-3
@@ -13,9 +13,10 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||
// Do not show on the homepage, video hub index page, or technical tags index page (performance)
|
||||
const h1 = contentArea.querySelector("h1");
|
||||
if (h1 && (
|
||||
h1.textContent.includes("Nubenetes Elite Portal (V2)") ||
|
||||
h1.textContent.includes("Agentic Video Hub") ||
|
||||
h1.textContent.includes("Technical Tags Index")
|
||||
h1.textContent.includes("Nubenetes Elite Portal (V2)") ||
|
||||
h1.textContent.includes("Agentic Video Hub") ||
|
||||
h1.textContent.includes("Technical Tags Index") ||
|
||||
h1.textContent.includes("Intelligence Digest")
|
||||
)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -4,3 +4,4 @@ yt-dlp
|
||||
youtube-transcript-api
|
||||
mkdocs-redirects>=1.2.3
|
||||
mkdocs-minify-plugin>=0.8.0
|
||||
mkdocs-rss-plugin>=1.15.0
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
"""
|
||||
Backfill script: adds discovered_at to inventory entries that lack it.
|
||||
Priority: gh_pushed > last_checked > year > default "2024-01-01T00:00:00"
|
||||
Run once: python -m scripts.backfill_discovered_at
|
||||
"""
|
||||
import sys
|
||||
import os
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from datetime import datetime
|
||||
from src.inventory_manager import load_inventory, save_inventory
|
||||
from src.config import MADRID_TZ
|
||||
|
||||
|
||||
def backfill():
|
||||
inv = load_inventory()
|
||||
updated = 0
|
||||
total = 0
|
||||
|
||||
for url, entry in inv.items():
|
||||
if not isinstance(entry, dict):
|
||||
continue
|
||||
total += 1
|
||||
if entry.get("discovered_at"):
|
||||
continue
|
||||
|
||||
discovered = None
|
||||
|
||||
gh_pushed = entry.get("gh_pushed")
|
||||
if gh_pushed and isinstance(gh_pushed, str) and len(gh_pushed) >= 10:
|
||||
discovered = gh_pushed
|
||||
|
||||
if not discovered:
|
||||
last_checked = entry.get("last_checked")
|
||||
if isinstance(last_checked, (int, float)) and last_checked > 0:
|
||||
try:
|
||||
discovered = datetime.fromtimestamp(last_checked, tz=MADRID_TZ).isoformat()
|
||||
except (OSError, ValueError):
|
||||
pass
|
||||
|
||||
if not discovered:
|
||||
year = entry.get("year", "")
|
||||
if isinstance(year, str) and year.isdigit() and len(year) == 4:
|
||||
discovered = f"{year}-06-01T00:00:00+02:00"
|
||||
elif isinstance(year, int):
|
||||
discovered = f"{year}-06-01T00:00:00+02:00"
|
||||
|
||||
if not discovered:
|
||||
discovered = "2024-01-01T00:00:00+02:00"
|
||||
|
||||
entry["discovered_at"] = discovered
|
||||
updated += 1
|
||||
|
||||
print(f"Backfill complete: {updated}/{total} entries updated with discovered_at")
|
||||
save_inventory(inv)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
backfill()
|
||||
+34
-17579
File diff suppressed because it is too large
Load Diff
+21
-6
@@ -38,7 +38,8 @@ async def _get_github_activity(url: str) -> Dict:
|
||||
"gh_pushed": data.get("pushed_at"),
|
||||
"gh_license": data.get("license", {}).get("spdx_id", "N/A")
|
||||
}
|
||||
except: pass
|
||||
except Exception as e:
|
||||
log_event(f"[WARN] fetch GitHub activity for {url}: {str(e)[:100]}")
|
||||
return {}
|
||||
|
||||
async def _deep_fetch_content(url: str) -> Tuple[str, Dict]:
|
||||
@@ -61,7 +62,8 @@ async def _deep_fetch_content(url: str) -> Tuple[str, Dict]:
|
||||
img_match = re.search(r'meta property="og:image" content="(.*?)"', resp.text)
|
||||
if img_match: og_image = img_match.group(1)
|
||||
return resp.text, {"og_image": og_image}
|
||||
except: pass
|
||||
except Exception as e:
|
||||
log_event(f"[WARN] deep fetch content for {url}: {str(e)[:100]}")
|
||||
return "", {}
|
||||
|
||||
async def evaluate_extracted_assets(raw_assets: List[Dict]) -> Dict[str, Dict]:
|
||||
@@ -76,7 +78,8 @@ async def evaluate_extracted_assets(raw_assets: List[Dict]) -> Dict[str, Dict]:
|
||||
try:
|
||||
memory_data = json.load(open(memory_file, "r"))
|
||||
domain_blacklist = set(memory_data.get("blacklisted_domains", []))
|
||||
except: pass
|
||||
except Exception as e:
|
||||
log_event(f"[WARN] load blacklist from health_learning.json: {str(e)[:100]}")
|
||||
|
||||
# 1. Pre-filter
|
||||
for asset in raw_assets:
|
||||
@@ -140,7 +143,10 @@ async def evaluate_extracted_assets(raw_assets: List[Dict]) -> Dict[str, Dict]:
|
||||
"- Assign tags. You MUST include:\n"
|
||||
" 1. 1 to 2 maturity tags from: [DE FACTO STANDARD], [ENTERPRISE-STABLE], [EMERGING], [GUIDE], [CASE STUDY], [COMMUNITY-TOOL], [LEGACY].\n"
|
||||
" 2. Fine-grained technical/architectural tags from the content (e.g., [EBPF], [WASM], [GITOPS], [IAC], [SERVICE-MESH], [SERVERLESS], [MLOPS], [DB]). Keep them uppercase and wrapped in brackets.\n"
|
||||
"Respond ONLY JSON list: [{\"url\": \"...\", \"impact_score\": int, \"reputation_penalty\": bool, \"reputation_summary\": \"...\", \"pub_date\": \"YYYY-MM-DD\", \"primary_category\": \"...\", \"suggested_new_category\": \"...\", \"title\": \"...\", \"desc\": \"...\", \"en_summary\": \"High-density summary...\", \"language\": \"...\", \"type\": \"...\", \"level\": \"...\", \"technical_hierarchy\": [...], \"tags\": [...], \"is_microservice\": bool}, ...]\n\n"
|
||||
"PHASE 5: COMPANY & GEO CLASSIFICATION\n"
|
||||
"- Identify 'company': The company/organization that authored or is the primary subject (e.g., 'Google', 'Netflix', 'CNCF', 'Independent').\n"
|
||||
"- Identify 'geo_region': The HQ region of that company. Use one of: 'americas', 'europe', 'spain', 'asia_pacific', 'global'.\n"
|
||||
"Respond ONLY JSON list: [{\"url\": \"...\", \"impact_score\": int, \"reputation_penalty\": bool, \"reputation_summary\": \"...\", \"pub_date\": \"YYYY-MM-DD\", \"primary_category\": \"...\", \"suggested_new_category\": \"...\", \"title\": \"...\", \"desc\": \"...\", \"en_summary\": \"High-density summary...\", \"language\": \"...\", \"type\": \"...\", \"level\": \"...\", \"technical_hierarchy\": [...], \"tags\": [...], \"is_microservice\": bool, \"company\": \"...\", \"geo_region\": \"...\"}, ...]\n\n"
|
||||
"RESOURCES:\n" + "\n".join([f"- {d['asset']['url']}: (MVQ Penalty: {d['mvq_penalty']}) {d['content']}" for d in batch_data])
|
||||
)
|
||||
|
||||
@@ -205,9 +211,16 @@ async def evaluate_extracted_assets(raw_assets: List[Dict]) -> Dict[str, Dict]:
|
||||
"reputation_status": "Vetted" if not data.get("reputation_penalty") else "Suspicious",
|
||||
"reputation_summary": data.get("reputation_summary", ""),
|
||||
"source_provenance": d["asset"].get("source_type", "Social"), "social_preview_url": d["rich_meta"].get("og_image", ""),
|
||||
"company": data.get("company", ""), "geo_region": data.get("geo_region", ""),
|
||||
"category": primary_cat, "status": "online", "last_checked": datetime.now().timestamp(),
|
||||
"discovered_at": datetime.now(MADRID_TZ).isoformat(),
|
||||
"last_ai_eval": datetime.now(MADRID_TZ).isoformat(),
|
||||
"suggested_new_category": data.get("suggested_new_category", ""),
|
||||
"addition_method": "automatic", **d["gh_meta"]
|
||||
"addition_method": {
|
||||
"rss": "rss_ingestion", "GitHub Trending": "github_trending",
|
||||
"Twitter": "twitter_ingestion", "nubenetes": "manual"
|
||||
}.get(d["asset"].get("source_type", ""), "automatic"),
|
||||
**d["gh_meta"]
|
||||
}
|
||||
if "youtube.com" in url or "youtu.be" in url:
|
||||
title_desc = f"{data['title']} {data['desc']}".lower()
|
||||
@@ -250,7 +263,9 @@ class AgenticCurator:
|
||||
prompt = "Identify 5 high-quality Cloud Native or K8s engineering blogs or 'Awesome' repos active in 2026. Return ONLY JSON list of URLs."
|
||||
try:
|
||||
return await call_gemini_with_retry(prompt, use_grounding=True)
|
||||
except: return []
|
||||
except Exception as e:
|
||||
log_event(f"[WARN] autonomous source discovery: {str(e)[:100]}")
|
||||
return []
|
||||
|
||||
async def decide_smart_injection(self, content: str, asset: Dict) -> str:
|
||||
# Extract headers from the markdown content
|
||||
|
||||
@@ -2,20 +2,31 @@ import aiohttp
|
||||
import json
|
||||
import httpx
|
||||
import re
|
||||
from src.config import GEMINI_API_KEY, NUBENETES_CATEGORIES
|
||||
from src.config import GEMINI_API_KEY, NUBENETES_CATEGORIES, GH_TOKEN
|
||||
from src.gemini_utils import call_gemini_with_retry
|
||||
from src.logger import log_event
|
||||
|
||||
async def fetch_github_trending_cloud_native() -> list[dict]:
|
||||
queries = [
|
||||
"topic:kubernetes+stars:>1000",
|
||||
"topic:mcp-server+stars:>0",
|
||||
"topic:kubernetes+stars:>1000",
|
||||
"topic:mcp-server+stars:>0",
|
||||
"topic:model-context-protocol+stars:>0",
|
||||
"topic:ai-agents+stars:>50",
|
||||
"awesome+stars:>1000",
|
||||
"topic:generative-ai+stars:>500"
|
||||
"topic:generative-ai+stars:>500",
|
||||
"topic:devops+stars:>500",
|
||||
"topic:observability+stars:>200",
|
||||
"topic:cloud-security+stars:>200",
|
||||
"topic:terraform+stars:>500",
|
||||
"topic:database+stars:>500",
|
||||
"topic:cicd+stars:>200",
|
||||
"topic:service-mesh+stars:>100",
|
||||
"topic:platform-engineering+stars:>100",
|
||||
]
|
||||
all_repos = []
|
||||
headers = {'Accept': 'application/vnd.github.v3+json'}
|
||||
if GH_TOKEN:
|
||||
headers['Authorization'] = f'token {GH_TOKEN}'
|
||||
async with aiohttp.ClientSession(headers=headers) as session:
|
||||
for q in queries:
|
||||
url = f"https://api.github.com/search/repositories?q={q}&sort=updated&order=desc"
|
||||
@@ -29,7 +40,9 @@ async def fetch_github_trending_cloud_native() -> list[dict]:
|
||||
"url": repo['html_url'],
|
||||
"desc": repo['description'] or "No description provided."
|
||||
})
|
||||
except: continue
|
||||
except Exception as e:
|
||||
log_event(f"[WARN] GitHub search query '{q}': {str(e)[:100]}")
|
||||
continue
|
||||
return all_repos
|
||||
|
||||
async def discover_trending_assets() -> list[dict]:
|
||||
|
||||
+194
@@ -0,0 +1,194 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import asyncio
|
||||
from collections import defaultdict
|
||||
from difflib import SequenceMatcher
|
||||
from typing import Dict, List, Tuple
|
||||
from urllib.parse import urlparse, parse_qs, urlencode, urlunparse
|
||||
|
||||
from src.inventory_manager import load_inventory, save_inventory
|
||||
from src.logger import log_event
|
||||
|
||||
TRACKING_PARAMS = {"utm_source", "utm_medium", "utm_campaign", "utm_content", "utm_term",
|
||||
"ref", "source", "fbclid", "gclid", "mc_cid", "mc_eid", "s", "share"}
|
||||
|
||||
|
||||
def normalize_url_deep(url: str) -> str:
|
||||
parsed = urlparse(url.strip().lower())
|
||||
scheme = "https"
|
||||
netloc = parsed.netloc.removeprefix("www.")
|
||||
path = parsed.path.rstrip("/") or "/"
|
||||
params = parse_qs(parsed.query)
|
||||
clean_params = {k: v for k, v in params.items() if k not in TRACKING_PARAMS}
|
||||
query = urlencode(clean_params, doseq=True) if clean_params else ""
|
||||
return urlunparse((scheme, netloc, path, "", query, ""))
|
||||
|
||||
|
||||
def normalize_title(title: str) -> str:
|
||||
if not title:
|
||||
return ""
|
||||
t = title.lower().strip()
|
||||
t = re.sub(r'^[\w.-]+\.\w{2,}:\s*', '', t)
|
||||
t = re.sub(r'[^\w\s]', ' ', t)
|
||||
t = re.sub(r'\s+', ' ', t).strip()
|
||||
return t
|
||||
|
||||
|
||||
def find_url_duplicates(inventory: Dict) -> List[Tuple[str, str]]:
|
||||
norm_map = defaultdict(list)
|
||||
for url in inventory:
|
||||
if not isinstance(inventory[url], dict):
|
||||
continue
|
||||
deep = normalize_url_deep(url)
|
||||
norm_map[deep].append(url)
|
||||
|
||||
duplicates = []
|
||||
for norm, urls in norm_map.items():
|
||||
if len(urls) > 1:
|
||||
for i in range(1, len(urls)):
|
||||
duplicates.append((urls[0], urls[i]))
|
||||
return duplicates
|
||||
|
||||
|
||||
def find_hash_duplicates(inventory: Dict) -> List[List[str]]:
|
||||
hash_map = defaultdict(list)
|
||||
for url, entry in inventory.items():
|
||||
if not isinstance(entry, dict):
|
||||
continue
|
||||
ch = entry.get("content_hash")
|
||||
if ch and ch != "N/A":
|
||||
hash_map[ch].append(url)
|
||||
return [urls for urls in hash_map.values() if len(urls) > 1]
|
||||
|
||||
|
||||
def find_title_duplicates(inventory: Dict, threshold: float = 0.85) -> List[Tuple[str, str, float]]:
|
||||
entries = []
|
||||
for url, entry in inventory.items():
|
||||
if not isinstance(entry, dict):
|
||||
continue
|
||||
title = entry.get("title", "")
|
||||
norm = normalize_title(title)
|
||||
if len(norm) < 10:
|
||||
continue
|
||||
entries.append((url, norm, entry.get("stars") or 0))
|
||||
|
||||
log_event(f"[Dedup] Building title index for {len(entries)} entries...")
|
||||
|
||||
prefix_groups = defaultdict(list)
|
||||
for url, norm, stars in entries:
|
||||
words = norm.split()
|
||||
prefix = " ".join(words[:3]) if len(words) >= 3 else norm
|
||||
prefix_groups[prefix].append((url, norm, stars))
|
||||
|
||||
duplicates = []
|
||||
checked = 0
|
||||
for prefix, group in prefix_groups.items():
|
||||
if len(group) < 2:
|
||||
continue
|
||||
for i in range(len(group)):
|
||||
for j in range(i + 1, len(group)):
|
||||
url1, norm1, stars1 = group[i]
|
||||
url2, norm2, stars2 = group[j]
|
||||
if stars1 >= 4 and stars2 >= 4:
|
||||
continue
|
||||
ratio = SequenceMatcher(None, norm1, norm2).ratio()
|
||||
if ratio >= threshold:
|
||||
duplicates.append((url1, url2, ratio))
|
||||
checked += 1
|
||||
if checked % 500 == 0:
|
||||
log_event(f"[Dedup] Checked {checked}/{len(prefix_groups)} prefix groups...")
|
||||
|
||||
log_event(f"[Dedup] Title scan complete: {len(duplicates)} potential duplicates found")
|
||||
return duplicates
|
||||
|
||||
|
||||
def _entry_score(entry: Dict) -> Tuple:
|
||||
return (
|
||||
entry.get("stars") or 0,
|
||||
1 if entry.get("ai_summary") else 0,
|
||||
1 if entry.get("hierarchy") else 0,
|
||||
len(entry.get("tags", [])),
|
||||
-len(str(entry.get("url", "")))
|
||||
)
|
||||
|
||||
|
||||
def resolve_duplicates(inventory: Dict, duplicate_pairs: List[Tuple[str, str, float]]) -> int:
|
||||
resolved = 0
|
||||
seen = set()
|
||||
|
||||
for url1, url2, score in sorted(duplicate_pairs, key=lambda x: -x[2]):
|
||||
if url1 in seen or url2 in seen:
|
||||
continue
|
||||
|
||||
entry1 = inventory.get(url1, {})
|
||||
entry2 = inventory.get(url2, {})
|
||||
|
||||
if not isinstance(entry1, dict) or not isinstance(entry2, dict):
|
||||
continue
|
||||
|
||||
score1 = _entry_score(entry1)
|
||||
score2 = _entry_score(entry2)
|
||||
|
||||
if score1 >= score2:
|
||||
winner, loser = url1, url2
|
||||
else:
|
||||
winner, loser = url2, url1
|
||||
|
||||
inventory[loser]["status"] = "duplicate"
|
||||
inventory[loser]["duplicate_of"] = winner
|
||||
seen.add(loser)
|
||||
resolved += 1
|
||||
|
||||
return resolved
|
||||
|
||||
|
||||
async def run_dedup(dry_run: bool = True) -> Dict:
|
||||
log_event("STARTING DEDUPLICATION SCAN", section_break=True)
|
||||
inventory = load_inventory()
|
||||
|
||||
url_dups = find_url_duplicates(inventory)
|
||||
log_event(f"[Dedup] URL duplicates: {len(url_dups)}")
|
||||
|
||||
hash_groups = find_hash_duplicates(inventory)
|
||||
hash_dups = []
|
||||
for group in hash_groups:
|
||||
for i in range(1, len(group)):
|
||||
hash_dups.append((group[0], group[i], 1.0))
|
||||
log_event(f"[Dedup] Content hash duplicates: {len(hash_dups)}")
|
||||
|
||||
title_dups = find_title_duplicates(inventory)
|
||||
|
||||
all_dups = [(u1, u2, 1.0) for u1, u2 in url_dups] + hash_dups + title_dups
|
||||
unique_pairs = {}
|
||||
for u1, u2, s in all_dups:
|
||||
key = tuple(sorted([u1, u2]))
|
||||
if key not in unique_pairs or s > unique_pairs[key]:
|
||||
unique_pairs[key] = s
|
||||
deduped_pairs = [(k[0], k[1], v) for k, v in unique_pairs.items()]
|
||||
|
||||
stats = {
|
||||
"url_duplicates": len(url_dups),
|
||||
"hash_duplicates": len(hash_dups),
|
||||
"title_duplicates": len(title_dups),
|
||||
"total_unique_pairs": len(deduped_pairs),
|
||||
}
|
||||
|
||||
if dry_run:
|
||||
log_event(f"[Dedup] DRY RUN — {len(deduped_pairs)} duplicates found, no changes made")
|
||||
for u1, u2, score in sorted(deduped_pairs, key=lambda x: -x[2])[:20]:
|
||||
t1 = inventory.get(u1, {}).get("title", "?")[:60]
|
||||
t2 = inventory.get(u2, {}).get("title", "?")[:60]
|
||||
log_event(f" [{score:.0%}] {t1} <-> {t2}")
|
||||
else:
|
||||
resolved = resolve_duplicates(inventory, deduped_pairs)
|
||||
stats["resolved"] = resolved
|
||||
save_inventory(inventory)
|
||||
log_event(f"[Dedup] Resolved {resolved} duplicates")
|
||||
|
||||
log_event(f"DEDUP COMPLETE: {stats}")
|
||||
return stats
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(run_dedup(dry_run=True))
|
||||
@@ -5,6 +5,7 @@ import asyncio
|
||||
import httpx
|
||||
from src.logger import log_event
|
||||
from src.gemini_utils import call_gemini_with_retry, fetch_youtube_metadata
|
||||
from src.inventory_manager import load_inventory, save_inventory
|
||||
|
||||
INVENTORY_PATH = "data/inventory.yaml"
|
||||
|
||||
@@ -72,8 +73,7 @@ async def main():
|
||||
|
||||
force_enrich = os.getenv("FORCE_ENRICH", "false").lower() == "true"
|
||||
|
||||
with open(INVENTORY_PATH, "r") as f:
|
||||
inventory = yaml.safe_load(f)
|
||||
inventory = load_inventory()
|
||||
|
||||
video_urls = [u for u, e in inventory.items() if e.get("is_featured_video")]
|
||||
|
||||
@@ -99,8 +99,7 @@ async def main():
|
||||
await asyncio.gather(*batch)
|
||||
|
||||
# Incremental Persistence: Save after each batch
|
||||
with open(INVENTORY_PATH, "w") as f:
|
||||
yaml.dump(inventory, f, sort_keys=False, allow_unicode=True)
|
||||
save_inventory(inventory)
|
||||
log_event(f" [💾] Saved progress: {min(i + batch_size, len(tasks))}/{len(tasks)} videos.")
|
||||
|
||||
if i + batch_size < len(tasks):
|
||||
|
||||
@@ -0,0 +1,373 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import re
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
|
||||
import httpx
|
||||
|
||||
from src.config import GH_TOKEN, MADRID_TZ
|
||||
from src.logger import log_event
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Constants
|
||||
# ---------------------------------------------------------------------------
|
||||
CNCF_LANDSCAPE_URL = "https://landscape.cncf.io/api/items" # Legacy, now SPA — fallback to GitHub topic search
|
||||
GITHUB_API_BASE = "https://api.github.com"
|
||||
GITHUB_RATE_DELAY = 0.5 # seconds between GitHub API calls (5000/hr limit = ~1.4/s safe)
|
||||
MAX_REPOS_DEFAULT = 200 # cap per run — 200 × 0.5s = ~100s, well within CI timeout
|
||||
ACTIVITY_STALENESS_DAYS = 30
|
||||
|
||||
# Community health thresholds
|
||||
HEALTH_ACTIVE = 50
|
||||
HEALTH_HEALTHY = 10
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _github_headers() -> Dict[str, str]:
|
||||
"""Build GitHub API request headers with optional auth."""
|
||||
headers = {"Accept": "application/vnd.github.v3+json"}
|
||||
if GH_TOKEN:
|
||||
headers["Authorization"] = f"token {GH_TOKEN}"
|
||||
return headers
|
||||
|
||||
|
||||
def _parse_github_repo(url: str) -> Optional[str]:
|
||||
"""Extract 'owner/repo' from a GitHub URL. Returns None if not a GitHub URL."""
|
||||
match = re.search(r"github\.com/([^/]+/[^/]+)", url)
|
||||
if not match:
|
||||
return None
|
||||
repo = match.group(1).split("#")[0].split("?")[0].rstrip("/")
|
||||
# Strip trailing .git if present
|
||||
if repo.endswith(".git"):
|
||||
repo = repo[:-4]
|
||||
return repo
|
||||
|
||||
|
||||
def _normalize_repo_url(url: str) -> str:
|
||||
"""Normalize a GitHub repo URL for comparison (lowercase, no trailing slash)."""
|
||||
url = url.lower().rstrip("/")
|
||||
if url.endswith(".git"):
|
||||
url = url[:-4]
|
||||
# Strip protocol
|
||||
url = re.sub(r"^https?://", "", url)
|
||||
return url
|
||||
|
||||
|
||||
def _is_activity_stale(entry: Dict) -> bool:
|
||||
"""Check whether an entry's activity data is older than ACTIVITY_STALENESS_DAYS."""
|
||||
checked = entry.get("gh_activity_checked")
|
||||
if not checked:
|
||||
return True
|
||||
try:
|
||||
checked_dt = datetime.fromisoformat(checked)
|
||||
return datetime.now(MADRID_TZ) - checked_dt > timedelta(days=ACTIVITY_STALENESS_DAYS)
|
||||
except (ValueError, TypeError):
|
||||
return True
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Module 1: CNCF Landscape Status
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
async def fetch_cncf_landscape() -> Dict[str, str]:
|
||||
"""Fetch CNCF project graduation status via GitHub topic search.
|
||||
|
||||
The legacy landscape.cncf.io/api/items endpoint is now a SPA and no
|
||||
longer returns JSON. Instead, we search GitHub for repos with CNCF
|
||||
maturity topics (cncf-sandbox, cncf-incubating, cncf-graduated).
|
||||
|
||||
Returns dict mapping repo_url (normalized) -> maturity.
|
||||
"""
|
||||
result: Dict[str, str] = {}
|
||||
headers = _github_headers()
|
||||
maturity_queries = {
|
||||
"graduated": "topic:cncf-graduated",
|
||||
"incubating": "topic:cncf-incubating",
|
||||
"sandbox": "topic:cncf-sandbox",
|
||||
}
|
||||
|
||||
async with httpx.AsyncClient(timeout=30.0) as client:
|
||||
for maturity, query in maturity_queries.items():
|
||||
try:
|
||||
url = f"{GITHUB_API_BASE}/search/repositories?q={query}&per_page=100"
|
||||
resp = await client.get(url, headers=headers)
|
||||
resp.raise_for_status()
|
||||
data = resp.json()
|
||||
for repo in data.get("items", []):
|
||||
repo_url = repo.get("html_url", "")
|
||||
if repo_url:
|
||||
result[_normalize_repo_url(repo_url)] = maturity
|
||||
log_event(f" [CNCF] {maturity}: {len(data.get('items', []))} repos found")
|
||||
except Exception as e:
|
||||
log_event(f"[WARN] CNCF {maturity} search failed: {str(e)[:100]}")
|
||||
await asyncio.sleep(GITHUB_RATE_DELAY)
|
||||
|
||||
log_event(f"[CNCF] Fetched {len(result)} projects from CNCF landscape")
|
||||
return result
|
||||
|
||||
|
||||
async def enrich_cncf_status(inventory: Dict) -> int:
|
||||
"""Update inventory entries with cncf_status field.
|
||||
|
||||
If an entry maps to a CNCF-graduated project and lacks the
|
||||
``[DE FACTO STANDARD]`` tag, the tag is appended.
|
||||
|
||||
Returns count of entries updated.
|
||||
"""
|
||||
landscape = await fetch_cncf_landscape()
|
||||
if not landscape:
|
||||
return 0
|
||||
|
||||
updated = 0
|
||||
for url, entry in inventory.items():
|
||||
if url.startswith("INTRO:"):
|
||||
continue
|
||||
normalized = _normalize_repo_url(url)
|
||||
maturity = landscape.get(normalized)
|
||||
if not maturity:
|
||||
continue
|
||||
|
||||
old_status = entry.get("cncf_status")
|
||||
entry["cncf_status"] = maturity
|
||||
if old_status != maturity:
|
||||
updated += 1
|
||||
|
||||
# Auto-tag graduated projects
|
||||
if maturity == "graduated":
|
||||
tags = entry.get("tags")
|
||||
if isinstance(tags, list) and "[DE FACTO STANDARD]" not in tags:
|
||||
tags.append("[DE FACTO STANDARD]")
|
||||
entry["tags"] = tags
|
||||
|
||||
log_event(f"[CNCF] Enriched {updated} inventory entries with CNCF status")
|
||||
return updated
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Module 2: Social Signal Enrichment (GitHub Activity)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
async def _fetch_repo_activity(
|
||||
client: httpx.AsyncClient,
|
||||
owner_repo: str,
|
||||
) -> Tuple[int, int]:
|
||||
"""Fetch open issue + PR count for a single repo in one API call.
|
||||
|
||||
GitHub's open_issues_count includes PRs, so a single /repos endpoint
|
||||
call is sufficient. Returns (open_issues_count, 0) — the caller uses
|
||||
the combined metric for health classification.
|
||||
"""
|
||||
headers = _github_headers()
|
||||
open_issues = 0
|
||||
|
||||
try:
|
||||
resp = await client.get(
|
||||
f"{GITHUB_API_BASE}/repos/{owner_repo}",
|
||||
headers=headers,
|
||||
timeout=15.0,
|
||||
)
|
||||
if resp.status_code == 200:
|
||||
data = resp.json()
|
||||
open_issues = data.get("open_issues_count", 0)
|
||||
elif resp.status_code == 429:
|
||||
# Rate limited — back off
|
||||
await asyncio.sleep(5.0)
|
||||
except Exception as e:
|
||||
log_event(f"[WARN] GitHub repo fetch failed for {owner_repo}: {str(e)[:120]}")
|
||||
|
||||
await asyncio.sleep(GITHUB_RATE_DELAY)
|
||||
|
||||
return open_issues, 0
|
||||
|
||||
|
||||
def _classify_health(total_activity: int) -> str:
|
||||
"""Classify community health based on combined issue + PR activity."""
|
||||
if total_activity > HEALTH_ACTIVE:
|
||||
return "active"
|
||||
if total_activity >= HEALTH_HEALTHY:
|
||||
return "healthy"
|
||||
if total_activity > 0:
|
||||
return "low"
|
||||
return "dormant"
|
||||
|
||||
|
||||
async def enrich_github_activity(inventory: Dict, max_repos: int = MAX_REPOS_DEFAULT) -> int:
|
||||
"""Fetch recent issue/PR velocity for GitHub repos in the inventory.
|
||||
|
||||
Adds fields:
|
||||
- ``gh_open_issues_30d``
|
||||
- ``gh_open_prs_30d``
|
||||
- ``gh_community_health`` ("active" | "healthy" | "low" | "dormant")
|
||||
- ``gh_activity_checked`` (ISO timestamp)
|
||||
|
||||
Skips entries whose ``gh_activity_checked`` is less than 30 days old.
|
||||
Processes at most *max_repos* repos per run.
|
||||
|
||||
Returns count of entries enriched.
|
||||
"""
|
||||
candidates: List[Tuple[str, str]] = [] # (url, owner/repo)
|
||||
|
||||
for url, entry in inventory.items():
|
||||
if url.startswith("INTRO:"):
|
||||
continue
|
||||
repo = _parse_github_repo(url)
|
||||
if not repo:
|
||||
continue
|
||||
if not _is_activity_stale(entry):
|
||||
continue
|
||||
candidates.append((url, repo))
|
||||
if len(candidates) >= max_repos:
|
||||
break
|
||||
|
||||
if not candidates:
|
||||
log_event("[Activity] No GitHub repos require activity enrichment")
|
||||
return 0
|
||||
|
||||
log_event(f"[Activity] Enriching {len(candidates)} GitHub repos (max {max_repos})")
|
||||
|
||||
enriched = 0
|
||||
async with httpx.AsyncClient() as client:
|
||||
for url, owner_repo in candidates:
|
||||
try:
|
||||
open_issues, open_prs = await _fetch_repo_activity(client, owner_repo)
|
||||
total = open_issues + open_prs
|
||||
health = _classify_health(total)
|
||||
|
||||
entry = inventory[url]
|
||||
entry["gh_open_issues_30d"] = open_issues
|
||||
entry["gh_open_prs_30d"] = open_prs
|
||||
entry["gh_community_health"] = health
|
||||
entry["gh_activity_checked"] = datetime.now(MADRID_TZ).isoformat()
|
||||
enriched += 1
|
||||
except Exception as e:
|
||||
log_event(f"[WARN] Activity enrichment failed for {owner_repo}: {str(e)[:150]}")
|
||||
|
||||
log_event(f"[Activity] Enriched {enriched}/{len(candidates)} repos with community health data")
|
||||
return enriched
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Module 3: License Change Detection
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
async def _fetch_current_license(
|
||||
client: httpx.AsyncClient,
|
||||
owner_repo: str,
|
||||
) -> Optional[str]:
|
||||
"""Fetch the current SPDX license identifier for a GitHub repo."""
|
||||
headers = _github_headers()
|
||||
try:
|
||||
resp = await client.get(
|
||||
f"{GITHUB_API_BASE}/repos/{owner_repo}",
|
||||
headers=headers,
|
||||
timeout=15.0,
|
||||
)
|
||||
if resp.status_code == 200:
|
||||
data = resp.json()
|
||||
lic = data.get("license")
|
||||
if isinstance(lic, dict):
|
||||
return lic.get("spdx_id", "N/A")
|
||||
return "N/A"
|
||||
elif resp.status_code == 404:
|
||||
return None # repo not found / deleted
|
||||
except Exception as e:
|
||||
log_event(f"[WARN] License fetch failed for {owner_repo}: {str(e)[:120]}")
|
||||
return None
|
||||
|
||||
|
||||
async def detect_license_changes(inventory: Dict) -> List[Dict]:
|
||||
"""Compare current gh_license with stored license, flag changes.
|
||||
|
||||
For each GitHub repo in inventory that has ``gh_license``:
|
||||
- Fetches the current license from the GitHub API.
|
||||
- Compares with the stored ``gh_license``.
|
||||
- If different, updates the entry: sets ``gh_license`` to the new value
|
||||
and stores the old value in ``gh_license_previous``.
|
||||
|
||||
Returns list of ``{url, old_license, new_license, title}`` dicts.
|
||||
"""
|
||||
candidates: List[Tuple[str, str, Dict]] = [] # (url, owner/repo, entry)
|
||||
|
||||
for url, entry in inventory.items():
|
||||
if url.startswith("INTRO:"):
|
||||
continue
|
||||
if not entry.get("gh_license") or entry["gh_license"] == "N/A":
|
||||
continue
|
||||
repo = _parse_github_repo(url)
|
||||
if not repo:
|
||||
continue
|
||||
candidates.append((url, repo, entry))
|
||||
|
||||
if not candidates:
|
||||
log_event("[License] No repos with stored licenses to check")
|
||||
return []
|
||||
|
||||
candidates = candidates[:MAX_REPOS_DEFAULT] # cap to same limit as activity enrichment
|
||||
log_event(f"[License] Checking {len(candidates)} repos for license changes")
|
||||
|
||||
changes: List[Dict] = []
|
||||
async with httpx.AsyncClient() as client:
|
||||
for url, owner_repo, entry in candidates:
|
||||
try:
|
||||
current = await _fetch_current_license(client, owner_repo)
|
||||
if current is None:
|
||||
# Repo not found or API error — skip
|
||||
continue
|
||||
|
||||
stored = entry.get("gh_license", "N/A")
|
||||
if current != stored:
|
||||
change_record = {
|
||||
"url": url,
|
||||
"old_license": stored,
|
||||
"new_license": current,
|
||||
"title": entry.get("title", url),
|
||||
}
|
||||
changes.append(change_record)
|
||||
entry["gh_license_previous"] = stored
|
||||
entry["gh_license"] = current
|
||||
log_event(
|
||||
f"[License] Change detected: {owner_repo} "
|
||||
f"{stored} -> {current}"
|
||||
)
|
||||
except Exception as e:
|
||||
log_event(f"[WARN] License check failed for {owner_repo}: {str(e)[:150]}")
|
||||
|
||||
await asyncio.sleep(GITHUB_RATE_DELAY)
|
||||
|
||||
log_event(f"[License] Detected {len(changes)} license change(s)")
|
||||
return changes
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Full Enrichment Pipeline
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
async def run_enrichment():
|
||||
"""Full enrichment pipeline: CNCF + GitHub activity + license detection."""
|
||||
from src.inventory_manager import load_inventory, save_inventory
|
||||
|
||||
log_event("ENRICHMENT PIPELINE STARTING", section_break=True)
|
||||
inventory = load_inventory()
|
||||
log_event(f"[*] Loaded {len(inventory)} inventory entries")
|
||||
|
||||
cncf_count = await enrich_cncf_status(inventory)
|
||||
activity_count = await enrich_github_activity(inventory)
|
||||
license_changes = await detect_license_changes(inventory)
|
||||
|
||||
save_inventory(inventory)
|
||||
log_event(
|
||||
f"Enrichment complete: {cncf_count} CNCF, "
|
||||
f"{activity_count} activity, "
|
||||
f"{len(license_changes)} license changes",
|
||||
section_break=True,
|
||||
)
|
||||
|
||||
return license_changes
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(run_enrichment())
|
||||
+3
-2
@@ -94,10 +94,11 @@ async def fetch_github_metadata(client: httpx.AsyncClient, url: str, sem: asynci
|
||||
return url, default_meta
|
||||
|
||||
|
||||
from src.inventory_manager import load_inventory
|
||||
|
||||
async def run_enrichment():
|
||||
print("[*] Loading inventory database...")
|
||||
with open(INVENTORY_PATH, "r") as f:
|
||||
inventory = yaml.safe_load(f) or {}
|
||||
inventory = load_inventory()
|
||||
|
||||
print(f"[*] Loaded {len(inventory)} total entries.")
|
||||
|
||||
|
||||
+20
-9
@@ -122,7 +122,8 @@ async def discover_optimal_models():
|
||||
if name not in all_supported: all_supported.append(name)
|
||||
elif resp.status_code == 429:
|
||||
log_event(f" [!] Discovery Key is rate-limited (429). Skipping.")
|
||||
except: pass
|
||||
except Exception as e:
|
||||
log_event(f"[WARN] model discovery for key: {str(e)[:100]}")
|
||||
|
||||
if not all_supported:
|
||||
log_event(" [!] Discovery failed. Falling back to safe defaults.")
|
||||
@@ -136,7 +137,8 @@ async def discover_optimal_models():
|
||||
try:
|
||||
version = float(version_match.group(1))
|
||||
score += version * 50
|
||||
except: pass
|
||||
except Exception as e:
|
||||
log_event(f"[WARN] parse model version for {name}: {str(e)[:100]}")
|
||||
if "-ultra" in name: score += 100
|
||||
elif "-pro" in name: score += 50
|
||||
elif "-flash" in name: score += 25
|
||||
@@ -169,7 +171,9 @@ class GeminiDiagnostics:
|
||||
async def resolve_url(url: str) -> str:
|
||||
shorteners = ['t.co', 'bit.ly', 'buff.ly', 'goo.gl', 'tinyurl.com', 't.ly', 'rb.gy', 'is.gd', 'drp.li', 't.me', 'lnkd.in']
|
||||
try: domain = url.split("//")[-1].split("/")[0].lower()
|
||||
except: return url
|
||||
except Exception as e:
|
||||
log_event(f"[WARN] parse domain from URL {url[:50]}: {str(e)[:100]}")
|
||||
return url
|
||||
final_url, max_hops, current_hop = url, 5, 0
|
||||
async with httpx.AsyncClient(follow_redirects=True, timeout=8) as client:
|
||||
while current_hop < max_hops:
|
||||
@@ -180,7 +184,9 @@ async def resolve_url(url: str) -> str:
|
||||
new_url = str(resp.url)
|
||||
if new_url == final_url: break
|
||||
final_url, current_hop = new_url, current_hop + 1
|
||||
except: break
|
||||
except Exception as e:
|
||||
log_event(f"[WARN] resolve URL hop for {final_url[:50]}: {str(e)[:100]}")
|
||||
break
|
||||
|
||||
# Mandate 34: Prevent multiple trailing slashes using centralized utility
|
||||
return sanitize_trailing_slashes(final_url)
|
||||
@@ -217,7 +223,8 @@ async def get_github_activity(url: str) -> Dict:
|
||||
try:
|
||||
from src.config import GH_TOKEN
|
||||
headers = {"Authorization": f"token {GH_TOKEN}"} if GH_TOKEN else {}
|
||||
except:
|
||||
except Exception as e:
|
||||
log_event(f"[WARN] import GH_TOKEN for GitHub activity: {str(e)[:100]}")
|
||||
headers = {}
|
||||
|
||||
try:
|
||||
@@ -232,7 +239,8 @@ async def get_github_activity(url: str) -> Dict:
|
||||
"gh_pushed": data.get("pushed_at", "N/A"),
|
||||
"gh_license": lic_id
|
||||
}
|
||||
except: pass
|
||||
except Exception as e:
|
||||
log_event(f"[WARN] fetch GitHub activity for {url}: {str(e)[:100]}")
|
||||
return default_meta
|
||||
|
||||
|
||||
@@ -436,7 +444,8 @@ async def call_gemini_with_retry(prompt: str, response_format: str = "json", max
|
||||
|
||||
resp_json = {}
|
||||
try: resp_json = response.json()
|
||||
except: pass
|
||||
except Exception as e:
|
||||
log_event(f"[WARN] parse Gemini response JSON: {str(e)[:100]}")
|
||||
|
||||
usage = resp_json.get("usageMetadata", {})
|
||||
SESSION_TRACKER.track_call(current_idx, model, response.status_code, usage, role=role)
|
||||
@@ -452,7 +461,8 @@ async def call_gemini_with_retry(prompt: str, response_format: str = "json", max
|
||||
try:
|
||||
data = json.loads(match.group(0))
|
||||
return data
|
||||
except: pass
|
||||
except Exception as e:
|
||||
log_event(f"[WARN] parse Gemini content JSON for model {model}: {str(e)[:100]}")
|
||||
|
||||
# QUALITY UPGRADE: If flash failed parsing, don't give up on the key, try a Pro model
|
||||
if ("flash" in model or "lite" in model) and any("pro" in m for m in models):
|
||||
@@ -574,7 +584,8 @@ async def fetch_youtube_metadata(url: str) -> Optional[Dict]:
|
||||
try:
|
||||
transcript = YouTubeTranscriptApi.get_transcript(vid, languages=['en', 'es'])
|
||||
transcript_text = " ".join([t['text'] for t in transcript[:100]])
|
||||
except: pass
|
||||
except Exception as e:
|
||||
log_event(f"[WARN] fetch YouTube transcript for {vid}: {str(e)[:100]}")
|
||||
|
||||
full_description = f"{description}\n\n[Transcript Snippet]: {transcript_text}" if transcript_text else description
|
||||
|
||||
|
||||
@@ -21,14 +21,16 @@ class RepositoryController:
|
||||
try:
|
||||
file_meta = self.repository.get_contents(file_path, ref=branch_name)
|
||||
return base64.b64decode(file_meta.content).decode("utf-8")
|
||||
except:
|
||||
except Exception as e:
|
||||
print(f"[WARN] Failed to get file '{file_path}' from branch '{branch_name}': {str(e)[:100]}")
|
||||
return ""
|
||||
|
||||
def apply_historical_chunk(self, updates: dict, next_since: str) -> None:
|
||||
branch_name = "bot/historical-accumulator"
|
||||
try:
|
||||
self.repository.get_branch(branch_name)
|
||||
except:
|
||||
except Exception as e:
|
||||
print(f"[WARN] Branch '{branch_name}' not found, creating: {str(e)[:100]}")
|
||||
self._create_feature_branch(branch_name)
|
||||
|
||||
for file_path, content in updates.items():
|
||||
@@ -54,7 +56,8 @@ class RepositoryController:
|
||||
|
||||
try:
|
||||
self._create_feature_branch(branch_name)
|
||||
except:
|
||||
except Exception as e:
|
||||
print(f"[WARN] Branch creation failed, retrying with unique suffix: {str(e)[:100]}")
|
||||
branch_name = f"bot/knowledge-update-{timestamp_slug}-{id(updates)}"
|
||||
self._create_feature_branch(branch_name)
|
||||
|
||||
|
||||
@@ -88,11 +88,12 @@ class BackupDataExtractor:
|
||||
def parse_date(x):
|
||||
try:
|
||||
return datetime.strptime(x["timestamp"], '%a %b %d %H:%M:%S +0000 %Y')
|
||||
except:
|
||||
except Exception as e:
|
||||
print(f"[WARN] Date parse failed for timestamp: {str(e)[:100]}")
|
||||
return datetime.min
|
||||
results.sort(key=parse_date)
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
print(f"[WARN] Sorting results by date failed: {str(e)[:100]}")
|
||||
|
||||
self.log_audit("Backup Ingestion", True, f"Total links extracted: {len(results)}")
|
||||
return results
|
||||
|
||||
@@ -52,8 +52,8 @@ class SocialDataExtractor:
|
||||
try:
|
||||
from playwright.async_api import async_playwright
|
||||
import playwright_stealth
|
||||
except:
|
||||
self.log_audit("Playwright", False, "Libraries not available.")
|
||||
except Exception as e:
|
||||
self.log_audit("Playwright", False, f"Libraries not available: {str(e)[:100]}")
|
||||
return []
|
||||
|
||||
collected_tweets = {}
|
||||
@@ -74,14 +74,16 @@ class SocialDataExtractor:
|
||||
for k in ['sameSite', 'storeId', 'id']: c.pop(k, None)
|
||||
formatted.append(c)
|
||||
await context.add_cookies(formatted)
|
||||
except: pass
|
||||
except Exception as e:
|
||||
log_event(f"[WARN] Failed to load Twitter cookies: {str(e)[:100]}")
|
||||
|
||||
for account in accounts:
|
||||
page = await context.new_page()
|
||||
try:
|
||||
if hasattr(playwright_stealth, 'stealth_async'): await playwright_stealth.stealth_async(page)
|
||||
elif hasattr(playwright_stealth, 'stealth'): playwright_stealth.stealth(page)
|
||||
except: pass
|
||||
except Exception as e:
|
||||
log_event(f"[WARN] Playwright stealth setup failed: {str(e)[:100]}")
|
||||
|
||||
if strategy == "search":
|
||||
import urllib.parse
|
||||
|
||||
@@ -44,7 +44,8 @@ class IntelligentLinkCleaner:
|
||||
def _load_memory(self) -> Dict:
|
||||
if os.path.exists(MEMORY_FILE):
|
||||
try: return json.load(open(MEMORY_FILE, 'r'))
|
||||
except: pass
|
||||
except Exception as e:
|
||||
log_event(f"[WARN] load health learning memory: {str(e)[:100]}")
|
||||
return {"domains": {}, "known_soft_404_patterns": []}
|
||||
|
||||
def _save_memory(self):
|
||||
@@ -198,8 +199,10 @@ class IntelligentLinkCleaner:
|
||||
else:
|
||||
log_event(f" [✨] RESCUED: {u} -> {new_loc}")
|
||||
check_results[u] = (True, "resurrected", new_loc)
|
||||
except: pass
|
||||
except: pass
|
||||
except Exception as e:
|
||||
log_event(f"[WARN] verify rescued URL {new_loc[:50]}: {str(e)[:100]}")
|
||||
except Exception as e:
|
||||
log_event(f"[WARN] AI rescue batch: {str(e)[:100]}")
|
||||
|
||||
# 2.8. Finalize Status
|
||||
log_event("FINALIZING STATUS AND METRICS...", section_break=True)
|
||||
@@ -309,7 +312,8 @@ class IntelligentLinkCleaner:
|
||||
log_event(f" [⚖️] LICENSE ALERT: {url} -> {new_lic}")
|
||||
entry["status"] = "review_required"
|
||||
entry["gh_license"] = new_lic
|
||||
except: pass
|
||||
except Exception as e:
|
||||
log_event(f"[WARN] license guard check for {url}: {str(e)[:100]}")
|
||||
|
||||
if final_url != url:
|
||||
u_p = url.split("://")[-1].rstrip("/"); f_p = final_url.split("://")[-1].rstrip("/")
|
||||
@@ -325,12 +329,15 @@ class IntelligentLinkCleaner:
|
||||
h = url.replace("/master/", "/main/")
|
||||
try:
|
||||
if (await client.get(h)).status_code < 400: return True, "healed", h
|
||||
except: pass
|
||||
except Exception as e:
|
||||
log_event(f"[WARN] heal master->main for {url}: {str(e)[:100]}")
|
||||
m = re.search(r'(https?://github\.com/[^/]+/[^/]+)', url)
|
||||
if m and (await client.get(m.group(1))).status_code < 400: return True, "consolidated", m.group(1)
|
||||
return False, "404", None
|
||||
return True, f"Soft Block {resp.status_code}", None
|
||||
except: return True, "Error", None
|
||||
except Exception as e:
|
||||
log_event(f"[WARN] URL check logic for {url}: {str(e)[:100]}")
|
||||
return True, "Error", None
|
||||
|
||||
async def prune_orphaned_metadata(self):
|
||||
valid_map = {}
|
||||
|
||||
+169
-17
@@ -1,46 +1,198 @@
|
||||
import os
|
||||
import json
|
||||
import sqlite3
|
||||
import yaml
|
||||
from typing import Dict
|
||||
|
||||
INVENTORY_PATH = "data/inventory.yaml"
|
||||
SQL_PATH = "data/inventory.sql"
|
||||
|
||||
try:
|
||||
from yaml import CSafeLoader as Loader, CSafeDumper as Dumper
|
||||
except ImportError:
|
||||
from yaml import SafeLoader as Loader, SafeDumper as Dumper
|
||||
|
||||
def load_inventory(shard_file: str = None) -> Dict:
|
||||
"""
|
||||
Loads the entire inventory from a single YAML file.
|
||||
Legacy/Fast-Track standard restored: Zero-sharding complexity.
|
||||
Loads the entire inventory.
|
||||
Option 3: Imports inventory.sql to temporary SQLite database in-memory,
|
||||
queries the database to reconstruct the Python dictionary, and returns it.
|
||||
Falls back to inventory.yaml if SQL file is not present.
|
||||
"""
|
||||
if os.path.exists(INVENTORY_PATH):
|
||||
if not os.path.exists(SQL_PATH) and os.path.exists(INVENTORY_PATH):
|
||||
try:
|
||||
with open(INVENTORY_PATH, "r") as file:
|
||||
return yaml.safe_load(file) or {}
|
||||
except: pass
|
||||
return {}
|
||||
with open(INVENTORY_PATH, "r", encoding="utf-8") as file:
|
||||
return yaml.load(file, Loader=Loader) or {}
|
||||
except Exception as e:
|
||||
pass
|
||||
return {}
|
||||
|
||||
if not os.path.exists(SQL_PATH):
|
||||
return {}
|
||||
|
||||
conn = sqlite3.connect(":memory:")
|
||||
try:
|
||||
with open(SQL_PATH, "r", encoding="utf-8") as f:
|
||||
conn.executescript(f.read())
|
||||
except Exception as e:
|
||||
conn.close()
|
||||
# Fallback to YAML if SQL import fails
|
||||
if os.path.exists(INVENTORY_PATH):
|
||||
try:
|
||||
with open(INVENTORY_PATH, "r", encoding="utf-8") as file:
|
||||
return yaml.load(file, Loader=Loader) or {}
|
||||
except Exception as e:
|
||||
print(f"[WARN] YAML fallback load failed: {str(e)[:100]}")
|
||||
return {}
|
||||
|
||||
cursor = conn.cursor()
|
||||
try:
|
||||
cursor.execute("SELECT * FROM resources")
|
||||
rows = cursor.fetchall()
|
||||
col_names = [description[0] for description in cursor.description]
|
||||
except Exception as e:
|
||||
conn.close()
|
||||
return {}
|
||||
|
||||
inv = {}
|
||||
for row in rows:
|
||||
record = dict(zip(col_names, row))
|
||||
url = record.pop("url")
|
||||
|
||||
# Deserialize JSON lists/dicts
|
||||
for json_field in ["hierarchy", "tags", "v1_locations", "v2_locations", "youtube_mosaic", "extra_metadata"]:
|
||||
val = record.get(json_field)
|
||||
if val:
|
||||
try:
|
||||
record[json_field] = json.loads(val)
|
||||
except Exception as e:
|
||||
print(f"[WARN] JSON parse failed for field '{json_field}': {str(e)[:100]}")
|
||||
record[json_field] = [] if json_field not in ["youtube_mosaic", "extra_metadata"] else {}
|
||||
else:
|
||||
record[json_field] = [] if json_field not in ["youtube_mosaic", "extra_metadata"] else {}
|
||||
|
||||
# Merge extra_metadata keys back into the record dictionary
|
||||
extra = record.pop("extra_metadata", {})
|
||||
if isinstance(extra, dict):
|
||||
record.update(extra)
|
||||
|
||||
# Restore types
|
||||
if record.get("is_microservice") is not None:
|
||||
record["is_microservice"] = bool(record["is_microservice"])
|
||||
if record.get("needs_ai_refresh") is not None:
|
||||
record["needs_ai_refresh"] = bool(record["needs_ai_refresh"])
|
||||
|
||||
inv[url] = record
|
||||
|
||||
conn.close()
|
||||
return inv
|
||||
|
||||
def save_inventory(inv: Dict, shard_file: str = None):
|
||||
"""
|
||||
Saves the entire inventory to a single YAML file.
|
||||
Saves the entire inventory.
|
||||
Option 3: Creates an in-memory SQLite table, populates it,
|
||||
and exports it back to inventory.sql.
|
||||
Also dual-saves a backup to inventory.yaml using fast CDumper.
|
||||
"""
|
||||
conn = sqlite3.connect(":memory:")
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS resources (
|
||||
url TEXT PRIMARY KEY,
|
||||
title TEXT,
|
||||
description TEXT,
|
||||
year TEXT,
|
||||
stars INTEGER,
|
||||
ai_summary TEXT,
|
||||
language TEXT,
|
||||
resource_type TEXT,
|
||||
complexity TEXT,
|
||||
is_microservice BOOLEAN,
|
||||
status TEXT,
|
||||
addition_method TEXT,
|
||||
content_hash TEXT,
|
||||
health_score REAL,
|
||||
last_checked REAL,
|
||||
needs_ai_refresh BOOLEAN,
|
||||
discovered_at TEXT,
|
||||
last_ai_eval TEXT,
|
||||
company TEXT,
|
||||
geo_region TEXT,
|
||||
hierarchy TEXT,
|
||||
tags TEXT,
|
||||
v1_locations TEXT,
|
||||
v2_locations TEXT,
|
||||
youtube_mosaic TEXT,
|
||||
extra_metadata TEXT
|
||||
);
|
||||
""")
|
||||
|
||||
columns = [
|
||||
"url", "title", "description", "year", "stars", "ai_summary", "language",
|
||||
"resource_type", "complexity", "is_microservice", "status", "addition_method",
|
||||
"content_hash", "health_score", "last_checked", "needs_ai_refresh",
|
||||
"discovered_at", "last_ai_eval", "company", "geo_region",
|
||||
"hierarchy", "tags", "v1_locations", "v2_locations", "youtube_mosaic", "extra_metadata"
|
||||
]
|
||||
|
||||
for url, entry in inv.items():
|
||||
if not isinstance(entry, dict):
|
||||
continue
|
||||
|
||||
record = {col: entry.get(col) for col in columns if col not in ["hierarchy", "tags", "v1_locations", "v2_locations", "youtube_mosaic", "extra_metadata"]}
|
||||
record["url"] = url
|
||||
|
||||
# Serialize lists/dicts
|
||||
record["hierarchy"] = json.dumps(entry.get("hierarchy", []))
|
||||
record["tags"] = json.dumps(entry.get("tags", []))
|
||||
record["v1_locations"] = json.dumps(entry.get("v1_locations", []))
|
||||
record["v2_locations"] = json.dumps(entry.get("v2_locations", []))
|
||||
record["youtube_mosaic"] = json.dumps(entry.get("youtube_mosaic", {}))
|
||||
|
||||
# Pull arbitrary extra fields
|
||||
extra = {}
|
||||
for k, v in entry.items():
|
||||
if k not in columns:
|
||||
extra[k] = v
|
||||
record["extra_metadata"] = json.dumps(extra)
|
||||
|
||||
# Conversions
|
||||
record["is_microservice"] = 1 if record.get("is_microservice") else 0
|
||||
record["needs_ai_refresh"] = 1 if record.get("needs_ai_refresh") else 0
|
||||
|
||||
placeholders = ", ".join(["?"] * len(columns))
|
||||
values = [record[col] for col in columns]
|
||||
cursor.execute(f"INSERT OR REPLACE INTO resources ({', '.join(columns)}) VALUES ({placeholders})", values)
|
||||
|
||||
conn.commit()
|
||||
|
||||
# Dump to SQL
|
||||
os.makedirs(os.path.dirname(SQL_PATH), exist_ok=True)
|
||||
with open(SQL_PATH, "w", encoding="utf-8") as f:
|
||||
for line in conn.iterdump():
|
||||
f.write(f"{line}\n")
|
||||
|
||||
conn.close()
|
||||
|
||||
# Dual-Save to YAML (Fast C-Dumper)
|
||||
os.makedirs(os.path.dirname(INVENTORY_PATH), exist_ok=True)
|
||||
with open(INVENTORY_PATH, "w") as file:
|
||||
yaml.dump(inv, file, sort_keys=False, allow_unicode=True)
|
||||
with open(INVENTORY_PATH, "w", encoding="utf-8") as file:
|
||||
yaml.dump(inv, file, Dumper=Dumper, sort_keys=False, allow_unicode=True)
|
||||
|
||||
def get_shard_name(url: str) -> str:
|
||||
# Kept for backward compatibility but unused in single-file mode
|
||||
return "inventory.yaml"
|
||||
|
||||
def update_inventory_entry(inventory: Dict, norm_url: str, new_data: Dict):
|
||||
"""
|
||||
Updates an inventory entry by merging new_data with existing data,
|
||||
preserving metadata keys like 'youtube_mosaic' if they are not in new_data.
|
||||
"""
|
||||
if norm_url not in inventory:
|
||||
inventory[norm_url] = {}
|
||||
|
||||
existing = inventory[norm_url]
|
||||
if isinstance(existing, dict):
|
||||
merged = existing.copy()
|
||||
existing_discovered = existing.get("discovered_at")
|
||||
merged.update(new_data)
|
||||
if existing_discovered:
|
||||
merged["discovered_at"] = existing_discovered
|
||||
inventory[norm_url] = merged
|
||||
else:
|
||||
inventory[norm_url] = new_data
|
||||
|
||||
|
||||
+2
-2
@@ -32,5 +32,5 @@ def _write_to_file(message: str):
|
||||
os.makedirs(os.path.dirname(DEFAULT_LOG_PATH), exist_ok=True)
|
||||
with open(DEFAULT_LOG_PATH, "a") as f:
|
||||
f.write(message + "\n")
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
print(f"[WARN] write to log file: {str(e)[:100]}")
|
||||
|
||||
+34
-20
@@ -4,6 +4,10 @@ import os
|
||||
import json
|
||||
import re
|
||||
import yaml
|
||||
try:
|
||||
from yaml import CSafeLoader as Loader
|
||||
except ImportError:
|
||||
from yaml import SafeLoader as Loader
|
||||
import httpx
|
||||
from urllib.parse import urlparse
|
||||
from datetime import datetime, timedelta
|
||||
@@ -55,7 +59,8 @@ async def master_orchestrator():
|
||||
since_date = until_date - timedelta(days=days)
|
||||
log_event(f"[*] Mode: Relative range (Last {days} days) -> {since_date.date()}")
|
||||
is_historical = False # Force normal mode for relative range
|
||||
except:
|
||||
except Exception as e:
|
||||
log_event(f"[WARN] parse CURATION_DAYS_BACK: {str(e)[:100]}")
|
||||
since_date = get_last_date()
|
||||
elif is_historical:
|
||||
# DEFAULT START DATE: 2026-05-15 (as requested)
|
||||
@@ -82,7 +87,8 @@ async def master_orchestrator():
|
||||
try:
|
||||
since_date = datetime.fromisoformat(env_start).replace(tzinfo=MADRID_TZ)
|
||||
log_event(f"[*] Normal Mode: From manual workflow date {since_date.date()}")
|
||||
except:
|
||||
except Exception as e:
|
||||
log_event(f"[WARN] parse CURATION_START_DATE: {str(e)[:100]}")
|
||||
since_date = get_last_date()
|
||||
log_event(f"[*] Normal Mode: Error parsing manual date, using state.json {since_date.date()}")
|
||||
else:
|
||||
@@ -114,8 +120,8 @@ async def master_orchestrator():
|
||||
|
||||
if os.path.exists(sources_file):
|
||||
try:
|
||||
with open(sources_file, 'r') as f:
|
||||
data = yaml.safe_load(f)
|
||||
with open(sources_file, 'r', encoding='utf-8') as f:
|
||||
data = yaml.load(f, Loader=Loader)
|
||||
all_accounts = set()
|
||||
for topic_data in data.get("sources", []):
|
||||
topic_name = topic_data.get("topic")
|
||||
@@ -206,11 +212,12 @@ async def master_orchestrator():
|
||||
parsed = urlparse(expanded_url)
|
||||
domain = parsed.netloc.lower()
|
||||
|
||||
domain_info = health_learning["domains"].setdefault(domain, {"attempts": 0, "failures": 0, "consecutive_failures": 0})
|
||||
domain_info = health_learning["domains"].setdefault(domain, {"attempts": 0, "failures": 0, "consecutive_failures": 0, "success_rate": 100.0})
|
||||
consecutive_failures = domain_info.get("consecutive_failures", 0)
|
||||
success_rate = domain_info.get("success_rate", 100.0)
|
||||
|
||||
timeout_val = 12.0
|
||||
if consecutive_failures >= 3:
|
||||
if consecutive_failures >= 3 or success_rate < 50.0:
|
||||
timeout_val = 3.0
|
||||
ua = fallback_user_agents[idx % len(fallback_user_agents)]
|
||||
else:
|
||||
@@ -226,18 +233,21 @@ async def master_orchestrator():
|
||||
resp = await client.get(expanded_url)
|
||||
if resp.status_code == 404:
|
||||
asset["health"] = "dead" # Definitively dead
|
||||
info = health_learning["domains"][domain]
|
||||
info["failures"] = info.get("failures", 0) + 1
|
||||
info["consecutive_failures"] = info.get("consecutive_failures", 0) + 1
|
||||
domain_info["failures"] = domain_info.get("failures", 0) + 1
|
||||
domain_info["consecutive_failures"] = domain_info.get("consecutive_failures", 0) + 1
|
||||
else:
|
||||
asset["health"] = "online"
|
||||
info = health_learning["domains"][domain]
|
||||
info["consecutive_failures"] = 0
|
||||
except:
|
||||
domain_info["consecutive_failures"] = 0
|
||||
except Exception as e:
|
||||
log_event(f"[WARN] health check for {expanded_url}: {str(e)[:100]}")
|
||||
asset["health"] = "timeout" # Assume alive but unreachable for now
|
||||
info = health_learning["domains"][domain]
|
||||
info["failures"] = info.get("failures", 0) + 1
|
||||
info["consecutive_failures"] = info.get("consecutive_failures", 0) + 1
|
||||
domain_info["failures"] = domain_info.get("failures", 0) + 1
|
||||
domain_info["consecutive_failures"] = domain_info.get("consecutive_failures", 0) + 1
|
||||
|
||||
# Recalculate success rate and store it
|
||||
attempts = domain_info.get("attempts", 1)
|
||||
failures = domain_info.get("failures", 0)
|
||||
domain_info["success_rate"] = round(((attempts - failures) / attempts) * 100.0, 2)
|
||||
|
||||
# 3. GitHub Metadata Enrichment
|
||||
if "github.com" in expanded_url:
|
||||
@@ -254,7 +264,8 @@ async def master_orchestrator():
|
||||
gh_data = gh_resp.json()
|
||||
asset["gh_stars"] = gh_data.get("stargazers_count")
|
||||
asset["gh_updated"] = gh_data.get("updated_at", "").split("T")[0]
|
||||
except: pass
|
||||
except Exception as e:
|
||||
log_event(f"[WARN] GitHub metadata enrichment for {expanded_url}: {str(e)[:100]}")
|
||||
|
||||
return asset
|
||||
|
||||
@@ -291,7 +302,8 @@ async def master_orchestrator():
|
||||
found = re.findall(r'\]\((https?://[^\)]+)\)', content)
|
||||
for url in found:
|
||||
existing_urls.add(url.split('#')[0].rstrip('/').lower())
|
||||
except: pass
|
||||
except Exception as e:
|
||||
log_event(f"[WARN] read docs/{file} for URL extraction: {str(e)[:100]}")
|
||||
|
||||
log_event(f"[*] Global Deduplication: {len(existing_urls)} existing URLs loaded.")
|
||||
|
||||
@@ -329,12 +341,14 @@ async def master_orchestrator():
|
||||
if isinstance(ts, str):
|
||||
try:
|
||||
asset_date = datetime.strptime(ts, '%a %b %d %H:%M:%S +0000 %Y').replace(tzinfo=MADRID_TZ)
|
||||
except:
|
||||
except Exception as e:
|
||||
try: asset_date = datetime.fromisoformat(ts.replace('Z', '+00:00'))
|
||||
except: pass
|
||||
except Exception as e2:
|
||||
log_event(f"[WARN] parse timestamp '{ts[:30]}': {str(e2)[:100]}")
|
||||
if asset_date and asset_date > max_tweet_date:
|
||||
max_tweet_date = asset_date
|
||||
except: pass
|
||||
except Exception as e:
|
||||
log_event(f"[WARN] process asset timestamp: {str(e)[:100]}")
|
||||
|
||||
assets_to_evaluate.append(asset)
|
||||
|
||||
|
||||
@@ -66,7 +66,9 @@ def get_system_mandates() -> str:
|
||||
if os.path.exists(MANDATES_JSON):
|
||||
try:
|
||||
return json.load(open(MANDATES_JSON, "r")).get("system_snippet", "")
|
||||
except: return ""
|
||||
except Exception as e:
|
||||
log_event(f"[WARN] Failed to load system mandates: {str(e)[:100]}")
|
||||
return ""
|
||||
return ""
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -0,0 +1,445 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import json
|
||||
import asyncio
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Dict, List, Any
|
||||
|
||||
from src.inventory_manager import load_inventory
|
||||
from src.gemini_utils import call_gemini_with_retry
|
||||
from src.config import MADRID_TZ
|
||||
from src.logger import log_event
|
||||
|
||||
DIGEST_OUTPUT_PATH = "data/news_digest.json"
|
||||
|
||||
|
||||
class NewsDigestEngine:
|
||||
"""Generates a curated news digest by filtering inventory entries by
|
||||
recency and using Gemini AI to rank the most relevant ones per category.
|
||||
|
||||
Three time windows (3 / 6 / 12 months) are produced in a single run.
|
||||
Each window contains up to 10 AI-ranked items per digest category.
|
||||
"""
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# 26 Digest Categories – mapped from V2 category slugs #
|
||||
# ------------------------------------------------------------------ #
|
||||
DIGEST_CATEGORIES: Dict[str, List[str]] = {
|
||||
# --- TECH CORE (9) ---
|
||||
"Kubernetes & Orchestration": [
|
||||
"kubernetes", "kubernetes-tools", "kubernetes-tutorials",
|
||||
"kubectl-commands", "kubernetes-releases", "kubernetes-autoscaling",
|
||||
"kubernetes-operators-controllers", "kubernetes-based-devel",
|
||||
"kubernetes-alternatives", "kubernetes-client-libraries",
|
||||
"kubernetes-bigdata", "managed-kubernetes-in-public-cloud", "helm",
|
||||
],
|
||||
"Containers & Runtime": [
|
||||
"docker", "container-managers", "serverless", "noops", "registries",
|
||||
],
|
||||
"Networking & Service Mesh": [
|
||||
"networking", "kubernetes-networking", "servicemesh", "istio",
|
||||
"caching", "web-servers", "cloudflare",
|
||||
],
|
||||
"Architecture & Microservices": [
|
||||
"introduction", "faq", "cloud-arch-diagrams", "matrix-table",
|
||||
"other-awesome-lists", "about",
|
||||
],
|
||||
"Data, Messaging & Storage": [
|
||||
"databases", "nosql", "newsql", "message-queue", "crunchydata",
|
||||
"yaml", "kubernetes-storage", "kubernetes-backup-migrations",
|
||||
],
|
||||
"AI & Agents": [
|
||||
"ai", "ai-agents-mcp", "chatgpt",
|
||||
],
|
||||
"MLOps & Data Science": [
|
||||
"mlops",
|
||||
],
|
||||
"Python, Java & Developer Ecosystem": [
|
||||
"python", "golang", "java_frameworks", "java_app_servers",
|
||||
"java-and-java-performance-optimization", "javascript", "dotnet",
|
||||
"angular", "react", "web3", "api",
|
||||
"swagger-code-generator-for-rest-apis", "postman",
|
||||
"lowcode-nocode", "devel-sites", "dom", "linux-dev-env",
|
||||
"ChromeDevTools", "xamarin", "jvm-parameters-matrix-table",
|
||||
"maven-gradle", "embedded-servlet-containers", "visual-studio",
|
||||
],
|
||||
"Linux & System Foundations": [
|
||||
"linux", "git",
|
||||
],
|
||||
# --- PLATFORM & OPS (8) ---
|
||||
"Security & Compliance": [
|
||||
"securityascode", "kubernetes-security", "aws-security", "oauth",
|
||||
"devsecops",
|
||||
],
|
||||
"Infrastructure as Code": [
|
||||
"iac", "terraform", "pulumi", "crossplane", "ansible",
|
||||
"kustomize", "chef", "liquibase",
|
||||
],
|
||||
"CI/CD & GitOps": [
|
||||
"cicd", "gitops", "argo", "flux", "tekton", "jenkins",
|
||||
"jenkins-alternatives", "sonarqube", "cicd-kubernetes-plugins",
|
||||
"openshift-pipelines", "stackstorm", "keptn",
|
||||
],
|
||||
"Observability, SRE & Testing": [
|
||||
"sre", "monitoring", "prometheus", "grafana",
|
||||
"kubernetes-monitoring", "chaos-engineering", "qa",
|
||||
"test-automation-frameworks", "testops",
|
||||
"performance-testing-with-jenkins-and-jmeter",
|
||||
"kubernetes-troubleshooting",
|
||||
],
|
||||
"DevOps & Culture": [
|
||||
"devops", "devops-tools", "project-management-methodology",
|
||||
"project-management-tools",
|
||||
],
|
||||
"Platform Engineering & DevEx": [
|
||||
"developerportals", "scaffolding", "mkdocs",
|
||||
],
|
||||
"FinOps & Cloud Cost": [
|
||||
"finops", "aws-pricing",
|
||||
],
|
||||
"Certification & Training": [
|
||||
"elearning", "interview-questions", "aws-training", "cheatsheets",
|
||||
"demos",
|
||||
],
|
||||
# --- CLOUD & ENTERPRISE (5) ---
|
||||
"AWS": [
|
||||
"aws", "aws-architecture", "aws-security", "aws-networking",
|
||||
"aws-databases", "aws-storage", "aws-monitoring", "aws-iac",
|
||||
"aws-tools-scripts", "aws-messaging", "aws-data", "aws-devops",
|
||||
"aws-serverless", "aws-containers", "aws-backup",
|
||||
"aws-newfeatures", "aws-miscellaneous", "aws-spain",
|
||||
],
|
||||
"Azure": [
|
||||
"azure",
|
||||
],
|
||||
"GCP, OCI & Others": [
|
||||
"GoogleCloudPlatform", "ibm_cloud", "oraclecloud",
|
||||
"digitalocean", "scaleway", "edge-computing",
|
||||
"public-cloud-solutions",
|
||||
],
|
||||
"OpenShift / Red Hat": [
|
||||
"openshift", "ocp3", "ocp4", "openshift-pipelines", "rancher",
|
||||
],
|
||||
"Virtualization & Private Cloud": [
|
||||
"kubernetes-on-premise", "kubernetes-alternatives",
|
||||
"private-cloud-solutions",
|
||||
],
|
||||
# --- INDUSTRY / GEO (4) – resolved via geo_region, not slugs ---
|
||||
"Americas": [],
|
||||
"Europe": [],
|
||||
"España": [],
|
||||
"Asia-Pacific": [],
|
||||
}
|
||||
|
||||
GEO_CATEGORIES: Dict[str, str] = {
|
||||
"Americas": "americas",
|
||||
"Europe": "europe",
|
||||
"España": "spain",
|
||||
"Asia-Pacific": "asia_pacific",
|
||||
}
|
||||
|
||||
PERIODS: Dict[str, int] = {
|
||||
"3_months": 90,
|
||||
"6_months": 180,
|
||||
"12_months": 365,
|
||||
}
|
||||
|
||||
ITEMS_PER_PERIOD: Dict[str, int] = {
|
||||
"3_months": 10,
|
||||
"6_months": 15,
|
||||
"12_months": 20,
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.inventory: Dict[str, Any] = load_inventory()
|
||||
|
||||
# Reverse map: v2_category_slug -> digest_category_name
|
||||
self.category_map: Dict[str, str] = {}
|
||||
for digest_cat, slugs in self.DIGEST_CATEGORIES.items():
|
||||
for slug in slugs:
|
||||
self.category_map[slug] = digest_cat
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# Classification helpers #
|
||||
# ------------------------------------------------------------------ #
|
||||
|
||||
def _get_entry_category(self, entry: dict) -> str | None:
|
||||
"""Determine which digest category an entry belongs to.
|
||||
|
||||
Checks ``v2_locations`` paths first (more specific), then falls
|
||||
back to the ``category`` field.
|
||||
"""
|
||||
for loc in entry.get("v2_locations", []):
|
||||
slug = loc.replace(".md", "")
|
||||
if slug in self.category_map:
|
||||
return self.category_map[slug]
|
||||
cat = entry.get("category", "")
|
||||
if cat in self.category_map:
|
||||
return self.category_map[cat]
|
||||
return None
|
||||
|
||||
def _get_entry_geo(self, entry: dict) -> str | None:
|
||||
"""Return the geo digest category using geo_region field, falling back to URL TLD inference."""
|
||||
region = entry.get("geo_region", "")
|
||||
for geo_name, geo_val in self.GEO_CATEGORIES.items():
|
||||
if region == geo_val:
|
||||
return geo_name
|
||||
# Fallback: infer from URL TLD
|
||||
return self._infer_geo_from_url(entry.get("url", ""))
|
||||
|
||||
@staticmethod
|
||||
def _infer_geo_from_url(url: str) -> str | None:
|
||||
"""Infer geo category from URL TLD. Returns GEO_CATEGORIES key or None."""
|
||||
try:
|
||||
from urllib.parse import urlparse
|
||||
host = urlparse(url).hostname or ""
|
||||
# Ordered longest-first to avoid .uk matching before .co.uk
|
||||
tld_to_region = [
|
||||
(".com.au", "Asia-Pacific"), (".co.uk", "Europe"), (".co.jp", "Asia-Pacific"),
|
||||
(".co.kr", "Asia-Pacific"), (".com.br", "Americas"), (".com.mx", "Americas"),
|
||||
(".es", "España"), (".de", "Europe"), (".fr", "Europe"), (".it", "Europe"),
|
||||
(".pt", "Europe"), (".nl", "Europe"), (".be", "Europe"), (".se", "Europe"),
|
||||
(".dk", "Europe"), (".fi", "Europe"), (".no", "Europe"), (".ch", "Europe"),
|
||||
(".at", "Europe"), (".pl", "Europe"), (".cz", "Europe"), (".uk", "Europe"),
|
||||
(".ie", "Europe"), (".eu", "Europe"), (".cn", "Asia-Pacific"),
|
||||
(".jp", "Asia-Pacific"), (".kr", "Asia-Pacific"), (".sg", "Asia-Pacific"),
|
||||
(".in", "Asia-Pacific"), (".au", "Asia-Pacific"), (".nz", "Asia-Pacific"),
|
||||
(".ca", "Americas"), (".mx", "Americas"), (".br", "Americas"),
|
||||
]
|
||||
for tld, region in tld_to_region:
|
||||
if host.endswith(tld):
|
||||
return region
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _is_within_period(entry: dict, cutoff_iso: str) -> bool:
|
||||
"""Check if entry falls within the time period using discovered_at,
|
||||
with year field as fallback for backfilled entries."""
|
||||
discovered = entry.get("discovered_at", "")
|
||||
if discovered:
|
||||
try:
|
||||
if discovered >= cutoff_iso:
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
year = entry.get("year", "")
|
||||
if year and isinstance(year, str) and year.isdigit():
|
||||
cutoff_year = cutoff_iso[:4] if len(cutoff_iso) >= 4 else "2020"
|
||||
return year >= cutoff_year
|
||||
return False
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# Prompt builder #
|
||||
# ------------------------------------------------------------------ #
|
||||
|
||||
@staticmethod
|
||||
def _build_ranking_prompt(
|
||||
category: str, entries: List[dict], period: str
|
||||
) -> str:
|
||||
"""Assemble the Gemini prompt that asks for a ranked TOP-10."""
|
||||
period_label = period.replace("_", " ")
|
||||
lines: List[str] = []
|
||||
for i, e in enumerate(entries[:50]):
|
||||
summary_fragment = (e.get("ai_summary", "") or "")[:200]
|
||||
lines.append(
|
||||
f'{i}. "{e.get("title", "Unknown")}" '
|
||||
f'({e.get("url", "")}) | '
|
||||
f'Stars: {e.get("stars", 0)} | '
|
||||
f'Year: {e.get("year", "N/A")} | '
|
||||
f'Summary: {summary_fragment}'
|
||||
)
|
||||
entries_text = "\n".join(lines)
|
||||
|
||||
return (
|
||||
"You are a Senior Technical Curator for a Cloud Native "
|
||||
"knowledge portal.\n"
|
||||
f'Given these resources discovered in the last {period_label} '
|
||||
f'for "{category}", select the TOP 10 most relevant.\n\n'
|
||||
"SCORING CRITERIA:\n"
|
||||
"- Industry Impact (30%): Does this change how teams "
|
||||
"build/operate?\n"
|
||||
"- Technical Novelty (25%): New capability, paradigm shift, "
|
||||
"major release?\n"
|
||||
"- Enterprise Adoption (20%): GA releases, production-ready?\n"
|
||||
"- Community Signal (15%): CNCF graduations, major blog posts?\n"
|
||||
"- Nubenetes Relevance (10%): Directly related to cloud native?\n\n"
|
||||
'Respond ONLY JSON: {"items": [{"idx": int, '
|
||||
'"impact": "critical|high|medium", '
|
||||
'"why": "1 sentence explaining why this matters"}]}\n\n'
|
||||
f"RESOURCES:\n{entries_text}"
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# Star-based fallback (used when Gemini is unavailable / < 3 entries) #
|
||||
# ------------------------------------------------------------------ #
|
||||
|
||||
@staticmethod
|
||||
def _fallback_items(
|
||||
entries: List[dict], cat_name: str, limit: int = 10
|
||||
) -> List[dict]:
|
||||
"""Return up to *limit* items using a deterministic star-based
|
||||
ranking (no AI call required)."""
|
||||
return [
|
||||
{
|
||||
"url": e["url"],
|
||||
"title": e.get("title", "Unknown"),
|
||||
"date": e.get("discovered_at", "")[:10],
|
||||
"stars": e.get("stars") or 0,
|
||||
"impact": "high" if (e.get("stars") or 0) >= 4 else "medium",
|
||||
"why": (e.get("ai_summary", "") or "")[:200],
|
||||
"category": cat_name,
|
||||
}
|
||||
for e in entries[:limit]
|
||||
]
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# Core generation loop #
|
||||
# ------------------------------------------------------------------ #
|
||||
|
||||
async def generate_digest(self) -> dict:
|
||||
"""Generate the full digest for all categories and time periods.
|
||||
|
||||
Returns a nested dict::
|
||||
|
||||
{
|
||||
"3_months": { "Kubernetes & Orchestration": [...], ... },
|
||||
"6_months": { ... },
|
||||
"12_months": { ... }
|
||||
}
|
||||
"""
|
||||
digest: Dict[str, Dict[str, List[dict]]] = {}
|
||||
|
||||
for period_name, days in self.PERIODS.items():
|
||||
cutoff = (
|
||||
datetime.now(MADRID_TZ) - timedelta(days=days)
|
||||
).isoformat()
|
||||
digest[period_name] = {}
|
||||
|
||||
# Bucket entries into their digest categories
|
||||
category_pools: Dict[str, List[dict]] = {}
|
||||
for url, entry in self.inventory.items():
|
||||
if not isinstance(entry, dict):
|
||||
continue
|
||||
if not self._is_within_period(entry, cutoff):
|
||||
continue
|
||||
|
||||
# Tech / topic category
|
||||
cat = self._get_entry_category(entry)
|
||||
if cat:
|
||||
category_pools.setdefault(cat, []).append(
|
||||
dict(entry, url=url)
|
||||
)
|
||||
|
||||
# Geo category (entry may belong to both)
|
||||
geo = self._get_entry_geo(entry)
|
||||
if geo:
|
||||
category_pools.setdefault(geo, []).append(
|
||||
dict(entry, url=url)
|
||||
)
|
||||
|
||||
# Rank each category pool
|
||||
for cat_name, entries in category_pools.items():
|
||||
entries.sort(
|
||||
key=lambda x: (
|
||||
x.get("stars") or 0,
|
||||
x.get("discovered_at") or "",
|
||||
),
|
||||
reverse=True,
|
||||
)
|
||||
|
||||
max_items = self.ITEMS_PER_PERIOD.get(period_name, 10)
|
||||
|
||||
if len(entries) < 3:
|
||||
digest[period_name][cat_name] = self._fallback_items(
|
||||
entries, cat_name, limit=max_items
|
||||
)
|
||||
continue
|
||||
|
||||
try:
|
||||
prompt = self._build_ranking_prompt(
|
||||
cat_name, entries, period_name
|
||||
)
|
||||
result = await call_gemini_with_retry(
|
||||
prompt,
|
||||
prefer_flash=True,
|
||||
role="Digest-Analyst",
|
||||
)
|
||||
|
||||
ranked: List[dict] = []
|
||||
for item in result.get("items", []):
|
||||
idx = int(item.get("idx", -1))
|
||||
if 0 <= idx < len(entries):
|
||||
e = entries[idx]
|
||||
ranked.append(
|
||||
{
|
||||
"url": e["url"],
|
||||
"title": e.get("title", "Unknown"),
|
||||
"date": e.get("discovered_at", "")[:10],
|
||||
"stars": e.get("stars", 0),
|
||||
"impact": item.get("impact", "medium"),
|
||||
"why": item.get("why", ""),
|
||||
"category": cat_name,
|
||||
}
|
||||
)
|
||||
|
||||
digest[period_name][cat_name] = ranked[:max_items]
|
||||
log_event(
|
||||
f" [Digest] {period_name}/{cat_name}: "
|
||||
f"{len(ranked)} items ranked"
|
||||
)
|
||||
|
||||
except Exception as exc:
|
||||
log_event(
|
||||
f" [Digest WARN] {period_name}/{cat_name}: "
|
||||
f"Gemini failed ({str(exc)[:80]}), "
|
||||
"using star-based fallback"
|
||||
)
|
||||
digest[period_name][cat_name] = self._fallback_items(
|
||||
entries, cat_name, limit=max_items
|
||||
)
|
||||
|
||||
# Respect Gemini rate limits
|
||||
await asyncio.sleep(1.0)
|
||||
|
||||
return digest
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# Persistence #
|
||||
# ------------------------------------------------------------------ #
|
||||
|
||||
@staticmethod
|
||||
def save_digest(digest: dict) -> None:
|
||||
"""Serialise *digest* to ``data/news_digest.json``."""
|
||||
os.makedirs(os.path.dirname(DIGEST_OUTPUT_PATH), exist_ok=True)
|
||||
with open(DIGEST_OUTPUT_PATH, "w", encoding="utf-8") as fh:
|
||||
json.dump(digest, fh, indent=2, ensure_ascii=False)
|
||||
log_event(f"[Digest] Saved to {DIGEST_OUTPUT_PATH}")
|
||||
|
||||
|
||||
# ====================================================================== #
|
||||
# CLI / CI entry point #
|
||||
# ====================================================================== #
|
||||
|
||||
|
||||
async def run_news_digest() -> None:
|
||||
"""Entry point for the CI pipeline."""
|
||||
log_event("STARTING NEWS DIGEST GENERATION", section_break=True)
|
||||
engine = NewsDigestEngine()
|
||||
digest = await engine.generate_digest()
|
||||
engine.save_digest(digest)
|
||||
|
||||
total_items = sum(
|
||||
len(items) for period in digest.values() for items in period.values()
|
||||
)
|
||||
log_event(
|
||||
f"NEWS DIGEST COMPLETE: {total_items} total items across all periods"
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(run_news_digest())
|
||||
+2
-1
@@ -47,7 +47,8 @@ def auto_format_file(filepath: str):
|
||||
try:
|
||||
cleaned = normalize_url(url)
|
||||
return f"[{text}]({cleaned})"
|
||||
except:
|
||||
except Exception as e:
|
||||
print(f"[WARN] URL normalization failed for '{url[:60]}': {str(e)[:100]}")
|
||||
return match.group(0)
|
||||
return match.group(0)
|
||||
|
||||
|
||||
Executable
+107
@@ -0,0 +1,107 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
def check_file(file_path):
|
||||
errors = []
|
||||
warnings = []
|
||||
|
||||
with open(file_path, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
lines = content.splitlines()
|
||||
|
||||
seen_urls = set()
|
||||
|
||||
for line_num, line in enumerate(lines, 1):
|
||||
# 1. Check section titles (H2-H6) for Emojis, Special Characters, and Ampersands (Mandate 32)
|
||||
if line.startswith("#"):
|
||||
header_level = len(line) - len(line.lstrip('#'))
|
||||
if 2 <= header_level <= 6:
|
||||
title_text = line.lstrip('#').strip()
|
||||
# Check for ampersand
|
||||
if "&" in title_text:
|
||||
errors.append(f"Line {line_num}: Title contains ampersand '&': '{title_text}' (replace with 'and')")
|
||||
|
||||
# Check for emojis or special characters
|
||||
for char in title_text:
|
||||
# Allow alphanumeric, spaces, hyphens, colons, parentheses, commas, periods, quotes
|
||||
if ord(char) > 0x2000 and ord(char) not in (0x2013, 0x2014, 0x2018, 0x2019, 0x201c, 0x201d):
|
||||
errors.append(f"Line {line_num}: Title contains emojis or special characters: '{char}' in '{title_text}'")
|
||||
|
||||
# 2. Check for Markdown link rules (Mandate 33)
|
||||
# Match [ Link ](URL) - space at start or end of brackets
|
||||
if re.search(r'\[\s+[^\]]*\]\(', line) or re.search(r'\[[^\]]*\s+\]\(', line):
|
||||
errors.append(f"Line {line_num}: Link text contains leading or trailing spaces inside brackets: '{line.strip()}'")
|
||||
|
||||
# 3. Extract and validate URLs
|
||||
links = re.findall(r'\[([^\]]+)\]\(([^)]+)\)', line)
|
||||
for link_text, url in links:
|
||||
url = url.strip()
|
||||
if url.startswith("http://") or url.startswith("https://"):
|
||||
if url.startswith("https:/") and not url.startswith("https://"):
|
||||
errors.append(f"Line {line_num}: Corrupted protocol prefix: '{url}'")
|
||||
elif url.startswith("http:/") and not url.startswith("http://"):
|
||||
errors.append(f"Line {line_num}: Corrupted protocol prefix: '{url}'")
|
||||
|
||||
# Check for duplicates in this category file
|
||||
if os.path.basename(file_path) not in ["index.md", "about.md"]:
|
||||
# Normalize URL to check duplicate
|
||||
clean_url = url.split("?")[0].split("#")[0].lower().rstrip('/')
|
||||
if clean_url in seen_urls:
|
||||
# Report as warning rather than blocking error
|
||||
warnings.append(f"Line {line_num}: Duplicate URL found in this file: '{url}'")
|
||||
else:
|
||||
seen_urls.add(clean_url)
|
||||
|
||||
# Check year tag if present
|
||||
match_year = re.search(r'\*\*\(([0-9]{4})\)\*\*', line)
|
||||
if match_year:
|
||||
year = int(match_year.group(1))
|
||||
if not (1990 <= year <= 2030):
|
||||
errors.append(f"Line {line_num}: Year tag '{year}' is outside reasonable bounds: '{line.strip()}'")
|
||||
|
||||
# Global multi-line check for link text line breaks
|
||||
if re.search(r'\[[^\]]*\n[^\]]*\]\(', content):
|
||||
errors.append("Global: Found a link with a line break inside the link text brackets.")
|
||||
|
||||
return errors, warnings
|
||||
|
||||
def main():
|
||||
docs_dir = "docs"
|
||||
if not os.path.exists(docs_dir):
|
||||
print(f"Directory {docs_dir} not found.")
|
||||
sys.exit(0)
|
||||
|
||||
total_errors = 0
|
||||
total_warnings = 0
|
||||
|
||||
for root, dirs, files in os.walk(docs_dir):
|
||||
if "images" in root or "static" in root:
|
||||
continue
|
||||
for file in files:
|
||||
if file.endswith(".md"):
|
||||
path = os.path.join(root, file)
|
||||
errors, warnings = check_file(path)
|
||||
if errors:
|
||||
print(f"❌ {path}:")
|
||||
for err in errors:
|
||||
print(f" {err}")
|
||||
total_errors += len(errors)
|
||||
if warnings:
|
||||
# By default do not flood output with duplicate warnings unless verbose
|
||||
if "--verbose" in sys.argv:
|
||||
print(f"⚠️ {path}:")
|
||||
for warn in warnings:
|
||||
print(f" {warn}")
|
||||
total_warnings += len(warnings)
|
||||
|
||||
print(f"\nScan complete: Found {total_errors} errors and {total_warnings} warnings in markdown files.")
|
||||
if total_errors > 0:
|
||||
sys.exit(1)
|
||||
else:
|
||||
print("All Markdown files passed the schema check successfully!")
|
||||
sys.exit(0)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -12,7 +12,9 @@ V2_DIR = "v2-docs"
|
||||
def run_command(cmd):
|
||||
try:
|
||||
return subprocess.check_output(cmd, shell=True).decode('utf-8').strip()
|
||||
except: return "0"
|
||||
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."""
|
||||
@@ -26,7 +28,8 @@ def get_stats():
|
||||
inventory = {}
|
||||
try:
|
||||
inventory = load_inventory()
|
||||
except: pass
|
||||
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:")])
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import yaml
|
||||
import os
|
||||
from src.inventory_manager import load_inventory
|
||||
|
||||
# Map category IDs to their friendly names and outline border colors (V2 only)
|
||||
CATEGORIES = {
|
||||
@@ -13,10 +14,7 @@ CATEGORIES = {
|
||||
}
|
||||
|
||||
def load_inventory_channels():
|
||||
repo_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
inventory_path = os.path.join(repo_root, 'data', 'inventory.yaml')
|
||||
with open(inventory_path, 'r', encoding='utf-8') as f:
|
||||
inventory = yaml.safe_load(f) or {}
|
||||
inventory = load_inventory()
|
||||
|
||||
channels = []
|
||||
for url, entry in inventory.items():
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
"""RSS 2.0 feed generator for the Nubenetes Intelligence Digest.
|
||||
|
||||
Reads data/news_digest.json and writes v2-docs/feed.xml with the top
|
||||
items from the 3-month digest window across all tech categories.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
from datetime import datetime
|
||||
from email.utils import format_datetime
|
||||
from xml.sax.saxutils import escape
|
||||
|
||||
from src.logger import log_event
|
||||
|
||||
DIGEST_PATH = "data/news_digest.json"
|
||||
OUTPUT_PATH = "v2-docs/feed.xml"
|
||||
FEED_TITLE = "Nubenetes Intelligence Digest"
|
||||
FEED_LINK = "https://nubenetes.com/"
|
||||
FEED_DESCRIPTION = "AI-curated top picks from the Cloud Native & Kubernetes ecosystem"
|
||||
FEED_LANGUAGE = "en"
|
||||
ITEMS_PER_FEED = 20
|
||||
|
||||
TECH_CATS = [
|
||||
"Kubernetes & Orchestration", "AI & Agents", "Security & Compliance",
|
||||
"CI/CD & GitOps", "Observability, SRE & Testing", "Infrastructure as Code",
|
||||
"Containers & Runtime", "Networking & Service Mesh", "Cloud Providers & FinOps",
|
||||
"MLOps & Data Science", "Data, Messaging & Storage",
|
||||
]
|
||||
|
||||
|
||||
def _rfc822(dt: datetime) -> str:
|
||||
return format_datetime(dt)
|
||||
|
||||
|
||||
def generate_rss() -> None:
|
||||
if not os.path.exists(DIGEST_PATH):
|
||||
log_event("[WARN] rss_generator: news_digest.json not found, skipping RSS generation")
|
||||
return
|
||||
|
||||
try:
|
||||
with open(DIGEST_PATH, "r", encoding="utf-8") as f:
|
||||
digest = json.load(f)
|
||||
except Exception as e:
|
||||
log_event(f"[WARN] rss_generator: failed to load digest: {str(e)[:100]}")
|
||||
return
|
||||
|
||||
period_data = digest.get("3_months", {})
|
||||
items: list[dict] = []
|
||||
|
||||
for cat in TECH_CATS:
|
||||
for entry in period_data.get(cat, []):
|
||||
items.append({**entry, "_cat": cat})
|
||||
|
||||
# Sort by impact then date
|
||||
impact_rank = {"critical": 3, "high": 2, "medium": 1}
|
||||
items.sort(
|
||||
key=lambda x: (impact_rank.get(x.get("impact", "medium"), 0), x.get("date", "")),
|
||||
reverse=True,
|
||||
)
|
||||
items = items[:ITEMS_PER_FEED]
|
||||
|
||||
build_date = _rfc822(datetime.utcnow())
|
||||
|
||||
lines = [
|
||||
'<?xml version="1.0" encoding="UTF-8"?>',
|
||||
'<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">',
|
||||
" <channel>",
|
||||
f" <title>{escape(FEED_TITLE)}</title>",
|
||||
f" <link>{FEED_LINK}</link>",
|
||||
f" <description>{escape(FEED_DESCRIPTION)}</description>",
|
||||
f" <language>{FEED_LANGUAGE}</language>",
|
||||
f" <lastBuildDate>{build_date}</lastBuildDate>",
|
||||
f' <atom:link href="{FEED_LINK}feed.xml" rel="self" type="application/rss+xml"/>',
|
||||
]
|
||||
|
||||
for item in items:
|
||||
title = escape(item.get("title", "Unknown"))
|
||||
url = item.get("url", "#")
|
||||
why = escape(item.get("why", ""))
|
||||
cat = escape(item.get("_cat", ""))
|
||||
impact = item.get("impact", "medium")
|
||||
date_str = item.get("date", "")
|
||||
try:
|
||||
pub_date = _rfc822(datetime.strptime(date_str, "%Y-%m-%d")) if date_str else build_date
|
||||
except Exception:
|
||||
pub_date = build_date
|
||||
|
||||
lines += [
|
||||
" <item>",
|
||||
f" <title>{title}</title>",
|
||||
f" <link>{url}</link>",
|
||||
f" <guid isPermaLink=\"true\">{url}</guid>",
|
||||
f" <pubDate>{pub_date}</pubDate>",
|
||||
f" <category>{cat}</category>",
|
||||
f" <description>[{impact.upper()}] {why}</description>",
|
||||
" </item>",
|
||||
]
|
||||
|
||||
lines += [" </channel>", "</rss>"]
|
||||
|
||||
os.makedirs(os.path.dirname(OUTPUT_PATH), exist_ok=True)
|
||||
with open(OUTPUT_PATH, "w", encoding="utf-8") as f:
|
||||
f.write("\n".join(lines) + "\n")
|
||||
|
||||
log_event(f"[INFO] rss_generator: wrote {len(items)} items to {OUTPUT_PATH}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
generate_rss()
|
||||
+32
-22
@@ -6,6 +6,12 @@ from datetime import datetime
|
||||
from src.logger import log_event
|
||||
from src.gemini_utils import normalize_url, clean_toc_text
|
||||
from src.config import INVENTORY_PATH
|
||||
from src.inventory_manager import load_inventory
|
||||
|
||||
try:
|
||||
from yaml import CSafeLoader as Loader
|
||||
except ImportError:
|
||||
from yaml import SafeLoader as Loader
|
||||
|
||||
V1_DIR = "docs"
|
||||
V2_DIR = "v2-docs"
|
||||
@@ -20,19 +26,16 @@ class SafetyGuard:
|
||||
self.inventory = self._load_inventory()
|
||||
|
||||
def _load_inventory(self):
|
||||
if os.path.exists(INVENTORY_PATH):
|
||||
try:
|
||||
with open(INVENTORY_PATH, "r") as f:
|
||||
return yaml.safe_load(f) or {}
|
||||
except: return {}
|
||||
return {}
|
||||
return load_inventory()
|
||||
|
||||
def _load_exempt_files(self):
|
||||
try:
|
||||
with open("data/link_rules.yaml", "r") as f:
|
||||
rules = yaml.safe_load(f)
|
||||
with open("data/link_rules.yaml", "r", encoding="utf-8") as f:
|
||||
rules = yaml.load(f, Loader=Loader)
|
||||
return rules.get("hierarchy_rules", {}).get("toc_exempt_files", [])
|
||||
except: return []
|
||||
except Exception as e:
|
||||
log_event(f"[WARN] load exempt files from link_rules.yaml: {str(e)[:100]}")
|
||||
return []
|
||||
|
||||
def validate_data_integrity(self, old_inventory: dict):
|
||||
"""Mandate 1: Información Preservation."""
|
||||
@@ -59,9 +62,11 @@ class SafetyGuard:
|
||||
"""Mandate 27: Special Assets Exhaustive Inclusion."""
|
||||
if not os.path.exists(SPECIAL_ASSETS_PATH): return
|
||||
try:
|
||||
with open(SPECIAL_ASSETS_PATH, "r") as f:
|
||||
special = yaml.safe_load(f).get("special_assets", [])
|
||||
except: return
|
||||
with open(SPECIAL_ASSETS_PATH, "r", encoding="utf-8") as f:
|
||||
special = yaml.load(f, Loader=Loader).get("special_assets", [])
|
||||
except Exception as e:
|
||||
log_event(f"[WARN] load special_assets.yaml for validation: {str(e)[:100]}")
|
||||
return
|
||||
|
||||
for sa in special:
|
||||
if "Include 100%" in sa.get("v2_rule", "") or "Exhaustive" in sa.get("v2_rule", ""):
|
||||
@@ -88,7 +93,8 @@ class SafetyGuard:
|
||||
stars = meta.get("gh_stars", meta.get("stars", 0) * 100)
|
||||
if inactive_years > 4 and stars < 30:
|
||||
self.warnings.append(f"🏚️ **MVQ Violation**: Stale repo `{url}` (>4yrs) in V2 with low impact")
|
||||
except: pass
|
||||
except Exception as e:
|
||||
log_event(f"[WARN] MVQ compliance check for {url}: {str(e)[:100]}")
|
||||
|
||||
def validate_linguistic_tagging(self):
|
||||
"""Mandate 10: Explicit Language Tagging."""
|
||||
@@ -175,9 +181,11 @@ class SafetyGuard:
|
||||
"""Mandate 11: Workflow-Config Synchronization."""
|
||||
if not os.path.exists(WORKFLOW_PATH) or not os.path.exists(CURATION_SOURCES_PATH): return
|
||||
try:
|
||||
with open(CURATION_SOURCES_PATH, "r") as f:
|
||||
sources = yaml.safe_load(f).get("sources", [])
|
||||
except: return
|
||||
with open(CURATION_SOURCES_PATH, "r", encoding="utf-8") as f:
|
||||
sources = yaml.load(f, Loader=Loader).get("sources", [])
|
||||
except Exception as e:
|
||||
log_event(f"[WARN] load curation_sources.yaml for nav sync: {str(e)[:100]}")
|
||||
return
|
||||
|
||||
topics = [s["topic"] for s in sources]
|
||||
with open(WORKFLOW_PATH, "r") as f:
|
||||
@@ -191,10 +199,11 @@ class SafetyGuard:
|
||||
def validate_forbidden_tags(self):
|
||||
"""Mandate 51/Safety: Check for forbidden HTML tags in docs and v2-docs, except allowing iframes in videos."""
|
||||
try:
|
||||
with open("data/link_rules.yaml", "r") as f:
|
||||
rules = yaml.safe_load(f)
|
||||
with open("data/link_rules.yaml", "r", encoding="utf-8") as f:
|
||||
rules = yaml.load(f, Loader=Loader)
|
||||
forbidden = rules.get("safety_guard", {}).get("forbidden_tags", [])
|
||||
except:
|
||||
except Exception as e:
|
||||
log_event(f"[WARN] load forbidden tags from link_rules.yaml: {str(e)[:100]}")
|
||||
return
|
||||
|
||||
if not forbidden:
|
||||
@@ -275,9 +284,10 @@ class SafetyGuard:
|
||||
# 2. Run standard validations
|
||||
if old_inv_path and os.path.exists(old_inv_path):
|
||||
try:
|
||||
with open(old_inv_path, "r") as f:
|
||||
self.validate_data_integrity(yaml.safe_load(f) or {})
|
||||
except: pass
|
||||
with open(old_inv_path, "r", encoding="utf-8") as f:
|
||||
self.validate_data_integrity(yaml.load(f, Loader=Loader) or {})
|
||||
except Exception as e:
|
||||
log_event(f"[WARN] load old inventory for data integrity check: {str(e)[:100]}")
|
||||
|
||||
self.validate_semantic_interlinking()
|
||||
self.validate_special_assets_completeness()
|
||||
|
||||
@@ -8,7 +8,7 @@ REQUIRED_SECTIONS = [
|
||||
"3. The Agentic Stack",
|
||||
"4. The 2026 Architectural Shift",
|
||||
"5. Dual-Edition Architecture (V1 vs V2)",
|
||||
"6. The Unified Agentic Database (Knowledge Graph)",
|
||||
"6. The Unified Agentic Database (Coexistence Knowledge Graph)",
|
||||
"7. AI Economic Architecture and Cost Analysis",
|
||||
"8. The Agentic AI Engine",
|
||||
"9. GitHub Workflows and Automation",
|
||||
|
||||
@@ -6,6 +6,11 @@ from src.logger import log_event
|
||||
CURATION_SOURCES_PATH = "data/curation_sources.yaml"
|
||||
WORKFLOW_PATH = ".github/workflows/01.1.agentic_cron.yml"
|
||||
|
||||
try:
|
||||
from yaml import CSafeLoader as Loader
|
||||
except ImportError:
|
||||
from yaml import SafeLoader as Loader
|
||||
|
||||
class WorkflowUISync:
|
||||
"""
|
||||
Automates Mandate 11: Workflow-Config Synchronization.
|
||||
@@ -16,8 +21,8 @@ class WorkflowUISync:
|
||||
return False
|
||||
|
||||
try:
|
||||
with open(CURATION_SOURCES_PATH, "r") as f:
|
||||
sources = yaml.safe_load(f).get("sources", [])
|
||||
with open(CURATION_SOURCES_PATH, "r", encoding="utf-8") as f:
|
||||
sources = yaml.load(f, Loader=Loader).get("sources", [])
|
||||
except Exception as e:
|
||||
log_event(f" [!] Error loading curation sources: {e}")
|
||||
return False
|
||||
|
||||
+43
-10
@@ -31,6 +31,27 @@ async def run_debate_protocol(item: Dict, is_new_link: bool = False) -> Tuple[in
|
||||
tags = item.get("tags", [])
|
||||
initial_score = item.get("impact_score", item.get("stars", 3) * 20) # Fallback mapping if stars is used
|
||||
|
||||
# 0. Check cache using content hash (Recommendation #4)
|
||||
import hashlib
|
||||
raw_content = f"{title}||{desc}||{','.join(sorted(tags))}"
|
||||
content_hash = hashlib.sha256(raw_content.encode("utf-8")).hexdigest()
|
||||
|
||||
if os.path.exists(DEBATE_MEMORY_FILE):
|
||||
try:
|
||||
with open(DEBATE_MEMORY_FILE, "r") as f:
|
||||
memory_data = json.load(f)
|
||||
cached = memory_data.get("resolved_debates", {}).get(normalize_url(url))
|
||||
if cached and cached.get("content_hash") == content_hash:
|
||||
log_event(f" [⚖️] CACHE HIT: Skipping debate for '{title}'. Returning cached consensus.")
|
||||
return (
|
||||
cached["final_consensus_score"],
|
||||
cached.get("final_tags", tags),
|
||||
cached.get("refined_summary", desc),
|
||||
cached
|
||||
)
|
||||
except Exception as e:
|
||||
log_event(f" [!] Error checking debate cache: {e}")
|
||||
|
||||
log_event(f" [⚖️] DEBATE TRIGGERED: '{title}' (Initial Score: {initial_score})", section_break=False)
|
||||
|
||||
# 0. Check if mock mode is requested or required (no keys configured)
|
||||
@@ -128,7 +149,10 @@ async def run_debate_protocol(item: Dict, is_new_link: bool = False) -> Tuple[in
|
||||
"scores": scores,
|
||||
"justifications": justifications,
|
||||
"rebuttals": debate_transcript,
|
||||
"timestamp": datetime.now().isoformat()
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"final_tags": sorted(list(final_tags)),
|
||||
"refined_summary": refined_summary,
|
||||
"content_hash": content_hash
|
||||
}
|
||||
|
||||
try:
|
||||
@@ -136,13 +160,14 @@ async def run_debate_protocol(item: Dict, is_new_link: bool = False) -> Tuple[in
|
||||
if os.path.exists(DEBATE_MEMORY_FILE):
|
||||
try:
|
||||
memory_data = json.load(open(DEBATE_MEMORY_FILE, "r"))
|
||||
except: pass
|
||||
except Exception as e:
|
||||
log_event(f"[WARN] load debate memory for mock persist: {str(e)[:100]}")
|
||||
memory_data.setdefault("resolved_debates", {})[normalize_url(url)] = debate_data
|
||||
with open(DEBATE_MEMORY_FILE, "w") as f:
|
||||
json.dump(memory_data, f, indent=2)
|
||||
except Exception as e:
|
||||
log_event(f" [!] Failed to persist debate memory: {e}")
|
||||
|
||||
|
||||
return final_score, sorted(list(final_tags)), refined_summary, debate_data
|
||||
|
||||
system_mandates = get_system_mandates()
|
||||
@@ -187,7 +212,10 @@ async def run_debate_protocol(item: Dict, is_new_link: bool = False) -> Tuple[in
|
||||
"final_consensus_score": fast_pass_score,
|
||||
"fast_pass": True,
|
||||
"justification": fast_pass_justification,
|
||||
"timestamp": datetime.now().isoformat()
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"final_tags": fast_pass_tags,
|
||||
"refined_summary": fast_pass_summary,
|
||||
"content_hash": content_hash
|
||||
}
|
||||
|
||||
try:
|
||||
@@ -195,13 +223,14 @@ async def run_debate_protocol(item: Dict, is_new_link: bool = False) -> Tuple[in
|
||||
if os.path.exists(DEBATE_MEMORY_FILE):
|
||||
try:
|
||||
memory_data = json.load(open(DEBATE_MEMORY_FILE, "r"))
|
||||
except: pass
|
||||
except Exception as e:
|
||||
log_event(f"[WARN] load debate memory for fast-pass persist: {str(e)[:100]}")
|
||||
memory_data.setdefault("resolved_debates", {})[normalize_url(url)] = debate_data
|
||||
with open(DEBATE_MEMORY_FILE, "w") as f:
|
||||
json.dump(memory_data, f, indent=2)
|
||||
except Exception as e:
|
||||
log_event(f" [!] Failed to persist debate memory: {e}")
|
||||
|
||||
|
||||
return fast_pass_score, fast_pass_tags, fast_pass_summary, debate_data
|
||||
|
||||
log_event(f" [⚖️] Borderline score detected ({fast_pass_score}). Escalating to full Multi-Agent Debate Panel...")
|
||||
@@ -325,7 +354,10 @@ async def run_debate_protocol(item: Dict, is_new_link: bool = False) -> Tuple[in
|
||||
"scores": scores,
|
||||
"justifications": justifications,
|
||||
"rebuttals": debate_transcript,
|
||||
"timestamp": datetime.now().isoformat()
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"final_tags": final_tags,
|
||||
"refined_summary": refined_summary,
|
||||
"content_hash": content_hash
|
||||
}
|
||||
|
||||
# Persist the resolved debate to memory log (Mandate 3.1)
|
||||
@@ -334,10 +366,11 @@ async def run_debate_protocol(item: Dict, is_new_link: bool = False) -> Tuple[in
|
||||
if os.path.exists(DEBATE_MEMORY_FILE):
|
||||
try:
|
||||
memory_data = json.load(open(DEBATE_MEMORY_FILE, "r"))
|
||||
except: pass
|
||||
|
||||
except Exception as e:
|
||||
log_event(f"[WARN] load debate memory for final persist: {str(e)[:100]}")
|
||||
|
||||
memory_data.setdefault("resolved_debates", {})[normalize_url(url)] = debate_data
|
||||
|
||||
|
||||
# Keep blacklist and other fields intact
|
||||
with open(DEBATE_MEMORY_FILE, "w") as f:
|
||||
json.dump(memory_data, f, indent=2)
|
||||
|
||||
+289
-67
@@ -4,6 +4,10 @@ import json
|
||||
import hashlib
|
||||
import asyncio
|
||||
import yaml
|
||||
try:
|
||||
from yaml import CSafeLoader as Loader
|
||||
except ImportError:
|
||||
from yaml import SafeLoader as Loader
|
||||
import httpx
|
||||
from datetime import datetime
|
||||
from typing import List, Dict, Set, Any, Tuple
|
||||
@@ -37,14 +41,30 @@ class V2VisionEngine:
|
||||
"AI": ["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 (Hyperscalers)": ["aws", "azure", "GoogleCloudPlatform", "ibm_cloud", "oraclecloud", "digitalocean", "cloudflare", "scaleway", "managed-kubernetes-in-public-cloud", "public-cloud-solutions", "private-cloud-solutions", "edge-computing", "aws-architecture", "aws-security", "aws-networking", "aws-databases", "aws-storage", "aws-monitoring", "aws-iac", "aws-tools-scripts", "aws-messaging", "aws-data", "aws-devops", "aws-serverless", "aws-containers", "aws-backup", "aws-training", "aws-newfeatures", "aws-miscellaneous", "aws-pricing", "aws-spain"],
|
||||
"Hardened Infrastructure": ["iac", "terraform", "pulumi", "crossplane", "ansible", "securityascode", "kubernetes-security", "aws-security", "devsecops", "kustomize", "liquibase"],
|
||||
"Cloud Providers (Hyperscalers)": ["aws", "azure", "GoogleCloudPlatform", "ibm_cloud", "oraclecloud", "digitalocean", "cloudflare", "managed-kubernetes-in-public-cloud", "public-cloud-solutions", "edge-computing", "aws-architecture", "aws-security", "aws-networking", "aws-databases", "aws-storage", "aws-monitoring", "aws-iac", "aws-tools-scripts", "aws-messaging", "aws-data", "aws-devops", "aws-serverless", "aws-containers", "aws-backup", "aws-training", "aws-newfeatures", "aws-miscellaneous", "aws-pricing"],
|
||||
"Networking & Service Mesh": ["networking", "kubernetes-networking", "servicemesh", "istio", "caching", "web-servers", "cloudflare"],
|
||||
"The 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 & Advanced 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"],
|
||||
"Developer Ecosystem": ["visual-studio", "javascript", "golang", "python", "java_frameworks", "java_app_servers", "java-and-java-performance-optimization", "dotnet", "angular", "react", "web3", "api", "swagger-code-generator-for-rest-apis", "postman", "lowcode-nocode", "devel-sites", "dom", "linux-dev-env", "ChromeDevTools", "xamarin", "jvm-parameters-matrix-table", "maven-gradle", "embedded-servlet-containers"],
|
||||
"Career & Industry": ["recruitment", "hr", "finops", "freelancing", "remote-tech-jobs", "workfromhome", "interview-questions", "elearning", "digital-money", "appointment-scheduling", "newsfeeds"]
|
||||
"Data & Advanced Analytics": ["databases", "nosql", "message-queue", "crunchydata", "yaml", "bigdata"],
|
||||
"Engineering Pipeline": ["cicd", "gitops", "argo", "flux", "tekton", "jenkins", "jenkins-alternatives", "openshift-pipelines", "sonarqube", "registries", "keptn", "cicd-kubernetes-plugins"],
|
||||
"Developer Ecosystem": ["visual-studio", "javascript", "golang", "python", "java_frameworks", "java_app_servers", "java-and-java-performance-optimization", "dotnet", "angular", "web3", "api", "swagger-code-generator-for-rest-apis", "postman", "lowcode-nocode", "devel-sites", "linux-dev-env", "ChromeDevTools", "maven-gradle", "embedded-servlet-containers"],
|
||||
"Career & Industry": ["recruitment", "hr", "finops", "freelancing", "remote-tech-jobs", "workfromhome", "interview-questions", "elearning", "appointment-scheduling", "newsfeeds"]
|
||||
}
|
||||
|
||||
# Stub page merge map: content from source pages renders on target pages
|
||||
self.merge_map = {
|
||||
"jvm-parameters-matrix-table": "java-and-java-performance-optimization",
|
||||
"private-cloud-solutions": "kubernetes-on-premise",
|
||||
"stackstorm": "cicd",
|
||||
"chef": "ansible",
|
||||
"newsql": "databases",
|
||||
"scaleway": "digitalocean",
|
||||
"xamarin": "dotnet",
|
||||
"dom": "javascript",
|
||||
"react": "javascript",
|
||||
"oauth": "securityascode",
|
||||
"digital-money": "finops",
|
||||
"aws-spain": "aws",
|
||||
}
|
||||
|
||||
self.library_criteria = (
|
||||
@@ -74,15 +94,23 @@ class V2VisionEngine:
|
||||
def _load_special_assets(self) -> Dict:
|
||||
path = "data/special_assets.yaml"
|
||||
if os.path.exists(path):
|
||||
try: return yaml.safe_load(open(path, "r")) or {}
|
||||
except: return {}
|
||||
try:
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
return yaml.load(f, Loader=Loader) or {}
|
||||
except Exception as e:
|
||||
log_event(f"[WARN] load special_assets.yaml: {str(e)[:100]}")
|
||||
return {}
|
||||
return {}
|
||||
|
||||
def _load_link_rules(self) -> Dict:
|
||||
path = "data/link_rules.yaml"
|
||||
if os.path.exists(path):
|
||||
try: return yaml.safe_load(open(path, "r")) or {}
|
||||
except: return {}
|
||||
try:
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
return yaml.load(f, Loader=Loader) or {}
|
||||
except Exception as e:
|
||||
log_event(f"[WARN] load link_rules.yaml: {str(e)[:100]}")
|
||||
return {}
|
||||
return {}
|
||||
|
||||
def _load_inventory(self) -> Dict:
|
||||
@@ -98,18 +126,20 @@ class V2VisionEngine:
|
||||
|
||||
# Mandate 30: MD039 - Global Data Sanitization (Purge all whitespace/hidden chars from titles)
|
||||
for url in list(self.inventory.keys()):
|
||||
if isinstance(self.inventory[url], dict) and "title" in self.inventory[url]:
|
||||
# Purge all known whitespace characters (standard, non-breaking, thin, etc.)
|
||||
if isinstance(self.inventory[url], dict) and self.inventory[url].get("title") is not None:
|
||||
t = self.inventory[url]["title"]
|
||||
t = re.sub(r'^[\s\u00a0\u200b\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000]+', '', t)
|
||||
t = re.sub(r'[\s\u00a0\u200b\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000]+$', '', t)
|
||||
self.inventory[url]["title"] = t
|
||||
if isinstance(t, str):
|
||||
# Purge all known whitespace characters (standard, non-breaking, thin, etc.)
|
||||
t = re.sub(r'^[\s\u00a0\u200b\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000]+', '', t)
|
||||
t = re.sub(r'[\s\u00a0\u200b\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000]+$', '', t)
|
||||
self.inventory[url]["title"] = t
|
||||
|
||||
# 0. Mandate Sync
|
||||
try:
|
||||
from src.mandate_ingestor import MandateIngestor
|
||||
MandateIngestor().save_system_instructions()
|
||||
except: pass
|
||||
except Exception as e:
|
||||
log_event(f"[WARN] mandate sync: {str(e)[:100]}")
|
||||
|
||||
all_v1_links, mosaic_html, videos_html = await self._gather_all_v1_content()
|
||||
|
||||
@@ -139,21 +169,35 @@ class V2VisionEngine:
|
||||
|
||||
# --- SURGICAL GARBAGE COLLECTION ---
|
||||
# Track every file we generate
|
||||
generated_files = {"index.md", "audit-log.md", "videos.md", "tags.md"}
|
||||
generated_files = {"index.md", "audit-log.md", "videos.md", "tags.md", "tech-digest.md", "industry-digest.md"}
|
||||
for f_name in v2_data.keys():
|
||||
generated_files.add(f_name)
|
||||
|
||||
|
||||
await self._write_premium_files(v2_data, mosaic_html, videos_html)
|
||||
self._generate_digest_pages()
|
||||
await self._generate_global_tag_index(v2_data)
|
||||
await self._sync_enterprise_navigation(v2_data)
|
||||
|
||||
# Delete only orphaned files
|
||||
log_event("[*] Phase 5: Pruning Orphaned V2 Assets...")
|
||||
for f in os.listdir(V2_DIR):
|
||||
if f.endswith(".md") and f not in generated_files:
|
||||
log_event(f" [DEL] Pruning obsolete V2 page: {f}")
|
||||
os.remove(os.path.join(V2_DIR, f))
|
||||
|
||||
# Phase 5: Structural changes — ONLY in full (non-render-only) mode
|
||||
# In render-only mode we never delete pages or rewrite the nav, because
|
||||
# the inventory pass is conservative and some pages may not be regenerated
|
||||
# in a given run even though they should still exist (e.g. low-hit pages).
|
||||
# Deleting them would break MkDocs nav references and corrupt the site.
|
||||
if self.render_only:
|
||||
log_event("[*] Phase 5: Skipped (render-only mode — nav and pages preserved)")
|
||||
else:
|
||||
log_event("[*] Phase 5: Syncing navigation and pruning orphaned pages...")
|
||||
nav_ok = await self._sync_enterprise_navigation(v2_data)
|
||||
if nav_ok:
|
||||
# Only prune if nav was successfully updated, and never delete
|
||||
# pages that are defined in self.dimensions (expected to exist).
|
||||
dimension_pages = {f"{slug}.md" for pages in self.dimensions.values() for slug in pages}
|
||||
for f in os.listdir(V2_DIR):
|
||||
if f.endswith(".md") and f not in generated_files and f not in dimension_pages:
|
||||
log_event(f" [DEL] Pruning truly orphaned V2 page: {f}")
|
||||
os.remove(os.path.join(V2_DIR, f))
|
||||
else:
|
||||
log_event("[WARN] Phase 5: Nav sync failed — skipping page deletion to avoid corruption")
|
||||
|
||||
self._save_inventory()
|
||||
|
||||
@@ -197,7 +241,11 @@ class V2VisionEngine:
|
||||
if not url.startswith(("http", "mailto", "#")):
|
||||
url = f"https://nubenetes.com/{url.replace('.md', '/')}"
|
||||
# Mandate 30: MD039 - Strip all whitespace (including non-breaking space) from link text
|
||||
all_links.append({"title": nuclear_strip(title), "url": url.strip(), "description": full_desc.strip(), "original_file": file})
|
||||
orig_file = file
|
||||
slug = file.replace(".md", "")
|
||||
if slug in self.merge_map:
|
||||
orig_file = self.merge_map[slug] + ".md"
|
||||
all_links.append({"title": nuclear_strip(title), "url": url.strip(), "description": full_desc.strip(), "original_file": orig_file})
|
||||
return all_links, mosaic_html, videos_html
|
||||
|
||||
async def _verify_link_health(self, links: List[Dict]):
|
||||
@@ -212,7 +260,11 @@ class V2VisionEngine:
|
||||
if entry.get("status") == "review_required": continue
|
||||
|
||||
if not force_full and entry.get("status") == "online":
|
||||
fast_online.append(l)
|
||||
last_checked = entry.get("last_checked", 0)
|
||||
if isinstance(last_checked, (int, float)) and (datetime.now().timestamp() - last_checked) > 30 * 86400:
|
||||
needs_check.append(l)
|
||||
else:
|
||||
fast_online.append(l)
|
||||
else:
|
||||
needs_check.append(l)
|
||||
|
||||
@@ -261,7 +313,8 @@ class V2VisionEngine:
|
||||
# Mandate 22: Update last_checked for the inventory entry
|
||||
self.inventory[normalize_url(final_url)]["last_checked"] = datetime.now().timestamp()
|
||||
return link
|
||||
except: pass
|
||||
except Exception as e:
|
||||
log_event(f"[WARN] resilient link check for {url}: {str(e)[:100]}")
|
||||
return None
|
||||
|
||||
async def _evaluate_and_score_resources(self, links: List[Dict]):
|
||||
@@ -333,8 +386,16 @@ class V2VisionEngine:
|
||||
if is_special: item["is_special"] = True
|
||||
# Mandate 30: Hierarchy and AI Summaries are mandatory for ELITE AI curation.
|
||||
# Optimized Skip Logic: Only skip if we already have BOTH hierarchy and a summary.
|
||||
if ((cached.get("hierarchy") and cached.get("ai_summary")) or self.render_only) and not force_eval:
|
||||
if project_id not in project_registry or item.get("stars", 0) > project_registry[project_id].get("stars", 0):
|
||||
last_eval = cached.get("last_ai_eval", "")
|
||||
eval_stale = False
|
||||
if last_eval and isinstance(last_eval, str) and len(last_eval) >= 10:
|
||||
try:
|
||||
eval_age = (datetime.now(MADRID_TZ) - datetime.fromisoformat(last_eval)).days
|
||||
eval_stale = eval_age > 180
|
||||
except Exception:
|
||||
pass
|
||||
if ((cached.get("hierarchy") and cached.get("ai_summary") and not eval_stale) or self.render_only) and not force_eval:
|
||||
if project_id not in project_registry or item.get("stars") or 0 > project_registry[project_id].get("stars") or 0:
|
||||
if project_id in project_registry and project_registry[project_id].get("is_special"): item["is_special"] = True
|
||||
project_registry[project_id] = item
|
||||
continue
|
||||
@@ -396,17 +457,21 @@ class V2VisionEngine:
|
||||
if idx < len(batch_links):
|
||||
item = batch_links[idx].copy()
|
||||
eval_data = {
|
||||
"year": str(res.get("year", "N/A")), "stars": min(max(int(res.get("stars", 0)), 0), 5),
|
||||
"year": str(res.get("year", "N/A")), "stars": min(max(int(res.get("stars") or 0), 0), 5),
|
||||
"ai_summary": res.get("summary", item.get("ai_summary", "")),
|
||||
"language": res.get("language", "English"),
|
||||
"resource_type": res.get("type", "Reference"), "complexity": res.get("complexity", "Intermediate"),
|
||||
"hierarchy": res.get("hierarchy", ["General"]), "tags": res.get("tags", []),
|
||||
"is_microservice": bool(res.get("is_microservice", False)),
|
||||
"status": "online", "is_special": item.get("is_special", False)
|
||||
"status": "online", "is_special": item.get("is_special", False),
|
||||
"last_ai_eval": datetime.now(MADRID_TZ).isoformat()
|
||||
}
|
||||
existing_entry = self.inventory.get(normalize_url(item["url"]), {})
|
||||
if existing_entry.get("discovered_at"):
|
||||
eval_data["discovered_at"] = existing_entry["discovered_at"]
|
||||
item.update(eval_data)
|
||||
batch_results.append(item)
|
||||
|
||||
|
||||
# Incremental Persistence
|
||||
norm_url = normalize_url(item["url"])
|
||||
from src.inventory_manager import update_inventory_entry
|
||||
@@ -483,13 +548,17 @@ class V2VisionEngine:
|
||||
if idx < len(batch):
|
||||
item = batch[idx].copy()
|
||||
eval_data = {
|
||||
"year": str(res.get("year", "N/A")), "stars": min(max(int(res.get("stars", 0)), 0), 5),
|
||||
"year": str(res.get("year", "N/A")), "stars": min(max(int(res.get("stars") or 0), 0), 5),
|
||||
"ai_summary": res.get("summary", ""), "language": res.get("language", "English"),
|
||||
"resource_type": res.get("type", "Reference"), "complexity": res.get("complexity", "Intermediate"),
|
||||
"hierarchy": res.get("hierarchy", ["General"]), "tags": res.get("tags", []),
|
||||
"is_microservice": bool(res.get("is_microservice", False)),
|
||||
"status": "online", "is_special": item.get("is_special", False)
|
||||
"status": "online", "is_special": item.get("is_special", False),
|
||||
"last_ai_eval": datetime.now(MADRID_TZ).isoformat()
|
||||
}
|
||||
existing_entry = self.inventory.get(normalize_url(item["url"]), {})
|
||||
if existing_entry.get("discovered_at"):
|
||||
eval_data["discovered_at"] = existing_entry["discovered_at"]
|
||||
item.update(eval_data)
|
||||
analyst_results.append(item)
|
||||
except Exception:
|
||||
@@ -503,7 +572,7 @@ class V2VisionEngine:
|
||||
l for l in analyst_results
|
||||
if "[DE FACTO STANDARD]" in l.get("tags", [])
|
||||
or "[ENTERPRISE-STABLE]" in l.get("tags", [])
|
||||
or l.get("stars", 0) in [3, 4]
|
||||
or l.get("stars") or 0 in [3, 4]
|
||||
]
|
||||
|
||||
if debate_candidates:
|
||||
@@ -540,7 +609,7 @@ class V2VisionEngine:
|
||||
new_data["addition_method"] = "manual"
|
||||
update_inventory_entry(self.inventory, norm_url, new_data)
|
||||
|
||||
if p_id not in project_registry or item.get("stars", 0) > project_registry[p_id].get("stars", 0):
|
||||
if p_id not in project_registry or item.get("stars") or 0 > project_registry[p_id].get("stars") or 0:
|
||||
if p_id in project_registry and project_registry[p_id].get("is_special"): item["is_special"] = True
|
||||
project_registry[p_id] = item
|
||||
|
||||
@@ -573,7 +642,7 @@ class V2VisionEngine:
|
||||
# 1. GitHub Objective Reality (Mandate 43)
|
||||
raw_gh = item.get("gh_stars", 0)
|
||||
gh_stars = int(raw_gh) if str(raw_gh).isdigit() else 0
|
||||
curator_stars = int(item.get("stars", 0))
|
||||
curator_stars = int(item.get("stars") or 0)
|
||||
|
||||
if gh_stars > 15000 or curator_stars >= 5:
|
||||
tags.add("[DE FACTO STANDARD]")
|
||||
@@ -583,7 +652,7 @@ class V2VisionEngine:
|
||||
if "[COMMUNITY-TOOL]" in tags: tags.remove("[COMMUNITY-TOOL]")
|
||||
|
||||
# 2. Type Mapping (AI based labels)
|
||||
res_type = item.get("resource_type", "Reference").lower()
|
||||
res_type = (item.get("resource_type") or "Reference").lower()
|
||||
if any(x in res_type for x in ["guide", "tutorial", "hands-on", "learning", "course"]):
|
||||
tags.add("[GUIDE]")
|
||||
if any(x in res_type for x in ["case study", "report", "whitepaper", "success story", "usage"]):
|
||||
@@ -643,7 +712,7 @@ class V2VisionEngine:
|
||||
|
||||
# Mandate 29: Special Assets must include 100% of ALIVE links, bypassing impact filters.
|
||||
is_special = item.get("is_special", False) or orig_file in special_rules
|
||||
if not is_special and orig_file == "introduction.md" and item.get("stars", 0) < 3 and not item.get("is_microservice"):
|
||||
if not is_special and orig_file == "introduction.md" and item.get("stars") or 0 < 3 and not item.get("is_microservice"):
|
||||
continue
|
||||
|
||||
if orig_file not in v2_structure:
|
||||
@@ -679,7 +748,7 @@ class V2VisionEngine:
|
||||
audit_entry = {
|
||||
"url": item["url"],
|
||||
"tag": ", ".join(item["tags"]),
|
||||
"stars": item.get("stars", 0),
|
||||
"stars": item.get("stars") or 0,
|
||||
"dimension": dim,
|
||||
"v2_locations": True
|
||||
}
|
||||
@@ -697,7 +766,7 @@ class V2VisionEngine:
|
||||
current["__links__"].append(item)
|
||||
|
||||
def sort_rec(node):
|
||||
if "__links__" in node: node["__links__"].sort(key=lambda x: (-x.get("stars", 1), -(int(x["year"]) if str(x.get("year", "")).isdigit() else 0)))
|
||||
if "__links__" in node: node["__links__"].sort(key=lambda x: (-(x.get("stars") or 1), -(int(x["year"]) if str(x.get("year", "")).isdigit() else 0)))
|
||||
for k, v in node.items():
|
||||
if k != "__links__" and isinstance(v, dict): sort_rec(v)
|
||||
|
||||
@@ -706,14 +775,27 @@ class V2VisionEngine:
|
||||
|
||||
return v2_structure
|
||||
|
||||
def _collect_tags_from_tree(self, node: Dict) -> List[Set]:
|
||||
"""Recursively collect maturity/tech tags from a content tree for cross-referencing."""
|
||||
results = []
|
||||
if "__links__" in node:
|
||||
for link in node["__links__"]:
|
||||
tags = set(link.get("tags", []))
|
||||
if tags:
|
||||
results.append(tags)
|
||||
for key, val in node.items():
|
||||
if key != "__links__" and isinstance(val, dict):
|
||||
results.extend(self._collect_tags_from_tree(val))
|
||||
return results
|
||||
|
||||
async def _generate_comparison_table(self, links: List[Dict]) -> str:
|
||||
standard_tools = [l for l in links if l.get("stars", 0) >= 3]
|
||||
if len(standard_tools) < 5: return ""
|
||||
standard_tools = [l for l in links if l.get("stars") or 0 >= 3]
|
||||
if len(standard_tools) < 8: return ""
|
||||
table = "\n??? abstract \"Architect's Technical Comparison Table\"\n"
|
||||
table += " | Solution | Maturity | Primary Focus | Language | Stars |\n"
|
||||
table += " | :--- | :--- | :--- | :--- | :--- |\n"
|
||||
for l in standard_tools[:10]:
|
||||
stars = "🌟" * l.get("stars", 0)
|
||||
stars = "🌟" * l.get("stars") or 0
|
||||
focus = l.get("topic", l.get("hierarchy", ["General"])[-1])
|
||||
# Mandate 30: MD039 - Strip all whitespace (including non-breaking space) from link text
|
||||
clean_title = nuclear_strip(l['title'])
|
||||
@@ -757,7 +839,7 @@ class V2VisionEngine:
|
||||
|
||||
async def _render_single_link(self, l: Dict, is_intro: bool) -> str:
|
||||
md = ""
|
||||
is_gold = is_intro and l.get("stars", 0) >= 4
|
||||
is_gold = is_intro and l.get("stars") or 0 >= 4
|
||||
title = nuclear_strip(l['title'])
|
||||
if is_gold:
|
||||
img = f" })\n" if l.get('social_preview_url') else ""
|
||||
@@ -798,7 +880,7 @@ class V2VisionEngine:
|
||||
tag_html += f" <span class='md-tag md-tag--{color}'>{tag}</span>"
|
||||
|
||||
# Apply Visual Highlighting based on stars
|
||||
raw_stars = l.get('stars', 0)
|
||||
raw_stars = l.get('stars') or 0
|
||||
link_content = title
|
||||
if raw_stars >= 5:
|
||||
link_content = f"=={link_content}=="
|
||||
@@ -826,7 +908,7 @@ class V2VisionEngine:
|
||||
year = l.get("year", "")
|
||||
year_prefix = f"**({year})** " if year and str(year).lower() != "n/a" else ""
|
||||
|
||||
raw_stars = l.get("stars", 0)
|
||||
raw_stars = l.get("stars") or 0
|
||||
stars_str = f" {'🌟' * raw_stars}" if raw_stars > 0 else ""
|
||||
|
||||
# Title formatting based on impact
|
||||
@@ -863,10 +945,109 @@ class V2VisionEngine:
|
||||
|
||||
|
||||
|
||||
def _generate_digest_pages(self):
|
||||
"""Generate tech-digest.md and industry-digest.md from news_digest.json."""
|
||||
digest_path = "data/news_digest.json"
|
||||
if not os.path.exists(digest_path):
|
||||
log_event("[Digest] No digest data found, skipping page generation")
|
||||
return
|
||||
with open(digest_path, "r", encoding="utf-8") as f:
|
||||
digest_data = json.load(f)
|
||||
|
||||
tech_cats = [
|
||||
"Kubernetes & Orchestration", "Containers & Runtime", "Networking & Service Mesh",
|
||||
"Architecture & Microservices", "Data, Messaging & Storage", "AI & Agents",
|
||||
"MLOps & Data Science", "Python, Java & Developer Ecosystem", "Linux & System Foundations",
|
||||
"Security & Compliance", "Infrastructure as Code", "CI/CD & GitOps",
|
||||
"Observability, SRE & Testing", "DevOps & Culture", "Platform Engineering & DevEx",
|
||||
"FinOps & Cloud Cost", "Certification & Training",
|
||||
"AWS", "Azure", "GCP, OCI & Others", "OpenShift / Red Hat", "Virtualization & Private Cloud"
|
||||
]
|
||||
geo_cats = ["Americas", "Europe", "España", "Asia-Pacific"]
|
||||
period_labels = {"3_months": "Last 3 Months", "6_months": "Last 6 Months", "12_months": "Last 12 Months"}
|
||||
|
||||
def render_digest_page(title, categories, digest_data, search_boost=1):
|
||||
md = f"---\nsearch:\n boost: {search_boost}\n---\n\n"
|
||||
md += f"# {title}\n\n"
|
||||
md += "!!! tip \"Nubenetes Intelligence Digest\"\n"
|
||||
md += " AI-curated ranking of the most impactful resources, updated monthly.\n\n"
|
||||
for period_key, period_label in period_labels.items():
|
||||
md += f'=== "{period_label}"\n\n'
|
||||
period_data = digest_data.get(period_key, {})
|
||||
for cat in categories:
|
||||
items = period_data.get(cat, [])
|
||||
if not items:
|
||||
continue
|
||||
md += f" **{cat}**\n\n"
|
||||
md += " | Date | Resource | Impact | Why It Matters |\n"
|
||||
md += " | :--- | :--- | :---: | :--- |\n"
|
||||
for item in items:
|
||||
impact_badge = {"critical": "🔴", "high": "🟡", "medium": "🔵"}.get(item.get("impact", "medium"), "🔵")
|
||||
t = nuclear_strip(item.get("title", "Unknown"))
|
||||
why = (item.get("why", "") or "").replace("|", "-").replace("\n", " ")
|
||||
md += f' | {item.get("date", "")} | [{t}]({item.get("url", "#")}) | {impact_badge} {item.get("impact", "medium")} | {why} |\n'
|
||||
md += "\n"
|
||||
md += "\n"
|
||||
return md
|
||||
|
||||
tech_md = render_digest_page("📊 Nubenetes Tech & Cloud Intelligence Digest", tech_cats, digest_data, search_boost=2)
|
||||
with open(os.path.join(V2_DIR, "tech-digest.md"), "w", encoding="utf-8") as f:
|
||||
f.write(tech_md)
|
||||
|
||||
industry_md = render_digest_page("🌍 Nubenetes Industry & Geo Intelligence Digest", geo_cats, digest_data, search_boost=2)
|
||||
with open(os.path.join(V2_DIR, "industry-digest.md"), "w", encoding="utf-8") as f:
|
||||
f.write(industry_md)
|
||||
|
||||
log_event("[Digest] Generated tech-digest.md and industry-digest.md")
|
||||
|
||||
async def _write_premium_files(self, data: Dict[str, Dict], mosaic_html: str, videos_html: str):
|
||||
# 1. Update Index with Pulse
|
||||
trending_pool = sorted([dict(meta, url=url) for url, meta in self.inventory.items() if isinstance(meta, dict) and meta.get("stars", 0) >= 4], key=lambda x: (str(x.get("year", "0000")) if str(x.get("year", "")).isdigit() else "0000", -x.get("stars", 0)), reverse=True)
|
||||
pulse_md = "## The Agentic Pulse\n" + "\n".join([f"- **({l.get('year', 'N/A')})** [**=={nuclear_strip(l['title'])}==**]({l['url'].strip()}) {'🌟'*l.get('stars',3)}" for l in trending_pool[:5]])
|
||||
# 1. Build Trending Now from digest data, or fallback to star-based pulse
|
||||
digest_data = {}
|
||||
digest_path = "data/news_digest.json"
|
||||
if os.path.exists(digest_path):
|
||||
try:
|
||||
with open(digest_path, "r", encoding="utf-8") as df:
|
||||
digest_data = json.load(df)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if digest_data and "3_months" in digest_data:
|
||||
top_items = []
|
||||
for cat_name, items in digest_data.get("3_months", {}).items():
|
||||
for item in items[:2]:
|
||||
top_items.append({**item, "digest_category": cat_name})
|
||||
top_items.sort(key=lambda x: {"critical": 3, "high": 2, "medium": 1}.get(x.get("impact", "medium"), 0), reverse=True)
|
||||
top_items = top_items[:6]
|
||||
|
||||
impact_icons = {"critical": "🔴", "high": "🟡", "medium": "🔵"}
|
||||
try:
|
||||
digest_mtime = os.path.getmtime(digest_path)
|
||||
from datetime import datetime as _dt
|
||||
digest_updated = _dt.fromtimestamp(digest_mtime).strftime("%b %d, %Y")
|
||||
except Exception:
|
||||
digest_updated = ""
|
||||
updated_badge = f'<span class="trending-section__updated">Updated {digest_updated}</span>' if digest_updated else ""
|
||||
cards_html = f'<div class="trending-section">\n<div class="trending-section__title">🔥 Trending Now — Cloud Native Intelligence {updated_badge}</div>\n<div class="trending-grid">\n'
|
||||
for item in top_items:
|
||||
impact = item.get("impact", "medium")
|
||||
cards_html += (
|
||||
f'<div class="trending-card">\n'
|
||||
f' <div class="trending-card__impact trending-card__impact--{impact}">{impact_icons.get(impact, "🔵")} {impact.upper()}</div>\n'
|
||||
f' <div class="trending-card__category">{item.get("digest_category", "")}</div>\n'
|
||||
f' <div class="trending-card__title"><a href="{item.get("url", "#")}">{nuclear_strip(item.get("title", "Unknown"))}</a></div>\n'
|
||||
f' <div class="trending-card__meta">{item.get("date", "")} · {"🌟" * item.get("stars") or 0}</div>\n'
|
||||
f' <div class="trending-card__why">{item.get("why", "")}</div>\n'
|
||||
f'</div>\n'
|
||||
)
|
||||
cards_html += '</div>\n'
|
||||
cards_html += '<div class="digest-links">\n'
|
||||
cards_html += ' <a href="./tech-digest/" class="digest-link-card">📊 Full Tech & Cloud Digest →</a>\n'
|
||||
cards_html += ' <a href="./industry-digest/" class="digest-link-card">🌍 Industry & Geo Digest →</a>\n'
|
||||
cards_html += '</div>\n</div>\n'
|
||||
pulse_md = cards_html
|
||||
else:
|
||||
trending_pool = sorted([dict(meta, url=url) for url, meta in self.inventory.items() if isinstance(meta, dict) and (meta.get("stars") or 0) >= 4], key=lambda x: (str(x.get("year", "0000")) if str(x.get("year", "")).isdigit() else "0000", -(x.get("stars") or 0)), reverse=True)
|
||||
pulse_md = "## The Agentic Pulse\n" + "\n".join([f"- **({l.get('year', 'N/A')})** [**=={nuclear_strip(l['title'])}==**]({l['url'].strip()}) {'🌟'*l.get('stars',3)}" for l in trending_pool[:5]])
|
||||
|
||||
# Calculate coverage for the index
|
||||
total_v1 = len(self.inventory)
|
||||
@@ -902,7 +1083,7 @@ class V2VisionEngine:
|
||||
"<center markdown=\"1\">\n"
|
||||
"<div class=\"hero-showcase-wrapper\">\n"
|
||||
" <a href=\"https://www.cncf.io/certification/software-conformance\" class=\"hero-showcase-link\">\n"
|
||||
" <img src=\"images/container_with_cars_v2.png\" alt=\"container_with_cars\" class=\"hero-showcase-image\" />\n"
|
||||
" <img src=\"/images/container_with_cars_v2.png\" alt=\"container_with_cars\" class=\"hero-showcase-image\" />\n"
|
||||
" <div class=\"hero-showcase-footer\">\n"
|
||||
" <span class=\"hero-showcase-badge\">CNCF Conformance</span>\n"
|
||||
" <span class=\"hero-showcase-caption\">Standardized conformance guarantees seamless workload portability across the Cloud Native landscape.</span>\n"
|
||||
@@ -933,6 +1114,13 @@ class V2VisionEngine:
|
||||
" <div class=\"hero-badge-subtitle\">Agentic Ecosystem</div>\n"
|
||||
" </div>\n"
|
||||
" </a>\n"
|
||||
" <a href=\"./tech-digest/\" style=\"text-decoration: none; color: inherit; display: block;\">\n"
|
||||
" <div class=\"hero-badge-card hero-badge-card--amber\">\n"
|
||||
" <div class=\"hero-badge-icon\">📊</div>\n"
|
||||
" <div class=\"hero-badge-title\">Intelligence Digest</div>\n"
|
||||
" <div class=\"hero-badge-subtitle\">Top picks · 3/6/12 months</div>\n"
|
||||
" </div>\n"
|
||||
" </a>\n"
|
||||
" <a href=\"./videos/\" style=\"text-decoration: none; color: inherit; display: block;\">\n"
|
||||
" <div class=\"hero-badge-card hero-badge-card--pink\">\n"
|
||||
" <img src=\"/images/video_hub_logo.png\" alt=\"Agentic Video Hub\"/>\n"
|
||||
@@ -1158,10 +1346,30 @@ class V2VisionEngine:
|
||||
|
||||
md += await render_node(info["content"], -1, f_name.replace(".md", ""), used_headers, is_intro=(f_name=="introduction.md" or f_name=="about.md"))
|
||||
|
||||
# Add Semantic "See Also" ONLY ONCE at the end of the page
|
||||
related = [f"[{data[f]['title']}](./{f})" for f in data if f != f_name and data[f]["dim"] == info["dim"]]
|
||||
if related:
|
||||
md += f"\n---\n💡 **Explore Related:** {' | '.join(related[:3])}\n\n"
|
||||
# Add Semantic "See Also" — same dimension + cross-dimension by shared tags
|
||||
same_dim = [f for f in data if f != f_name and data[f]["dim"] == info["dim"]]
|
||||
cross_dim = []
|
||||
if info.get("content") and isinstance(info["content"], dict):
|
||||
page_tags = set()
|
||||
for node_links in self._collect_tags_from_tree(info["content"]):
|
||||
page_tags.update(node_links)
|
||||
if page_tags:
|
||||
for f in data:
|
||||
if f != f_name and data[f]["dim"] != info["dim"]:
|
||||
other_tags = set()
|
||||
if isinstance(data[f].get("content"), dict):
|
||||
for t in self._collect_tags_from_tree(data[f]["content"]):
|
||||
other_tags.update(t)
|
||||
if page_tags & other_tags:
|
||||
cross_dim.append(f)
|
||||
related = [f"[{data[f]['title']}](./{f})" for f in same_dim[:3]]
|
||||
cross = [f"[{data[f]['title']}](./{f})" for f in cross_dim[:2]]
|
||||
if related or cross:
|
||||
md += "\n---\n"
|
||||
if related:
|
||||
md += f"💡 **Explore Related:** {' | '.join(related)}\n\n"
|
||||
if cross:
|
||||
md += f"🔗 **See Also:** {' | '.join(cross)}\n\n"
|
||||
|
||||
# Smart Write: Only update disk if content changed
|
||||
target_path = os.path.join(V2_DIR, f_name)
|
||||
@@ -1258,7 +1466,7 @@ class V2VisionEngine:
|
||||
md += f"<summary>{summary_text}</summary>\n\n"
|
||||
|
||||
# Sort links under this tag by impact stars and then by year
|
||||
sorted_links = sorted(by_tag[tag], key=lambda x: (-x.get("stars", 1), -(int(x["year"]) if str(x.get("year", "")).isdigit() else 0)))
|
||||
sorted_links = sorted(by_tag[tag], key=lambda x: (-(x.get("stars") or 1), -(int(x["year"]) if str(x.get("year", "")).isdigit() else 0)))
|
||||
|
||||
rendered_links = sorted_links[:100]
|
||||
for l in rendered_links:
|
||||
@@ -1280,14 +1488,17 @@ class V2VisionEngine:
|
||||
if md != existing_content:
|
||||
with open(target_path, "w") as f: f.write(md)
|
||||
|
||||
async def _sync_enterprise_navigation(self, data: Dict[str, Dict]):
|
||||
async def _sync_enterprise_navigation(self, data: Dict[str, Dict]) -> bool:
|
||||
try:
|
||||
with open("v2-mkdocs.yml", "r") as f: content = f.read()
|
||||
nav = [
|
||||
"nav:",
|
||||
" - \"🔙 Back to V1 (Exhaustive)\": https://nubenetes.com/v1/",
|
||||
"nav:",
|
||||
" - \"🔙 Back to V1 (Exhaustive)\": https://nubenetes.com/v1/",
|
||||
" - \"The 2026 Vision\": index.md",
|
||||
" - \"Technical Tags\": tags.md",
|
||||
" - \"Intelligence Digest\":",
|
||||
" - \"Tech & Cloud Digest\": tech-digest.md",
|
||||
" - \"Industry & Geo Digest\": industry-digest.md",
|
||||
" - \"Agentic Video Hub\":",
|
||||
" - videos/index.md",
|
||||
" - \"AI Agents and MCP\": videos/ai-agents.md",
|
||||
@@ -1295,8 +1506,7 @@ class V2VisionEngine:
|
||||
" - \"Cloud Native Core\": videos/cloud-native.md",
|
||||
" - \"Fundamentals\": videos/fundamentals.md"
|
||||
]
|
||||
|
||||
# Group files by dimension
|
||||
|
||||
dim_groups = {}
|
||||
for f_name, info in data.items():
|
||||
dim_groups.setdefault(info["dim"], []).append(f_name)
|
||||
@@ -1306,10 +1516,21 @@ class V2VisionEngine:
|
||||
dim_nav = [f" - \"{dim}\":"]
|
||||
for f in sorted(dim_groups[dim]):
|
||||
dim_nav.append(f" - \"{data[f]['title']}\": {f}")
|
||||
nav.extend(dim_nav)
|
||||
updated = re.sub(r'nav:.*', "\n".join(nav), content, flags=re.DOTALL)
|
||||
nav.extend(dim_nav)
|
||||
|
||||
# Replace only the nav section (from 'nav:' to end of file)
|
||||
# Use a marker to ensure we don't accidentally eat extra_css etc.
|
||||
if "nav:" not in content:
|
||||
log_event("[WARN] _sync_enterprise_navigation: 'nav:' not found in v2-mkdocs.yml")
|
||||
return False
|
||||
nav_start = content.index("nav:")
|
||||
updated = content[:nav_start] + "\n".join(nav) + "\n"
|
||||
with open("v2-mkdocs.yml", "w") as f: f.write(updated)
|
||||
except: pass
|
||||
log_event(f" [OK] Nav synced: {len(nav)} entries written")
|
||||
return True
|
||||
except Exception as e:
|
||||
log_event(f"[WARN] sync enterprise navigation: {str(e)[:100]}")
|
||||
return False
|
||||
|
||||
import argparse
|
||||
if __name__ == "__main__":
|
||||
@@ -1361,7 +1582,8 @@ if __name__ == "__main__":
|
||||
with open(os.path.join(V2_DIR, f), "r") as doc:
|
||||
line = doc.readline()
|
||||
if line.startswith("# "): title = line.replace("# ", "").strip()
|
||||
except: pass
|
||||
except Exception as e:
|
||||
log_event(f"[WARN] extract title from V2 file {f}: {str(e)[:100]}")
|
||||
file_list_md += f"| {i} | `{f}` | {title} |\n"
|
||||
|
||||
# 3. Decision Matrix (Maturity Audit)
|
||||
@@ -1369,7 +1591,7 @@ if __name__ == "__main__":
|
||||
header_table = "| # | Status | Maturity | Stars | Dimension | Resource |\n| :--- | :--- | :--- | :---: | :--- | :--- |\n"
|
||||
for idx, entry in enumerate(engine.maturity_audit, 1):
|
||||
status = "💎 ELITE" if entry.get('v2_locations') else "📦 ARCHIVE"
|
||||
row = f"| {idx} | {status} | {entry.get('tag', 'N/A')} | {'🌟'*entry.get('stars',0)} | {entry.get('dimension', 'N/A')} | {entry.get('url', 'N/A')} |\n"
|
||||
row = f"| {idx} | {status} | {entry.get('tag', 'N/A')} | {'🌟'*(entry.get('stars') or 0)} | {entry.get('dimension', 'N/A')} | {entry.get('url', 'N/A')} |\n"
|
||||
matrix_rows.append(row)
|
||||
|
||||
# 4. Generate PR Body (Main Report)
|
||||
|
||||
+28
-9
@@ -78,22 +78,41 @@ def extract_youtube_id(url):
|
||||
def get_target_file(category, technology):
|
||||
cat_lower = category.lower()
|
||||
tech_lower = technology.lower()
|
||||
|
||||
if "agent" in tech_lower or "mcp" in tech_lower or "ai and future operations" in cat_lower:
|
||||
|
||||
if "agent" in tech_lower or "mcp" in tech_lower or "ai and future operations" in cat_lower or "llm" in tech_lower or "chatgpt" in tech_lower:
|
||||
return "ai-agents.md", "AI Agents and MCP"
|
||||
elif "infrastructure as code" in cat_lower or "security" in cat_lower or "observability" in cat_lower or "monitoring" in cat_lower or "devops" in tech_lower or "iac" in tech_lower or "sre" in tech_lower:
|
||||
elif "mlops" in tech_lower or "data science" in cat_lower or "machine learning" in tech_lower:
|
||||
return "ai-agents.md", "AI Agents and MCP"
|
||||
elif "security" in cat_lower or "devsecops" in tech_lower or "zero trust" in tech_lower or "vulnerability" in tech_lower:
|
||||
return "devops-iac.md", "DevOps, IaC, and SRE"
|
||||
elif "fundamentals" in cat_lower:
|
||||
elif "infrastructure as code" in cat_lower or "observability" in cat_lower or "monitoring" in cat_lower or "devops" in tech_lower or "iac" in tech_lower or "sre" in tech_lower:
|
||||
return "devops-iac.md", "DevOps, IaC, and SRE"
|
||||
elif "terraform" in tech_lower or "ansible" in tech_lower or "pulumi" in tech_lower or "crossplane" in tech_lower:
|
||||
return "devops-iac.md", "DevOps, IaC, and SRE"
|
||||
elif "gitops" in tech_lower or "argo" in tech_lower or "flux" in tech_lower or "tekton" in tech_lower or "jenkins" in tech_lower or "cicd" in tech_lower:
|
||||
return "devops-iac.md", "DevOps, IaC, and SRE"
|
||||
elif "prometheus" in tech_lower or "grafana" in tech_lower or "opentelemetry" in tech_lower or "otel" in tech_lower:
|
||||
return "devops-iac.md", "DevOps, IaC, and SRE"
|
||||
elif "finops" in tech_lower or "cost" in tech_lower or "kubecost" in tech_lower:
|
||||
return "devops-iac.md", "DevOps, IaC, and SRE"
|
||||
elif "fundamentals" in cat_lower or "certification" in tech_lower or "cka" in tech_lower or "training" in cat_lower:
|
||||
return "fundamentals.md", "Fundamentals"
|
||||
elif "aws" in tech_lower or "azure" in tech_lower or "gcp" in tech_lower or "google cloud" in tech_lower:
|
||||
return "cloud-native.md", "Cloud Native Core"
|
||||
elif "openshift" in tech_lower or "red hat" in tech_lower or "rancher" in tech_lower:
|
||||
return "cloud-native.md", "Cloud Native Core"
|
||||
elif "vmware" in tech_lower or "proxmox" in tech_lower or "virtualization" in tech_lower:
|
||||
return "cloud-native.md", "Cloud Native Core"
|
||||
elif "docker" in tech_lower or "container" in tech_lower or "podman" in tech_lower:
|
||||
return "cloud-native.md", "Cloud Native Core"
|
||||
elif "python" in tech_lower or "golang" in tech_lower or "java" in tech_lower or "javascript" in tech_lower:
|
||||
return "fundamentals.md", "Fundamentals"
|
||||
else:
|
||||
return "cloud-native.md", "Cloud Native Core"
|
||||
|
||||
def generate_v2_videos():
|
||||
if not os.path.exists(INVENTORY_PATH):
|
||||
return
|
||||
|
||||
with open(INVENTORY_PATH, "r") as f:
|
||||
inventory = yaml.safe_load(f)
|
||||
from src.inventory_manager import load_inventory
|
||||
inventory = load_inventory()
|
||||
|
||||
featured_videos = []
|
||||
for url, entry in inventory.items():
|
||||
|
||||
+2
-2
@@ -65,7 +65,7 @@ We prioritize established frameworks and enterprise standards over ad-hoc, unmai
|
||||
#### 2.4. Avoiding Engineering Anti-Patterns
|
||||
We combat the culture of **Promotion-Based Development (PBD)**, where complexity is manufactured for personal career visibility rather than business value.
|
||||
|
||||
- [Promotion-Based Development: A Fast Track to Mediocrity](https://vadimkravcenko.com/shorts/promotion-based-development/) <span class='md-tag md-tag--secondary'>[GUIDE]</span> — Dissects how rewarding "shiny new things" over battle-tested stability leads to fragile architectures.
|
||||
- [Promotion-Based Development: A Fast Track to Mediocrity](https://vadimkravcenko.com/shorts/promotion-based-development) <span class='md-tag md-tag--secondary'>[GUIDE]</span> — Dissects how rewarding "shiny new things" over battle-tested stability leads to fragile architectures.
|
||||
- [Reddit: The Reality of Promotion-Driven Development](https://www.reddit.com/r/ExperiencedDevs/comments/pw6vuv/promotion_driven_development) <span class='md-tag md-tag--info'>[COMMUNITY-TOOL]</span> — A raw, evidence-based discussion from senior engineers on the industry's most common misaligned incentives.
|
||||
|
||||
### 3. The Architectural North Star
|
||||
@@ -220,7 +220,7 @@ While certifications like CKA are prominent on CVs, they are frequently utilized
|
||||
#### Terraform Boilerplates
|
||||
|
||||
??? note "Terraform Kubernetes Boilerplates 🌟"
|
||||
**[Access Resource](https://nubenetes.com/terraform/)** 🌟🌟🌟🌟🌟 | Level: Advanced
|
||||
**[Access Resource](https://nubenetes.com/terraform)** 🌟🌟🌟🌟🌟 | Level: Advanced
|
||||
|
||||
A library of enterprise-stable Terraform templates configured specifically for modern Kubernetes environments (EKS, GKE, AKS). Includes pre-tested infrastructure specifications for VPC topologies, private nodes, and dynamic ingress setups.
|
||||
|
||||
|
||||
@@ -161,7 +161,7 @@
|
||||
|
||||
#### AWS Templates
|
||||
|
||||
- **(2024)** [AWS Samples (Boilerplates)](https://nubenetes.com/demos/#aws-samples-boilerplates) <span class='md-tag md-tag--warning'>[MULTI-LANGUAGE CONTENT]</span> <span class='md-tag md-tag--info'>[COMMUNITY-TOOL]</span> — A consolidated hub of official and community AWS deployment samples. Houses structured patterns and CloudFormation/Terraform codebases to fast-track prototype development in compliance with AWS architecture standards.
|
||||
- **(2024)** [AWS Samples (Boilerplates)](https://nubenetes.com/demos) <span class='md-tag md-tag--warning'>[MULTI-LANGUAGE CONTENT]</span> <span class='md-tag md-tag--info'>[COMMUNITY-TOOL]</span> — A consolidated hub of official and community AWS deployment samples. Houses structured patterns and CloudFormation/Terraform codebases to fast-track prototype development in compliance with AWS architecture standards.
|
||||
|
||||
---
|
||||
💡 **Explore Related:** [Googlecloudplatform](./GoogleCloudPlatform.md) | [AWS Pricing](./aws-pricing.md) | [AWS Spain](./aws-spain.md)
|
||||
|
||||
@@ -639,7 +639,7 @@
|
||||
|
||||
#### Network Cheat Sheets
|
||||
|
||||
- **(2022)** [Networking Cheat Sheet](https://nubenetes.com/networking/) <span class='md-tag md-tag--warning'>[N/A CONTENT]</span> <span class='md-tag md-tag--info'>[COMMUNITY-TOOL]</span> — Consolidated technical reference detailing essential low-level networking parameters, CIDR calculations, subnetting concepts, and critical routing architectures. Serves as a quick diagnostic lookup sheet for standard network protocol analysis and port troubleshooting.
|
||||
- **(2022)** [Networking Cheat Sheet](https://nubenetes.com/networking) <span class='md-tag md-tag--warning'>[N/A CONTENT]</span> <span class='md-tag md-tag--info'>[COMMUNITY-TOOL]</span> — Consolidated technical reference detailing essential low-level networking parameters, CIDR calculations, subnetting concepts, and critical routing architectures. Serves as a quick diagnostic lookup sheet for standard network protocol analysis and port troubleshooting.
|
||||
## Observability
|
||||
|
||||
### Metrics and Monitoring
|
||||
|
||||
@@ -766,7 +766,7 @@
|
||||
|
||||
#### Crunchy PostgreSQL
|
||||
|
||||
- **(2023)** [Crunchy Data PostgreSQL Operator](https://nubenetes.com/crunchydata/) <span class='md-tag md-tag--critical'>[ADVANCED LEVEL]</span> <span class='md-tag md-tag--info'>[COMMUNITY-TOOL]</span> <span class='md-tag md-tag--secondary'>[GUIDE]</span> — Evaluates the Crunchy PostgreSQL Operator (PGO) which automates production-grade PostgreSQL deployments on Kubernetes. Features include automated high availability, pgBackRest-driven backup orchestration, connection pooling via pgBouncer, and deep monitoring metrics. A de facto standard solution for enterprises migrating critical relational engines into Kubernetes platforms.
|
||||
- **(2023)** [Crunchy Data PostgreSQL Operator](https://nubenetes.com/crunchydata) <span class='md-tag md-tag--critical'>[ADVANCED LEVEL]</span> <span class='md-tag md-tag--info'>[COMMUNITY-TOOL]</span> <span class='md-tag md-tag--secondary'>[GUIDE]</span> — Evaluates the Crunchy PostgreSQL Operator (PGO) which automates production-grade PostgreSQL deployments on Kubernetes. Features include automated high availability, pgBackRest-driven backup orchestration, connection pooling via pgBouncer, and deep monitoring metrics. A de facto standard solution for enterprises migrating critical relational engines into Kubernetes platforms.
|
||||
## Time-Series
|
||||
|
||||
### Architecture (1)
|
||||
|
||||
+1
-1
@@ -2050,7 +2050,7 @@
|
||||
|
||||
#### EKS Training
|
||||
|
||||
- **(2025)** [eksworkshop.com](https://eksworkshop.com/) <span class='md-tag md-tag--warning'>[N/A CONTENT]</span> <span class='md-tag md-tag--info'>[COMMUNITY-TOOL]</span> <span class='md-tag md-tag--secondary'>[GUIDE]</span> — The canonical AWS EKS workshop framework. Outlines standard cluster orchestration procedures, highlighting network configurations (AWS VPC CNI), identity management (IAM Roles for Service Accounts - IRSA), and modern storage drivers (EBS/EFS CSI).
|
||||
- **(2025)** [eksworkshop.com](https://eksworkshop.com) <span class='md-tag md-tag--warning'>[N/A CONTENT]</span> <span class='md-tag md-tag--info'>[COMMUNITY-TOOL]</span> <span class='md-tag md-tag--secondary'>[GUIDE]</span> — The canonical AWS EKS workshop framework. Outlines standard cluster orchestration procedures, highlighting network configurations (AWS VPC CNI), identity management (IAM Roles for Service Accounts - IRSA), and modern storage drivers (EBS/EFS CSI).
|
||||
### Kubernetes Security
|
||||
|
||||
#### RKE Best Practices
|
||||
|
||||
@@ -218,7 +218,7 @@
|
||||
|
||||
#### Keptn
|
||||
|
||||
- **(2026)** [**Keptn**](https://nubenetes.com/keptn/) <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> <span class='md-tag md-tag--critical'>[ADVANCED LEVEL]</span> <span class='md-tag md-tag--primary'>[DOCUMENTATION]</span> 🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[ENTERPRISE-STABLE]</span> — Nubenetes architectural reference on Keptn, a CNCF enterprise-grade control plane for cloud-native application lifecycle orchestration. Integrates SLO-based evaluations, automated canary promotions, and zero-touch application remediation out of the box.
|
||||
- **(2026)** [**Keptn**](https://nubenetes.com/keptn) <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> <span class='md-tag md-tag--critical'>[ADVANCED LEVEL]</span> <span class='md-tag md-tag--primary'>[DOCUMENTATION]</span> 🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[ENTERPRISE-STABLE]</span> — Nubenetes architectural reference on Keptn, a CNCF enterprise-grade control plane for cloud-native application lifecycle orchestration. Integrates SLO-based evaluations, automated canary promotions, and zero-touch application remediation out of the box.
|
||||
|
||||
---
|
||||
💡 **Explore Related:** [Demos](./demos.md) | [Kubernetes](./kubernetes.md) | [Cloud Arch Diagrams](./cloud-arch-diagrams.md)
|
||||
|
||||
+3
-3
@@ -523,7 +523,7 @@
|
||||
|
||||
#### Overview
|
||||
|
||||
- **(2026)** [**NoOps**](https://nubenetes.com/noops/) <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> <span class='md-tag md-tag--primary'>[DOCUMENTATION]</span> 🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[ENTERPRISE-STABLE]</span> — Comprehensive conceptual guide on NoOps (No Operations). Describes the strategic path to fully outsourcing infrastructure layers to automated platforms, serverless paradigms, and self-healing systems so engineering can focus 100% on application logic.
|
||||
- **(2026)** [**NoOps**](https://nubenetes.com/noops) <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> <span class='md-tag md-tag--primary'>[DOCUMENTATION]</span> 🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[ENTERPRISE-STABLE]</span> — Comprehensive conceptual guide on NoOps (No Operations). Describes the strategic path to fully outsourcing infrastructure layers to automated platforms, serverless paradigms, and self-healing systems so engineering can focus 100% on application logic.
|
||||
### Serverless Systems
|
||||
|
||||
#### DevOps Pipelines
|
||||
@@ -869,7 +869,7 @@
|
||||
|
||||
#### Overview (1)
|
||||
|
||||
- **(2026)** [**DevOps Tools**](https://nubenetes.com/devops-tools/) <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> <span class='md-tag md-tag--primary'>[DOCUMENTATION]</span> 🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[ENTERPRISE-STABLE]</span> — Catalog of modern DevOps tooling encompassing continuous integration, artifact storage, automated testing, container scheduling, and real-time telemetry pipelines to build stable, production-ready release processes.
|
||||
- **(2026)** [**DevOps Tools**](https://nubenetes.com/devops-tools) <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> <span class='md-tag md-tag--primary'>[DOCUMENTATION]</span> 🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[ENTERPRISE-STABLE]</span> — Catalog of modern DevOps tooling encompassing continuous integration, artifact storage, automated testing, container scheduling, and real-time telemetry pipelines to build stable, production-ready release processes.
|
||||
## DevOps Methodology
|
||||
|
||||
### Application Delivery
|
||||
@@ -1129,7 +1129,7 @@
|
||||
|
||||
#### Overview (2)
|
||||
|
||||
- **(2026)** [==IaC Infrastructure as Code==](https://nubenetes.com/iac/) <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> <span class='md-tag md-tag--primary'>[DOCUMENTATION]</span> 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> — Nubenetes architectural reference portal on Infrastructure as Code (IaC). Outlines fundamental philosophies, lifecycle management, and paradigm shifts of treating bare-metal, cloud, or cluster state as declarative, version-controlled code.
|
||||
- **(2026)** [==IaC Infrastructure as Code==](https://nubenetes.com/iac) <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> <span class='md-tag md-tag--primary'>[DOCUMENTATION]</span> 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> — Nubenetes architectural reference portal on Infrastructure as Code (IaC). Outlines fundamental philosophies, lifecycle management, and paradigm shifts of treating bare-metal, cloud, or cluster state as declarative, version-controlled code.
|
||||
### Terraform
|
||||
|
||||
#### Entra ID Integration
|
||||
|
||||
+1
-1
@@ -843,7 +843,7 @@
|
||||
|
||||
#### Docker Swarm
|
||||
|
||||
- **(2024)** [Docker Swarm](https://nubenetes.com/kubernetes-alternatives/#docker-swarm) <span class='md-tag md-tag--warning'>[GO CONTENT]</span> <span class='md-tag md-tag--info'>[COMMUNITY-TOOL]</span> — Analyzes Docker Swarm as a simple container orchestration alternative to Kubernetes. Evaluates its built-in overlay network routing mesh and single-node setup advantages, noting that its enterprise adoption has decreased in favor of Kubernetes-native environments.
|
||||
- **(2024)** [Docker Swarm](https://nubenetes.com/kubernetes-alternatives) <span class='md-tag md-tag--warning'>[GO CONTENT]</span> <span class='md-tag md-tag--info'>[COMMUNITY-TOOL]</span> — Analyzes Docker Swarm as a simple container orchestration alternative to Kubernetes. Evaluates its built-in overlay network routing mesh and single-node setup advantages, noting that its enterprise adoption has decreased in favor of Kubernetes-native environments.
|
||||
## Performance
|
||||
|
||||
### Diagnostics (1)
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
|
||||
<channel>
|
||||
<title>Nubenetes Intelligence Digest</title>
|
||||
<link>https://nubenetes.com/</link>
|
||||
<description>AI-curated top picks from the Cloud Native & Kubernetes ecosystem</description>
|
||||
<language>en</language>
|
||||
<lastBuildDate>Fri, 19 Jun 2026 10:51:12 -0000</lastBuildDate>
|
||||
<atom:link href="https://nubenetes.com/feed.xml" rel="self" type="application/rss+xml"/>
|
||||
<item>
|
||||
<title>antigravity.google: Google Antigravity Agentic Platform</title>
|
||||
<link>https://antigravity.google</link>
|
||||
<guid isPermaLink="true">https://antigravity.google</guid>
|
||||
<pubDate>Thu, 18 Jun 2026 00:00:00 -0000</pubDate>
|
||||
<category>AI & Agents</category>
|
||||
<description>[CRITICAL] It provides an SDK and platform specifically designed to transition stateful AI agents from local development to secure GKE production environments.</description>
|
||||
</item>
|
||||
<item>
|
||||
<title>docs.anthropic.com: Claude Code CLI</title>
|
||||
<link>https://docs.anthropic.com/en/docs/agents-and-tools/claude-code/overview</link>
|
||||
<guid isPermaLink="true">https://docs.anthropic.com/en/docs/agents-and-tools/claude-code/overview</guid>
|
||||
<pubDate>Thu, 18 Jun 2026 00:00:00 -0000</pubDate>
|
||||
<category>AI & Agents</category>
|
||||
<description>[CRITICAL] This official CLI tool from Anthropic introduces highly autonomous agentic engineering capabilities directly into local developer environments.</description>
|
||||
</item>
|
||||
<item>
|
||||
<title>Crossplane</title>
|
||||
<link>https://nubenetes.com/crossplane</link>
|
||||
<guid isPermaLink="true">https://nubenetes.com/crossplane</guid>
|
||||
<pubDate>Sun, 14 Jun 2026 00:00:00 -0000</pubDate>
|
||||
<category>Kubernetes & Orchestration</category>
|
||||
<description>[CRITICAL] It transforms Kubernetes into a universal control plane, allowing teams to manage external cloud infrastructure alongside containerized workloads.</description>
|
||||
</item>
|
||||
<item>
|
||||
<title>NVIDIA/k8s-device-plugin: NVIDIA device plugin for Kubernetes</title>
|
||||
<link>https://github.com/NVIDIA/k8s-device-plugin</link>
|
||||
<guid isPermaLink="true">https://github.com/NVIDIA/k8s-device-plugin</guid>
|
||||
<pubDate>Sun, 14 Jun 2026 00:00:00 -0000</pubDate>
|
||||
<category>Kubernetes & Orchestration</category>
|
||||
<description>[CRITICAL] It acts as the essential orchestrator bridging physical GPU capabilities to workloads, a prerequisite for modern AI/ML execution on Kubernetes.</description>
|
||||
</item>
|
||||
<item>
|
||||
<title>vLLM on Kubernetes</title>
|
||||
<link>https://github.com/vllm-project/vllm</link>
|
||||
<guid isPermaLink="true">https://github.com/vllm-project/vllm</guid>
|
||||
<pubDate>Sun, 14 Jun 2026 00:00:00 -0000</pubDate>
|
||||
<category>AI & Agents</category>
|
||||
<description>[CRITICAL] It standardizes memory-efficient LLM serving with vLLM directly on Kubernetes, bridging cloud-native orchestration with state-of-the-art AI inference.</description>
|
||||
</item>
|
||||
<item>
|
||||
<title>Tetragon (Cilium)</title>
|
||||
<link>https://github.com/cilium/tetragon</link>
|
||||
<guid isPermaLink="true">https://github.com/cilium/tetragon</guid>
|
||||
<pubDate>Sun, 14 Jun 2026 00:00:00 -0000</pubDate>
|
||||
<category>Security & Compliance</category>
|
||||
<description>[CRITICAL] Tetragon leverages eBPF to deliver low-overhead, kernel-level runtime enforcement and security observability directly tailored for cloud-native workloads.</description>
|
||||
</item>
|
||||
<item>
|
||||
<title>Helm</title>
|
||||
<link>https://nubenetes.com/helm</link>
|
||||
<guid isPermaLink="true">https://nubenetes.com/helm</guid>
|
||||
<pubDate>Sun, 14 Jun 2026 00:00:00 -0000</pubDate>
|
||||
<category>CI/CD & GitOps</category>
|
||||
<description>[CRITICAL] Helm is the undisputed package manager for Kubernetes, serving as the core template and packaging engine for cloud-native deployments.</description>
|
||||
</item>
|
||||
<item>
|
||||
<title>==cert-manager/cert-manager==</title>
|
||||
<link>https://github.com/cert-manager/cert-manager</link>
|
||||
<guid isPermaLink="true">https://github.com/cert-manager/cert-manager</guid>
|
||||
<pubDate>Sun, 14 Jun 2026 00:00:00 -0000</pubDate>
|
||||
<category>Containers & Runtime</category>
|
||||
<description>[CRITICAL] This project is the undisputed cloud-native standard for automating TLS/SSL certificate lifecycle management in Kubernetes clusters.</description>
|
||||
</item>
|
||||
<item>
|
||||
<title>github.com: Istio</title>
|
||||
<link>https://github.com/istio/istio</link>
|
||||
<guid isPermaLink="true">https://github.com/istio/istio</guid>
|
||||
<pubDate>Sun, 14 Jun 2026 00:00:00 -0000</pubDate>
|
||||
<category>Networking & Service Mesh</category>
|
||||
<description>[CRITICAL] As the industry-standard enterprise service mesh, Istio provides the critical infrastructure for secure, observable, and resilient microservice communications.</description>
|
||||
</item>
|
||||
<item>
|
||||
<title>github: Flux Version 2</title>
|
||||
<link>https://github.com/fluxcd/flux2</link>
|
||||
<guid isPermaLink="true">https://github.com/fluxcd/flux2</guid>
|
||||
<pubDate>Sat, 13 Jun 2026 00:00:00 -0000</pubDate>
|
||||
<category>CI/CD & GitOps</category>
|
||||
<description>[CRITICAL] Flux v2 is a foundational CNCF graduated project implementing highly parallel, secure GitOps reconciliation controllers.</description>
|
||||
</item>
|
||||
<item>
|
||||
<title>github.com/prometheus/prometheus</title>
|
||||
<link>https://github.com/prometheus/prometheus</link>
|
||||
<guid isPermaLink="true">https://github.com/prometheus/prometheus</guid>
|
||||
<pubDate>Sat, 13 Jun 2026 00:00:00 -0000</pubDate>
|
||||
<category>Observability, SRE & Testing</category>
|
||||
<description>[CRITICAL] Prometheus is the de facto standard metric engine for cloud-native ecosystems, providing critical sub-second query speeds and robust local storage.</description>
|
||||
</item>
|
||||
<item>
|
||||
<title>containerd - An open and reliable container runtime</title>
|
||||
<link>https://github.com/containerd/containerd</link>
|
||||
<guid isPermaLink="true">https://github.com/containerd/containerd</guid>
|
||||
<pubDate>Sat, 13 Jun 2026 00:00:00 -0000</pubDate>
|
||||
<category>Containers & Runtime</category>
|
||||
<description>[CRITICAL] As the de facto industry-standard container runtime for Kubernetes, containerd is critical for running enterprise cloud-native workloads.</description>
|
||||
</item>
|
||||
<item>
|
||||
<title>runc</title>
|
||||
<link>https://github.com/opencontainers/runc</link>
|
||||
<guid isPermaLink="true">https://github.com/opencontainers/runc</guid>
|
||||
<pubDate>Sat, 13 Jun 2026 00:00:00 -0000</pubDate>
|
||||
<category>Containers & Runtime</category>
|
||||
<description>[CRITICAL] Runc is the foundational, low-level OCI-compliant runtime that interfaces directly with the Linux kernel to execute modern containers.</description>
|
||||
</item>
|
||||
<item>
|
||||
<title>==github.com/Netflix/metaflow== 🌟</title>
|
||||
<link>https://github.com/Netflix/metaflow</link>
|
||||
<guid isPermaLink="true">https://github.com/Netflix/metaflow</guid>
|
||||
<pubDate>Sat, 13 Jun 2026 00:00:00 -0000</pubDate>
|
||||
<category>MLOps & Data Science</category>
|
||||
<description>[CRITICAL] Metaflow bridges local development and cloud infrastructure, simplifying production-grade data science pipeline execution at scale.</description>
|
||||
</item>
|
||||
<item>
|
||||
<title>hashicorp/vault</title>
|
||||
<link>https://github.com/hashicorp/vault</link>
|
||||
<guid isPermaLink="true">https://github.com/hashicorp/vault</guid>
|
||||
<pubDate>Fri, 12 Jun 2026 00:00:00 -0000</pubDate>
|
||||
<category>Security & Compliance</category>
|
||||
<description>[CRITICAL] HashiCorp Vault remains the foundational, enterprise-grade standard for securing dynamic secrets and implementing zero-trust architectures across multi-cloud environments.</description>
|
||||
</item>
|
||||
<item>
|
||||
<title>OpenTelemetry Collector</title>
|
||||
<link>https://github.com/open-telemetry/opentelemetry-collector</link>
|
||||
<guid isPermaLink="true">https://github.com/open-telemetry/opentelemetry-collector</guid>
|
||||
<pubDate>Fri, 12 Jun 2026 00:00:00 -0000</pubDate>
|
||||
<category>Observability, SRE & Testing</category>
|
||||
<description>[CRITICAL] OpenTelemetry Collector acts as the universal, high-performance telemetry pipeline for enterprise observability, unifying logs, metrics, and traces.</description>
|
||||
</item>
|
||||
<item>
|
||||
<title>Kubernetes Gateway API</title>
|
||||
<link>https://github.com/kubernetes-sigs/gateway-api</link>
|
||||
<guid isPermaLink="true">https://github.com/kubernetes-sigs/gateway-api</guid>
|
||||
<pubDate>Fri, 12 Jun 2026 00:00:00 -0000</pubDate>
|
||||
<category>Networking & Service Mesh</category>
|
||||
<description>[CRITICAL] The Gateway API represents a major paradigm shift, replacing legacy Ingress with an expressive, role-oriented, and extensible routing standard.</description>
|
||||
</item>
|
||||
<item>
|
||||
<title>github.com/vmware-tanzu/velero</title>
|
||||
<link>https://github.com/velero-io/velero</link>
|
||||
<guid isPermaLink="true">https://github.com/velero-io/velero</guid>
|
||||
<pubDate>Fri, 12 Jun 2026 00:00:00 -0000</pubDate>
|
||||
<category>Data, Messaging & Storage</category>
|
||||
<description>[CRITICAL] It is the industry-standard open-source tool for Kubernetes cluster backup, disaster recovery, and stateful volume migration.</description>
|
||||
</item>
|
||||
<item>
|
||||
<title>trivy</title>
|
||||
<link>https://github.com/aquasecurity/trivy</link>
|
||||
<guid isPermaLink="true">https://github.com/aquasecurity/trivy</guid>
|
||||
<pubDate>Thu, 11 Jun 2026 00:00:00 -0000</pubDate>
|
||||
<category>Security & Compliance</category>
|
||||
<description>[CRITICAL] Trivy represents the industry standard for fast, comprehensive security scanning of container images, Kubernetes configurations, and software bills of materials.</description>
|
||||
</item>
|
||||
<item>
|
||||
<title>OpenTofu 1.12: the Feature Terraform Never Shipped</title>
|
||||
<link>https://www.infoq.com/news/2026/05/opentofu-release-terraform</link>
|
||||
<guid isPermaLink="true">https://www.infoq.com/news/2026/05/opentofu-release-terraform</guid>
|
||||
<pubDate>Tue, 02 Jun 2026 00:00:00 -0000</pubDate>
|
||||
<category>Infrastructure as Code</category>
|
||||
<description>[CRITICAL] This landmark OpenTofu release solves a decade-old limitation of upstream Terraform by enabling the pre-evaluation of providers using variables.</description>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
||||
+1
-1
@@ -1074,7 +1074,7 @@ Guides developers on configuring autonomous multi-file refactoring, debugging, a
|
||||
- **(2022)** [grafana: How we use the Grafana GitHub plugin to track outstanding pull requests](https://grafana.com/blog/how-we-use-the-grafana-github-plugin-to-track-outstanding-pull-requests) <span class='md-tag md-tag--secondary'>[CASE STUDY]</span> <span class='md-tag md-tag--info'>[COMMUNITY-TOOL]</span> — Detailed technical guide on configuring Grafana dashboards with GitHub plugins. Demonstrates building engineering performance visualizations to track commit frequencies, PR lifetimes, and team review velocities.
|
||||
#### VS Code Extensions
|
||||
|
||||
- **(2025)** [Visual Studio Code (Git Extensions)](https://nubenetes.com/visual-studio/) <span class='md-tag md-tag--info'>[COMMUNITY-TOOL]</span> — A guide to utilizing visual Git extensions inside VS Code. Helps developers manage commit sequences, visualize branch merges, and resolve conflicts within a unified IDE workspace.
|
||||
- **(2025)** [Visual Studio Code (Git Extensions)](https://nubenetes.com/visual-studio) <span class='md-tag md-tag--info'>[COMMUNITY-TOOL]</span> — A guide to utilizing visual Git extensions inside VS Code. Helps developers manage commit sequences, visualize branch merges, and resolve conflicts within a unified IDE workspace.
|
||||
### Documentation (2)
|
||||
|
||||
#### Markup Languages
|
||||
|
||||
+2
-2
@@ -289,10 +289,10 @@
|
||||
- **(2021)** [ibm.com: Enable GitOps](https://www.ibm.com/garage) <span class='md-tag md-tag--warning'>[N/A CONTENT]</span> <span class='md-tag md-tag--secondary'>[GUIDE]</span> <span class='md-tag md-tag--critical'>[LEGACY]</span> — An enterprise change-management guide from IBM Garage focusing on GitOps adoption. Details organizational processes, environment categorization, and verification configurations required to transition legacy pipelines into declarative GitOps models.
|
||||
#### FluxCD
|
||||
|
||||
- **(2025)** [Flux. The GitOps operator for Kubernetes](https://nubenetes.com/flux/) <span class='md-tag md-tag--warning'>[GO CONTENT]</span> <span class='md-tag md-tag--critical'>[ADVANCED LEVEL]</span> <span class='md-tag md-tag--info'>[COMMUNITY-TOOL]</span> — The main technical documentation and resources for Flux, the CNCF-graduated continuous delivery tool for Kubernetes. Analyzes multi-tenancy configurations, automated image update policies, and source controller optimizations that make Flux a core component of modern GitOps workflows.
|
||||
- **(2025)** [Flux. The GitOps operator for Kubernetes](https://nubenetes.com/flux) <span class='md-tag md-tag--warning'>[GO CONTENT]</span> <span class='md-tag md-tag--critical'>[ADVANCED LEVEL]</span> <span class='md-tag md-tag--info'>[COMMUNITY-TOOL]</span> — The main technical documentation and resources for Flux, the CNCF-graduated continuous delivery tool for Kubernetes. Analyzes multi-tenancy configurations, automated image update policies, and source controller optimizations that make Flux a core component of modern GitOps workflows.
|
||||
#### Kustomize Manifests
|
||||
|
||||
- **(2025)** [Kustomize - Template-Free Kubernetes Configuration Customization](https://nubenetes.com/kustomize/) <span class='md-tag md-tag--warning'>[GO CONTENT]</span> <span class='md-tag md-tag--info'>[COMMUNITY-TOOL]</span> — Technical reference for Kustomize, the template-free engine used to manage Kubernetes configurations. Details declarative base and overlay architectures, allowing developers to manage configurations for different environments (dev, staging, prod) without using complex Helm template structures.
|
||||
- **(2025)** [Kustomize - Template-Free Kubernetes Configuration Customization](https://nubenetes.com/kustomize) <span class='md-tag md-tag--warning'>[GO CONTENT]</span> <span class='md-tag md-tag--info'>[COMMUNITY-TOOL]</span> — Technical reference for Kustomize, the template-free engine used to manage Kubernetes configurations. Details declarative base and overlay architectures, allowing developers to manage configurations for different environments (dev, staging, prod) without using complex Helm template structures.
|
||||
## Cloud Infrastructure
|
||||
|
||||
### Infrastructure as Code (1)
|
||||
|
||||
+12
-5
@@ -1,4 +1,4 @@
|
||||
# Nubenetes Elite Portal (V2) | Awesome Kubernetes & Cloud [](https://github.com/sindresorhus/awesome)
|
||||
# Nubenetes Elite Portal (V2) | Awesome Kubernetes and Cloud [](https://github.com/sindresorhus/awesome)
|
||||
|
||||
!!! tip "Nubenetes V2 Elite Portal: AI-Curated & High-Density"
|
||||
You are browsing the AI-Curated V2 Elite Edition of Nubenetes. Looking for the complete historical archive? Explore the [**V1 Historical Archive**](/v1/).
|
||||
@@ -39,6 +39,13 @@
|
||||
<div class="hero-badge-subtitle">Agentic Ecosystem</div>
|
||||
</div>
|
||||
</a>
|
||||
<a href="./tech-digest/" style="text-decoration: none; color: inherit; display: block;">
|
||||
<div class="hero-badge-card hero-badge-card--amber">
|
||||
<div class="hero-badge-icon">📊</div>
|
||||
<div class="hero-badge-title">Intelligence Digest</div>
|
||||
<div class="hero-badge-subtitle">Top picks · 3/6/12 months</div>
|
||||
</div>
|
||||
</a>
|
||||
<a href="./videos/" style="text-decoration: none; color: inherit; display: block;">
|
||||
<div class="hero-badge-card hero-badge-card--pink">
|
||||
<img src="/images/video_hub_logo.png" alt="Agentic Video Hub"/>
|
||||
@@ -153,7 +160,7 @@
|
||||
- **[Monitoring](./monitoring.md)**
|
||||
- **[Other Awesome Lists](./other-awesome-lists.md)**
|
||||
- **[Prometheus](./prometheus.md)**
|
||||
### Platform & Site Reliability
|
||||
### Platform and Site Reliability
|
||||
- **[Chaos Engineering](./chaos-engineering.md)**
|
||||
- **[Developerportals](./developerportals.md)**
|
||||
- **[DevOps](./devops.md)**
|
||||
@@ -209,7 +216,7 @@
|
||||
- **[Private Cloud Solutions](./private-cloud-solutions.md)**
|
||||
- **[Public Cloud Solutions](./public-cloud-solutions.md)**
|
||||
- **[Scaleway](./scaleway.md)**
|
||||
### Networking & Service Mesh
|
||||
### Networking and Service Mesh
|
||||
- **[Caching](./caching.md)**
|
||||
- **[Cloudflare](./cloudflare.md)**
|
||||
- **[Istio](./istio.md)**
|
||||
@@ -239,7 +246,7 @@
|
||||
- **[Openshift](./openshift.md)**
|
||||
- **[Rancher](./rancher.md)**
|
||||
- **[Serverless](./serverless.md)**
|
||||
### Data & Advanced Analytics
|
||||
### Data and Advanced Analytics
|
||||
- **[Crunchydata](./crunchydata.md)**
|
||||
- **[Databases](./databases.md)**
|
||||
- **[Message Queue](./message-queue.md)**
|
||||
@@ -284,7 +291,7 @@
|
||||
- **[Visual Studio](./visual-studio.md)**
|
||||
- **[Web3](./web3.md)**
|
||||
- **[Xamarin](./xamarin.md)**
|
||||
### Career & Industry
|
||||
### Career and Industry
|
||||
- **[Appointment Scheduling](./appointment-scheduling.md)**
|
||||
- **[Digital Money](./digital-money.md)**
|
||||
- **[Elearning](./elearning.md)**
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
# Nubenetes Industry and Geo Intelligence Digest
|
||||
|
||||
!!! tip "Nubenetes Intelligence Digest"
|
||||
AI-curated ranking of the most impactful resources, updated monthly.
|
||||
|
||||
=== "Last 3 Months"
|
||||
|
||||
|
||||
=== "Last 6 Months"
|
||||
|
||||
|
||||
=== "Last 12 Months"
|
||||
|
||||
|
||||
@@ -204,7 +204,7 @@
|
||||
|
||||
#### Tekton Pipelines
|
||||
|
||||
- **(2023)** [Tekton](https://nubenetes.com/tekton/) <span class='md-tag md-tag--critical'>[ADVANCED LEVEL]</span> <span class='md-tag md-tag--info'>[COMMUNITY-TOOL]</span> <span class='md-tag md-tag--secondary'>[GUIDE]</span> — Deep-dive review of Tekton, a Kubernetes-native open-source framework for building continuous integration and delivery (CI/CD) pipelines. It structures pipeline blocks using standard CRDs (Tasks, Pipelines, PipelineRuns), eliminating VM-based runner dependencies. Live validation establishes Tekton as the standard engine powering modern cloud-native container build environments like OpenShift Pipelines.
|
||||
- **(2023)** [Tekton](https://nubenetes.com/tekton) <span class='md-tag md-tag--critical'>[ADVANCED LEVEL]</span> <span class='md-tag md-tag--info'>[COMMUNITY-TOOL]</span> <span class='md-tag md-tag--secondary'>[GUIDE]</span> — Deep-dive review of Tekton, a Kubernetes-native open-source framework for building continuous integration and delivery (CI/CD) pipelines. It structures pipeline blocks using standard CRDs (Tasks, Pipelines, PipelineRuns), eliminating VM-based runner dependencies. Live validation establishes Tekton as the standard engine powering modern cloud-native container build environments like OpenShift Pipelines.
|
||||
#### Tool Comparison
|
||||
|
||||
- **(2021)** [k21academy.com: Azure pipelines VS Jenkins](https://k21academy.com/azure-cloud/azure-pipelines-vs-jenkins) <span class='md-tag md-tag--info'>[COMMUNITY-TOOL]</span> — A comprehensive comparative analysis contrasting Jenkins (self-hosted, highly extensible, plugin-heavy) against Azure Pipelines (managed, cloud-native SaaS, deep Azure integration). Highlights differences in maintenance overhead, security configurations, build agent execution, and enterprise scaling architectures. Essential reading for platform teams deciding on their continuous delivery stack.
|
||||
|
||||
@@ -149,7 +149,7 @@
|
||||
|
||||
#### Gradle Reference
|
||||
|
||||
- **(2026)** [==Gradle Cheat Sheets==](https://nubenetes.com/cheatsheets/) <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> <span class='md-tag md-tag--primary'>[DOCUMENTATION]</span> 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> — High-density command syntax cheatsheet for Gradle, highlighting Kotlin/Groovy DSL setups, caching options, task graphs management, and daemon management to significantly improve build execution times.
|
||||
- **(2026)** [==Gradle Cheat Sheets==](https://nubenetes.com/cheatsheets) <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> <span class='md-tag md-tag--primary'>[DOCUMENTATION]</span> 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> — High-density command syntax cheatsheet for Gradle, highlighting Kotlin/Groovy DSL setups, caching options, task graphs management, and daemon management to significantly improve build execution times.
|
||||
## Developer Experience
|
||||
|
||||
### Shell
|
||||
@@ -168,7 +168,7 @@
|
||||
|
||||
#### Kubectl Plugins
|
||||
|
||||
- **(2025)** [Kubectl plugins and tools](https://nubenetes.com/kubernetes/#kubectl-plugins) <span class='md-tag md-tag--warning'>[GO CONTENT]</span> <span class='md-tag md-tag--info'>[COMMUNITY-TOOL]</span> — This reference compilation highlights external tools and kubectl extensions managed via Krew. It details how third-party plugins (like `neat`, `kns`, or security-focused extensions) expand basic kubectl operational debugging and cluster-inspection capabilities.
|
||||
- **(2025)** [Kubectl plugins and tools](https://nubenetes.com/kubernetes) <span class='md-tag md-tag--warning'>[GO CONTENT]</span> <span class='md-tag md-tag--info'>[COMMUNITY-TOOL]</span> — This reference compilation highlights external tools and kubectl extensions managed via Krew. It details how third-party plugins (like `neat`, `kns`, or security-focused extensions) expand basic kubectl operational debugging and cluster-inspection capabilities.
|
||||
#### Productivity (1)
|
||||
|
||||
- **(2021)** [Kubernetes productivity tips and tricks 🌟](https://www.theodo.com/en-fr/blog) <span class='md-tag md-tag--info'>[COMMUNITY-TOOL]</span> — A practitioner's guide to enhancing CLI-based Kubernetes productivity. It explores advanced setups such as custom shell autocompletion, kubectx/kubens utilities, smart aliases, and log-tailing helpers designed to reduce cognitive overhead during real-time incident responses.
|
||||
@@ -242,7 +242,7 @@
|
||||
|
||||
#### Helm Overview
|
||||
|
||||
- **(2026)** [==Helm==](https://nubenetes.com/helm/) <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> <span class='md-tag md-tag--primary'>[DOCUMENTATION]</span> 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> — Deep-dive architecture portal on Helm, the package manager for Kubernetes. Focuses on structuring dry templates, lifecycle hooks, chart dependencies, release versioning, and secure variables management inside GitOps pipelines.
|
||||
- **(2026)** [==Helm==](https://nubenetes.com/helm) <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> <span class='md-tag md-tag--primary'>[DOCUMENTATION]</span> 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> — Deep-dive architecture portal on Helm, the package manager for Kubernetes. Focuses on structuring dry templates, lifecycle hooks, chart dependencies, release versioning, and secure variables management inside GitOps pipelines.
|
||||
## Storage and Data
|
||||
|
||||
### Performance Benchmarking
|
||||
|
||||
@@ -253,12 +253,12 @@
|
||||
|
||||
#### Multi-Cluster
|
||||
|
||||
- **(2026)** [Rancher: Enterprise management for Kubernetes](https://nubenetes.com/rancher/) <span class='md-tag md-tag--warning'>[GO CONTENT]</span> <span class='md-tag md-tag--critical'>[ADVANCED LEVEL]</span> <span class='md-tag md-tag--info'>[COMMUNITY-TOOL]</span> — Rancher is a unified platform for managing multi-cluster, heterogeneous Kubernetes deployments across diverse cloud providers and bare metal hosts. It simplifies operational management by providing centralized authentication, unified RBAC access, structured audit logs, and simplified Helm catalog deployments.
|
||||
- **(2026)** [Rancher: Enterprise management for Kubernetes](https://nubenetes.com/rancher) <span class='md-tag md-tag--warning'>[GO CONTENT]</span> <span class='md-tag md-tag--critical'>[ADVANCED LEVEL]</span> <span class='md-tag md-tag--info'>[COMMUNITY-TOOL]</span> — Rancher is a unified platform for managing multi-cluster, heterogeneous Kubernetes deployments across diverse cloud providers and bare metal hosts. It simplifies operational management by providing centralized authentication, unified RBAC access, structured audit logs, and simplified Helm catalog deployments.
|
||||
### Red Hat OpenShift
|
||||
|
||||
#### Enterprise (1)
|
||||
|
||||
- **(2026)** [Openshift Container Platform](https://nubenetes.com/openshift/) <span class='md-tag md-tag--warning'>[GO CONTENT]</span> <span class='md-tag md-tag--critical'>[ADVANCED LEVEL]</span> <span class='md-tag md-tag--info'>[COMMUNITY-TOOL]</span> — Red Hat OpenShift is a premier enterprise-grade hybrid cloud Kubernetes application platform. It adds out-of-the-box developer tooling, integrated security standards, cluster virtualization, internal registry configurations, and Operator-based life cycle management directly over raw Kubernetes.
|
||||
- **(2026)** [Openshift Container Platform](https://nubenetes.com/openshift) <span class='md-tag md-tag--warning'>[GO CONTENT]</span> <span class='md-tag md-tag--critical'>[ADVANCED LEVEL]</span> <span class='md-tag md-tag--info'>[COMMUNITY-TOOL]</span> — Red Hat OpenShift is a premier enterprise-grade hybrid cloud Kubernetes application platform. It adds out-of-the-box developer tooling, integrated security standards, cluster virtualization, internal registry configurations, and Operator-based life cycle management directly over raw Kubernetes.
|
||||
## Networking
|
||||
|
||||
### CNI Plugins
|
||||
|
||||
@@ -1321,7 +1321,7 @@
|
||||
|
||||
#### Overview
|
||||
|
||||
- **(2026)** [==Serverless Architectures==](https://nubenetes.com/serverless/) <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> <span class='md-tag md-tag--primary'>[DOCUMENTATION]</span> 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> — In-depth analysis exploring execution concepts, billing architectures, scalability curves, and performance tradeoffs inherent in Serverless patterns. Details key differences between FaaS, cloud-managed runtimes, and self-hosted Knative workloads.
|
||||
- **(2026)** [==Serverless Architectures==](https://nubenetes.com/serverless) <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> <span class='md-tag md-tag--primary'>[DOCUMENTATION]</span> 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> — In-depth analysis exploring execution concepts, billing architectures, scalability curves, and performance tradeoffs inherent in Serverless patterns. Details key differences between FaaS, cloud-managed runtimes, and self-hosted Knative workloads.
|
||||
## Cloud Infrastructure
|
||||
|
||||
### Kubernetes (1)
|
||||
@@ -1585,7 +1585,7 @@
|
||||
|
||||
#### API Client Libraries
|
||||
|
||||
- **(2026)** [==Client Libraries for Kubernetes==](https://nubenetes.com/kubernetes-client-libraries/) <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> <span class='md-tag md-tag--critical'>[ADVANCED LEVEL]</span> <span class='md-tag md-tag--primary'>[DOCUMENTATION]</span> 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> — Complete directory of supported Kubernetes API client libraries (Python, Go, Java, JavaScript, etc.). Details patterns for programmatic service discovery, controller building, and custom automation direct from application runtime code.
|
||||
- **(2026)** [==Client Libraries for Kubernetes==](https://nubenetes.com/kubernetes-client-libraries) <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> <span class='md-tag md-tag--critical'>[ADVANCED LEVEL]</span> <span class='md-tag md-tag--primary'>[DOCUMENTATION]</span> 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> — Complete directory of supported Kubernetes API client libraries (Python, Go, Java, JavaScript, etc.). Details patterns for programmatic service discovery, controller building, and custom automation direct from application runtime code.
|
||||
### Kubernetes Operations
|
||||
|
||||
#### Pod Restarts
|
||||
@@ -1595,7 +1595,7 @@
|
||||
|
||||
#### Volumes Overview
|
||||
|
||||
- **(2026)** [==Kubernetes Storage - Volumes==](https://nubenetes.com/kubernetes-storage/#kubernetes-volumes) <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> <span class='md-tag md-tag--critical'>[ADVANCED LEVEL]</span> <span class='md-tag md-tag--primary'>[DOCUMENTATION]</span> 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> — Detailed catalog explaining stateful execution patterns inside Kubernetes. Focuses on lifecycle dynamics of Ephemeral, Persistent (PV), and PersistentVolumeClaims (PVC), alongside container storage interfaces (CSI) used to integrate modern storage backends.
|
||||
- **(2026)** [==Kubernetes Storage - Volumes==](https://nubenetes.com/kubernetes-storage) <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> <span class='md-tag md-tag--critical'>[ADVANCED LEVEL]</span> <span class='md-tag md-tag--primary'>[DOCUMENTATION]</span> 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> — Detailed catalog explaining stateful execution patterns inside Kubernetes. Focuses on lifecycle dynamics of Ephemeral, Persistent (PV), and PersistentVolumeClaims (PVC), alongside container storage interfaces (CSI) used to integrate modern storage backends.
|
||||
### Virtualization VS Containers
|
||||
|
||||
#### Kubernetes Concept
|
||||
@@ -2145,7 +2145,7 @@
|
||||
|
||||
#### Overview (1)
|
||||
|
||||
- **(2026)** [==Crossplane==](https://nubenetes.com/crossplane/) <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> <span class='md-tag md-tag--critical'>[ADVANCED LEVEL]</span> <span class='md-tag md-tag--primary'>[DOCUMENTATION]</span> 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> — Comprehensive review of Crossplane, a CNCF control-plane framework transforming Kubernetes clusters into universal infrastructure schedulers. Permits declarative definition of cloud resources (RDS, S3, VMs) alongside native Kubernetes schemas.
|
||||
- **(2026)** [==Crossplane==](https://nubenetes.com/crossplane) <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> <span class='md-tag md-tag--critical'>[ADVANCED LEVEL]</span> <span class='md-tag md-tag--primary'>[DOCUMENTATION]</span> 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> — Comprehensive review of Crossplane, a CNCF control-plane framework transforming Kubernetes clusters into universal infrastructure schedulers. Permits declarative definition of cloud resources (RDS, S3, VMs) alongside native Kubernetes schemas.
|
||||
## Introductory
|
||||
|
||||
### Concepts (1)
|
||||
|
||||
@@ -747,7 +747,7 @@
|
||||
|
||||
#### Metrics Collection
|
||||
|
||||
- **(2024)** [Prometheus](https://nubenetes.com/prometheus/) <span class='md-tag md-tag--warning'>[GO CONTENT]</span> <span class='md-tag md-tag--critical'>[ADVANCED LEVEL]</span> <span class='md-tag md-tag--info'>[COMMUNITY-TOOL]</span> — Prometheus is an open-source systems monitoring and alerting toolkit originally built at SoundCloud. It utilizes a pull-based metrics collection model over HTTP, powered by a highly efficient dimensional data model (TSDB) with PromQL. Essential for Kubernetes cloud-native environments, it excels in dynamic service discovery and real-time operational visibility.
|
||||
- **(2024)** [Prometheus](https://nubenetes.com/prometheus) <span class='md-tag md-tag--warning'>[GO CONTENT]</span> <span class='md-tag md-tag--critical'>[ADVANCED LEVEL]</span> <span class='md-tag md-tag--info'>[COMMUNITY-TOOL]</span> — Prometheus is an open-source systems monitoring and alerting toolkit originally built at SoundCloud. It utilizes a pull-based metrics collection model over HTTP, powered by a highly efficient dimensional data model (TSDB) with PromQL. Essential for Kubernetes cloud-native environments, it excels in dynamic service discovery and real-time operational visibility.
|
||||
### OpenTelemetry (1)
|
||||
|
||||
#### Collector Infrastructure
|
||||
@@ -789,7 +789,7 @@
|
||||
|
||||
#### Dashboards
|
||||
|
||||
- **(2024)** [Grafana](https://nubenetes.com/grafana/) <span class='md-tag md-tag--warning'>[GO/TYPESCRIPT CONTENT]</span> <span class='md-tag md-tag--info'>[COMMUNITY-TOOL]</span> — Grafana is the industry-standard multi-platform open-source analytics and interactive visualization web application. It supports query, visualization, alerting, and analysis of metrics, logs, and traces from diverse backends (Prometheus, Elasticsearch, Loki, Jaeger). Its pluggable architecture allows organizations to build unified operational dashboards across heterogeneous data layers.
|
||||
- **(2024)** [Grafana](https://nubenetes.com/grafana) <span class='md-tag md-tag--warning'>[GO/TYPESCRIPT CONTENT]</span> <span class='md-tag md-tag--info'>[COMMUNITY-TOOL]</span> — Grafana is the industry-standard multi-platform open-source analytics and interactive visualization web application. It supports query, visualization, alerting, and analysis of metrics, logs, and traces from diverse backends (Prometheus, Elasticsearch, Loki, Jaeger). Its pluggable architecture allows organizations to build unified operational dashboards across heterogeneous data layers.
|
||||
## Observability and Monitoring
|
||||
|
||||
### Application Performance Monitoring (1)
|
||||
|
||||
+2
-2
@@ -343,7 +343,7 @@
|
||||
|
||||
#### Red Hat Quay
|
||||
|
||||
- **(2022)** [OpenShift Registry & Quay](https://nubenetes.com/registries/) <span class='md-tag md-tag--critical'>[ADVANCED LEVEL]</span> <span class='md-tag md-tag--info'>[COMMUNITY-TOOL]</span> <span class='md-tag md-tag--secondary'>[GUIDE]</span> — Comprehensive analysis of Red Hat Quay and the integrated OpenShift Container Registry. Details secure image storage, vulnerability scanning with Clair, and geo-replication capabilities. It highlights Quay's enterprise-grade multi-tenancy and RBAC controls, which ensure secure artifact promotion within high-performance microservices pipelines.
|
||||
- **(2022)** [OpenShift Registry & Quay](https://nubenetes.com/registries) <span class='md-tag md-tag--critical'>[ADVANCED LEVEL]</span> <span class='md-tag md-tag--info'>[COMMUNITY-TOOL]</span> <span class='md-tag md-tag--secondary'>[GUIDE]</span> — Comprehensive analysis of Red Hat Quay and the integrated OpenShift Container Registry. Details secure image storage, vulnerability scanning with Clair, and geo-replication capabilities. It highlights Quay's enterprise-grade multi-tenancy and RBAC controls, which ensure secure artifact promotion within high-performance microservices pipelines.
|
||||
## CI-CD
|
||||
|
||||
### App Migration (1)
|
||||
@@ -431,7 +431,7 @@
|
||||
- **(2019)** [cloudowski.com: Openshift ImageStreams](https://cloudowski.com/articles/why-managing-container-images-on-openshift-is-better-than-on-kubernetes) <span class='md-tag md-tag--warning'>[N/A CONTENT]</span> <span class='md-tag md-tag--critical'>[ADVANCED LEVEL]</span> <span class='md-tag md-tag--info'>[COMMUNITY-TOOL]</span> — This analysis compares OpenShift ImageStreams with vanilla Kubernetes image pulling mechanisms. It explains how ImageStreams provide abstraction layers over container registries, enabling automatic redeployments upon detection of updated remote images (triggers). By decoupling pods from concrete repository URLs, it automates deployment lifecycle workflows for platform engineering teams.
|
||||
## Container Platforms
|
||||
|
||||
### OKD OpenShift
|
||||
### OKD OpenShift
|
||||
|
||||
#### Cluster Bootstrap
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@
|
||||
|
||||
#### General Reference
|
||||
|
||||
- [uncontained.io/articles/openshift-ha-installation](https://uncontained.io/articles/openshift-ha-installation/) <span class='md-tag md-tag--info'>[COMMUNITY-TOOL]</span> — A curated technical resource and architectural guide covering uncontained.io in the Kubernetes Tools ecosystem.
|
||||
- [uncontained.io/articles/openshift-ha-installation](https://uncontained.io/articles/openshift-ha-installation) <span class='md-tag md-tag--info'>[COMMUNITY-TOOL]</span> — A curated technical resource and architectural guide covering uncontained.io in the Kubernetes Tools ecosystem.
|
||||
- [aroworkshop.io 🌟](https://aroworkshop.io) <span class='md-tag md-tag--info'>[COMMUNITY-TOOL]</span> — A curated technical resource and architectural guide covering aroworkshop.io in the Kubernetes Tools ecosystem.
|
||||
- [O'Reilly Free Book: **Openshift for developers**](https://www.redhat.com/en/technologies/cloud-computing/openshift/for-developers) <span class='md-tag md-tag--info'>[COMMUNITY-TOOL]</span> — A curated technical resource and architectural guide covering www.redhat.com in the Kubernetes Tools ecosystem.
|
||||
- [NetworkPolicies and Microsegmentation](https://www.redhat.com/en/blog/channel/hybrid-cloud-infrastructure/networkpolicies-and-microsegmentation) <span class='md-tag md-tag--info'>[COMMUNITY-TOOL]</span> — A curated technical resource and architectural guide covering www.redhat.com in the Kubernetes Tools ecosystem.
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
|
||||
#### Java Ecosystem
|
||||
|
||||
- **(2025)** [Maven](https://nubenetes.com/maven-gradle/) <span class='md-tag md-tag--warning'>[JAVA CONTENT]</span> <span class='md-tag md-tag--info'>[COMMUNITY-TOOL]</span> — Comparative architectural overview of Maven and Gradle. Outlines declarative XML configurations versus programmatic Groovy/Kotlin Gradle DSL scripts, analyzing cache efficiency, parallel build runtimes, and enterprise dependency-resolution policies.
|
||||
- **(2025)** [Maven](https://nubenetes.com/maven-gradle) <span class='md-tag md-tag--warning'>[JAVA CONTENT]</span> <span class='md-tag md-tag--info'>[COMMUNITY-TOOL]</span> — Comparative architectural overview of Maven and Gradle. Outlines declarative XML configurations versus programmatic Groovy/Kotlin Gradle DSL scripts, analyzing cache efficiency, parallel build runtimes, and enterprise dependency-resolution policies.
|
||||
|
||||
---
|
||||
💡 **Explore Related:** [DevOps](./devops.md) | [Developerportals](./developerportals.md) | [SRE](./sre.md)
|
||||
|
||||
@@ -240,7 +240,7 @@
|
||||
|
||||
#### Cloud Governance
|
||||
|
||||
- **(2026)** [**Azure Policy**](https://nubenetes.com/azure/) <span class='md-tag md-tag--critical'>[ADVANCED LEVEL]</span> <span class='md-tag md-tag--primary'>[DOCUMENTATION]</span> 🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[ENTERPRISE-STABLE]</span> — Specialized gateway and reference documentation for enforcing structural compliance, resource auditing, and governance across Azure resource environments. Explains custom definition policies, policy initiatives, and automated remediation workflows. Critical reference for maintaining operational guardrails in enterprise cloud architectures.
|
||||
- **(2026)** [**Azure Policy**](https://nubenetes.com/azure) <span class='md-tag md-tag--critical'>[ADVANCED LEVEL]</span> <span class='md-tag md-tag--primary'>[DOCUMENTATION]</span> 🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[ENTERPRISE-STABLE]</span> — Specialized gateway and reference documentation for enforcing structural compliance, resource auditing, and governance across Azure resource environments. Explains custom definition policies, policy initiatives, and automated remediation workflows. Critical reference for maintaining operational guardrails in enterprise cloud architectures.
|
||||
## Security
|
||||
|
||||
### Policy Enforcement
|
||||
|
||||
@@ -134,7 +134,7 @@
|
||||
|
||||
#### Istio
|
||||
|
||||
- **(2026)** [Istio](https://nubenetes.com/istio/) <span class='md-tag md-tag--critical'>[ADVANCED LEVEL]</span> <span class='md-tag md-tag--info'>[COMMUNITY-TOOL]</span> — A comprehensive entry point to Istio architecture, the enterprise-grade service mesh. Details how engineers manage traffic routes, secure service-to-service communication with mutual TLS, and gain deep tracing observability across distributed Kubernetes deployments.
|
||||
- **(2026)** [Istio](https://nubenetes.com/istio) <span class='md-tag md-tag--critical'>[ADVANCED LEVEL]</span> <span class='md-tag md-tag--info'>[COMMUNITY-TOOL]</span> — A comprehensive entry point to Istio architecture, the enterprise-grade service mesh. Details how engineers manage traffic routes, secure service-to-service communication with mutual TLS, and gain deep tracing observability across distributed Kubernetes deployments.
|
||||
## Cloud Native Infrastructure
|
||||
|
||||
### API Management
|
||||
|
||||
+20
-20
@@ -174,7 +174,7 @@
|
||||
|
||||
- **(2026)** [==github.com/backstage/backstage==](https://github.com/backstage/backstage) 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> <span class='md-tag md-tag--warning'>[TYPESCRIPT CONTENT]</span> — *Go to [Section](./devops.md)*
|
||||
- **(2026)** [==How-To Secure A Linux Server==](https://github.com/imthenachoman/How-To-Secure-A-Linux-Server) 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> <span class='md-tag md-tag--warning'>[SHELL CONTENT]</span> — *Go to [Section](./devops.md)*
|
||||
- **(2026)** [==IaC Infrastructure as Code==](https://nubenetes.com/iac/) 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> — *Go to [Section](./devops.md)*
|
||||
- **(2026)** [==IaC Infrastructure as Code==](https://nubenetes.com/iac) 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> — *Go to [Section](./devops.md)*
|
||||
- **(2026)** [==bregman-arie/devops-exercises 🌟==](https://github.com/bregman-arie/devops-exercises) 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> <span class='md-tag md-tag--secondary'>[GUIDE]</span> <span class='md-tag md-tag--warning'>[PYTHON/YAML CONTENT]</span> — *Go to [Section](./demos.md)*
|
||||
- **(2026)** [==Developer Sandbox==](https://developers.redhat.com/developer-sandbox) 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> — *Go to [Section](./demos.md)*
|
||||
- **(2026)** [==github: Spring Cloud Kubernetes 🌟==](https://github.com/spring-cloud/spring-cloud-kubernetes) 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> <span class='md-tag md-tag--warning'>[JAVA CONTENT]</span> — *Go to [Section](./demos.md)*
|
||||
@@ -202,15 +202,15 @@
|
||||
- **(2026)** [==github.com/sharadbhat/KubernetesPatterns: YAML and Golang implementations' of common Kubernetes patterns==](https://github.com/sharadbhat/KubernetesPatterns) 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> <span class='md-tag md-tag--warning'>[GO CONTENT]</span> — *Go to [Section](./kubernetes.md)*
|
||||
- **(2026)** [==kustomize==](https://github.com/kubernetes-sigs/kustomize) 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> <span class='md-tag md-tag--warning'>[GO CONTENT]</span> — *Go to [Section](./kubernetes.md)*
|
||||
- **(2026)** [==jsonnet data templating language==](https://github.com/google/jsonnet/tree/master/case_studies/kubernetes) 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> <span class='md-tag md-tag--warning'>[JSONNET CONTENT]</span> — *Go to [Section](./kubernetes.md)*
|
||||
- **(2026)** [==Kubernetes Storage - Volumes==](https://nubenetes.com/kubernetes-storage/#kubernetes-volumes) 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> — *Go to [Section](./kubernetes.md)*
|
||||
- **(2026)** [==Client Libraries for Kubernetes==](https://nubenetes.com/kubernetes-client-libraries/) 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> — *Go to [Section](./kubernetes.md)*
|
||||
- **(2026)** [==Kubernetes Storage - Volumes==](https://nubenetes.com/kubernetes-storage) 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> — *Go to [Section](./kubernetes.md)*
|
||||
- **(2026)** [==Client Libraries for Kubernetes==](https://nubenetes.com/kubernetes-client-libraries) 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> — *Go to [Section](./kubernetes.md)*
|
||||
- **(2026)** [==kubectl-trace==](https://github.com/iovisor/kubectl-trace) 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> <span class='md-tag md-tag--warning'>[GO CONTENT]</span> — *Go to [Section](./kubernetes.md)*
|
||||
- **(2026)** [==kubespy==](https://github.com/huazhihao/kubespy) 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> <span class='md-tag md-tag--warning'>[GO CONTENT]</span> — *Go to [Section](./kubernetes.md)*
|
||||
- **(2026)** [==kubectl netshoot==](https://github.com/nilic/kubectl-netshoot) 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> <span class='md-tag md-tag--warning'>[GO CONTENT]</span> — *Go to [Section](./kubernetes.md)*
|
||||
- **(2026)** [==kubernetes.io: Kubernetes API==](https://kubernetes.io/docs/reference/kubernetes-api) 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> — *Go to [Section](./kubernetes.md)*
|
||||
- **(2026)** [==Crossplane==](https://nubenetes.com/crossplane/) 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> — *Go to [Section](./kubernetes.md)*
|
||||
- **(2026)** [==Crossplane==](https://nubenetes.com/crossplane) 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> — *Go to [Section](./kubernetes.md)*
|
||||
- **(2026)** [==davidB/kubectl-view-allocations==](https://github.com/davidB/kubectl-view-allocations) 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> <span class='md-tag md-tag--warning'>[RUST CONTENT]</span> — *Go to [Section](./kubernetes.md)*
|
||||
- **(2026)** [==Serverless Architectures==](https://nubenetes.com/serverless/) 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> — *Go to [Section](./kubernetes.md)*
|
||||
- **(2026)** [==Serverless Architectures==](https://nubenetes.com/serverless) 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> — *Go to [Section](./kubernetes.md)*
|
||||
- **(2026)** [==Pulumi: Infrastructure as Code in Any Programming Language==](https://github.com/pulumi/pulumi) 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> <span class='md-tag md-tag--warning'>[GO CONTENT]</span> — *Go to [Section](./iac.md)*
|
||||
- **(2026)** [==Awesome Sysadmin==](https://github.com/awesome-foss/awesome-sysadmin) 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> <span class='md-tag md-tag--warning'>[MARKDOWN CONTENT]</span> — *Go to [Section](./devops-tools.md)*
|
||||
- **(2026)** [==github.com/hashicorp/hcl: HCL==](https://github.com/hashicorp/hcl) 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> <span class='md-tag md-tag--warning'>[GO CONTENT]</span> — *Go to [Section](./terraform.md)*
|
||||
@@ -287,8 +287,8 @@
|
||||
|
||||
- **(2026)** [==**GitHub build-push-action**==](https://github.com/docker/build-push-action) 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> <span class='md-tag md-tag--success'>[ENTERPRISE-STABLE]</span> <span class='md-tag md-tag--warning'>[TYPESCRIPT CONTENT]</span> — *Go to [Section](./docker.md)*
|
||||
- **(2026)** [**googlecloudcheatsheet.withgoogle.com: Google Cloud Developer cheat sheet**](https://cloud.google.com/products) 🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[ENTERPRISE-STABLE]</span> <span class='md-tag md-tag--warning'>[HTML CONTENT]</span> — *Go to [Section](./GoogleCloudPlatform.md)*
|
||||
- **(2026)** [**DevOps Tools**](https://nubenetes.com/devops-tools/) 🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[ENTERPRISE-STABLE]</span> <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> — *Go to [Section](./devops.md)*
|
||||
- **(2026)** [**NoOps**](https://nubenetes.com/noops/) 🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[ENTERPRISE-STABLE]</span> <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> — *Go to [Section](./devops.md)*
|
||||
- **(2026)** [**DevOps Tools**](https://nubenetes.com/devops-tools) 🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[ENTERPRISE-STABLE]</span> <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> — *Go to [Section](./devops.md)*
|
||||
- **(2026)** [**NoOps**](https://nubenetes.com/noops) 🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[ENTERPRISE-STABLE]</span> <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> — *Go to [Section](./devops.md)*
|
||||
- **(2026)** [**redhatgov.io**](https://redhatgov.io) 🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[ENTERPRISE-STABLE]</span> — *Go to [Section](./demos.md)*
|
||||
- **(2026)** [**Terraform & OpenTofu Skill for AI Agents**](https://github.com/antonbabenko/terraform-skill) 🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[ENTERPRISE-STABLE]</span> <span class='md-tag md-tag--warning'>[TYPESCRIPT CONTENT]</span> — *Go to [Section](./cicd.md)*
|
||||
- **(2026)** [**Nelm: A Helm Alternative for Kubernetes Deployments**](https://github.com/werf/nelm) 🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[ENTERPRISE-STABLE]</span> <span class='md-tag md-tag--warning'>[GO CONTENT]</span> — *Go to [Section](./gitops.md)*
|
||||
@@ -932,23 +932,23 @@
|
||||
<details markdown="1">
|
||||
<summary>Click to view top 100 of 212 resources under Spanish Content</summary>
|
||||
|
||||
- **(2026)** [==IaC Infrastructure as Code==](https://nubenetes.com/iac/) 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> — *Go to [Section](./devops.md)*
|
||||
- **(2026)** [==Kubernetes Storage - Volumes==](https://nubenetes.com/kubernetes-storage/#kubernetes-volumes) 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> — *Go to [Section](./kubernetes.md)*
|
||||
- **(2026)** [==Client Libraries for Kubernetes==](https://nubenetes.com/kubernetes-client-libraries/) 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> — *Go to [Section](./kubernetes.md)*
|
||||
- **(2026)** [==Crossplane==](https://nubenetes.com/crossplane/) 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> — *Go to [Section](./kubernetes.md)*
|
||||
- **(2026)** [==Serverless Architectures==](https://nubenetes.com/serverless/) 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> — *Go to [Section](./kubernetes.md)*
|
||||
- **(2026)** [==Gradle Cheat Sheets==](https://nubenetes.com/cheatsheets/) 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> — *Go to [Section](./kubectl-commands.md)*
|
||||
- **(2026)** [==Helm==](https://nubenetes.com/helm/) 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> — *Go to [Section](./kubectl-commands.md)*
|
||||
- **(2026)** [==IaC Infrastructure as Code==](https://nubenetes.com/iac) 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> — *Go to [Section](./devops.md)*
|
||||
- **(2026)** [==Kubernetes Storage - Volumes==](https://nubenetes.com/kubernetes-storage) 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> — *Go to [Section](./kubernetes.md)*
|
||||
- **(2026)** [==Client Libraries for Kubernetes==](https://nubenetes.com/kubernetes-client-libraries) 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> — *Go to [Section](./kubernetes.md)*
|
||||
- **(2026)** [==Crossplane==](https://nubenetes.com/crossplane) 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> — *Go to [Section](./kubernetes.md)*
|
||||
- **(2026)** [==Serverless Architectures==](https://nubenetes.com/serverless) 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> — *Go to [Section](./kubernetes.md)*
|
||||
- **(2026)** [==Gradle Cheat Sheets==](https://nubenetes.com/cheatsheets) 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> — *Go to [Section](./kubectl-commands.md)*
|
||||
- **(2026)** [==Helm==](https://nubenetes.com/helm) 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> — *Go to [Section](./kubectl-commands.md)*
|
||||
- **(2026)** [==codely.tv==](https://codely.com/en) 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> — *Go to [Section](./elearning.md)*
|
||||
- **(2024)** [==Tabularis: Open Source Desktop Client for Modern Databases with AI and MCP' Integration==](https://github.com/TabularisDB/tabularis/blob/main/README.es.md) 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> — *Go to [Section](./databases.md)*
|
||||
- **(2024)** [==NodeJS Best Practices (Spanish Translation)==](https://github.com/goldbergyoni/nodebestpractices/blob/spanish-translation/README.spanish.md) 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> — *Go to [Section](./golang.md)*
|
||||
- **(2024)** [==genbeta.com: Hace 20 años, este correo de Jeff Bezos en Amazon cambió para siempre la forma en que programamos apps==](https://www.genbeta.com/desarrollo/hace-22-anos-este-correo-jeff-bezos-amazon-cambio-para-siempre-forma-que-programamos-apps) 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> — *Go to [Section](./api.md)*
|
||||
- **(2023)** [==mariocortes.net: La crisis de seniority==](https://www.mariocortes.net/la-crisis-de-seniority) 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> <span class='md-tag md-tag--warning'>[ES CONTENT]</span> — *Go to [Section](./project-management-methodology.md)*
|
||||
- **(2022)** [==estrategiadeproducto.com: La espiral de mierda==](https://www.estrategiadeproducto.com/p/evitar-caer-espiral-de-mierda) 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> <span class='md-tag md-tag--warning'>[ES CONTENT]</span> — *Go to [Section](./project-management-methodology.md)*
|
||||
- **(2026)** [**DevOps Tools**](https://nubenetes.com/devops-tools/) 🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[ENTERPRISE-STABLE]</span> <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> — *Go to [Section](./devops.md)*
|
||||
- **(2026)** [**NoOps**](https://nubenetes.com/noops/) 🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[ENTERPRISE-STABLE]</span> <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> — *Go to [Section](./devops.md)*
|
||||
- **(2026)** [**DevOps Tools**](https://nubenetes.com/devops-tools) 🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[ENTERPRISE-STABLE]</span> <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> — *Go to [Section](./devops.md)*
|
||||
- **(2026)** [**NoOps**](https://nubenetes.com/noops) 🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[ENTERPRISE-STABLE]</span> <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> — *Go to [Section](./devops.md)*
|
||||
- **(2026)** [**BBVA API Market**](https://www.bbvaapimarket.com/es) 🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[ENTERPRISE-STABLE]</span> <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> — *Go to [Section](./developerportals.md)*
|
||||
- **(2026)** [**Keptn**](https://nubenetes.com/keptn/) 🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[ENTERPRISE-STABLE]</span> <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> — *Go to [Section](./devops-tools.md)*
|
||||
- **(2026)** [**Keptn**](https://nubenetes.com/keptn) 🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[ENTERPRISE-STABLE]</span> <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> — *Go to [Section](./devops-tools.md)*
|
||||
- **(2024)** [**xataka.com: El Excel se ha usado en la Fórmula 1 hasta que se han dado cuenta que no es la mejor forma de controlar las 20.000 piezas del coche**](https://www.xataka.com/automovil/excel-se-ha-usado-formula-1-que-se-han-dado-cuenta-que-no-mejor-forma-controlar-20-000-piezas-coche) 🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[ENTERPRISE-STABLE]</span> <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> — *Go to [Section](./customer.md)*
|
||||
- **(2024)** [**redhat.com: OpenShift Backup and Recovery with Kasten K10**](https://www.redhat.com/es/blog) 🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[ENTERPRISE-STABLE]</span> <span class='md-tag md-tag--warning'>[SPANISH CONTENT]</span> — *Go to [Section](./ansible.md)*
|
||||
- **(2023)** [**businessinsider.es: Avanzar en la carrera profesional y conseguir ascensos dentro de la empresa será mucho más difícil para las personas que teletrabajan, según el CEO de IBM**](https://www.businessinsider.es/desarrollo-profesional/teletrabajar-perjudica-carrera-profesional-posibles-ascensos-1240782) 🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[ENTERPRISE-STABLE]</span> <span class='md-tag md-tag--warning'>[ES CONTENT]</span> — *Go to [Section](./project-management-methodology.md)*
|
||||
@@ -2077,7 +2077,7 @@
|
||||
<details markdown="1">
|
||||
<summary>Click to view 1 resources under Go/Typescript Content</summary>
|
||||
|
||||
- **(2024)** [Grafana](https://nubenetes.com/grafana/) <span class='md-tag md-tag--info'>[COMMUNITY-TOOL]</span> <span class='md-tag md-tag--warning'>[GO/TYPESCRIPT CONTENT]</span> — *Go to [Section](./monitoring.md)*
|
||||
- **(2024)** [Grafana](https://nubenetes.com/grafana) <span class='md-tag md-tag--info'>[COMMUNITY-TOOL]</span> <span class='md-tag md-tag--warning'>[GO/TYPESCRIPT CONTENT]</span> — *Go to [Section](./monitoring.md)*
|
||||
|
||||
</details>
|
||||
|
||||
@@ -2232,7 +2232,7 @@
|
||||
- **(2024)** [==aws-samples/aws-network-hub-for-terraform: Network Hub Account with Terraform==](https://github.com/aws-samples/aws-network-hub-for-terraform) 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> <span class='md-tag md-tag--warning'>[HCL CONTENT]</span> — *Go to [Section](./demos.md)*
|
||||
- **(2024)** [==registry.terraform.io: Terraform Azure Resources 🌟==](https://registry.terraform.io/modules/azurerm/resources/azure/latest) 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> <span class='md-tag md-tag--warning'>[HCL CONTENT]</span> — *Go to [Section](./terraform.md)*
|
||||
- **(2024)** [==Azure-Samples/jmeter-aci-terraform==](https://github.com/Azure-Samples/jmeter-aci-terraform) 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> <span class='md-tag md-tag--warning'>[HCL CONTENT]</span> — *Go to [Section](./azure.md)*
|
||||
- **(2024)** [==Terraform Kubernetes Boilerplates 🌟==](https://nubenetes.com/terraform/) 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> <span class='md-tag md-tag--warning'>[HCL CONTENT]</span> — *Go to [Section](./managed-kubernetes-in-public-cloud.md)*
|
||||
- **(2024)** [==Terraform Kubernetes Boilerplates 🌟==](https://nubenetes.com/terraform) 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> <span class='md-tag md-tag--warning'>[HCL CONTENT]</span> — *Go to [Section](./managed-kubernetes-in-public-cloud.md)*
|
||||
- **(2023)** [==Terraform Automation Demo using Google Cloud Provider==](https://github.com/tfxor/terraform-google-automation-demo) 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> <span class='md-tag md-tag--warning'>[HCL CONTENT]</span> — *Go to [Section](./demos.md)*
|
||||
- **(2023)** [==terraform.io: Creation-Time Provisioners 🌟==](https://developer.hashicorp.com/terraform/language/provisioners) 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> <span class='md-tag md-tag--critical'>[LEGACY]</span> <span class='md-tag md-tag--warning'>[HCL CONTENT]</span> — *Go to [Section](./terraform.md)*
|
||||
- **(2023)** [==AWS Lambda the Terraform Way==](https://github.com/nsriram/lambda-the-terraform-way) 🌟🌟🌟🌟🌟 <span class='md-tag md-tag--success'>[DE FACTO STANDARD]</span> <span class='md-tag md-tag--warning'>[HCL CONTENT]</span> — *Go to [Section](./terraform.md)*
|
||||
@@ -3060,7 +3060,7 @@
|
||||
- **(2026)** [Librerías cliente](https://prometheus.io/docs/instrumenting/clientlibs) <span class='md-tag md-tag--info'>[COMMUNITY-TOOL]</span> <span class='md-tag md-tag--warning'>[MULTI-LANGUAGE CONTENT]</span> — *Go to [Section](./prometheus.md)*
|
||||
- **(2026)** [refactoring.guru: Design Patterns](https://refactoring.guru/design-patterns) <span class='md-tag md-tag--info'>[COMMUNITY-TOOL]</span> <span class='md-tag md-tag--warning'>[MULTI-LANGUAGE CONTENT]</span> — *Go to [Section](./devel-sites.md)*
|
||||
- **(2024)** [Pulumi Cloud Providers](https://www.pulumi.com/registry/packages) <span class='md-tag md-tag--info'>[COMMUNITY-TOOL]</span> <span class='md-tag md-tag--warning'>[MULTI-LANGUAGE CONTENT]</span> — *Go to [Section](./pulumi.md)*
|
||||
- **(2024)** [AWS Samples (Boilerplates)](https://nubenetes.com/demos/#aws-samples-boilerplates) <span class='md-tag md-tag--info'>[COMMUNITY-TOOL]</span> <span class='md-tag md-tag--warning'>[MULTI-LANGUAGE CONTENT]</span> — *Go to [Section](./aws-tools-scripts.md)*
|
||||
- **(2024)** [AWS Samples (Boilerplates)](https://nubenetes.com/demos) <span class='md-tag md-tag--info'>[COMMUNITY-TOOL]</span> <span class='md-tag md-tag--warning'>[MULTI-LANGUAGE CONTENT]</span> — *Go to [Section](./aws-tools-scripts.md)*
|
||||
- **(2015)** [developers.googleblog.com: Introducing gRPC, a new open source HTTP/2 RPC Framework](https://developers.googleblog.com/introducing-grpc-a-new-open-source-http2-rpc-framework) <span class='md-tag md-tag--info'>[COMMUNITY-TOOL]</span> <span class='md-tag md-tag--warning'>[MULTI-LANGUAGE CONTENT]</span> — *Go to [Section](./api.md)*
|
||||
|
||||
</details>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
# 🎥 Cloud Native Core
|
||||
# Cloud Native Core
|
||||
|
||||
!!! tip "Nubenetes V2 Elite Portal"
|
||||
You are browsing the AI-Curated V2 Elite Edition. Looking for the exhaustive list of references? Check out the [**V1 Historical Archive**](/v1/).
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# 🎥 DevOps, IaC, and SRE
|
||||
# DevOps, IaC, and SRE
|
||||
|
||||
!!! tip "Nubenetes V2 Elite Portal"
|
||||
You are browsing the AI-Curated V2 Elite Edition. Looking for the exhaustive list of references? Check out the [**V1 Historical Archive**](/v1/).
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# 🎥 Fundamentals
|
||||
# Fundamentals
|
||||
|
||||
!!! tip "Nubenetes V2 Elite Portal"
|
||||
You are browsing the AI-Curated V2 Elite Edition. Looking for the exhaustive list of references? Check out the [**V1 Historical Archive**](/v1/).
|
||||
|
||||
+49
-15
@@ -12,6 +12,7 @@ edit_uri: "edit/master/v2-docs/"
|
||||
|
||||
theme:
|
||||
name: material
|
||||
custom_dir: docs/overrides
|
||||
language: en
|
||||
favicon: images/favicon-ultra.png
|
||||
palette:
|
||||
@@ -35,6 +36,11 @@ theme:
|
||||
- navigation.sections
|
||||
- navigation.expand
|
||||
- navigation.indexes
|
||||
- navigation.instant
|
||||
- navigation.instant.prefetch
|
||||
- navigation.path
|
||||
- navigation.footer
|
||||
- announce.dismiss
|
||||
- search.suggest
|
||||
- search.highlight
|
||||
- search.share
|
||||
@@ -45,7 +51,7 @@ theme:
|
||||
- navigation.prune
|
||||
- toc.integrate
|
||||
|
||||
plugins:
|
||||
plugins:
|
||||
- search
|
||||
- privacy
|
||||
- social:
|
||||
@@ -55,8 +61,29 @@ theme:
|
||||
font_family: "Inter"
|
||||
custom_dir: "docs/images/"
|
||||
logo: favicon-ultra.png
|
||||
- tags:
|
||||
tags_file: tags.md
|
||||
- minify:
|
||||
minify_html: true
|
||||
- rss:
|
||||
match_path: "(tech-digest|industry-digest).*"
|
||||
date_from_meta:
|
||||
as_creation: "date"
|
||||
abstract_chars_count: 200
|
||||
- redirects:
|
||||
redirect_maps: {}
|
||||
redirect_maps:
|
||||
jvm-parameters-matrix-table.md: java-and-java-performance-optimization.md
|
||||
private-cloud-solutions.md: kubernetes-on-premise.md
|
||||
stackstorm.md: cicd.md
|
||||
chef.md: ansible.md
|
||||
newsql.md: databases.md
|
||||
scaleway.md: digitalocean.md
|
||||
xamarin.md: dotnet.md
|
||||
dom.md: javascript.md
|
||||
react.md: javascript.md
|
||||
oauth.md: securityascode.md
|
||||
digital-money.md: finops.md
|
||||
aws-spain.md: aws.md
|
||||
|
||||
extra:
|
||||
social:
|
||||
@@ -69,10 +96,13 @@ extra:
|
||||
version:
|
||||
provider: mike # Ready for version switching
|
||||
|
||||
extra_head:
|
||||
- '<link rel="alternate" type="application/rss+xml" title="Nubenetes Intelligence Digest" href="/feed.xml"/>'
|
||||
|
||||
extra_css:
|
||||
- https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap
|
||||
- static/extra.css
|
||||
- static/v2_elite.css?v=2.3.44
|
||||
- static/v2_elite.css?v=2.7.0
|
||||
|
||||
extra_javascript:
|
||||
- static/v2_filter.js
|
||||
@@ -95,6 +125,19 @@ markdown_extensions:
|
||||
- pymdownx.tabbed:
|
||||
alternate_style: true
|
||||
- pymdownx.mark
|
||||
- pymdownx.tasklist:
|
||||
custom_checkbox: true
|
||||
- pymdownx.keys
|
||||
- pymdownx.highlight:
|
||||
anchor_linenums: true
|
||||
- pymdownx.inlinehilite
|
||||
- pymdownx.smartsymbols
|
||||
- pymdownx.caret
|
||||
- pymdownx.tilde
|
||||
- tables
|
||||
- footnotes
|
||||
- abbr
|
||||
- def_list
|
||||
|
||||
nav:
|
||||
- "🔙 Back to V1 (Exhaustive)": https://nubenetes.com/v1/
|
||||
@@ -106,6 +149,9 @@ nav:
|
||||
- "DevOps, IaC, and SRE": videos/devops-iac.md
|
||||
- "Cloud Native Core": videos/cloud-native.md
|
||||
- "Fundamentals": videos/fundamentals.md
|
||||
- "Intelligence Digest":
|
||||
- "Tech & Cloud Digest": tech-digest.md
|
||||
- "Industry & Geo Digest": industry-digest.md
|
||||
- "AI":
|
||||
- "AI Agents MCP": ai-agents-mcp.md
|
||||
- "AI": ai.md
|
||||
@@ -147,14 +193,12 @@ nav:
|
||||
- "Testops": testops.md
|
||||
- "Hardened Infrastructure":
|
||||
- "Ansible": ansible.md
|
||||
- "Chef": chef.md
|
||||
- "Crossplane": crossplane.md
|
||||
- "Devsecops": devsecops.md
|
||||
- "IaC": iac.md
|
||||
- "Kubernetes Security": kubernetes-security.md
|
||||
- "Kustomize": kustomize.md
|
||||
- "Liquibase": liquibase.md
|
||||
- "Oauth": oauth.md
|
||||
- "Pulumi": pulumi.md
|
||||
- "Securityascode": securityascode.md
|
||||
- "Terraform": terraform.md
|
||||
@@ -175,7 +219,6 @@ nav:
|
||||
- "AWS Pricing": aws-pricing.md
|
||||
- "AWS Security": aws-security.md
|
||||
- "AWS Serverless": aws-serverless.md
|
||||
- "AWS Spain": aws-spain.md
|
||||
- "AWS Storage": aws-storage.md
|
||||
- "AWS Tools Scripts": aws-tools-scripts.md
|
||||
- "AWS Training": aws-training.md
|
||||
@@ -186,9 +229,7 @@ nav:
|
||||
- "Ibm_Cloud": ibm_cloud.md
|
||||
- "Managed Kubernetes In Public Cloud": managed-kubernetes-in-public-cloud.md
|
||||
- "Oraclecloud": oraclecloud.md
|
||||
- "Private Cloud Solutions": private-cloud-solutions.md
|
||||
- "Public Cloud Solutions": public-cloud-solutions.md
|
||||
- "Scaleway": scaleway.md
|
||||
- "Networking & Service Mesh":
|
||||
- "Caching": caching.md
|
||||
- "Cloudflare": cloudflare.md
|
||||
@@ -223,7 +264,6 @@ nav:
|
||||
- "Crunchydata": crunchydata.md
|
||||
- "Databases": databases.md
|
||||
- "Message Queue": message-queue.md
|
||||
- "Newsql": newsql.md
|
||||
- "NoSQL": nosql.md
|
||||
- "Yaml": yaml.md
|
||||
- "Engineering Pipeline":
|
||||
@@ -238,14 +278,12 @@ nav:
|
||||
- "Openshift Pipelines": openshift-pipelines.md
|
||||
- "Registries": registries.md
|
||||
- "Sonarqube": sonarqube.md
|
||||
- "Stackstorm": stackstorm.md
|
||||
- "Tekton": tekton.md
|
||||
- "Developer Ecosystem":
|
||||
- "Chromedevtools": ChromeDevTools.md
|
||||
- "Angular": angular.md
|
||||
- "API": api.md
|
||||
- "Devel Sites": devel-sites.md
|
||||
- "Dom": dom.md
|
||||
- "Dotnet": dotnet.md
|
||||
- "Embedded Servlet Containers": embedded-servlet-containers.md
|
||||
- "Golang": golang.md
|
||||
@@ -253,20 +291,16 @@ nav:
|
||||
- "Java_App_Servers": java_app_servers.md
|
||||
- "Java_Frameworks": java_frameworks.md
|
||||
- "Javascript": javascript.md
|
||||
- "JVM Parameters Matrix Table": jvm-parameters-matrix-table.md
|
||||
- "Linux Dev Env": linux-dev-env.md
|
||||
- "Lowcode Nocode": lowcode-nocode.md
|
||||
- "Maven Gradle": maven-gradle.md
|
||||
- "Postman": postman.md
|
||||
- "Python": python.md
|
||||
- "React": react.md
|
||||
- "Swagger Code Generator For Rest APIs": swagger-code-generator-for-rest-apis.md
|
||||
- "Visual Studio": visual-studio.md
|
||||
- "Web3": web3.md
|
||||
- "Xamarin": xamarin.md
|
||||
- "Career & Industry":
|
||||
- "Appointment Scheduling": appointment-scheduling.md
|
||||
- "Digital Money": digital-money.md
|
||||
- "Elearning": elearning.md
|
||||
- "Finops": finops.md
|
||||
- "Freelancing": freelancing.md
|
||||
|
||||
Reference in New Issue
Block a user