feat: BreakPilot PWA - Full codebase (clean push without large binaries)
Some checks failed
Tests / Go Tests (push) Has been cancelled
Tests / Python Tests (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / Go Lint (push) Has been cancelled
Tests / Python Lint (push) Has been cancelled
Tests / Security Scan (push) Has been cancelled
Tests / All Checks Passed (push) Has been cancelled
Security Scanning / Secret Scanning (push) Has been cancelled
Security Scanning / Dependency Vulnerability Scan (push) Has been cancelled
Security Scanning / Go Security Scan (push) Has been cancelled
Security Scanning / Python Security Scan (push) Has been cancelled
Security Scanning / Node.js Security Scan (push) Has been cancelled
Security Scanning / Docker Image Security (push) Has been cancelled
Security Scanning / Security Summary (push) Has been cancelled
CI/CD Pipeline / Go Tests (push) Has been cancelled
CI/CD Pipeline / Python Tests (push) Has been cancelled
CI/CD Pipeline / Website Tests (push) Has been cancelled
CI/CD Pipeline / Linting (push) Has been cancelled
CI/CD Pipeline / Security Scan (push) Has been cancelled
CI/CD Pipeline / Docker Build & Push (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / CI Summary (push) Has been cancelled
ci/woodpecker/manual/build-ci-image Pipeline was successful
ci/woodpecker/manual/main Pipeline failed
Some checks failed
Tests / Go Tests (push) Has been cancelled
Tests / Python Tests (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / Go Lint (push) Has been cancelled
Tests / Python Lint (push) Has been cancelled
Tests / Security Scan (push) Has been cancelled
Tests / All Checks Passed (push) Has been cancelled
Security Scanning / Secret Scanning (push) Has been cancelled
Security Scanning / Dependency Vulnerability Scan (push) Has been cancelled
Security Scanning / Go Security Scan (push) Has been cancelled
Security Scanning / Python Security Scan (push) Has been cancelled
Security Scanning / Node.js Security Scan (push) Has been cancelled
Security Scanning / Docker Image Security (push) Has been cancelled
Security Scanning / Security Summary (push) Has been cancelled
CI/CD Pipeline / Go Tests (push) Has been cancelled
CI/CD Pipeline / Python Tests (push) Has been cancelled
CI/CD Pipeline / Website Tests (push) Has been cancelled
CI/CD Pipeline / Linting (push) Has been cancelled
CI/CD Pipeline / Security Scan (push) Has been cancelled
CI/CD Pipeline / Docker Build & Push (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / CI Summary (push) Has been cancelled
ci/woodpecker/manual/build-ci-image Pipeline was successful
ci/woodpecker/manual/main Pipeline failed
All services: admin-v2, studio-v2, website, ai-compliance-sdk, consent-service, klausur-service, voice-service, and infrastructure. Large PDFs and compiled binaries excluded via .gitignore.
This commit is contained in:
258
scripts/pre-commit-check.py
Executable file
258
scripts/pre-commit-check.py
Executable file
@@ -0,0 +1,258 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
BreakPilot Pre-Commit Check Script
|
||||
|
||||
Prüft vor einem Commit:
|
||||
1. Sind alle geänderten Dateien dokumentiert?
|
||||
2. Haben alle geänderten Funktionen Tests?
|
||||
3. Sind Security-relevante Änderungen markiert?
|
||||
4. Sind ADRs für neue Module vorhanden?
|
||||
|
||||
Verwendung:
|
||||
python3 scripts/pre-commit-check.py # Prüft staged files
|
||||
python3 scripts/pre-commit-check.py --all # Prüft alle uncommitted changes
|
||||
python3 scripts/pre-commit-check.py --fix # Versucht automatische Fixes
|
||||
|
||||
Exit Codes:
|
||||
0 - Alles OK
|
||||
1 - Warnungen (nicht blockierend)
|
||||
2 - Fehler (blockierend)
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import List, Tuple, Dict
|
||||
|
||||
# ============================================
|
||||
# Konfiguration
|
||||
# ============================================
|
||||
|
||||
# Dateien die dokumentiert sein sollten
|
||||
DOC_REQUIRED_PATTERNS = {
|
||||
r"consent-service/internal/handlers/.*\.go$": "docs/api/consent-service-api.md",
|
||||
r"backend/.*_api\.py$": "docs/api/backend-api.md",
|
||||
r"website/app/api/.*/route\.ts$": "docs/api/frontend-api.md",
|
||||
}
|
||||
|
||||
# Dateien die Tests haben sollten
|
||||
TEST_REQUIRED_PATTERNS = {
|
||||
r"consent-service/internal/services/([^/]+)\.go$": r"consent-service/internal/services/\1_test.go",
|
||||
r"backend/([^/]+)\.py$": r"backend/tests/test_\1.py",
|
||||
}
|
||||
|
||||
# Neue Module die ADRs benötigen
|
||||
ADR_REQUIRED_PATTERNS = [
|
||||
r"consent-service/internal/services/\w+_service\.go$",
|
||||
r"backend/[^/]+_service\.py$",
|
||||
r"website/app/admin/[^/]+/page\.tsx$",
|
||||
]
|
||||
|
||||
# Security-relevante Patterns
|
||||
SECURITY_PATTERNS = [
|
||||
(r"password|secret|token|api_key|apikey", "Credentials"),
|
||||
(r"exec\(|eval\(|subprocess|os\.system", "Code Execution"),
|
||||
(r"sql|query.*\+|f['\"].*select|f['\"].*insert", "SQL Injection Risk"),
|
||||
]
|
||||
|
||||
# ============================================
|
||||
# Git Helpers
|
||||
# ============================================
|
||||
|
||||
def get_staged_files() -> List[str]:
|
||||
"""Gibt die für Commit vorgemerkten Dateien zurück."""
|
||||
result = subprocess.run(
|
||||
["git", "diff", "--cached", "--name-only", "--diff-filter=ACMR"],
|
||||
capture_output=True, text=True
|
||||
)
|
||||
return [f.strip() for f in result.stdout.strip().split("\n") if f.strip()]
|
||||
|
||||
def get_all_changed_files() -> List[str]:
|
||||
"""Gibt alle geänderten Dateien zurück (staged + unstaged)."""
|
||||
result = subprocess.run(
|
||||
["git", "diff", "--name-only", "--diff-filter=ACMR", "HEAD"],
|
||||
capture_output=True, text=True
|
||||
)
|
||||
return [f.strip() for f in result.stdout.strip().split("\n") if f.strip()]
|
||||
|
||||
def get_file_content(filepath: str) -> str:
|
||||
"""Liest den Inhalt einer Datei."""
|
||||
try:
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
return f.read()
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
def file_exists(filepath: str) -> bool:
|
||||
"""Prüft ob eine Datei existiert."""
|
||||
return Path(filepath).exists()
|
||||
|
||||
# ============================================
|
||||
# Checks
|
||||
# ============================================
|
||||
|
||||
def check_documentation(files: List[str]) -> List[Tuple[str, str]]:
|
||||
"""Prüft ob Dokumentation für geänderte Dateien existiert."""
|
||||
issues = []
|
||||
|
||||
for filepath in files:
|
||||
for pattern, doc_file in DOC_REQUIRED_PATTERNS.items():
|
||||
if re.match(pattern, filepath):
|
||||
if not file_exists(doc_file):
|
||||
issues.append((filepath, f"Dokumentation fehlt: {doc_file}"))
|
||||
break
|
||||
|
||||
return issues
|
||||
|
||||
def check_tests(files: List[str]) -> List[Tuple[str, str]]:
|
||||
"""Prüft ob Tests für geänderte Dateien existieren."""
|
||||
issues = []
|
||||
|
||||
for filepath in files:
|
||||
# Überspringe Test-Dateien selbst
|
||||
if "_test.go" in filepath or "test_" in filepath or "__tests__" in filepath:
|
||||
continue
|
||||
|
||||
for pattern, test_pattern in TEST_REQUIRED_PATTERNS.items():
|
||||
match = re.match(pattern, filepath)
|
||||
if match:
|
||||
test_file = re.sub(pattern, test_pattern, filepath)
|
||||
if not file_exists(test_file):
|
||||
issues.append((filepath, f"Test fehlt: {test_file}"))
|
||||
break
|
||||
|
||||
return issues
|
||||
|
||||
def check_adrs(files: List[str]) -> List[Tuple[str, str]]:
|
||||
"""Prüft ob ADRs für neue Module vorhanden sind."""
|
||||
issues = []
|
||||
adr_dir = Path("docs/adr")
|
||||
|
||||
for filepath in files:
|
||||
for pattern in ADR_REQUIRED_PATTERNS:
|
||||
if re.match(pattern, filepath):
|
||||
# Prüfe ob es ein neues File ist (nicht nur Änderung)
|
||||
result = subprocess.run(
|
||||
["git", "diff", "--cached", "--name-status", filepath],
|
||||
capture_output=True, text=True
|
||||
)
|
||||
if result.stdout.startswith("A"): # Added
|
||||
# Prüfe ob es einen ADR dafür gibt
|
||||
module_name = Path(filepath).stem
|
||||
adr_exists = any(
|
||||
adr_dir.glob(f"ADR-*{module_name}*.md")
|
||||
) if adr_dir.exists() else False
|
||||
|
||||
if not adr_exists:
|
||||
issues.append((
|
||||
filepath,
|
||||
f"Neues Modul ohne ADR. Erstelle: docs/adr/ADR-NNNN-{module_name}.md"
|
||||
))
|
||||
break
|
||||
|
||||
return issues
|
||||
|
||||
def check_security(files: List[str]) -> List[Tuple[str, str]]:
|
||||
"""Prüft auf security-relevante Änderungen."""
|
||||
warnings = []
|
||||
|
||||
for filepath in files:
|
||||
content = get_file_content(filepath)
|
||||
content_lower = content.lower()
|
||||
|
||||
for pattern, category in SECURITY_PATTERNS:
|
||||
if re.search(pattern, content_lower):
|
||||
warnings.append((filepath, f"Security-Review empfohlen: {category}"))
|
||||
break # Nur eine Warnung pro Datei
|
||||
|
||||
return warnings
|
||||
|
||||
# ============================================
|
||||
# Main
|
||||
# ============================================
|
||||
|
||||
def print_section(title: str, items: List[Tuple[str, str]], icon: str = "⚠️"):
|
||||
"""Gibt eine Sektion aus."""
|
||||
if items:
|
||||
print(f"\n{icon} {title}:")
|
||||
for filepath, message in items:
|
||||
print(f" {filepath}")
|
||||
print(f" → {message}")
|
||||
|
||||
def main():
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description="BreakPilot Pre-Commit Check")
|
||||
parser.add_argument("--all", action="store_true", help="Check all changed files, not just staged")
|
||||
parser.add_argument("--fix", action="store_true", help="Attempt automatic fixes")
|
||||
parser.add_argument("--strict", action="store_true", help="Treat warnings as errors")
|
||||
args = parser.parse_args()
|
||||
|
||||
print("=" * 60)
|
||||
print("BREAKPILOT PRE-COMMIT CHECK")
|
||||
print("=" * 60)
|
||||
|
||||
# Dateien ermitteln
|
||||
files = get_all_changed_files() if args.all else get_staged_files()
|
||||
|
||||
if not files:
|
||||
print("\n✅ Keine Dateien zu prüfen.")
|
||||
return 0
|
||||
|
||||
print(f"\nPrüfe {len(files)} Datei(en)...")
|
||||
|
||||
# Checks ausführen
|
||||
doc_issues = check_documentation(files)
|
||||
test_issues = check_tests(files)
|
||||
adr_issues = check_adrs(files)
|
||||
security_warnings = check_security(files)
|
||||
|
||||
# Ergebnisse ausgeben
|
||||
has_errors = False
|
||||
has_warnings = False
|
||||
|
||||
if doc_issues:
|
||||
print_section("Fehlende Dokumentation", doc_issues, "📝")
|
||||
has_warnings = True
|
||||
|
||||
if test_issues:
|
||||
print_section("Fehlende Tests", test_issues, "🧪")
|
||||
has_warnings = True
|
||||
|
||||
if adr_issues:
|
||||
print_section("Fehlende ADRs", adr_issues, "📋")
|
||||
has_warnings = True
|
||||
|
||||
if security_warnings:
|
||||
print_section("Security-Hinweise", security_warnings, "🔒")
|
||||
has_warnings = True
|
||||
|
||||
# Zusammenfassung
|
||||
print("\n" + "=" * 60)
|
||||
|
||||
if not has_errors and not has_warnings:
|
||||
print("✅ Alle Checks bestanden!")
|
||||
return 0
|
||||
|
||||
total_issues = len(doc_issues) + len(test_issues) + len(adr_issues)
|
||||
total_warnings = len(security_warnings)
|
||||
|
||||
print(f"📊 Zusammenfassung:")
|
||||
print(f" Issues: {total_issues}")
|
||||
print(f" Warnings: {total_warnings}")
|
||||
|
||||
if args.strict and (has_errors or has_warnings):
|
||||
print("\n❌ Commit blockiert (--strict mode)")
|
||||
return 2
|
||||
|
||||
if has_errors:
|
||||
print("\n❌ Commit blockiert wegen Fehlern")
|
||||
return 2
|
||||
|
||||
print("\n⚠️ Commit möglich, aber Warnings beachten!")
|
||||
return 1
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user