chore: install refactoring guardrails (Phase 0) [guardrail-change]
- scripts/check-loc.sh: LOC budget checker (500 LOC hard cap) - .claude/rules/architecture.md: split triggers, patterns per language - .claude/rules/loc-exceptions.txt: documented escape hatches - AGENTS.python.md: FastAPI conventions (routes thin, service layer) - AGENTS.go.md: Go/Gin conventions (handler ≤40 LOC) - AGENTS.typescript.md: Next.js conventions (page.tsx ≤250 LOC, colocation) - CLAUDE.md extended with guardrail section + commit markers 273 files currently exceed 500 LOC — to be addressed phase by phase. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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)
|
||||
```
|
||||
|
||||
46
.claude/rules/architecture.md
Normal file
46
.claude/rules/architecture.md
Normal file
@@ -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
|
||||
20
.claude/rules/loc-exceptions.txt
Normal file
20
.claude/rules/loc-exceptions.txt
Normal file
@@ -0,0 +1,20 @@
|
||||
# LOC Exceptions — BreakPilot Lehrer
|
||||
# Format: <glob> | owner=<person> | reason=<why> | review=<date>
|
||||
#
|
||||
# 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!
|
||||
9
.claude/settings.json
Normal file
9
.claude/settings.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash",
|
||||
"Write",
|
||||
"Read"
|
||||
]
|
||||
}
|
||||
}
|
||||
36
AGENTS.go.md
Normal file
36
AGENTS.go.md
Normal file
@@ -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 ./...
|
||||
```
|
||||
36
AGENTS.python.md
Normal file
36
AGENTS.python.md
Normal file
@@ -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
|
||||
```
|
||||
55
AGENTS.typescript.md
Normal file
55
AGENTS.typescript.md
Normal file
@@ -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.
|
||||
70
scripts/check-loc.sh
Executable file
70
scripts/check-loc.sh
Executable file
@@ -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 "$@"
|
||||
Reference in New Issue
Block a user