docs: LICENCE.md-Feature, .gitignore, Single-GPU-Doku

- pi-coder-judge-extension.ts: licenceMd() + writeLicenceMd() — erzeugt
  proprietäre LICENCE.md in der Doku-Phase (nur wenn noch nicht vorhanden)
- tests: Guards und pure-logic-Tests für LICENCE.md ergänzt (38 Tests, alle grün)
- .gitignore: Node, TypeScript-Artefakte, Backups, Editor-Dateien, OS-Metadaten
- README.md + BEDIENUNGSANLEITUNG.md: vollständig auf Single-GPU-Architektur
  aktualisiert (ein Server, System-Prompt-Rollen, kein Quick-Judge,
  PASS WITH CONCERNS-Verhalten, LICENCE.md in /update_doku, start-single.sh)
- SINGLE_SERVER_PLAN.md: entfernt (umgesetzt, kein Mehrwert mehr)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-06-15 05:03:50 +02:00
commit 485ab30265
7 changed files with 297 additions and 357 deletions

21
.gitignore vendored Normal file
View file

@ -0,0 +1,21 @@
# Node (aktuell keine Dependencies, aber falls welche hinzukommen)
node_modules/
npm-debug.log*
*.log
# TypeScript-/Build-Artefakte
*.tsbuildinfo
dist/
# Backup-Dateien (z. B. settings.json.bak aus dem Install-Skript)
*.bak
# Editor / IDE
.vscode/
.idea/
*.swp
*.swo
# OS
.DS_Store
Thumbs.db

View file

