chore(repo): replace app symlink/submodule with in-repo app directory; enable committing app/Dockerfile and tooling\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

This commit is contained in:
torsten committed 2026-07-17 13:13:02 +02:00
1 parent a12270ad01
commit ae2c674b81
25 files changed
+3417 -1

No files matched your search

+496
View File
@@ -0,0 +1,496 @@
# Qt5 → Qt6 Migrationspipeline
Asynchrone, agentische Pipeline zur automatisierten Migration einer C++/Qt5-Codebasis nach Qt6.
Entwickelt für den Pilotbatch **Q1** (~32 k LOC, 115 Files) als Vorstufe zur vollständigen Q2-Migration (~1,2 Mio LOC, ~6 500 Files).
---
## Inhaltsverzeichnis
1. [Überblick](#überblick)
2. [Architektur](#architektur)
3. [Dual-Track-Strategie](#dual-track-strategie)
4. [Semantisches Chunking](#semantisches-chunking)
5. [Multi-Turn-Konversation](#multi-turn-konversation)
6. [Quality Gates](#quality-gates)
7. [Quarantäne & Tickets](#quarantäne--tickets)
8. [Voraussetzungen](#voraussetzungen)
9. [Einrichtung](#einrichtung)
10. [Docker](#docker)
11. [Ausführung](#ausführung)
12. [Ausgaben](#ausgaben)
13. [Konfigurationsreferenz](#konfigurationsreferenz)
14. [Modulübersicht](#modulübersicht)
15. [Burn-Phase & Skalierung auf Q2](#burn-phase--skalierung-auf-q2)
16. [FAQ](#faq)
---
## Überblick
Die Pipeline übernimmt folgende Aufgaben vollautomatisch:
| Schritt | Beschreibung |
|---|---|
| **Batch-Isolation** | Pro Batch + Track ein eigener Git-Worktree und Build-Verzeichnis |
| **LLM-Migration** | Qt5-APIs werden per LLM (Scaleway) auf Qt6 portiert |
| **Chunk-Verarbeitung** | Dateien > 500 Zeilen werden semantisch gesplittet und parallel migriert |
| **Syntax-Check** | Schneller Per-File-Check via `compile_commands.json` ohne Full-Rebuild |
| **Test-Generierung** | QTest (Qt-naher Code) oder gtest + Smoke-Test pro Datei |
| **Quality Gates** | Build ✓ · Bestehende Tests ✓ · Neue Tests ✓ |
| **Quarantäne** | Dateien, die Gates nicht bestehen, werden isoliert + Ticket erzeugt |
| **Vergleichsreport** | Track A vs. Track B als Markdown + JSON |
---
## Architektur
```
run_migration.py ← CLI-Einstiegspunkt
│
└── migration_pipeline/
├── orchestrator.py ← Batch-Steuerung, beide Tracks parallel
│ ├── git_manager.py ← Worktree + Branch pro Batch×Track
│ ├── build_runner.py ← cmake configure/build/ctest (isoliert)
│ └── migrator.py ← Per-File Agentenloop
│ ├── chunker.py ← Semantisches C++ Chunking
│ ├── api_client.py ← Scaleway API
│ ├── test_generator.py ← QTest / gtest / Smoke
│ ├── quality_gate.py ← Gate-Auswertung
│ └── quarantine.py ← Quarantäne + Ticket
├── reporter.py ← Vergleichsreport A vs B
├── models.py ← Dataclasses & Enums
└── config.py ← Alle Konstanten
```
### Datenfluss pro Datei
```
Quelldatei
│
▼ split_semantic() wenn > 500 Zeilen
[Chunk 1] [Chunk 2] … [Chunk N] ← parallel per LLM migriert
│
▼ Consistency-Pass (Merge-Check via LLM)
Migrierter Code
│
▼ syntax_check_file() (compile_commands.json, kein Full-Rebuild)
│
▼ generate_tests() (QTest oder gtest + Smoke)
│
▼ cmake_configure() + cmake_build() + ctest
│
┌──┴──────────────────┐
│ Quality Gates │
│ ✓ Build │
│ ✓ Bestehende Tests │
│ ✓ Neue Tests │
└──┬──────────────────-┘
│ └── FAIL → Retry (max 3×)
│ └── Quarantäne
▼
git commit (Branch migration/<batch>/<track>)
```
---
## Dual-Track-Strategie
Beide Tracks laufen **gleichzeitig** auf dem selben Batch.
| | Track A – Konservativ | Track B – Modern |
|---|---|---|
| **Ziel** | Qt5→Qt6 Kompatibilität, minimale Änderungen | Qt6 + C++20 Modernisierung |
| **Stil** | Original-Stil beibehalten | Ranges, Concepts, `std::optional`, Lambdas, `[[nodiscard]]` usw. |
| **Git-Branch** | `migration/<batch>/track_a_conservative` | `migration/<batch>/track_b_modern` |
| **Risiko** | Niedrig | Mittel |
Nach Abschluss des Pilotbatches folgt die **Burn-Phase**: beide Tracks werden identischen Gates unterzogen und anhand von Erfolgsquote, Warnungsanzahl und Wartbarkeit verglichen. Der Vergleichsreport enthält eine automatische Empfehlung.
---
## Semantisches Chunking
Große Dateien (> 500 Zeilen) werden **nicht** an willkürlichen Zeilengrenzen getrennt, sondern an:
1. Klassen-/Struct-/Namespace-Definitionen (`class Foo {`, `namespace Bar {`)
2. Funktionsdefinitionen (Top-Level und Methoden)
3. Fallback: nächste Leerzeile unterhalb der 500-Zeilen-Marke
Alle Chunks einer Datei werden **parallel** per API migriert. Anschließend läuft ein **Consistency-Pass** (separater LLM-Aufruf), der:
- doppelte `#pragma once` / Include-Guards entfernt,
- offene Namespaces schließt,
- doppelte Deklarationen bereinigt.
---
## Multi-Turn-Konversation
Die Retry-Schleife des Migrations-Agenten verwendet eine **wachsende Gesprächshistorie**, sodass das Modell beim nächsten Versuch seine eigene fehlerhafte Ausgabe zusammen mit der Compiler-Fehlermeldung sieht – analog zum Verhalten des Copilot CLI.
### Konversationsstruktur
```
Iteration 0: [system, user(„Portiere folgenden Qt5-Code…")]
→ reply_v1
Iteration 1: [system, user(„Portiere…"), assistant(reply_v1),
user(„Fehler im vorherigen Versuch:\n<fehler>\nBitte korrigiere:\n<code>")]
→ reply_v2
```
### Drei unabhängige Konversations-Scopes
| Scope | Lebensdauer | Zweck |
|---|---|---|
| **Whole-file retry** (`whole_messages`) | Pro Datei; wächst über alle Retries | Modell lernt iterativ aus Compiler-Fehlern seiner eigenen Ausgabe |
| **Chunk-Migration** (je Chunk) | Pro Chunk, frisch + unabhängig | Parallele Chunk-Verarbeitung ohne gegenseitige Beeinflussung |
| **Consistency-Pass** (frisch) | Pro Datei, einmalig nach Chunk-Merge | Isoliert vom Retry-Loop; prüft nur Merge-Konsistenz |
### Per-File-Scoping
`whole_messages` ist eine **lokale Variable** innerhalb von `migrate_file` und wird nach Rückgabe der Funktion verworfen. Gesprächshistorie wird **niemals** zwischen Dateien oder Batches geteilt.
### Layered API in `api_client.py`
| Funktion | Typ | Verwendung |
|---|---|---|
| `_send_messages(client, model, messages)` | privat | Einziger HTTP-Aufruf; nimmt eine fertige Message-Liste entgegen |
| `call_api(client, model, system, user)` | öffentlich, stateless | Baut `[system, user]` und delegiert – rückwärtskompatibel |
| `call_api_multiturn(client, model, messages)` | öffentlich, multi-turn | Übergibt die komplette Verlaufsliste direkt an `_send_messages` |
`test_generator.py` verwendet weiterhin das stateless `call_api`; nur `migrator.py` nutzt `call_api_multiturn`.
---
## Quality Gates
| Gate | Bedingung | Blockierend? |
|---|---|---|
| **Build** | `cmake --build` returncode 0 | ✅ Ja |
| **Bestehende Tests** | Vollständiger CTest-Lauf grün | ✅ Ja |
| **Neue Tests** | Generierte Unit- + Smoke-Tests grün | ✅ Ja |
| **Warnungen** | Warnungsanzahl = 0 | ⚠️ Advisory (nicht blockierend) |
Warnungen werden vollständig geloggt und im Vergleichsreport ausgewiesen. Sie blockieren die Pipeline **nicht** (konfigurierbar über `WARNINGS_ARE_BLOCKING=True` in `config.py`).
### Fast Syntax-Check
Vor dem teuren Full-Build wird jede Datei einzeln per `compile_commands.json` (erzeugt bei `cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON`) syntaxgeprüft:
```
clang++ -fsyntax-only <compile_flags_aus_json> <datei.cpp>
```
Das spart bei einer 1,2-Mio-LOC-Codebasis erheblich Zeit.
---
## Quarantäne & Tickets
Dateien werden in Quarantäne verschoben, wenn:
- **3 Retry-Versuche** erschöpft sind, oder
- das **Zeitbudget** überschritten wurde (10 min für kleine Dateien, 25 min für Dateien > 1 000 Zeilen).
Quarantäne ist **nicht-blockierend**: der Rest des Batches läuft weiter.
### Generierte Artefakte
Für jede quarantänisierte Datei werden zwei Dateien in `quarantine/` erzeugt:
**`<stem>_<track>_<timestamp>.json`** – maschinenlesbar:
```json
{
"file_path": "src/core/MyClass.cpp",
"track": "track_a_conservative",
"failure_class": "compile_error",
"last_error": "error: 'QRegExp' was not declared ...",
"affected_apis": ["QRegExp"],
"chunk_info": "3 chunks, ~500 Zeilen/Chunk",
"manual_recommendation": "Compile-Fehler. Prüfe Qt6-API-Änderungen für: QRegExp.",
"timestamp": "2026-07-08T18:32:12Z",
"retry_count": 3,
"elapsed_seconds": 187.4
}
```
**`<stem>_<track>_<timestamp>.md`** – manuelles Review-Ticket:
```markdown
# Quarantäne-Ticket: MyClass.cpp
| Feld | Wert |
|---|---|
| Fehlerklasse | compile_error |
| Empfehlung | Qt6 Porting Guide: QRegExp … |
…
```
### Fehlerklassen
| Klasse | Bedeutung |
|---|---|
| `compile_error` | Compilerfehler (Qt5-API entfernt / geändert) |
| `link_error` | Linkfehler (fehlende Qt6-Modulabhängigkeit) |
| `test_failure` | Semantische Abweichung in Tests |
| `test_build_failure` | Generierter Test kompiliert nicht |
| `budget_exceeded` | Zeitbudget überschritten |
| `api_error` | Scaleway-API nicht erreichbar |
| `chunk_merge_error` | Inkonsistenz nach Chunk-Merge |
---
## Voraussetzungen
### System
- Python ≥ 3.11
- cmake ≥ 3.22
- clang++ (für `syntax_check_file`-Fallback; optional wenn `compile_commands.json` vorhanden)
- git ≥ 2.35 (für `git worktree`)
- GTest und Qt6 installiert und für cmake auffindbar
### Scaleway API
- Zugang zur [Scaleway AI API](https://www.scaleway.com/en/docs/ai-data/managed-inference/)
- Modelle: `glm-5.2` (große Dateien) · `qwen3.6-35b-a3b` (kleine Dateien)
---
## Einrichtung
```bash
# 1. Abhängigkeiten installieren
pip install -r requirements.txt
# 2. API-Key setzen
echo "SCW_SECRET_KEY=your_key_here" > .env
# 3. Prioritätsliste erstellen (eine Datei pro Zeile, in Bearbeitungsreihenfolge)
cat > q1_files.txt << 'EOF'
# cmake-Dateien zuerst (beeinflussen Build aller anderen)
src/CMakeLists.txt
src/core/CMakeLists.txt
# Header-Dateien
src/core/MyClass.h
src/core/OtherClass.h
# Implementierungsdateien
src/core/MyClass.cpp
src/core/OtherClass.cpp
EOF
```
> **Dateireihenfolge**: Die Pipeline verarbeitet Dateien **exakt in der angegebenen Reihenfolge** (innerhalb des Tracks). Empfohlene Reihenfolge: cmake-Dateien → Header → kleine .cpp → große .cpp.
---
## Docker
Das mitgelieferte `Dockerfile` enthält alle System-Abhängigkeiten (Ubuntu 22.04, Python 3.11, cmake, clang, git) und eignet sich für eine reproduzierbare Ausführung ohne lokale Installation.
### Image bauen & starten (empfohlen: `run_docker.sh`)
```bash
# Build + interaktive Shell (Host-UID/GID werden automatisch gemappt)
./run_docker.sh
# Build + direkte Ausführung der Pipeline
./run_docker.sh python3.11 run_migration.py \
--batch-id q1_pilot \
--file-list ./q1_files.txt
```
Das Skript:
- baut das Image unter dem Namen `migration-pipeline:ubuntu22.04`,
- mountet das aktuelle Verzeichnis als `/app` (Änderungen wirken sofort),
- mappt die Host-UID/GID, sodass erstellte Dateien dem aufrufenden Benutzer gehören,
- vergibt bei Bedarf `sudo`-Rechte im Container.
### Manuelles `docker build` / `docker run`
```bash
# Image bauen
docker build -t migration-pipeline:ubuntu22.04 .
# API-Key übergeben und Pipeline starten
docker run --rm -it \
-v "$(pwd):/app" \
-w /app \
-e SCW_SECRET_KEY="your_key_here" \
migration-pipeline:ubuntu22.04 \
python3.11 run_migration.py \
--batch-id q1_pilot \
--file-list ./q1_files.txt
# Dry-Run (kein API-Aufruf, kein Key erforderlich)
docker run --rm -it \
-v "$(pwd):/app" \
-w /app \
migration-pipeline:ubuntu22.04 \
python3.11 run_migration.py \
--batch-id q1_pilot \
--file-list ./q1_files.txt \
--dry-run
```
### Hinweise
| Thema | Empfehlung |
|---|---|
| **Scaleway-Env** | Benötigt: `SCW_ACCESS_KEY`, `SCW_SECRET_KEY`, `SCW_DEFAULT_ORGANIZATION_ID`, `SCW_DEFAULT_PROJECT_ID`, `SCW_SERVERLESS` (runtime env oder `.env`) |
| **Qt6 / GTest** | Nicht im Image enthalten – das C++-Repository muss bereits konfigurierte cmake-Targets mitbringen oder per Bind-Mount bereitgestellt werden |
| **Image-Name** | Über die Umgebungsvariable `IMAGE_NAME` anpassbar, z.B. `IMAGE_NAME=my-registry/migration-pipeline:1.0 ./run_docker.sh` |
---
## Ausführung
### Dry-Run (kein API-Aufruf)
```bash
python run_migration.py \
--batch-id q1_pilot \
--file-list ./q1_files.txt \
--dry-run
```
Gibt Dateiliste mit Zeilenanzahl und Chunk-Schätzung aus.
### Pilotbatch Q1
```bash
python run_migration.py \
--batch-id q1_pilot \
--file-list ./q1_files.txt \
--model glm \
--repo-root . \
--source-root ./src
```
### Mit Debug-Ausgabe
```bash
python run_migration.py --batch-id q1_pilot --file-list ./q1_files.txt -v
```
### Alle Optionen
```
--batch-id Eindeutiger Name für diesen Batch (z.B. q1_pilot)
--file-list Pfad zur priorisierten Dateiliste
--repo-root Wurzel des git-Repositories (Standard: .)
--source-root Wurzel des C++ Quellbaums (Standard: .)
--model Modellfamilie für API-Aufrufe: glm|qwen (Standard: glm)
--dry-run Nur Dateiliste und Chunk-Schätzung ausgeben
--verbose, -v Debug-Logging aktivieren
```
---
## Ausgaben
```
workspace/
q1_pilot/
track_a_conservative/ ← git worktree, Branch migration/q1_pilot/track_a_conservative
src/... ← migrierte Quelldateien (nach jedem Erfolg committed)
tests_generated/ ← generierte Unit- und Smoke-Tests
build/ ← isoliertes cmake Build-Verzeichnis
track_b_modern/ ← analog für Track B
quarantine/
MyClass_track_a_conservative_20260708T183200Z.json
MyClass_track_a_conservative_20260708T183200Z.md ← Review-Ticket
reports/
q1_pilot_comparison.md ← Track A vs B Vergleich (Markdown)
q1_pilot_comparison.json ← Track A vs B Vergleich (maschinenlesbar)
```
### Beispiel-Konsolenausgabe
```
================================================================
Batch: q1_pilot
================================================================
Track A (konservativ): 108/115 OK | 7 quarantined | 23 warnings | avg 142s
Track B (modern) : 97/115 OK | 18 quarantined | 41 warnings | avg 198s
================================================================
```
---
## Konfigurationsreferenz
Alle Werte in `migration_pipeline/config.py`:
| Variable | Standard | Beschreibung |
|---|---|---|
| `MODEL_GLM` | `glm-5.2` | Modell-ID der GLM-Familie |
| `MODEL_QWEN` | `qwen3.5-397b-a17b` | Modell-ID der QWEN-Familie |
| `DEFAULT_MODEL_FAMILY` | `glm` | Standard-Auswahl für `--model` |
| `LARGE_FILE_THRESHOLD` | `1000` | Zeilengrenze für Time-Budget (klein/groß) |
| `MAX_CONCURRENT_TASKS` | `3` | Parallele Worker pro Track |
| `CHUNK_SIZE_LINES` | `500` | Max. Zeilen pro Chunk |
| `SOFT_BUDGET_PER_FILE` | `600` s | Soft-Budget (Warnung, kein Abbruch) |
| `HARD_BUDGET_SMALL_FILE` | `600` s | Hard-Budget für kleine Dateien |
| `HARD_BUDGET_LARGE_FILE` | `1500` s | Hard-Budget für große Dateien |
| `MAX_RETRIES` | `3` | Max. Versuche pro Datei und Track |
| `WARNINGS_ARE_BLOCKING` | `False` | `True` = Warnungen blockieren Gate |
| `WORKSPACE_ROOT` | `workspace/` | Verzeichnis für git-Worktrees |
| `QUARANTINE_DIR` | `quarantine/` | Quarantäne-Artefakte |
| `REPORTS_DIR` | `reports/` | Vergleichsreports |
---
## Modulübersicht
| Modul | Verantwortung |
|---|---|
| `config.py` | Zentralkonfiguration, Konstanten, Umgebungsvariablen |
| `models.py` | Dataclasses (`FileResult`, `BatchResult`, `QuarantineEntry` usw.), Enums |
| `chunker.py` | Semantisches C++ Chunking an Klassen-/Funktionsgrenzen |
| `api_client.py` | Scaleway API Client (async, `httpx`); layered API: stateless `call_api` + multi-turn `call_api_multiturn` |
| `build_runner.py` | cmake configure/build, CTest, `compile_commands.json`-Parser |
| `test_generator.py` | QTest/gtest/Smoke-Generierung, idempotente CMakeLists-Registrierung |
| `quality_gate.py` | Aggregierte Gate-Auswertung (Build · bestehende Tests · neue Tests) |
| `quarantine.py` | Fehlerklassifikation, Qt5-API-Erkennung, JSON+Markdown-Ticket |
| `git_manager.py` | `git worktree` Lifecycle, Per-File-Commits |
| `migrator.py` | Per-File Agentenloop: Chunk-Strategie, Multi-Turn-Retry-Konversation, Budget-Kontrolle |
| `orchestrator.py` | Batch-Orchestrierung, Dual-Track parallel, cmake-Konfiguration |
| `reporter.py` | Track A vs B Vergleichsreport (Markdown + JSON) |
| `run_migration.py` | CLI, Argument-Parsing, Dry-Run |
---
## Burn-Phase & Skalierung auf Q2
### Burn-Phase (nach Q1)
1. Beide Branches (`track_a_conservative`, `track_b_modern`) auf identischer Testinfrastruktur deployen.
2. Stabilität, Warnungsfreiheit und Wartbarkeit vergleichen (Report `q1_pilot_comparison.md`).
3. Gewinner-Track als Basis für Q2 wählen (oder hybride Strategie: Track A für risikoarme Module, Track B für gut getestete Module).
### Skalierung auf Q2
| Parameter | Q1 (Pilot) | Q2 (Vollmigration) |
|---|---|---|
| Dateien | ~115 | ~6 500 |
| LOC | ~32 000 | ~1 200 000 |
| Empfohlene Worker | 3 | 5–8 (anpassen je API-Rate-Limit) |
| Batches | 1 | ~57 (à 115 Files) |
| Erwartete Laufzeit* | ~4–6 h | ~200–300 h gesamt |
> *Schätzung bei 3 Workern, avg. 150 s/Datei, 80 % Erfolgsquote.
Empfohlene Batch-Größe für Q2: **100–150 Files** (ca. 30 k LOC), sortiert nach Abhängigkeitstiefe (cmake → Header → Implementierung).
---
## FAQ
**Warum git worktrees statt direktem Arbeiten im Repository?**
Damit Track A und Track B niemals denselben Dateisystem- oder Build-Zustand teilen. Async-Worker würden sich sonst gegenseitig überschreiben.
**Was passiert mit quarantänierten Dateien nach dem Batch?**
Sie werden wiederhergestellt (Original bleibt erhalten), das Ticket landet in `quarantine/`. Die Datei ist mit Fehlerklasse, betroffenen APIs und Empfehlung indiziert und kann gezielt manuell migriert werden.
**Kann ich nur Track A oder nur Track B laufen lassen?**
Aktuell nicht über CLI-Flag. Einfachste Lösung: `TRACK_B_ID` in `orchestrator.py` auskommentieren. Ein `--track` CLI-Parameter ist als spätere Erweiterung vorgesehen.
**Was tun bei Rate-Limiting der Scaleway API?**
`MAX_CONCURRENT_TASKS` in `config.py` reduzieren. Alternativ Exponential-Backoff in `api_client.py` ergänzen.
**Sind die generierten Tests vollständig korrekt?**
Nein – sie sind ein Ausgangspunkt. Die Quality Gates stellen sicher, dass sie *kompilieren und laufen*, aber keine logischen Fehler enthalten. Manuelle Nachkontrolle empfohlen, besonders für fachkritische Module.
**Wie wird mit `.cmake`-Dateien umgegangen?**
`.cmake`-Dateien werden wie C++ Dateien per LLM migriert (cmake-spezifische Qt5→Qt6 Syntax). Da sie nicht kompiliert werden, entfällt der Syntax-Check; die Gates Build/Test gelten nach dem nächsten Full-Build.