# Ampel-CI fuer MC2 — auf dieses Multi-Service-Monorepo zugeschnitten (24.07.2026). # # Die universelle Vorlage (deploy/ampel-ci.yml) passt fuer EIN-Service-Repos. MC2 hat # 5 Python-Dienste (backend/voice_service/mem0_service/mcp/client) mit schweren ML- # Abhaengigkeiten (Whisper/TTS/Embeddings) + ein Frontend. "pip install ALLER requirements # + pytest repo-weit" in einem stateless Container ist weder machbar (GB-schwere Wheels, # CUDA) noch aussagekraeftig — die echten Tests sind der Pruefstand (deploy/pruefstand) # und die Integration auf der Box mit LIVE-Diensten/Modellen, nicht isolierte Unit-Tests. # # Darum prueft die MC2-Ampel, was im Container EHRLICH gruen sein kann und trotzdem echte # Fehler faengt: Lint (ruff, Projekt-Politik in ruff.toml) + Import/Syntax (compileall) + # Frontend-Build inkl. TypeScript-Typecheck (tsc) + Frontend-Tests (vitest/jsdom) + ESLint # + Buendel-Budget. Rot ist ein Ergebnis, kein Aergernis. name: Ampel on: [push, pull_request] jobs: ampel: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Ampel — Lint + Import + Frontend-Build (MC2-Zuschnitt) shell: bash run: | # Runner-bash laeuft mit -e; abschalten und JEDEN Fehler selbst werten (rot=1), # am Ende EIN Klartext-Urteil (Lehre Ampel-Lauf #5). set -u +e rot=0 echo "== Python: Lint (ruff, Projekt-Politik aus ruff.toml) ==" python3 -m venv /tmp/ampel-venv && . /tmp/ampel-venv/bin/activate || { echo "❌ venv kaputt"; exit 1; } pip install -q ruff || { echo "❌ ruff-Install kaputt"; exit 1; } ruff check . || rot=1 echo "== Python: Import/Syntax (compileall) ==" python3 -m compileall -q backend voice_service mem0_service mcp client deploy hermes scripts || rot=1 echo "== Frontend: reproduzierbarer Build (npm ci + tsc + vite) ==" if [ -f frontend/package.json ]; then if [ ! -f frontend/package-lock.json ]; then echo "❌ frontend/: kein package-lock.json — Build nicht reproduzierbar." rot=1 else ( cd frontend && npm ci --no-audit --no-fund && npm run build ) || rot=1 # Frontend-Tests + Linter (v3-Umbau P1, 28.08.2026). # Bis dahin lief das Frontend als einziger Teil des Stacks voellig ungeprueft # durch das Gate — 10 500 Zeilen, null Tests. Die Begruendung weiter oben # ("echte Tests sind der Pruefstand mit LIVE-Diensten") gilt fuer die # ML-schweren Python-Dienste, nicht fuer Frontend-Unit-Tests: die laufen in # jsdom, brauchen weder Modell noch GPU und sind in Sekunden durch. # Der Linter meldet 0 Fehler / ~90 Warnungen; rot wird nur bei Fehlern. if [ $rot -eq 0 ]; then echo "-- Vitest" ( cd frontend && npm test --silent ) || rot=1 echo "-- ESLint (Warnungen sind erlaubt, Fehler nicht)" ( cd frontend && npm run lint --silent ) || rot=1 fi # Bündel-Budget (v3-Umbau P0, 28.08.2026): Der Start-Chunk ist das, was der # Nutzer VOR dem ersten Bild lädt. Er lag bei 220 kB gzip, weil das eifrig # geladene Cockpit Recharts mitzog; dist lag bei 27 MB wegen eines verwaisten # Avatar-Modells. Ohne Deckel wächst beides unbemerkt zurück. Gemessen wird der # FRISCHE Build im Runner — kein Vergleich mit dem committeten dist (das wäre # über Node-Versionen hinweg flatterhaft und würde dauerhaft rot leuchten). if [ $rot -eq 0 ] && [ -d frontend/dist/assets ]; then budget_gz=125000 # Stand nach P0: 110 571 B gzip budget_dist=3145728 # Stand nach P0: 1 477 852 B # Den Einstiegs-Chunk aus index.html lesen, NICHT per Glob raten: # Rollup nennt auch kleine geteilte Module "index-*.js" (gemessen: ein # 67-Byte-Chunk neben dem 375-kB-Einstieg). `ls | head -1` haette je nach # Hash den falschen erwischt — und das Budget waere still immer gruen. einstieg=$(grep -o 'assets/index-[A-Za-z0-9_-]*\.js' frontend/dist/index.html | head -1) haupt="frontend/dist/$einstieg" if [ -n "$haupt" ]; then gz=$(gzip -c "$haupt" | wc -c) echo "-- Start-Chunk: $gz B gzip (Budget $budget_gz)" if [ "$gz" -gt "$budget_gz" ]; then echo "❌ Start-Bündel über Budget. Meist eine neue Bibliothek, die über" echo " das eifrig geladene Cockpit hereinkommt — hinter lazy() legen." rot=1 fi fi gesamt=$(du -sb frontend/dist | cut -f1) echo "-- dist gesamt: $gesamt B (Budget $budget_dist)" if [ "$gesamt" -gt "$budget_dist" ]; then echo "❌ dist über Budget — Ballast? (Schrift-Subsets, Medien, WOFF 1)" rot=1 fi fi if [ $rot -eq 0 ]; then echo "== Frontend: Push nach release-dist ==" git config --global user.name "Gitea Actions" git config --global user.email "actions@gitea.local" CURRENT_COMMIT=$(git rev-parse HEAD) # Erzeuge (oder hole) den orphan branch 'release-dist' git checkout --orphan release-dist 2>/dev/null || git checkout release-dist git rm -rf . >/dev/null 2>&1 || true # Checkout nur frontend/dist vom aktuellen Stand git add -f frontend/dist if ! git diff-index --quiet HEAD; then git commit -m "Automatischer Frontend Build ($CURRENT_COMMIT) [skip ci]" # Push to release-dist. Token auth from runner is expected to work for Gitea Actions. git push origin HEAD:release-dist -f else echo "Keine Änderungen am Frontend-Build." fi # Zurück zum originalen Branch für den Rest des Skripts git checkout $CURRENT_COMMIT fi fi fi if [ $rot -ne 0 ]; then echo "❌ AMPEL ROT — nichts heißt ‚fertig', solange das rot ist." else echo "✅ AMPEL GRÜN — Lint + Import + Frontend-Build sauber." fi exit $rot