diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 51a5bd5..54c6339 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -256,3 +256,45 @@ ssh macmini "cd /Users/benjaminadmin/Projekte/breakpilot-lehrer && git push all | `website/app/admin/klausur-korrektur/` | Korrektur-Workspace | | `backend-lehrer/classroom_api.py` | Classroom Engine | | `backend-lehrer/state_engine_api.py` | State Engine | + +--- + +## Code-Qualitaet Guardrails (NON-NEGOTIABLE) + +> Vollstaendige Details: `.claude/rules/architecture.md` +> Ausnahmen: `.claude/rules/loc-exceptions.txt` + +### File Size Budget + +- **Hard Cap: 500 LOC** pro Datei +- Wenn eine Aenderung eine Datei ueber 500 LOC bringen wuerde: **erst splitten, dann aendern** +- Ausnahmen nur mit Begruendung in `loc-exceptions.txt` + `[guardrail-change]` Commit-Marker + +### Architektur + +- **Python:** Routes duenn → Business Logic in Services → Persistenz in Repositories +- **Go:** Handler ≤40 LOC → Service-Layer → Repository-Pattern +- **TypeScript/Next.js:** page.tsx duenn → Server Actions, Queries, Components auslagern +- **Types:** Monolithische types.ts frueh splitten, types.ts + types/ Shadowing vermeiden + +### Workflow (bei jeder Aenderung) + +1. Datei lesen + LOC pruefen +2. Wenn nahe am Budget → erst splitten +3. Minimale kohaerente Aenderung +4. Verifikation (Tests + Lint) +5. Zusammenfassung: Was geaendert, was verifiziert, Restrisiko + +### Commit-Marker + +- `[migration-approved]` — Schema-/Migrations-Aenderungen +- `[guardrail-change]` — Aenderungen an .claude/**, scripts/check-loc.sh +- `[split-required]` — Aenderung beginnt mit Datei-Split +- `[interface-change]` — Public API Contracts geaendert + +### LOC-Check ausfuehren + +```bash +bash scripts/check-loc.sh --changed # nur geaenderte Dateien +bash scripts/check-loc.sh --all # alle Dateien (zeigt alle Violations) +``` diff --git a/.claude/rules/architecture.md b/.claude/rules/architecture.md new file mode 100644 index 0000000..4aa0f76 --- /dev/null +++ b/.claude/rules/architecture.md @@ -0,0 +1,46 @@ +# Architecture Rule — BreakPilot Lehrer + +## File Size Budget + +Hard default: **500 LOC max** per file. +Soft targets: +- Handler/Router/Service: 300-400 LOC +- Models/Schemas/Types: 200-300 LOC +- Utilities: 100-200 LOC + +Ausnahmen nur in `.claude/rules/loc-exceptions.txt` mit Begruendung. + +## Split-Trigger + +Sofort splitten wenn: +- Datei ueberschreitet 500 LOC +- Datei wuerde nach Aenderung 500 LOC ueberschreiten +- Datei mischt Transport + Business Logic + Persistence +- Datei enthaelt mehrere unabhaengig testbare Verantwortlichkeiten + +## Python (backend-lehrer, klausur-service, voice-service) + +- Routes duenn halten — Business Logic in Services +- Persistenz in Repositories/Data-Access-Module +- Pydantic Schemas nach Domain splitten +- Zirkulaere Imports vermeiden + +## Go (school-service, edu-search-service) + +- Handler duenn halten (≤40 LOC) +- Business Logic in Services/Use-Cases +- Transport/Request-Decoding getrennt von Domain-Logik + +## TypeScript / Next.js (admin-lehrer, studio-v2, website) + +- page.tsx duenn halten — Server Actions, Queries, Forms auslagern +- Monolithische types.ts frueh splitten +- types.ts + types/ Shadowing vermeiden +- Shared Client/Server Types explizit trennen + +## Entscheidungsreihenfolge + +1. Bestehendes kleines kohaeesives Modul wiederverwenden +2. Neues Modul in der Naehe erstellen +3. Ueberfuellte Datei splitten, neues Verhalten in richtiges Split-Modul +4. Nur als letzter Ausweg: Grosse bestehende Datei erweitern diff --git a/.claude/rules/loc-exceptions.txt b/.claude/rules/loc-exceptions.txt new file mode 100644 index 0000000..0f50bfd --- /dev/null +++ b/.claude/rules/loc-exceptions.txt @@ -0,0 +1,20 @@ +# LOC Exceptions — BreakPilot Lehrer +# Format: | owner= | reason= | review= +# +# Jede Ausnahme braucht Begruendung und Review-Datum. +# Temporaere Ausnahmen muessen mit [guardrail-change] Commit-Marker versehen werden. + +# Generated / Build Artifacts +**/node_modules/** | owner=infra | reason=npm packages | review=permanent +**/.next/** | owner=infra | reason=Next.js build output | review=permanent +**/__pycache__/** | owner=infra | reason=Python bytecode | review=permanent +**/venv/** | owner=infra | reason=Python virtualenv | review=permanent + +# Test-Dateien (duerfen groesser sein fuer Table-Driven Tests) +**/tests/test_cv_vocab_pipeline.py | owner=klausur | reason=umfangreiche OCR Pipeline Tests | review=2026-07-01 +**/tests/test_rbac.py | owner=klausur | reason=RBAC Test-Matrix | review=2026-07-01 +**/tests/test_grid_editor_api.py | owner=klausur | reason=Grid Editor Integrationstests | review=2026-07-01 + +# Legacy — TEMPORAER bis Refactoring abgeschlossen +# Dateien hier werden Phase fuer Phase abgearbeitet und entfernt. +# KEINE neuen Ausnahmen ohne [guardrail-change] Commit-Marker! diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..380d72b --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,9 @@ +{ + "permissions": { + "allow": [ + "Bash", + "Write", + "Read" + ] + } +} diff --git a/AGENTS.go.md b/AGENTS.go.md new file mode 100644 index 0000000..e291339 --- /dev/null +++ b/AGENTS.go.md @@ -0,0 +1,36 @@ +# AGENTS.go.md — Go/Gin Konventionen + +## Architektur + +- `handlers/`: HTTP Transport nur — Decode, Validate, Call Service, Encode Response +- `service/` oder `usecase/`: Business Logic +- `repo/`: Storage/Integration +- `model/` oder `domain/`: Domain Entities +- `tests/`: Table-driven Tests bevorzugen + +## Regeln + +1. Handler ≤40 LOC — nur Decode → Service → Encode +2. Business Logic NICHT in Handlers verstecken +3. Grosse Handler nach Resource/Verb splitten +4. Request/Response DTOs nah am Transport halten +5. Interfaces nur an echten Boundaries (nicht ueberall fuer Mocks) +6. Keine Giant-Utility-Dateien +7. Generated Files nicht manuell editieren + +## Split-Trigger + +- Handler-Datei ueberschreitet 400-500 LOC +- Unrelated Endpoints zusammengruppiert +- Encoding/Decoding dominiert die Handler-Datei +- Service-Logik und Transport-Logik gemischt + +## Verifikation + +```bash +gofmt -l . | grep -q . && exit 1 +go vet ./... +golangci-lint run --timeout=5m +go test -race ./... +go build ./... +``` diff --git a/AGENTS.python.md b/AGENTS.python.md new file mode 100644 index 0000000..40dac32 --- /dev/null +++ b/AGENTS.python.md @@ -0,0 +1,36 @@ +# AGENTS.python.md — Python/FastAPI Konventionen + +## Architektur + +- `routes/` oder `api/`: Request/Response nur — kein Business Logic +- `services/`: Business Logic +- `repositories/`: Persistenz/Data Access +- `schemas/`: Pydantic Models, nach Domain gesplittet +- `tests/`: Spiegelt Produktions-Layout + +## Regeln + +1. Route-Dateien duenn halten (≤300 LOC) +2. Wenn eine Route-Datei 300-400 LOC erreicht → nach Resource/Operation splitten +3. Schema-Dateien nach Domain splitten wenn sie wachsen +4. Modul-Level Singleton-Kopplung vermeiden (Tests patchen falsches Symbol) +5. Patch immer das Symbol das vom getesteten Modul importiert wird +6. Dependency Injection bevorzugen statt versteckte Imports +7. Pydantic v2: `from __future__ import annotations` NICHT verwenden (bricht Pydantic) +8. Migrationen getrennt von Refactorings halten + +## Split-Trigger + +- Datei naehert sich oder ueberschreitet 500 LOC +- Zirkulaere Imports erscheinen +- Tests brauchen tiefes Patching +- API-Schemas mischen verschiedene Domains +- Service-Datei macht Transport UND DB-Logik + +## Verifikation + +```bash +ruff check . +mypy . --ignore-missing-imports --no-error-summary +pytest tests/ -x -q --no-header +``` diff --git a/AGENTS.typescript.md b/AGENTS.typescript.md new file mode 100644 index 0000000..44b10d2 --- /dev/null +++ b/AGENTS.typescript.md @@ -0,0 +1,55 @@ +# AGENTS.typescript.md — Next.js Konventionen + +## Architektur + +- `app/.../page.tsx`: Minimale Seiten-Komposition (≤250 LOC) +- `app/.../actions.ts`: Server Actions +- `app/.../queries.ts`: Data Loading +- `app/.../_components/`: View-Teile (Colocation) +- `app/.../_hooks/`: Seiten-spezifische Hooks (Colocation) +- `types/` oder `types/*.ts`: Domain-spezifische Types +- `schemas/`: Zod/Validierungs-Schemas +- `lib/`: Shared Utilities + +## Regeln + +1. page.tsx duenn halten (≤250 LOC) +2. Grosse Seiten frueh in Sections/Components splitten +3. KEINE einzelne types.ts als Catch-All +4. types.ts UND types/ Shadowing vermeiden (eines waehlen!) +5. Server/Client Module-Grenzen explizit halten +6. Pure Helpers und schmale Props bevorzugen +7. API-Client Types getrennt von handgeschriebenen Domain Types + +## Colocation Pattern (bevorzugt) + +``` +app/(admin)/ai/rag/ + page.tsx ← duenn, komponiert nur + _components/ + SearchPanel.tsx + ResultsTable.tsx + FilterBar.tsx + _hooks/ + useRagSearch.ts + actions.ts ← Server Actions + queries.ts ← Data Fetching +``` + +## Split-Trigger + +- page.tsx ueberschreitet 250-350 LOC +- types.ts ueberschreitet 200-300 LOC +- Form-Logik, Server Actions und Rendering in einer Datei +- Mehrere unabhaengig testbare Sections vorhanden +- Imports werden broechig + +## Verifikation + +```bash +npx tsc --noEmit +npm run lint +npm run build +``` + +> `npm run build` ist PFLICHT — `tsc` allein reicht nicht. diff --git a/scripts/check-loc.sh b/scripts/check-loc.sh new file mode 100755 index 0000000..d635594 --- /dev/null +++ b/scripts/check-loc.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash +set -euo pipefail + +MAX_LOC="${MAX_LOC:-500}" +ROOT_DIR="$(git rev-parse --show-toplevel 2>/dev/null || pwd)" +EXCEPTIONS_FILE="${ROOT_DIR}/.claude/rules/loc-exceptions.txt" + +red() { printf '\033[31m%s\033[0m\n' "$*"; } +green() { printf '\033[32m%s\033[0m\n' "$*"; } + +is_exempt() { + local file="$1" + [[ -f "$EXCEPTIONS_FILE" ]] || return 1 + while IFS= read -r line; do + [[ -z "$line" ]] && continue + [[ "$line" =~ ^# ]] && continue + local pattern="${line%%|*}" + pattern="$(echo "$pattern" | xargs)" + if [[ -n "$pattern" ]] && [[ "$file" == $pattern ]]; then + return 0 + fi + done < "$EXCEPTIONS_FILE" + return 1 +} + +count_loc() { + [[ -f "$1" ]] && awk 'END { print NR }' "$1" || echo 0 +} + +collect_changed_files() { + { git diff --name-only --cached; git diff --name-only; git ls-files --others --exclude-standard; } | awk 'NF' | sort -u +} + +main() { + local mode="changed" + [[ "${1:-}" == "--changed" ]] && mode="changed" + [[ "${1:-}" == "--all" ]] && mode="all" + + local files=() + if [[ "$mode" == "changed" ]]; then + IFS=$'\n' read -d '' -r -a files < <(collect_changed_files) + else + IFS=$'\n' read -d '' -r -a files < <(find "$ROOT_DIR" \( -name '*.py' -o -name '*.go' -o -name '*.ts' -o -name '*.tsx' \) -not -path '*/node_modules/*' -not -path '*/.next/*' -not -path '*/__pycache__/*' -not -path '*/venv/*') + fi + + local failed=0 + local violations=0 + + for file in "${files[@]}"; do + [[ -f "$file" ]] || continue + is_exempt "$file" && continue + local loc + loc="$(count_loc "$file")" + if (( loc > MAX_LOC )); then + red "VIOLATION: $file ($loc LOC > $MAX_LOC)" + violations=$((violations + 1)) + failed=1 + fi + done + + if (( failed )); then + red "" + red "$violations file(s) exceed ${MAX_LOC} LOC budget." + exit 1 + fi + + green "LOC budget check passed." +} + +main "$@"