feat: --interactive-Checkpoint, direktes SHIP bei PASS, default rounds 2

- /optimize --interactive pausiert nach erstem PASS; /continue setzt fort,
  /continue "Zusatz" hängt weiteren Auftrag an und wiederholt den Judge-Loop
- Klares PASS → direkt SHIP ohne zweiten ShipIt-Inference-Call (1-3 min gespart)
- PASS WITH CONCERNS → ShipIt-Runde weiterhin als finale Abwägung
- Default --rounds 3→2 (~30 % schnellere Durchläufe für typische Tasks)
- /continue-Command erkennt interactivePauseActive und leitet Signal weiter
- Alle drei Interactive-Zustandsvariablen werden im finally-Block resettet
- Dokumentation (README, BEDIENUNGSANLEITUNG, CLAUDE.md) vollständig aktualisiert

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-05-29 17:51:54 +02:00
commit 11ac46e565
4 changed files with 248 additions and 79 deletions

View file

@ -13,7 +13,7 @@ einfache Slash-Kommandos in der pi-Agent-Oberfläche.
3. [Server starten und stoppen](#3-server-starten-und-stoppen)
4. [Neues Projekt anlegen](#4-neues-projekt-anlegen)
5. [Manueller Workflow: /coder → /judge → /fix → /shipit](#5-manueller-workflow)
6. [Automatischer Workflow: /optimize](#6-automatischer-workflow-optimize)
6. [Automatischer Workflow: /optimize](#6-automatischer-workflow-optimize) (inkl. [Interactive-Modus](#interactive-modus))
7. [Kleine Änderungen: /patch und /quick_check](#7-kleine-änderungen-patch-und-quick_check)
8. [Dokumentation generieren: /update_doku](#8-dokumentation-generieren-update_doku)
9. [Versionsverwaltung: /version](#9-versionsverwaltung-version)
@ -278,14 +278,16 @@ Empfohlene Sofortmaßnahmen: keine
### Syntax
```
/optimize <auftrag> [--rounds N] [--with-doku] [--continue]
/optimize <auftrag> [--rounds N] [--with-doku] [--continue] [--interactive]
```
- `--rounds N` — maximale Anzahl Runden (Standard: 3)
- `--rounds N` — maximale Anzahl Runden (Standard: 2)
- `--with-doku` — nach SHIP automatisch `/update_doku` ausführen
- `--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.
- `--interactive` — pausiert nach erstem PASS für einen menschlichen Checkpoint.
Details: siehe [Interactive-Modus](#interactive-modus) weiter unten.
### Beispiel: einfacher Auftrag
@ -296,19 +298,21 @@ Empfohlene Sofortmaßnahmen: keine
Was im Hintergrund passiert:
```
Phase 1: Coder implementiert...
Phase 2: Runde 1/3: Judge prüft...
Phase 2: Runde 1/2: Judge prüft...
→ Urteil: FAIL (2 Blocker)
Phase 3: Runde 1/3: Coder fixt...
Phase 4: Runde 2/3: Judge prüft...
Phase 3: Runde 1/2: Coder fixt...
Phase 4: Runde 2/2: Judge prüft...
→ Urteil: PASS WITH CONCERNS
✓ PASS WITH CONCERNS nach Runde 2
Finale ShipIt-Prüfung...
Finale ShipIt-Prüfung... (nur bei PASS WITH CONCERNS)
→ SHIP
[Dialog: Version → v0.1.0 (empfohlen)]
```
Bei klarem `PASS` entfällt die ShipIt-Runde — es wird direkt SHIP ausgelöst.
Während des Ablaufs zeigt die Statuszeile immer die aktuelle Aktivität:
`Coder implementiert…``Editiere src/main.rs…``Git-Commit…``Judge reviewt (Runde 1/3)…`
`Coder implementiert…``Editiere src/main.rs…``Git-Commit…``Judge reviewt (Runde 1/2)…`
### Beispiel: mehr Runden
@ -359,10 +363,57 @@ In diesem Fall: `/judge` manuell ausführen, Blocker lesen, mit `/fix` manuell e
### Max. Runden ohne PASS
```
3 Runden durchlaufen ohne PASS. Bitte manuell prüfen.
2 Runden durchlaufen ohne PASS. Bitte manuell prüfen.
```
Dann: `/judge` und `/fix` manuell für gezielte Eingriffe.
Mit `--rounds N` kann die Grenze hochgesetzt werden, z.B. `--rounds 5` für komplexe Aufgaben.
### Interactive-Modus
Mit `--interactive` pausiert `/optimize` nach dem ersten PASS und wartet auf menschliches
Feedback — bevor das abschließende SHIP ausgelöst wird.
```
/optimize Implementiere Feature X --interactive
```
Typischer Ablauf:
```
Phase 1: Coder implementiert...
Phase 2: Judge prüft...
→ Urteil: PASS
⏸ PASS erreicht. Weitere Features? /continue "Zusatzauftrag" — oder /continue zum Shippern.
```
Jetzt hast du drei Optionen:
**Option A: Direkt shippern**
```
/continue
```
→ ShipIt wird gestartet, Version-Dialog erscheint.
**Option B: Zusatzauftrag hinzufügen**
```
/continue "Füge außerdem eine --verbose Option hinzu"
```
→ Coder implementiert den Zusatz, dann läuft der Judge-Loop erneut an.
→ Nach erneutem PASS erscheint der Checkpoint wieder — du kannst beliebig viele
Iterationen anhängen, bevor du mit `/continue` zum SHIP gehst.
**Option C: Abbrechen**
```
/cancel
```
→ Loop wird abgebrochen, kein SHIP.
**Timeout:** Wenn du 30 Minuten lang nichts eingibst, bricht `/optimize` automatisch ab.
**Wann ist `--interactive` sinnvoll?**
- Wenn der Auftrag aus mehreren voneinander abhängigen Features besteht
- Wenn du nach jeder fertigen Stufe entscheiden möchtest, ob du weitermachst
- Wenn du sicherstellen willst, dass nichts unbeabsichtigt committed wird
---
@ -678,6 +729,28 @@ Die Checkboxen werden automatisch abgehakt:
/optimize Schreibe einen vollständigen Markdown-Parser mit AST in Python --rounds 5
```
### Schrittweise Features hinzufügen mit --interactive
```bash
# Erst Grundgerüst implementieren und PASS abwarten:
/optimize Schreibe ein CLI-Tool 'filewatch' das Dateiänderungen überwacht --interactive
# Nach PASS erscheint: ⏸ PASS warte auf /continue…
# Option A: Genug, direkt shippern:
/continue
# Option B: Weiteres Feature anhängen:
/continue "Füge außerdem einen --filter GLOB-Parameter hinzu"
# → Coder implementiert, Judge prüft erneut, PASS → Checkpoint wieder aktiv
# Nochmal erweitern:
/continue "Füge --output-log DATEI hinzu um Änderungen zu protokollieren"
# Fertig → shippern:
/continue
```
---
## 12. Fehlermeldungen und Lösungen
@ -750,7 +823,7 @@ bereit, das GNU `patch -p1` mit Fuzzy-Matching nutzt.
Lies die Datei neu ein und wende die Änderungen als unified diff mit apply_patch an.
```
### "Drei Runden ohne PASS" / Loop-Erkennung schlägt an
### "N Runden ohne PASS" / Loop-Erkennung schlägt an
```
⚠ Derselbe Blocker tritt erneut auf Schleife abgebrochen.
@ -767,6 +840,7 @@ Dann den Blocker analysieren und entweder:
- `/fix Ignoriere Blocker X, das ist nicht Teil dieser Aufgabe`
- Den Code selbst anpassen und dann `/fix` aufrufen
- Die Aufgabe in TASK.md präzisieren
- Bei komplexen Aufgaben mit mehr Runden wiederholen: `/optimize --continue --rounds 5`
### Server läuft, aber pi wechselt nicht das Modell

View file

@ -41,9 +41,12 @@ Beide nutzen dasselbe GGUF (`Qwen3.6-27B-Uncensored-HauhauCS-Aggressive-IQ4_XS.g
**Zentraler Ablauf in `/optimize`:**
1. `writeTaskMd()` → TASK.md anlegen
2. Coder: `coderKickoff()` → implementiert + committet
3. Loop (max. N Runden): Judge → `parseVerdict()` → PASS? → ShipIt. FAIL? → `parseBlockers()` → Fix → nächste Runde
4. Loop-Erkennung: gleicher Blocker zweimal → Abbruch (`finalNotify()`)
5. Optional: `runUpdateDoku()` bei `--with-doku`
3. Äußere `while(keepGoing)`-Schleife (für `--interactive`-Zusatzaufträge)
4. Loop (max. N Runden, Standard 2): Judge → `parseVerdict()` → PASS? → break. FAIL? → `parseBlockers()` → Fix → nächste Runde
5. Bei PASS + `--interactive`: Polling auf `interactiveContinueRequested`. Kein Zusatzauftrag → ShipIt. Zusatzauftrag → `coderKickoff()``keepGoing = true`
6. SHIP-Schritt: klares `PASS` → direkt SHIP (kein ShipIt-Call). `PASS WITH CONCERNS``shipitPrompt()` → SHIP/NO-SHIP
7. Loop-Erkennung: gleicher Blocker zweimal → Abbruch (`finalNotify()`)
8. Optional: `runUpdateDoku()` bei `--with-doku`
**`tool_call`-Hook (edit-Reordering):** Sortiert Multi-Edit-Aufrufe auf dieselbe Datei von hinten nach vorne. Verhindert den Fehler „edits[n] doesn't match" wenn mehrere Stellen einer Datei auf einmal geändert werden.
@ -60,6 +63,7 @@ Kritische Felder bei llama-cpp-Providern: `contextWindow` muss mit dem `-c`-Para
## Wichtige Invarianten
- **`cancelRequested`** ist eine modulare Variable — sie wird von `/cancel` gesetzt und nach jedem Loop-Schritt in `/optimize` geprüft und zurückgesetzt.
- **`interactivePauseActive` / `interactiveContinueRequested` / `interactivePauseTask`** — drei modulare Variablen für den `--interactive`-Modus. `interactivePauseActive` wird vom `/continue`-Command geprüft, um zwischen Interactive-Pause-Signal und normalem Fortsetzen zu unterscheiden. Alle drei werden im `finally`-Block zurückgesetzt.
- **`sendAndWait()`** wartet erst auf `idle`, dann `deliverAs: "followUp"` — verhindert „Agent is already processing".
- **`tickTaskMdStatus()`** nutzt Python3 für den String-Ersatz in TASK.md (kein Shell-Escaping-Problem).
- Beide Start-Skripte warten bis zu 90×2 s auf HTTP-Erreichbarkeit und führen dann einen Smoke-Test-Completion durch.

View file

@ -22,8 +22,10 @@ Nutzer gibt Auftrag
PASS? ▼
/shipit → qwen3.5-judge (:8002) → Finale Freigabe: SHIP / NO-SHIP
(nur bei "PASS WITH 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
@ -283,7 +285,7 @@ wenn pi agent Folgeanfragen schnell hintereinander schickt.
| `/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]` | beide | Vollautomatische Schleife bis PASS |
| `/optimize <auftrag> [--rounds N] [--with-doku] [--continue] [--interactive]` | beide | Vollautomatische Schleife bis PASS (Standard: 2 Runden) |
| `/optimize ... [--test-cmd "cmd"] [--test-timeout N]` | beide | Externe Test-Suite im Loop ausführen |
| `/patch <änderung>` | Coder | Gezielte Minimaländerung ohne Review |
| `/quick_check [was]` | Judge | Schnelle Prüfung der letzten Änderung |
@ -307,8 +309,9 @@ Während der Ausführung zeigt pi_coder in der Statuszeile, was gerade passiert:
| Coder implementiert | `Coder implementiert…` |
| edit-Tool aktiv | `Editiere src/main.py…` |
| git commit | `Git-Commit…` |
| Judge reviewt (Runde 2/3) | `Judge reviewt (Runde 2/3)…` |
| Judge reviewt (Runde 2/2) | `Judge reviewt (Runde 2/2)…` |
| Tests laufen | `Tests laufen…` |
| Fix-Phase | `Coder fixt Blocker…` |
| Interactive-Pause (--interactive) | `⏸ PASS warte auf /continue…` |
So ist jederzeit erkennbar, in welcher Phase sich der automatische Loop befindet.

View file

@ -827,6 +827,9 @@ function finalNotify(
// ── Extension ────────────────────────────────────────────────────────────────
let cancelRequested = false;
let interactivePauseActive = false;
let interactiveContinueRequested = false;
let interactivePauseTask = "";
let currentActivity = ""; // Working-Message für den aktuellen Command-Kontext
// Erzeugt eine knappe Statuszeile aus Tool-Name und Argumenten.
@ -1000,12 +1003,13 @@ export default function (pi: ExtensionAPI) {
// ── Automatische Optimierungsschleife ────────────────────────────────────
pi.registerCommand("optimize", {
description: "Coder→Judge→Fix-Schleife bis PASS. Tests werden automatisch erkannt und parallel ausgeführt. /optimize <auftrag> [--rounds N] [--with-doku] [--continue] [--test-cmd \"override\"] [--test-timeout N]",
description: "Coder→Judge→Fix-Schleife bis PASS (default 2 Runden). Klares PASS → direkt SHIP; PASS WITH CONCERNS → ShipIt-Runde. --interactive pausiert nach PASS für Zusatzaufträge via /continue. /optimize <auftrag> [--rounds N] [--with-doku] [--continue] [--interactive] [--test-cmd \"override\"] [--test-timeout N]",
handler: async function (args: string, ctx: ExtensionCommandContext) {
const roundsMatch = (args || "").match(/--rounds\s+(\d+)/);
const maxRounds = roundsMatch ? Math.max(1, parseInt(roundsMatch[1], 10)) : 3;
const maxRounds = roundsMatch ? Math.max(1, parseInt(roundsMatch[1], 10)) : 2;
const withDoku = /--with-doku/.test(args || "");
const continueMode = /--continue/.test(args || "");
const interactive = /--interactive/.test(args || "");
const testCmdMatch = (args || "").match(/--test-cmd\s+"([^"]+)"|--test-cmd\s+'([^']+)'|--test-cmd\s+(\S+)/);
const testCmd: string | null = testCmdMatch ? (testCmdMatch[1] ?? testCmdMatch[2] ?? testCmdMatch[3]) : null;
const testTimeoutMatch = (args || "").match(/--test-timeout\s+(\d+)/);
@ -1015,6 +1019,7 @@ export default function (pi: ExtensionAPI) {
.replace(/--test-timeout\s+\d+/, "")
.replace(/--with-doku/, "")
.replace(/--continue/, "")
.replace(/--interactive/, "")
.replace(/--test-cmd\s+"[^"]*"/, "")
.replace(/--test-cmd\s+\S+/, "")
.trim();
@ -1085,6 +1090,13 @@ export default function (pi: ExtensionAPI) {
let lastBlockers = "";
let verdict = "";
let keepGoing = true;
// Äußere Schleife für --interactive: nach PASS pausieren, Zusatzaufträge ermöglichen.
while (keepGoing) {
keepGoing = false;
verdict = "";
lastBlockers = "";
// Schleife: Judge → (PASS? fertig : Fix → nächste Runde)
for (let round = 1; round <= maxRounds; round++) {
@ -1115,7 +1127,8 @@ export default function (pi: ExtensionAPI) {
if (verdict === "PASS" || verdict === "PASS WITH CONCERNS") {
await tickTaskMdStatus(pi, ctx, "Review bestanden (PASS)");
ctx.ui.setStatus("optimize", `${"●".repeat(round)}${verdict} nach Runde ${round}/${maxRounds} — ShipIt…`);
const nextStep = interactive ? "warte auf /continue…" : "ShipIt…";
ctx.ui.setStatus("optimize", `${"●".repeat(round)}${verdict} nach Runde ${round}/${maxRounds}${nextStep}`);
break;
}
@ -1152,9 +1165,69 @@ export default function (pi: ExtensionAPI) {
if (cancelRequested) { finalNotify(ctx, "⛔ Abgebrochen", `Nach Fix Runde ${round}`); return; }
}
// Finale ShipIt-Prüfung nur bei PASS
if (verdict === "PASS" || verdict === "PASS WITH CONCERNS") {
ctx.ui.setStatus("optimize", `${"●".repeat(maxRounds)}◉ ShipIt — SHIP oder NO-SHIP?…`);
// Interactive-Modus: nach PASS pausieren und auf /continue warten (max 30 min).
// /continue ohne Args → direkt ShipIt. /continue "Zusatz" → Coder implementiert, Loop nochmal.
if (interactive && (verdict === "PASS" || verdict === "PASS WITH CONCERNS")) {
interactivePauseActive = true;
interactiveContinueRequested = false;
interactivePauseTask = "";
ctx.ui.setStatus("optimize", `${verdict} warte auf /continue…`);
ctx.ui.notify(
`${verdict} erreicht. Weitere Features? /continue "Zusatzauftrag" — oder /continue zum Shippern.`,
"info"
);
const waitStart = Date.now();
while (!interactiveContinueRequested && !cancelRequested) {
if (Date.now() - waitStart > 30 * 60 * 1000) {
interactivePauseActive = false;
finalNotify(ctx, "⚠ Timeout", "30 min ohne /continue — abgebrochen");
return;
}
await new Promise(r => setTimeout(r, 500));
}
interactivePauseActive = false;
if (cancelRequested) { finalNotify(ctx, "⛔ Abgebrochen", "Im Interactive-Modus"); return; }
if (interactivePauseTask) {
// Zusatzauftrag: Coder implementiert, dann Judge-Loop erneut
const addPreview = interactivePauseTask.length > 50
? interactivePauseTask.slice(0, 47) + "…"
: interactivePauseTask;
ctx.ui.setStatus("optimize", `◉ Coder implementiert Zusatzauftrag: ${addPreview}`);
await writeTaskMd(pi, ctx, interactivePauseTask);
if (!await switchModel(pi, ctx, "llama-cpp-coder", "qwen3.5-coder")) {
finalNotify(ctx, "⛔ Modell-Fehler", "Coder-Modell nicht verfügbar");
return;
}
currentActivity = "Coder implementiert Zusatzauftrag…";
await sendAndWait(pi, ctx, coderKickoff(interactivePauseTask));
await tickTaskMdStatus(pi, ctx, "Implementierung");
if (cancelRequested) { finalNotify(ctx, "⛔ Abgebrochen", "Nach Zusatz-Implementierung"); return; }
keepGoing = true;
continue;
}
// /continue ohne Args → direkt zu ShipIt (verdict bleibt PASS)
}
}
// Finaler SHIP-Schritt: klares PASS → direkt SHIP ohne zweiten Inference-Aufruf.
// "PASS WITH CONCERNS" → ShipIt-Runde als finale Abwägung.
if (verdict === "PASS") {
ctx.ui.setStatus("optimize", "🚀 SHIP produktionsreif");
await autoCommitIfDirty(pi, ctx);
notifyShipSuccess(ctx);
finalNotify(ctx, "🚀 SHIP", "Programm ist produktionsreif");
await runVersionBump(pi, ctx);
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");
}
} else if (verdict === "PASS WITH CONCERNS") {
ctx.ui.setStatus("optimize", `${"●".repeat(maxRounds)}◉ ShipIt — PASS WITH CONCERNS, finale Freigabe?…`);
if (!await switchModel(pi, ctx, "llama-cpp-judge", "qwen3.5-judge")) {
finalNotify(ctx, "⛔ Modell-Fehler", "Judge-Modell (llama-cpp-judge) nicht verfügbar");
return;
@ -1187,8 +1260,11 @@ export default function (pi: ExtensionAPI) {
} catch (e: any) {
finalNotify(ctx, "⛔ Fehler", String(e?.message ?? e));
} finally {
// Sicherstellen dass cancelRequested nie in einen späteren /optimize-Aufruf leckt
// Sicherstellen dass keine Zustandsvariable in späteren /optimize-Aufruf leckt
cancelRequested = false;
interactivePauseActive = false;
interactiveContinueRequested = false;
interactivePauseTask = "";
}
}
});
@ -1352,8 +1428,20 @@ export default function (pi: ExtensionAPI) {
});
pi.registerCommand("continue", {
description: "Nimmt unterbrochenen Prozess wieder auf — liest TASK.md, PLAN.md, git log und entscheidet den nächsten Schritt.",
handler: async function (_args: string, ctx: ExtensionCommandContext) {
description: "Im --interactive-Modus: bestätigt PASS und geht zu ShipIt — oder /continue \"Zusatz\" für weiteren Auftrag. Sonst: nimmt unterbrochenen Prozess wieder auf.",
handler: async function (args: string, ctx: ExtensionCommandContext) {
// Interactive-Pause-Handler: Signal an laufenden /optimize-Loop
if (interactivePauseActive) {
interactivePauseTask = (args || "").trim();
interactiveContinueRequested = true;
const msg = interactivePauseTask
? `Zusatzauftrag eingetragen: "${interactivePauseTask}" — Coder startet`
: "Fortfahren — ShipIt wird gestartet";
ctx.ui.notify(msg, "info");
return;
}
// Standard-Verhalten: unterbrochenen Prozess wieder aufnehmen
if (!await waitUntilModelReady(pi, ctx, 8001, "qwen3.5-coder")) {
ctx.ui.notify("Coder-Server nicht bereit (Port 8001) — start-coder.sh ausführen", "error");
return;