pre_rules_python.py
- Pfad:
/var/www/tools/ki-protokoll/claude-hook/quality/pre_rules_python.py - Namespace: claude-hook.quality
- Zeilen: 179 | Größe: 5,266 Bytes
- Geändert: 2025-12-28 09:48:06 | Gescannt: 2025-12-31 10:22:15
Code Hygiene Score: 100
- Dependencies: 100 (25%)
- LOC: 100 (20%)
- Methods: 100 (20%)
- Secrets: 100 (15%)
- Classes: 100 (10%)
- Magic Numbers: 100 (10%)
Keine Issues gefunden.
Dependencies 4
- use re
- use typing.Optional
- use rule_base.block
- use rule_base.is_in_allowlist
Funktionen 2
-
pp1_1_hardcoded_model_default()Zeile 61 -
pp1_2_hardcoded_pipeline_id()Zeile 122
Code
#!/usr/bin/env python3
"""
Pre-Hook Python Regeln (BLOCK) - Pipeline Code Quality.
PP1.x: Hardcoded Values
PP2.x: Config Patterns
Diese Regeln prüfen Python-Dateien in /var/www/scripts/pipeline/
auf häufige Fehler wie hardcoded Model-Namen oder Pipeline-IDs.
"""
import re
from typing import Optional
from .rule_base import block, is_in_allowlist
# =============================================================================
# ALLOWLIST
# =============================================================================
PYTHON_ALLOWLIST = [
"/venv/",
"/__pycache__/",
"/tests/",
"/test_",
"_test.py",
]
# =============================================================================
# HARDCODED VALUES DETECTION
# =============================================================================
# Model-Namen die als hardcoded Default blockiert werden
HARDCODED_MODELS = {
"mistral",
"llama",
"llama2",
"llama3",
"gemma",
"gemma2",
"gemma3",
"phi",
"phi3",
"qwen",
"qwen2",
"claude",
"gpt-4",
"gpt-3.5",
"minicpm",
"minicpm-v",
"nomic",
"nomic-embed",
}
# =============================================================================
# PRÜFUNG PP1: HARDCODED VALUES
# =============================================================================
def pp1_1_hardcoded_model_default(file_path: str, content: str) -> Optional[dict]:
"""
PP1.1: Hardcoded LLM Model-Namen als Default blockieren.
BLOCKIERT:
parser.add_argument("--model", default="mistral")
def foo(model: str = "gemma"):
ERLAUBT:
parser.add_argument("--model", default=None)
model = get_pipeline_model("step_type")
DEFAULT_MODEL = "mistral" # Als Konstante OK
# Kommentar: mistral ist gut
"""
if not file_path.endswith(".py"):
return None
if is_in_allowlist(file_path, PYTHON_ALLOWLIST):
return None
lines = content.split('\n')
for line_num, line in enumerate(lines, 1):
stripped = line.strip()
# Skip Kommentare
if stripped.startswith('#'):
continue
# Skip Docstrings (vereinfacht)
if stripped.startswith('"""') or stripped.startswith("'''"):
continue
# Skip Konstanten-Definitionen (UPPER_CASE = "value")
if re.match(r'^[A-Z][A-Z0-9_]*\s*=', stripped):
continue
# Skip Listen/Sets von erlaubten Werten
if 'VALID_' in line or 'ALLOWED_' in line or 'HARDCODED_' in line:
continue
# Suche nach hardcoded model defaults
for model in HARDCODED_MODELS:
# Pattern 1: default="model" oder default='model'
pattern1 = rf'default\s*=\s*["\']({model})["\']'
# Pattern 2: model: str = "model" (type hint mit default)
pattern2 = rf'model\s*:\s*str\s*=\s*["\']({model})["\']'
# Pattern 3: model="model" als Keyword-Argument
pattern3 = rf'\bmodel\s*=\s*["\']({model})["\']'
for pattern in [pattern1, pattern2, pattern3]:
match = re.search(pattern, line, re.IGNORECASE)
if match:
return block(
"PP1.1",
f"Hardcoded model name '{model}' at line {line_num}. "
f"Use get_pipeline_model() to read from pipeline_steps config, "
f"or define as UPPER_CASE constant."
)
return None
def pp1_2_hardcoded_pipeline_id(file_path: str, content: str) -> Optional[dict]:
"""
PP1.2: Hardcoded Pipeline-IDs blockieren (außer als Konstante).
BLOCKIERT:
pipeline_id = 5
run_pipeline(pipeline_id=3)
ERLAUBT:
DEFAULT_PIPELINE_ID = 5
pipeline_id = args.pipeline_id
pipeline_id = config.get("pipeline_id")
"""
if not file_path.endswith(".py"):
return None
if is_in_allowlist(file_path, PYTHON_ALLOWLIST):
return None
lines = content.split('\n')
for line_num, line in enumerate(lines, 1):
stripped = line.strip()
# Skip Kommentare
if stripped.startswith('#'):
continue
# Skip Konstanten-Definitionen
if re.match(r'^[A-Z][A-Z0-9_]*\s*=', stripped):
continue
# Skip wenn es eine Zuweisung von args/config ist
if 'args.' in line or 'config.' in line or 'get(' in line:
continue
# Suche nach hardcoded pipeline_id
# Pattern: pipeline_id = 5 oder pipeline_id=5 (nicht als Teil eines größeren Ausdrucks)
pattern = r'\bpipeline_id\s*=\s*(\d+)\b'
match = re.search(pattern, line)
if match:
pid = match.group(1)
return block(
"PP1.2",
f"Hardcoded pipeline_id={pid} at line {line_num}. "
f"Use DEFAULT_PIPELINE_ID constant or accept via parameter."
)
return None
# =============================================================================
# RULE COLLECTION
# =============================================================================
RULES = [
pp1_1_hardcoded_model_default,
pp1_2_hardcoded_pipeline_id,
]