@ -1,8 +1,8 @@
# Bedienungsanleitung: pi_coder
pi_coder ist ein Werkzeug, das zwei lokale KI-Modelle als **Coder** und **Judge** einsetzt,
um Software automatisch zu schreiben, zu prüfen und zu verbessern — alles gesteuert über
einfache Slash-Kommandos in der pi-Agent-Oberfläche.
pi_coder ist ein Werkzeug, das ein lokales KI-Modell abwechselnd als **Coder** und **Judge**
einsetzt, um Software automatisch zu schreiben, zu prüfen und zu verbessern — alles gesteuert
über einfache Slash-Kommandos in der pi-Agent-Oberfläche.
---
@ -25,17 +25,22 @@ einfache Slash-Kommandos in der pi-Agent-Oberfläche.
## 1. Konzept: Coder und Judge
pi_coder verwendet zwei Rollen:
pi_coder verwendet zwei Rollen — bedient von **einem einzigen Modell**, das je nach Aufgabe
eine andere Persona annimmt:
**Coder** (Port 8001): Schreibt und repariert Code. Liest die Aufgabe aus `TASK.md`,
implementiert sie, führt Tests aus und erstellt Git-Commits.
**Coder-Persona**: Schreibt und repariert Code. Liest die Aufgabe aus `TASK.md`, implementiert
sie, führt Tests aus und erstellt Git-Commits.
**Judge** (Port 8002): Überprüft den Code mit dem Blick eines skeptischen Senior-Entwicklers.
**Judge-Persona**: Überprüft den Code mit dem Blick eines skeptischen Senior-Entwicklers.
Prüft Korrektheit, Robustheit, Randfälle, Sicherheit und Produktionsreife. Gibt ein Urteil:
- `PASS` — Code ist in Ordnung
- `PASS WITH CONCERNS` — grundsätzlich akzeptabel, aber mit Anmerkungen
- `PASS WITH CONCERNS` — hat Anmerkungen, die behoben werden müssen (wird wie `FAIL` behandelt,
außer du verwendest `--approve-concerns`)
- `FAIL` — enthält Blocker, die behoben werden müssen
Die Umschaltung zwischen den Rollen erfolgt über unterschiedliche System-Prompts, die die
Extension automatisch vor jeder Inference einsetzt. Es läuft kein zweiter Server.
Der Grundgedanke: Coder und Judge haben keine „Höflichkeitsschranke" zueinander —
der Judge kritisiert direkt und konkret, der Coder repariert ohne Widerspruch.
@ -47,14 +52,14 @@ der Judge kritisiert direkt und konkret, der Coder repariert ohne Widerspruch.
```bash
cd ~/pi_coder
./start-servers.sh
./start-single.sh
```
Ausgabe bei Erfolg:
```
[*] Starte beide Server parallel ...
[✓] Coder (:8001) bereit
[✓] Judge (:8002) bereit
[*] Verwende HF_HOME = /home/.../huggingface
[*] GPU device = 1
[✓] Single-Server (:8001) bereit
```
Dauer: bis zu 5 Minuten (Modell wird in GPU-VRAM geladen; neuere llama.cpp-Versionen
@ -68,8 +73,7 @@ prüfen beim Start die VRAM-Verfügbarkeit, was zusätzliche Zeit kostet).
```
=== LLaMA-Server Status ===
qwen36-27b-coder (Port 8001): Container=RUNNING HTTP=OK
qwen36-27b-judge (Port 8002): Container=RUNNING HTTP=OK
qwen36-27b-single (Port 8001): Container=RUNNING HTTP=OK
```
### pi agent öffnen
@ -86,53 +90,41 @@ pi
## 3. Server starten und stoppen
### GPU-Zuordnung (3-GPU-Setup)
### GPU-Zuordnung
Bei einem System mit drei GPUs sind die Rollen fest in den Startskripten verankert —
**keine manuelle Konfiguration nötig:**
Der Server läuft standardmäßig auf **GPU 1** (überschreibbar):
| GPU | Modell | Rolle |
|-----|--------|-------|
| GPU 0 (NVIDIA T600) | — | Display / Monitor — wird von den Skripten nicht angetastet |
| GPU 1 (RTX 3090, 24 GB) | Qwen3.6-27B | **Coder** (Port 8001) |
| GPU 2 (RTX 3090, 24 GB) | Qwen3.6-27B | **Judge** (Port 8002) |
```bash
./start-single.sh # GPU 1 (Standard)
GPU_DEVICE=0 ./start-single.sh # GPU 0
```
Jeder Server bekommt seine eigene GPU exklusiv — so gibt es keinen VRAM-Konflikt,
auch wenn beide gleichzeitig laufen.
Auf einem System mit mehreren GPUs empfiehlt es sich, GPU 0 (Display) freizuhalten
und GPU 1 für den KI-Server zu verwenden.
**Wichtig:** Andere GPU-Prozesse (z. B. TTS-Dienste, Ollama) auf GPU 1 oder GPU 2 können
VRAM belegen und dazu führen, dass ein Server nicht startet. Im Zweifel prüfen:
**Wichtig:** Andere GPU-Prozesse (z. B. TTS-Dienste, Ollama) auf der verwendeten GPU
können VRAM belegen und dazu führen, dass der Server nicht startet. Im Zweifel prüfen:
```bash
nvidia-smi --query-gpu=index,name,memory.used,memory.free --format=csv
```
### Beide starten (empfohlen)
### Server starten
```bash
cd ~/pi_coder
./start-servers.sh
./start-single.sh
```
Erwartete Ausgabe:
```
[*] Starte beide Server parallel ...
[✓] Coder (:8001) bereit
[✓] Judge (:8002) bereit
[*] Verwende HF_HOME = /home/.../huggingface
[*] GPU device = 1
[✓] Single-Server (:8001) bereit
```
Dauer: bis zu 5 Minuten. Danach sind beide Modelle im VRAM und sofort antwortbereit.
Dauer: bis zu 5 Minuten. Danach ist das Modell im VRAM und sofort antwortbereit.
### Einzelnen Server neu starten
Z.B. wenn nur der Judge-Server abgestürzt ist:
```bash
./start-judge.sh # Port 8002, GPU 2
# oder:
./start-coder.sh # Port 8001, GPU 1
```
### Beide stoppen
### Server stoppen
```bash
./stop-servers.sh
@ -149,7 +141,7 @@ Z.B. wenn nur der Judge-Server abgestürzt ist:
Falls die GGUF-Datei an einem anderen Ort liegt:
```bash
HF_HOME=/mnt/daten/huggingface ./start-servers.sh
HF_HOME=/mnt/daten/huggingface ./start-single.sh
```
---
@ -235,7 +227,7 @@ Der Judge:
3. Führt Tests aus
4. Gibt ein strukturiertes Urteil aus
**Beispiel-Ausgabe PASS:**
**Beispiel-Ausgabe PASS WITH CONCERNS:**
```
Urteil: PASS WITH CONCERNS
@ -326,17 +318,22 @@ Empfohlene Sofortmaßnahmen: keine
- `--continue` — überspringt die Implementierungsphase und startet direkt mit dem
Judge→Fix-Zyklus ab dem aktuellen Code-Stand. Nützlich wenn man bereits manuell
`/coder`, `/judge` und `/fix` durchgeführt hat und den Rest automatisieren möchte.
Im `--continue`-Modus werden Coder- und Judge-Server gleichzeitig geprüft.
- `--interactive` — pausiert nach erstem PASS für einen menschlichen Checkpoint.
Details: siehe [Interactive-Modus](#interactive-modus) weiter unten.
- `--no-tests` — überspringt die automatische Test-Erkennung. Sinnvoll wenn keine
Test-Suite vorhanden ist oder Tests über externe Infrastruktur laufen.
- `--approve-concerns` — behandelt „PASS WITH CONCERNS" wie „PASS": kein ShipIt-Call,
direktes SHIP. Für Projekte, bei denen du dem Judge-Urteil vertraust.
direktes SHIP. Standardmäßig löst PASS WITH CONCERNS immer einen Coder-Fix aus.
- `--test-cmd "befehl"` — überschreibt die automatische Test-Erkennung mit einem
eigenen Befehl (z.B. `--test-cmd "pytest tests/ -x"`).
- `--test-timeout N` — maximale Laufzeit pro Test-Befehl in Sekunden (Standard: 120).
### PASS WITH CONCERNS — wichtig
Ohne `--approve-concerns` gilt `PASS WITH CONCERNS` **wie `FAIL`** — der Coder bekommt
einen Fix-Auftrag und der Loop geht weiter. Erst sauberes `PASS` beendet die Schleife.
Mit `--approve-concerns` gilt PASS WITH CONCERNS als bestanden.
### Beispiel: einfacher Auftrag
```
@ -346,31 +343,33 @@ Empfohlene Sofortmaßnahmen: keine
Was im Hintergrund passiert:
```
Phase 1: Coder implementiert...
Phase 2: Runde 1/2: Quick-Check (kompakter Erstcheck)...
Phase 2: Runde 1/2: Judge — TASK.md + letzter Commit + Tests...
→ Urteil: FAIL (2 Blocker)
Phase 3: Runde 1/2: Coder fixt...
Phase 4: Runde 2/2: Judge — TASK.md + letzter Commit + Tests...
→ Urteil: PASS WITH CONCERNS
✓ PASS WITH CONCERNS nach Runde 2
Finale ShipIt-Prüfung... (nur bei PASS WITH CONCERNS, nicht bei klarem PASS)
→ SHIP
→ Urteil: PASS
✓ PASS nach Runde 2 → SHIP
[Dialog: Version → v0.1.0 (empfohlen)]
```
**Runde 1 = Quick-Check:** Kompakter Prompt ohne TASK.md-Analyse — erkennt offensichtliche
Fehler schnell. Erst ab Runde 2 (oder bei `--continue`) kommt der vollständige Judge-Prompt.
Bei `PASS WITH CONCERNS` (ohne --approve-concerns):
```
→ Urteil: PASS WITH CONCERNS (Major: fehlendes --help)
Phase 3: Runde 2/2: Coder fixt Concerns...
Phase 4: Runde 3/3: Judge...
→ Urteil: PASS
✓ PASS → SHIP
```
Bei klarem `PASS` entfällt die ShipIt-Runde — es wird direkt SHIP ausgelöst.
Mit `--approve-concerns` gilt das auch für `PASS WITH CONCERNS`.
Während des Ablaufs zeigt die Statuszeile die aktuelle Phase — mit laufendem Timer,
der beweist, dass die LLM tatsächlich arbeitet (und nicht hängt):
Während des Ablaufs zeigt die Statuszeile die aktuelle Phase — mit laufendem Timer:
```
◉ Coder implementiert: Login-Flow mit JWT [01:23]
◉○ Runde 1/2: Quick-Check [00:47]
●◉ Runde 2/2: Coder fixt — fehlendes Null-Check [01:05]
●● ✓ PASS nach Runde 2/2 — ShipIt…
◉○ Runde 1/2: Judge — TASK.md + letzter Commit + Tests [00:47]
●◉ Runde 1/2: Coder fixt — fehlendes Null-Check [01:05]
●● ✓ PASS nach Runde 2/2 — SHIP
🚀 SHIP produktionsreif
```
@ -392,6 +391,7 @@ Nach SHIP werden automatisch ausgeführt:
1. Code-Kommentare einfügen
2. README.md schreiben
3. BEDIENUNGSANLEITUNG.md schreiben
4. LICENCE.md anlegen (falls noch nicht vorhanden)
### Vom manuellen Workflow in den automatischen wechseln
@ -415,7 +415,8 @@ Stand und der Fix-Zyklus läuft automatisch bis PASS oder max. N Runden.
### Loop-Erkennung
Wenn zweimal hintereinander genau dieselben Blocker auftreten, bricht `/optimize` ab:
Wenn zweimal hintereinander genau dieselben Blocker oder Concerns auftreten, bricht
`/optimize` ab:
```
⚠ Derselbe Blocker tritt erneut auf Schleife abgebrochen. Bitte manuell prüfen.
```
@ -563,11 +564,14 @@ Fix: Validierung in die Funktion generate_password() verschieben statt in parse_
## 8. Dokumentation generieren: /update_doku
Nach Abschluss der Entwicklung (nach `/shipit` oder `/optimize`) erstellt `/update_doku`
drei Dinge automatisch:
vier Dinge automatisch:
1. **Code-Kommentare** — erklärt das WARUM in den Quelldateien (Deutsch)
2. **README.md** — Entwicklerperspektive: Installation, Build, Verwendung
3. **BEDIENUNGSANLEITUNG.md** — Endnutzerperspektive: einfach, ohne Jargon
4. **LICENCE.md** — proprietäre Lizenz mit aktuellem Jahr und Inhaber aus `git config user.name`
(wird nur angelegt wenn noch keine `LICENCE.md` vorhanden ist — existierende Lizenzen
werden nie überschrieben)
```
/update_doku
@ -582,6 +586,7 @@ Nur geänderte Quelldateien werden neu kommentiert — unveränderte bleiben una
Code-Kommentare: keine Änderungen seit letztem Lauf übersprungen.
README.md: 2 Datei(en) geändert wird geprüft
BEDIENUNGSANLEITUNG.md: 2 Datei(en) geändert wird geprüft
LICENCE.md: bereits vorhanden übersprungen.
```
### Zusammen mit /optimize
@ -772,7 +777,7 @@ Die Checkboxen werden automatisch abgehakt:
# Für Projekte wo "PASS WITH CONCERNS" ausreicht:
/optimize "Kleines Refactoring" --approve-concerns
# Kombination: kein Test, kein ShipIt bei Concerns, 1 Runde
# Kombination: kein Test, PASS WITH CONCERNS akzeptieren, 1 Runde
/optimize "Typo-Fix in Fehlermeldungen" --rounds 1 --no-tests --approve-concerns
```
@ -851,7 +856,7 @@ Die Checkboxen werden automatisch abgehakt:
ls $HF_HOME/models/qwen3/
# Oder mit explizitem Pfad starten:
HF_HOME=/korrekter/pfad ./start-servers.sh
HF_HOME=/korrekter/pfad ./start-single.sh
```
### Server startet nicht / HTTP nicht erreichbar
@ -864,26 +869,26 @@ HF_HOME=/korrekter/pfad ./start-servers.sh
1. Zu wenig VRAM — Container bricht beim Laden ab:
```bash
docker logs qwen36-27b-coder | tail -50
docker logs qwen36-27b-single | tail -50
# Suche nach: "CUDA out of memory" oder "failed to allocate"
```
→ Kontext reduzieren: `-c 32768` statt `-c 131072`
→ Kontext reduzieren: `-c 65536` statt `-c 262144` in `start-single.sh`
2. GPU nicht verfügbar:
```bash
nvidia-smi # alle drei GPUs sichtbar?
# GPU 1 (Coder) testen:
nvidia-smi # GPU sichtbar?
# GPU 1 testen:
docker run --gpus '"device=1"' --rm nvidia/cuda:12.0-base nvidia-smi
# GPU 2 (Judge) testen:
docker run --gpus '"device=2"' --rm nvidia/cuda:12.0-base nvidia-smi
# Andere GPU testen:
GPU_DEVICE=0 ./start-single.sh
```
3. Port bereits belegt:
3. Port 8001 bereits belegt:
```bash
ss -tlnp | grep 800[12]
ss -tlnp | grep 8001
docker ps -a # alter Container noch vorhanden?
./stop-servers.sh
./start-servers.sh
./start-single.sh
```
### "Agent is already processing a prompt"
@ -914,8 +919,8 @@ Lies die Datei neu ein und wende die Änderungen als unified diff mit apply_patc
⚠ Derselbe Blocker tritt erneut auf Schleife abgebrochen.
```
**Ursache:** Der Coder kann einen bestimmten Blocker nicht beheben — z.B. weil die
Aufgabe einen Widerspruch enthält oder ein externes System fehlt.
**Ursache:** Der Coder kann einen bestimmten Blocker oder Concern nicht beheben — z.B.
weil die Aufgabe einen Widerspruch enthält oder ein externes System fehlt.
**Lösung:** Manuell eingreifen:
```
@ -927,9 +932,14 @@ Dann den Blocker analysieren und entweder:
- Die Aufgabe in TASK.md präzisieren
- Bei komplexen Aufgaben mit mehr Runden wiederholen: `/optimize --continue --rounds 5`
Falls der Judge wiederholt Concerns findet, die du akzeptierst:
```
/optimize --continue --approve-concerns
```
### Server läuft, aber pi wechselt nicht das Modell
**Ursache:** `models.json` wurde nach einer Änderung nicht neu deployt.
**Ursache:** `models.json` oder `settings.json` wurde nach einer Änderung nicht neu deployt.
**Lösung:**
```bash

236
README.md
View file

@ -1,8 +1,9 @@
# pi_coder — Automatisierter Coder/Judge-Workflow für pi agent
# pi_coder_2 — Automatisierter Coder/Judge-Workflow für pi agent
Dieses Repository enthält die Konfiguration und Skripte für einen automatisierten
Coding-Workflow mit zwei lokalen LLaMA-Modellen: ein Coder-Modell und ein Judge-Modell,
gesteuert über [pi agent](https://github.com/earendil-works/pi).
Coding-Workflow mit einem lokalen LLaMA-Modell, das abwechselnd als Coder und Judge
agiert — gesteuert über Pi Agent. Er dreht solange Optimierungsschleifen, bis das
Programm Produktionsreife hat.
---
@ -12,37 +13,40 @@ gesteuert über [pi agent](https://github.com/earendil-works/pi).
Nutzer gibt Auftrag
/coder → qwen3.5-coder (:8001) → Implementierung + git commit
/coder → qwen3.5-single (:8001, Coder-Persona) → Implementierung + git commit
/judge → qwen3.5-judge (:8002) → Review: PASS / FAIL + Blocker
/judge → qwen3.5-single (:8001, Judge-Persona) → Review: PASS / FAIL + Blocker
FAIL? ▼
/fix → qwen3.5-coder (:8001) → Fixes + git commit
/fix → qwen3.5-single (:8001, Coder-Persona) → Fixes + git commit
PASS WITH CONCERNS? ▼
│ (ohne --approve-concerns: wie FAIL → weiterer Fix-Zyklus)
PASS? ▼
/shipit → qwen3.5-judge (:8002) → Finale Freigabe: SHIP / NO-SHIP
(nur bei "PASS WITH CONCERNS" klares PASS → direkt SHIP)
/shipit → qwen3.5-single (:8001, Judge-Persona) → Finale Freigabe: SHIP / NO-SHIP
(nur bei "PASS WITH CONCERNS" --approve-concerns; klares PASS → direkt SHIP)
/optimize = Coder→Judge→Fix-Schleife automatisch (bis PASS oder max. N Runden)
--interactive: pausiert nach PASS für menschlichen Checkpoint + optionale Zusatzaufträge
```
Beide Modelle laufen als **separate llama.cpp-Docker-Container** und sprechen eine
OpenAI-kompatible API (`/v1/chat/completions`). pi agent wechselt automatisch zwischen
den Endpunkten wenn du ein `/judge`-, `/fix`- oder `/coder`-Kommando aufrufst.
Ein **einzelner** llama.cpp-Docker-Container bedient beide Rollen. Die Rollenumschaltung
(Coder ↔ Judge) erfolgt über unterschiedliche System-Prompts, die der `before_agent_start`-Hook
der Extension vor jeder Inference injiziert. pi agent wechselt keine Server mehr —
nur die Persona im System-Prompt ändert sich.
---
## Modelle
| Rolle | Modell | Port | Container | Alias | GPU |
|--------|---------------------------------------------------|------|------------------|----------------|----------|
| Coder | Qwen3.6-27B-Uncensored-HauhauCS-Aggressive-IQ4_XS | 8001 | qwen36-27b-coder | qwen3.5-coder | device=1 |
| Judge | Qwen3.6-27B-Uncensored-HauhauCS-Aggressive-IQ4_XS | 8002 | qwen36-27b-judge | qwen3.5-judge | device=2 |
|----------------|---------------------------------------------------|------|--------------------|----------------|----------|
| Coder + Judge | Qwen3.6-27B-Uncensored-HauhauCS-Aggressive-IQ4_XS | 8001 | qwen36-27b-single | qwen3.5-single | device=1 |
Beide Container verwenden dasselbe GGUF-Datei, aber mit unterschiedlichen
Serverparametern (Kontext, Temperatur, Parallelität).
Coder- und Judge-Rollen werden durch verschiedene System-Prompts realisiert —
dasselbe Modell, derselbe Container, unterschiedliche Persona.
---
@ -58,12 +62,12 @@ Serverparametern (Kontext, Temperatur, Parallelität).
sudo apt-get update && sudo apt-get install -y nvidia-container-toolkit
sudo systemctl restart docker
```
- NVIDIA-GPUs: empfohlen 2 × RTX 3090 (je 24 GB VRAM) für Coder und Judge parallel.
Bei 3 GPUs: GPU 0 für Display reservieren, GPU 1 → Coder, GPU 2 → Judge.
- NVIDIA-GPU: empfohlen RTX 3090 (24 GB VRAM). Standard: GPU 1. Überschreibbar mit
`GPU_DEVICE=0 ./start-single.sh`. Bei 3 GPUs: GPU 0 für Display reservieren.
- GGUF-Modell vorhanden unter:
`$HF_HOME/models/qwen3/Qwen3.6-27B-Uncensored-HauhauCS-Aggressive-IQ4_XS.gguf`
- Standard-Pfad: `HF_HOME=/home/dschlueter/nvme2n1p7_home/huggingface`
- Überschreibbar: `HF_HOME=/anderer/pfad ./start-servers.sh`
- Überschreibbar: `HF_HOME=/anderer/pfad ./start-single.sh`
- [pi agent](https://github.com/earendil-works/pi) installiert (`~/.pi/`)
---
@ -75,16 +79,20 @@ Serverparametern (Kontext, Temperatur, Parallelität).
git clone https://kitux.de/forgejo/dschlueter/pi_coder.git ~/pi_coder
cd ~/pi_coder
# 2. Extension und Modell-Config nach ~/.pi/agent/ deployen
# 2. Extension, Modell-Config und settings.json nach ~/.pi/agent/ deployen
./install_servers_and_pi_coder_extension.sh
# 3. pi agent neu laden (in der pi-Oberfläche)
# /reload
# 4. Server starten
./start-servers.sh
./start-single.sh
```
Das Install-Skript sichert eine vorhandene `~/.pi/agent/settings.json` als `.bak` und
kopiert dann `settings.json` aus dem Repo. Damit ist der korrekte Default-Provider
(`llama-cpp-single/qwen3.5-single`) automatisch konfiguriert.
Nach späteren Änderungen an `pi-coder-judge-extension.ts` oder `models.json`:
```bash
./install_servers_and_pi_coder_extension.sh # kopiert nach ~/.pi/agent/
@ -96,95 +104,71 @@ Nach späteren Änderungen an `pi-coder-judge-extension.ts` oder `models.json`:
## Server starten / stoppen / status
```bash
# Beide Server parallel starten (empfohlen — dauert 13 Minuten)
./start-servers.sh
# Server starten (empfohlen — dauert 13 Minuten)
./start-single.sh
# Einzeln starten (z.B. nur einen neu starten)
./start-coder.sh # Port 8001
./start-judge.sh # Port 8002
# Server auf anderer GPU starten
GPU_DEVICE=0 ./start-single.sh
# Beide stoppen
# Stoppen
./stop-servers.sh
# Status beider Server prüfen
# Status prüfen
./status.sh
```
`start-servers.sh` startet beide Container gleichzeitig und wartet bis beide
HTTP-ready sind (max. 5 Minuten — die neue llama.cpp-Version prüft beim Start, ob
Modell + KV-Cache in den VRAM passen, was zusätzliche Zeit kostet). Logs werden
getrennt gesammelt und nur bei Fehler ausgegeben.
`start-single.sh` startet den Container und wartet bis HTTP-ready (max. 5 Minuten — die
neue llama.cpp-Version prüft beim Start, ob Modell + KV-Cache in den VRAM passen, was
zusätzliche Zeit kostet).
Wenn Server bereits laufen und du `start-servers.sh` (oder ein Einzelskript)
aufrufst, werden die laufenden Container zuerst per `docker rm -f` gestoppt
und dann neu gestartet — ein laufender Inference-Request wird dabei abgebrochen.
Wenn der Server bereits läuft und du `start-single.sh` aufrufst, wird der laufende
Container zuerst per `docker rm -f` gestoppt und dann neu gestartet — ein laufender
Inference-Request wird dabei abgebrochen.
---
## llama.cpp-Serverparameter im Detail
### Gemeinsame Parameter
### Parameter des Single-Servers (Port 8001)
| Parameter | Wert | Bedeutung |
|---|---|---|
| `--jinja` | — | Verwendet das im GGUF eingebettete Jinja-Chat-Template (Qwen-Format). Notwendig für korrekte `<\|im_start\|>`-Tokens. |
| `--reasoning on` | — | Aktiviert das interne Thinking-Budget des Modells (Qwen3-spezifisch). Ersetzt das frühere `--chat-template-kwargs '{"enable_thinking":true}'`. |
| `--reasoning on` | — | Aktiviert das interne Thinking-Budget des Modells (Qwen3-spezifisch). |
| `--no-context-shift` | — | Kontextfenster wird **nicht** verschoben wenn es voll ist — stattdessen Fehler. Verhindert stille Datenverluste. |
| `--repeat-penalty 1.05` | — | Leichte Penalty für Wiederholungen. Wert > 1.0 unterdrückt Loops. |
| `--top-k 20` | — | Nur die 20 wahrscheinlichsten nächsten Tokens werden berücksichtigt. |
| `--repeat-penalty 1.05` | — | Leichte Penalty für Wiederholungen. |
| `--top-k 20` | — | Nur die 20 wahrscheinlichsten nächsten Tokens. |
| `--min-p 0.01` | — | Tokens mit Wahrscheinlichkeit < 1 % des wahrscheinlichsten Tokens werden ausgeschlossen. |
| `-ngl 999` | — | Alle Layer auf die GPU laden (999 = „alle"). Bei zu wenig VRAM reduzieren. |
| `-fa on` | — | Flash Attention — schnellere Attention-Berechnung, weniger VRAM für den Attention-Pass. |
| `--kv-unified` | — | Einheitlicher KV-Cache über alle Schichten. Effizienter bei langen Kontexten. |
| `--cache-type-k q4_0` | — | KV-Cache Keys in 4-Bit quantisiert. Spart ~75 % VRAM gegenüber fp16 — nötig für 256K Kontext auf einer 24-GB-GPU. |
| `-ngl 999` | — | Alle Layer auf die GPU laden. |
| `-fa on` | — | Flash Attention. |
| `--kv-unified` | — | Einheitlicher KV-Cache über alle Schichten. |
| `--cache-type-k q4_0` | — | KV-Cache Keys 4-Bit quantisiert. Nötig für 256K Kontext auf einer 24-GB-GPU. |
| `--cache-type-v q4_0` | — | KV-Cache Values ebenfalls 4-Bit quantisiert. |
| `--cont-batching` | — | Continuous Batching: neue Anfragen werden in laufende Batches eingefügt — höherer Durchsatz bei mehreren parallelen Anfragen. |
| `--main-gpu 0` | — | GPU-Index (0 = erste des Containers) für Nicht-Tensor-Operationen. |
| `--gpus '"device=1"'` (Coder) | — | Docker: nur GPU 1 dem Coder-Container zuweisen. |
| `--gpus '"device=2"'` (Judge) | — | Docker: nur GPU 2 dem Judge-Container zuweisen. |
### Coder-Server (Port 8001) — optimiert für Coding-Aufgaben
| Parameter | Wert | Erklärung / Wirkung |
|---|---|---|
| `-c 262144` | 256K Tokens | Sehr großes Kontextfenster: gesamte Codebasis + langer Gesprächsverlauf passt rein. **Sehr hoher VRAM-Bedarf.** Reduziere auf `65536` wenn VRAM knapp. |
| `-n 16384` | 16K Tokens | Maximale Ausgabelänge pro Anfrage. Für Kommentieraufgaben (`/update_doku`) nötig. |
| `--temp 0.2` | — | Niedrige Temperatur: deterministisch, konsistenter Code. Erhöhe auf `0.40.6` für kreativere Lösungsansätze. |
| `--top-p 0.95` | — | Nucleus Sampling: 95 % der Wahrscheinlichkeitsmasse. Passend zu temp 0.2. |
| `--batch-size 1024` | — | Prompt-Verarbeitungs-Batch. Größer = schnelleres Einlesen langer Dateien. |
| `--ubatch-size 512` | — | Micro-Batch für GPU-Kernel. Muss ≤ batch-size sein. |
| `--parallel 2` | — | 2 gleichzeitige Request-Slots. Nützlich wenn pi agent schnell Folgeanfragen schickt. |
### Judge-Server (Port 8002) — optimiert für Reviews
| Parameter | Wert | Erklärung / Wirkung |
|---|---|---|
| `-c 262144` | 256K Tokens | Großes Kontextfenster: nötig bei langen /optimize-Runden, wo der Gesprächsverlauf stark anwächst. |
| `-n 16384` | 16K Tokens | Lange Reviews und Begründungen passen vollständig in die Ausgabe. |
| `--temp 0.1` | — | Sehr niedrige Temperatur: maximale Konsistenz und Reproduzierbarkeit der Urteile. |
| `--top-p 0.9` | — | Etwas enger als beim Coder — weniger Variation im Urteil gewünscht. |
| `--batch-size 512` | — | Kleiner als beim Coder — Judge bekommt selten sehr lange Prompts. |
| `--ubatch-size 256` | — | Entsprechend kleiner. |
| `--parallel 1` | — | Judge-Aufgaben sind immer sequenziell im Workflow, daher 1 Slot ausreichend. |
| `--cont-batching` | — | Continuous Batching. |
| `-c 262144` | 256K | Großes Kontextfenster für langen Gesprächsverlauf im Optimize-Loop. |
| `-n 16384` | 16K | Maximale Ausgabelänge. |
| `--temp 0.2` | — | Deterministisch — Coder-Rolle. Der Judge-System-Prompt gleicht die etwas höhere Temperatur aus. |
| `--parallel 2` | — | 2 Slots, damit pi agent Folgeanfragen schnell hintereinander stellen kann. |
| `--gpus '"device=1"'` | — | Standard: GPU 1. Überschreibbar: `GPU_DEVICE=0 ./start-single.sh`. |
---
## GPU-Konfiguration und VRAM
### Aktuelles Setup (1 GPU pro Server)
### Aktuelles Setup (1 GPU, 1 Server)
Jeder Server bekommt eine dedizierte GPU — dadurch kein VRAM-Konflikt:
Ein einzelner Container bedient beide Rollen sequenziell:
```
GPU 0 NVIDIA T600 → Display / Monitor (nicht für KI genutzt)
GPU 1 RTX 3090 (24 GB) → qwen36-27b-coder (Port 8001)
GPU 2 RTX 3090 (24 GB) → qwen36-27b-judge (Port 8002)
GPU 1 RTX 3090 (24 GB) → qwen36-27b-single (Port 8001, Coder + Judge)
```
Die Skripte verwenden `--gpus '"device=1"'` (Coder) bzw. `'"device=2"'` (Judge).
Innerhalb des Containers ist dies stets `CUDA:0`, daher `--main-gpu 0` ohne `--tensor-split`.
Da Coder und Judge nicht gleichzeitig inferieren, gibt es keinen VRAM-Konflikt.
Die Umschaltung zwischen den Rollen erfolgt ausschließlich über den System-Prompt —
der Container läuft ohne Unterbrechung durch.
### VRAM-Abschätzung für das 27B IQ4_XS-Modell (eine GPU)
### VRAM-Abschätzung für das 27B IQ4_XS-Modell
| Komponente | Größe (ca.) |
|---|---|
@ -194,36 +178,28 @@ Innerhalb des Containers ist dies stets `CUDA:0`, daher `--main-gpu 0` ohne `--t
| KV-Cache bei 65 536 Tokens (q4_0) | ~23 GB |
| **Summe (262k Kontext)** | **~2224 GB** |
Bei einer **24-GB-GPU** ist 262k Kontext knapp kalkuliert. Wichtig: keine anderen Prozesse
Bei einer **24-GB-GPU** ist 262k Kontext knapp kalkuliert. Keine anderen Prozesse
dürfen nennenswert VRAM belegen (z. B. TTS-Dienste, Ollama). Prüfen mit:
```bash
nvidia-smi --query-gpu=index,memory.used,memory.free --format=csv
```
Falls der VRAM nicht reicht: Kontext in den Startskripten auf `131072` oder `65536` reduzieren.
Falls der VRAM nicht reicht: Kontext in `start-single.sh` auf `131072` oder `65536` reduzieren.
### Anpassung für andere GPU-Konfigurationen
```bash
# 2 GPUs (beide für KI, keine Display-GPU):
--gpus '"device=0"' # Coder
--gpus '"device=1"' # Judge
# Andere GPU verwenden:
GPU_DEVICE=0 ./start-single.sh
# 1 GPU (beide Server auf gleicher GPU — nicht empfohlen, VRAM-Konflikt):
--gpus '"device=0"' # beide Server
--tensor-split 0.5,0.5 # Modell hälftig aufteilen
-c 65536 # Kontext stark reduzieren
# 2 GPUs für einen Server (wenn sehr großer Kontext nötig):
# 2 GPUs für einen Server (sehr großer Kontext):
# start-single.sh anpassen:
--gpus '"device=0,1"' \
--tensor-split 0.5,0.5 \
--main-gpu 0 \
-c 262144
```
Bei einer **16-GB-GPU** ist die Modellgröße allein schon grenzwertig.
Entweder ein kleineres Modell verwenden oder die Quantisierung erhöhen (IQ3_XS, Q4_K_M).
---
## Parameter-Tuning-Guide
@ -232,23 +208,18 @@ Entweder ein kleineres Modell verwenden oder die Quantisierung erhöhen (IQ3_XS,
| Wert | Eignung |
|---|---|
| `0.00.1` | Maximale Reproduzierbarkeit. Gut für Judge/Review. |
| `0.10.3` | Guter Kompromiss für Coding. **Empfohlen für Coder.** |
| `0.40.6` | Kreativere Lösungen, mehr Varianz. Sinnvoll für Prototyping. |
| `0.71.0` | Kreativschreiben, Brainstorming. Für Coding meist zu viel Rauschen. |
| `0.00.1` | Maximale Reproduzierbarkeit. |
| `0.10.3` | Guter Kompromiss für Coding. **Aktuell gesetzt: 0.2.** |
| `0.40.6` | Kreativere Lösungen. |
| `0.71.0` | Für Coding meist zu viel Rauschen. |
### Kontextgröße (`-c`)
Je größer der Kontext, desto mehr VRAM braucht der KV-Cache.
Faustregel: KV-Cache ≈ `context_size × layers × head_dim × 2 × bytes_per_element`.
Bei q8_0 (1 Byte/Element) und Qwen3-27B (28 Schichten, 128 Head-Dim, 32 Heads):
KV-Cache ≈ `context_size × 28 × 128 × 32 × 2 × 1 Byte ≈ context_size × 0,23 MB`
| Kontext | KV-Cache (q4_0) | Empfehlung |
|---|---|---|
| 32 768 | ~1,9 GB | 1 × 16-GB-GPU |
| 65 536 | ~3,7 GB | 1 × 24-GB-GPU |
| 131 072 | ~7,5 GB | 2 × 16-GB-GPU |
| 65 536 | ~3,7 GB | 1 × 24-GB-GPU (konservativ) |
| 131 072 | ~7,5 GB | 1 × 24-GB-GPU |
| 262 144 | ~810 GB | 1 × 24-GB-GPU (q4_0) — **aktuell gesetzt** |
### KV-Cache-Quantisierung
@ -257,31 +228,35 @@ KV-Cache ≈ `context_size × 28 × 128 × 32 × 2 × 1 Byte ≈ context_size ×
|---|---|---|
| `f16` | 100 % (Basis) | Referenz |
| `q8_0` | ~50 % | Kaum merklich schlechter |
| `q4_0` | ~25 % | Merklicher Qualitätsverlust bei langen Kontexten — aber nötig für 256K Kontext auf 2× 24 GB. **Aktuell gesetzt.** |
| `q4_0` | ~25 % | Merklicher Qualitätsverlust bei langen Kontexten — aber nötig für 256K Kontext auf 24 GB. **Aktuell gesetzt.** |
### Parallelität (`--parallel`)
---
Mehr parallele Slots erhöhen den Durchsatz bei gleichzeitigen Anfragen, aber jeder Slot
reserviert Speicher im KV-Cache. Im pi-coder-Workflow sind echte Parallelaufrufe selten,
daher ist `--parallel 1` für den Judge ausreichend. Coder `--parallel 2` bietet Puffer
wenn pi agent Folgeanfragen schnell hintereinander schickt.
## Tests ausführen
```bash
./run-tests.sh
```
Keine npm-Installation nötig — nutzt den integrierten `node:test`-Runner und
`--experimental-strip-types` für TypeScript. Testet Parsing-Logik, Konfigurationskonsistenz
und Extension-Guards gegen Regressionen.
---
## Dateien
| Datei | Zweck |
| Datei / Verzeichnis | Zweck |
|---|---|
| `pi-coder-judge-extension.ts` | pi agent Extension (Kommandos, Tools, Hooks) |
| `pi-coder-judge-extension.ts` | pi agent Extension (Kommandos, Tools, Hooks, Rollen-Logik) |
| `models.json` | Provider- und Modell-Konfiguration für pi agent |
| `test-utils.ts` | Unit-Tests für Hilfsfunktionen der Extension |
| `run-tests.sh` | Unit-Tests ausführen (TypeScript-Stripping + node) |
| `start-servers.sh` | Beide Server parallel starten (empfohlen) |
| `start-coder.sh` | Nur Coder-Container starten (Port 8001) |
| `start-judge.sh` | Nur Judge-Container starten (Port 8002) |
| `stop-servers.sh` | Beide Container stoppen |
| `status.sh` | Laufstatus beider Server anzeigen |
| `install_servers_and_pi_coder_extension.sh` | Extension + models.json nach `~/.pi/agent/` kopieren |
| `settings.json` | pi-agent-Settings (Default-Modell, aktivierte Modelle) — wird von Install-Skript deployt |
| `run-tests.sh` | Tests ausführen |
| `tests/` | Testdateien (pure-logic, config, extension-guards, scripts) |
| `start-single.sh` | Single-Server starten (Port 8001, Coder + Judge) |
| `stop-servers.sh` | Container stoppen |
| `status.sh` | Laufstatus anzeigen |
| `install_servers_and_pi_coder_extension.sh` | Extension + models.json + settings.json nach `~/.pi/agent/` deployen |
| `examples/` | Demo-Projekte (Python, Rust, Go, C, Bash, HTML/JS) für Live-Demos |
| `examples/restore-all.sh` | Examples nach Demo-Lauf in Ausgangszustand zurücksetzen |
@ -289,18 +264,18 @@ wenn pi agent Folgeanfragen schnell hintereinander schickt.
## pi-Kommandos (Kurzübersicht)
| Kommando | Modell | Beschreibung |
| Kommando | Persona | Beschreibung |
|---|---|---|
| `/coder <auftrag>` | Coder | TASK.md anlegen, Implementierung starten |
| `/judge [fokus]` | Judge | Code-Review gegen TASK.md + letzten Commit |
| `/fix [hinweis]` | Coder | Judge-Kritik beheben, committen |
| `/shipit` | Judge | Finale Freigabeprüfung |
| `/optimize <auftrag> [--rounds N] [--with-doku] [--continue] [--interactive]` | beide | Vollautomatische Schleife bis PASS (Standard: 2 Runden, Runde 1: Quick-Judge) |
| `/optimize ... [--no-tests] [--approve-concerns] [--test-cmd "cmd"] [--test-timeout N]` | beide | Test-Erkennung überspringen / PASS WITH CONCERNS direkt shippern |
| `/optimize <auftrag> [--rounds N] [--with-doku] [--continue] [--interactive]` | beide | Vollautomatische Schleife bis PASS (Standard: 2 Runden) |
| `/optimize ... [--no-tests] [--approve-concerns] [--test-cmd "cmd"] [--test-timeout N]` | beide | Test-Erkennung überspringen / PASS WITH CONCERNS akzeptieren |
| `/patch <änderung>` | Coder | Gezielte Minimaländerung ohne Review |
| `/quick_check [was]` | Judge | Schnelle Prüfung der letzten Änderung |
| `/version` | — | Versionsnummer erhöhen (SemVer + Git-Tag) |
| `/update_doku` | Coder | Code kommentieren + README + Bedienungsanleitung |
| `/update_doku` | Coder | Code kommentieren + README + Bedienungsanleitung + LICENCE.md |
| `/plan <auftrag>` | Coder | Implementierungsplan in PLAN.md (kein Code) |
| `/continue` | Coder | Unterbrochenen Prozess fortsetzen |
| `/cancel` | — | Laufenden Loop nach aktuellem Schritt abbrechen |
@ -310,6 +285,15 @@ Ausführliche Beschreibung aller Kommandos mit Beispielen: siehe **BEDIENUNGSANL
---
## PASS WITH CONCERNS — Verhalten
Im `/optimize`-Loop gilt `PASS WITH CONCERNS` **nicht** als bestanden — der Coder erhält
einen weiteren Fix-Auftrag, genau wie bei `FAIL`. Erst sauberes `PASS` beendet die Schleife.
Opt-out: `--approve-concerns` lässt `PASS WITH CONCERNS` als bestanden gelten.
---
## Live-Aktivitätsstatus
Während der Ausführung zeigt pi_coder in der Statuszeile, was gerade passiert —
@ -320,11 +304,11 @@ inklusive eines laufenden `[MM:SS]`-Timers während jeder LLM-Inference-Phase:
| Coder implementiert | `◉ Coder implementiert: Login-Flow mit JWT [01:23]` |
| edit-Tool aktiv | `Editiere src/main.py…` |
| git commit | `Git-Commit…` |
| Judge (Quick-Check, Runde 1) | `◉○ Runde 1/2: Quick-Check [00:47]` |
| Judge (voller Review, Runde 2) | `●◉ Runde 2/2: Judge — TASK.md + letzter Commit + Tests [02:15]` |
| Judge (Runde 1) | `◉○ Runde 1/2: Judge — TASK.md + letzter Commit + Tests [00:47]` |
| Judge (Runde 2) | `●◉ Runde 2/2: Judge — TASK.md + letzter Commit + Tests [02:15]` |
| Tests laufen | `●○ Runde 1/2: Tests laufen (pytest, max. 120s)…` |
| Fix-Phase | `●◉ Runde 2/2: Coder fixt — fehlendes Null-Check [01:05]` |
| ShipIt | `●●◉ ShipIt — finale Freigabe [00:33]` |
| Interactive-Pause (--interactive) | `⏸ PASS warte auf /continue…` |
| Interactive-Pause | `⏸ PASS warte auf /continue…` |
Der Timer beweist, dass die LLM tatsächlich arbeitet. Steht er still, hängt der Prozess.

View file

@ -1,150 +0,0 @@
# Plan: pi_coder_v2 — Single-Server (Option B)
## Kontext
pi_coder verwendet zwei llama.cpp-Server für Coder (GPU 1) und Judge (GPU 2),
die aber dasselbe GGUF-Modell ausführen. Die Rollentrennung entsteht ausschließlich
durch System-Prompts. Wenn nur eine GPU verfügbar ist, kann ein einziger Server
beide Rollen übernehmen — Rollenwechsel per Prompt statt per Server-Neustart.
**pi_coder bleibt unverändert.** Die Umsetzung erfolgt im neuen Verzeichnis `~/pi_coder_v2`.
---
## Schritt 1: Verzeichnis anlegen und Dateien kopieren
```bash
mkdir ~/pi_coder_v2
cp ~/pi_coder/pi-coder-judge-extension.ts ~/pi_coder_v2/
cp ~/pi_coder/models.json ~/pi_coder_v2/
cp ~/pi_coder/start-coder.sh ~/pi_coder_v2/
cp ~/pi_coder/start-judge.sh ~/pi_coder_v2/
cp ~/pi_coder/start-servers.sh ~/pi_coder_v2/
cp ~/pi_coder/stop-servers.sh ~/pi_coder_v2/
cp ~/pi_coder/status.sh ~/pi_coder_v2/
cp ~/pi_coder/install_servers_and_pi_coder_extension.sh ~/pi_coder_v2/
cp ~/pi_coder/README.md ~/pi_coder_v2/
cp ~/pi_coder/BEDIENUNGSANLEITUNG.md ~/pi_coder_v2/
cp ~/pi_coder/CLAUDE.md ~/pi_coder_v2/
# Plan-Datei ebenfalls kopieren
cp <dieser-plan> ~/pi_coder_v2/SINGLE_SERVER_PLAN.md
# git init
cd ~/pi_coder_v2 && git init && git add -A && git commit -m "init: kopiert aus pi_coder"
```
Nicht kopieren: `run-tests.sh`, `test-utils.ts`, `examples/` (nicht relevant für Kernfunktion).
---
## Schritt 2: `start-single.sh` anlegen
Basiert auf `start-coder.sh` mit folgenden Änderungen:
| Parameter | Alt (coder) | Neu (single) |
|---|---|---|
| `CONTAINER_NAME` | `qwen36-27b-coder` | `qwen36-27b-single` |
| `HOST_PORT` | 8001 | 8001 |
| `MODEL_ALIAS` | `qwen3.5-coder` | `qwen3.5-single` |
| `--temp` | 0.6 | 0.65 (Kompromiss) |
| `--gpus` | `"device=1"` | `"device=1"` (oder konfigurierbar) |
Der Server registriert sich unter dem Alias `qwen3.5-single`.
---
## Schritt 3: `models.json` anpassen
Neuen Provider `llama-cpp-single` hinzufügen mit **beiden** Modell-IDs auf Port 8001:
```json
"llama-cpp-single": {
"baseUrl": "http://127.0.0.1:8001/v1",
"api": "openai-completions",
"apiKey": "none",
"compat": {
"supportsDeveloperRole": false,
"supportsReasoningEffort": false,
"maxTokensField": "max_tokens",
"thinkingFormat": "qwen-chat-template"
},
"models": [
{
"id": "qwen3.5-coder",
"name": "Qwen3.6 27B Single-Server Coder (llama.cpp :8001)",
"reasoning": true,
"input": ["text"],
"contextWindow": 262144,
"maxTokens": 16384,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
},
{
"id": "qwen3.5-judge",
"name": "Qwen3.6 27B Single-Server Judge (llama.cpp :8001)",
"reasoning": true,
"input": ["text"],
"contextWindow": 262144,
"maxTokens": 16384,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
}
]
}
```
Die bestehenden Provider `llama-cpp-coder` und `llama-cpp-judge` bleiben erhalten
(für Kompatibilität), werden aber nicht aktiv genutzt.
---
## Schritt 4: `pi-coder-judge-extension.ts` — switchModel-Aufrufe
Alle Aufrufe von `switchModel(pi, ctx, "llama-cpp-coder", "qwen3.5-coder")`
und `switchModel(pi, ctx, "llama-cpp-judge", "qwen3.5-judge")` werden auf
`"llama-cpp-single"` umgestellt. Der Modell-ID-Parameter (`"qwen3.5-coder"` /
`"qwen3.5-judge"`) bleibt unverändert — so bleiben die Registry-Wechsel erhalten,
sind aber sofort (kein Server-Neustart).
Betroffene Stellen (ca. 8):
- `/coder` handler
- `/judge` handler
- `/fix` handler
- `/shipit` handler
- `/optimize` loop (Coder-Kickoff, Judge-Phase, Fix-Phase, ShipIt-Phase)
- `/patch` handler
- `/quick_check` handler
- `/plan` handler
---
## Schritt 5: `install_servers_and_pi_coder_extension.sh` anpassen
Pfade auf `pi_coder_v2` anpassen (falls nötig — Skript referenziert vermutlich `$REPO`
als relatives Verzeichnis, was automatisch korrekt ist).
---
## Schritt 6: `waitUntilModelReady` im `--continue`-Modus
Im `--continue`-Modus prüft die Extension beide Server parallel:
```typescript
const [coderReady, judgeReady] = await Promise.all([
waitUntilModelReady(pi, ctx, 8001, "qwen3.5-coder"),
waitUntilModelReady(pi, ctx, 8002, "qwen3.5-judge"),
]);
```
Dies muss auf einen einzigen Check umgestellt werden:
```typescript
const ready = await waitUntilModelReady(pi, ctx, 8001, "qwen3.5-single");
```
---
## Verifikation
1. `cd ~/pi_coder_v2 && ./start-single.sh` → Server startet auf Port 8001
2. `nvidia-smi` → nur eine GPU belegt, ~20 GB VRAM
3. `./install_servers_and_pi_coder_extension.sh` → Extension in `~/.pi/agent/` deployen
4. In pi: `/reload` → Extension neu laden
5. `/optimize <kleiner Testauftrag>` → Loop läuft durch
6. In der Statuszeile: `switchModel` wechselt ohne spürbare Pause
7. Qualitätscheck: Judge-Urteil und Coder-Fix verhalten sich wie erwartet

View file

@ -324,6 +324,45 @@ function planPrompt(task: string): string {
// ── Hilfsfunktionen ─────────────────────────────────────────────────────────
// Proprietäre Default-Lizenz ("Alle Rechte vorbehalten"), deutsch — passend zur übrigen Doku.
// Reine Template-Funktion (deterministisch): Lizenztext wird NIE vom LLM generiert.
function licenceMd(year: string, holder: string): string {
return [
"# Lizenz",
"",
`Copyright (c) ${year} ${holder}`,
"",
"Alle Rechte vorbehalten.",
"",
"Diese Software und der zugehörige Quellcode sind urheberrechtlich geschützt.",
"Kein Teil dieser Software darf ohne vorherige ausdrückliche schriftliche",
"Genehmigung des Rechteinhabers kopiert, verändert, weitergegeben, veröffentlicht,",
"unterlizenziert oder anderweitig genutzt werden.",
"",
"DIE SOFTWARE WIRD OHNE MÄNGELGEWÄHR UND OHNE JEGLICHE AUSDRÜCKLICHE ODER",
"KONKLUDENTE GEWÄHRLEISTUNG BEREITGESTELLT. DER RECHTEINHABER HAFTET NICHT FÜR",
"SCHÄDEN, DIE AUS DER NUTZUNG DIESER SOFTWARE ENTSTEHEN.",
""
].join("\n");
}
// Legt LICENCE.md mit der proprietären Default-Lizenz an — nur wenn noch keine existiert
// (eine vorhandene Lizenz wird nie überschrieben). Inhaber aus 'git config user.name',
// sonst Platzhalter; Jahr aus dem aktuellen Datum.
async function writeLicenceMd(pi: ExtensionAPI, ctx: ExtensionCommandContext): Promise<void> {
const check = await pi.exec("bash", ["-c", "test -f LICENCE.md && echo exists"], { cwd: ctx.cwd });
if (check.stdout.trim() === "exists") {
ctx.ui.notify("LICENCE.md existiert bereits — unverändert.", "info");
return;
}
const who = await pi.exec("bash", ["-c", "git config user.name 2>/dev/null"], { cwd: ctx.cwd });
const holder = who.stdout.trim() || "[Rechteinhaber eintragen]";
const year = String(new Date().getFullYear());
// content als Argument ($1), nicht interpoliert — kein Shell-Escaping-Problem.
await pi.exec("bash", ["-c", `printf "%s" "$1" > LICENCE.md`, "_", licenceMd(year, holder)], { cwd: ctx.cwd });
ctx.ui.notify(`LICENCE.md angelegt (proprietär, © ${year} ${holder}).`, "info");
}
// Legt TASK.md neu an oder hängt einen Zusatzauftrag an.
async function writeTaskMd(
pi: ExtensionAPI,
@ -703,10 +742,18 @@ async function runUpdateDoku(pi: ExtensionAPI, ctx: ExtensionCommandContext): Pr
ctx.ui.notify(`3/3 BEDIENUNGSANLEITUNG.md fehlgeschlagen: ${String(e?.message ?? e)}`, "error");
}
// LICENCE.md: deterministisch anlegen (nur wenn nicht vorhanden) — kein LLM.
try {
ctx.ui.setStatus("update_doku", "LICENCE.md wird geprüft…");
await writeLicenceMd(pi, ctx);
} catch (e: any) {
ctx.ui.notify(`LICENCE.md fehlgeschlagen: ${String(e?.message ?? e)}`, "error");
}
// Abschließender Dokumentations-Commit (immer, auch bei Teilfehlern)
await pi.exec(
"bash",
["-c", "git add -A && git commit -m 'docs: update comments, README, BEDIENUNGSANLEITUNG' || true"],
["-c", "git add -A && git commit -m 'docs: update comments, README, BEDIENUNGSANLEITUNG, LICENCE' || true"],
{ cwd: ctx.cwd }
);
@ -1331,7 +1378,7 @@ export default function (pi: ExtensionAPI) {
if (withDoku) {
await runUpdateDoku(pi, ctx);
} else {
ctx.ui.notify("Nächster Schritt: /update_doku für Code-Kommentare, README.md und BEDIENUNGSANLEITUNG.md", "info");
ctx.ui.notify("Nächster Schritt: /update_doku für Code-Kommentare, README.md, BEDIENUNGSANLEITUNG.md und LICENCE.md", "info");
}
}
} catch (e: any) {
@ -1387,7 +1434,7 @@ export default function (pi: ExtensionAPI) {
// ── Dokumentations-Phase ─────────────────────────────────────────────────
pi.registerCommand("update_doku", {
description: "Inkrementelle Code-Kommentare + README.md + BEDIENUNGSANLEITUNG.md via Git-Tags.",
description: "Inkrementelle Code-Kommentare + README.md + BEDIENUNGSANLEITUNG.md via Git-Tags; legt LICENCE.md an (proprietär, falls fehlend).",
handler: async function (_args: string, ctx: ExtensionCommandContext) {
await runUpdateDoku(pi, ctx);
}
@ -1484,7 +1531,7 @@ export default function (pi: ExtensionAPI) {
"/patch <änderung> Gezielte Minimaländerung → Coder",
"/quick_check [was] Schnelle OK/PROBLEM-Prüfung → Judge",
"/plan <auftrag> Implementierungsplan in PLAN.md → Coder",
"/update_doku Code-Kommentare + README.md + BEDIENUNGSANLEITUNG.md",
"/update_doku Code-Kommentare + README.md + BEDIENUNGSANLEITUNG.md + LICENCE.md",
"/version Versionsnummer erhöhen (SemVer + Git-Tag)",
"/discard Verwirft PLAN.md",
"/new_project <pfad> Projektverzeichnis + git init + .gitignore",

View file

@ -44,3 +44,17 @@ test("Loop-Erkennung greift auch ohne Blocker-Abschnitt (Fallback auf Gesamttext
test("currentRole wird nach jedem optimize-Lauf auf 'coder' zurückgesetzt", () => {
assert.match(extSource, /currentRole\s*=\s*"coder"/);
});
test("LICENCE.md: deterministisch in der Doku-Phase, nur wenn nicht vorhanden, proprietär", () => {
assert.match(extSource, /function licenceMd\b/);
assert.match(extSource, /async function writeLicenceMd\b/);
// wird in runUpdateDoku aufgerufen
assert.match(extSource, /await writeLicenceMd\(pi, ctx\)/);
// existierende Lizenz wird nicht überschrieben
assert.match(extSource, /test -f LICENCE\.md/);
// proprietärer Default
assert.match(extSource, /Alle Rechte vorbehalten/);
// Inhaber aus git config, Jahr aus aktuellem Datum
assert.match(extSource, /git config user\.name/);
assert.match(extSource, /new Date\(\)\.getFullYear\(\)/);
});

View file

@ -8,6 +8,7 @@ import { extractFn } from "./helpers.mjs";
const parseVerdict = extractFn("parseVerdict");
const parseBlockers = extractFn("parseBlockers");
const normalizeForComparison = extractFn("normalizeForComparison");
const licenceMd = extractFn("licenceMd");
test("parseVerdict: klares PASS", () => {
assert.equal(parseVerdict("Review fertig.\nUrteil: PASS\nKeine Blocker."), "PASS");
@ -64,3 +65,16 @@ test("normalizeForComparison: gleiche Blocker trotz Formatierungsunterschied →
normalizeForComparison(b)
);
});
test("licenceMd: proprietärer Default mit eingesetztem Jahr und Inhaber", () => {
const txt = licenceMd("2026", "Dieter Schlüter");
assert.match(txt, /^# Lizenz/);
assert.match(txt, /Copyright \(c\) 2026 Dieter Schlüter/);
assert.match(txt, /Alle Rechte vorbehalten\./);
assert.match(txt, /ohne vorherige ausdrückliche schriftliche/);
assert.match(txt, /OHNE MÄNGELGEWÄHR/);
});
test("licenceMd: Platzhalter-Inhaber wird unverändert eingesetzt", () => {
assert.match(licenceMd("2030", "[Rechteinhaber eintragen]"), /Copyright \(c\) 2030 \[Rechteinhaber eintragen\]/);
});