From 4a31535b76b77dfd10c06ca86aafc518f974916c Mon Sep 17 00:00:00 2001 From: dschlueter Date: Wed, 20 May 2026 20:02:20 +0200 Subject: [PATCH] feat: /plan, /cancel, /continue, /discard + Context 262144 + KV-Cache q4_0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Neue Befehle: /plan (Planungsmodus, nur PLAN.md), /cancel (Loop-Abbruch), /continue (Resume nach Unterbrechung), /discard (PLAN.md verwerfen) - contextWindow in models.json und llama.cpp-Servern: 131072 → 262144 - KV-Cache: q8_0 → q4_0 (weniger VRAM, passt zu 262k-Kontext auf 2× 3090) - parallel: 2 → 1 beim Coder (stabiler bei großem Kontext) - Optimize-Status mit ASCII-Fortschrittsbalken + Blocker-Preview - cancelRequested-Flag prüft nach jedem Loop-Schritt Co-Authored-By: Claude Sonnet 4.6 --- models.json | 4 +- pi-coder-judge-extension.ts | 134 +++++++++++++++++++++++++++++++----- start-coder.sh | 8 +-- start-judge.sh | 6 +- 4 files changed, 126 insertions(+), 26 deletions(-) diff --git a/models.json b/models.json index 35d6871..0e9052a 100644 --- a/models.json +++ b/models.json @@ -54,7 +54,7 @@ "name": "Qwen3.6 27B Coder (llama.cpp :8001)", "reasoning": true, "input": ["text"], - "contextWindow": 131072, + "contextWindow": 262144, "maxTokens": 16384, "cost": { "input": 0, @@ -82,7 +82,7 @@ "name": "Qwen3.6 27B Judge (llama.cpp :8002)", "reasoning": true, "input": ["text"], - "contextWindow": 131072, + "contextWindow": 262144, "maxTokens": 8192, "cost": { "input": 0, diff --git a/pi-coder-judge-extension.ts b/pi-coder-judge-extension.ts index 6c91bb5..597fc53 100644 --- a/pi-coder-judge-extension.ts +++ b/pi-coder-judge-extension.ts @@ -239,6 +239,37 @@ function bedienungsanleitungPromptIncremental(files: string[]): string { ].join("\n"); } +function planPrompt(task: string): string { + return [ + "Du bist ein erfahrener Software-Architekt im PLANUNGSMODUS.", + "", + "ABSOLUTE VERBOTE — du darfst NICHT:", + "- Dateien editieren, schreiben oder löschen (kein edit, write, apply_patch)", + "- Git-Commits durchführen", + "- Tests oder Skripte ausführen die Seiteneffekte haben", + "", + "ERLAUBT:", + "- Dateien lesen (read, cat, grep, find)", + "- Git-History lesen (git log, git show, git diff)", + "- PLAN.md anlegen oder überschreiben (das ist dein Ausgabe-Dokument)", + "", + "Analysiere den Auftrag gründlich und erstelle einen konkreten Implementierungsplan.", + "", + "Auftrag:", + task, + "", + "Struktur deiner Ausgabe:", + "1. IST-Analyse (relevante Dateien, Architektur, Abhängigkeiten)", + "2. Implementierungsplan (nummerierte Schritte, konkret und umsetzbar)", + "3. Kritische Entscheidungen (Alternativen + Empfehlung)", + "4. Risiken und offene Fragen", + "5. Geschätzte Komplexität: einfach / mittel / komplex", + "", + "Schreibe den vollständigen Plan in PLAN.md.", + "Schließe ab mit: 'Plan bereit. Starte Umsetzung mit /coder oder /optimize --continue'", + ].join("\n"); +} + // ── Hilfsfunktionen ───────────────────────────────────────────────────────── // Legt TASK.md neu an oder hängt einen Zusatzauftrag an. @@ -471,24 +502,30 @@ function finalNotify( ctx.ui.setWidget("coder-judge", [ `Letzter Lauf: ${verdict} — ${detail} (${timestamp})`, "─────────────────────────────────────────", - "Workflow: /coder | /judge | /fix | /shipit", + "Workflow: /coder | /judge | /fix | /shipit", "Auto-Loop: /optimize [--rounds N] [--with-doku] [--continue]", - "Kleine Änderung: /patch <änderung> → /quick_check [was]", - "Finale Doku: /update_doku | Neues Projekt: /new_project ", + "Planung: /plan → /coder | /optimize --continue | /discard", + "Patch: /patch <änderung> → /quick_check [was]", + "Doku: /update_doku | Neues Projekt: /new_project ", + "Abbruch: Escape (Generation laufend) | /cancel (Loop nach aktuellem Schritt)", + "Resume: /continue | Modell: auto (Coder→:8001, Judge→:8002)", ]); } // ── Extension ──────────────────────────────────────────────────────────────── +let cancelRequested = false; + export default function (pi: ExtensionAPI) { pi.on("session_start", async function (_event, ctx) { ctx.ui.setWidget("coder-judge", [ - "Workflow: /coder | /judge | /fix | /shipit", - "Auto-Loop: /optimize [--rounds N] [--with-doku]", - "Kleine Änderung: /patch <änderung> → /quick_check [was]", - "Finale Doku: /update_doku (nach SHIP – Kommentare + README + Bedienungsanleitung)", - "Neues Projekt: /new_project ", - "Modell wird automatisch gewechselt (Coder→:8001, Judge→:8002)" + "Workflow: /coder | /judge | /fix | /shipit", + "Auto-Loop: /optimize [--rounds N] [--with-doku] [--continue]", + "Planung: /plan → /coder | /optimize --continue | /discard", + "Patch: /patch <änderung> → /quick_check [was]", + "Doku: /update_doku | Neues Projekt: /new_project ", + "Abbruch: Escape (Generation laufend) | /cancel (Loop nach aktuellem Schritt)", + "Resume: /continue | Modell: auto (Coder→:8001, Judge→:8002)", ]); }); @@ -595,10 +632,12 @@ export default function (pi: ExtensionAPI) { // TASK.md anlegen und Implementierung starten await writeTaskMd(pi, ctx, task); ctx.ui.setStatus("optimize", `Starte Optimierung (max ${maxRounds} Runden)…`); - ctx.ui.setStatus("optimize", "Phase 1: Coder implementiert…"); + const taskPreview = task.length > 55 ? task.slice(0, 52) + "…" : task; + ctx.ui.setStatus("optimize", `◉ Coder liest Anforderungen + implementiert: ${taskPreview}`); await switchModel(pi, ctx, "llama-cpp-coder", "qwen3.5-coder"); await sendAndWait(pi, ctx, coderKickoff(task)); await tickTaskMdStatus(pi, ctx, "Implementierung"); + if (cancelRequested) { cancelRequested = false; finalNotify(ctx, "⛔ Abgebrochen", "Nach Implementierung"); return; } } let lastBlockers = ""; @@ -606,43 +645,49 @@ export default function (pi: ExtensionAPI) { // Schleife: Judge → (PASS? fertig : Fix → nächste Runde) for (let round = 1; round <= maxRounds; round++) { - ctx.ui.setStatus("optimize", `Runde ${round}/${maxRounds}: Judge prüft…`); + const prog = "●".repeat(round - 1) + "◉" + "○".repeat(maxRounds - round); + ctx.ui.setStatus("optimize", `${prog} Runde ${round}/${maxRounds}: Judge — TASK.md + letzter Commit + Tests…`); await switchModel(pi, ctx, "llama-cpp-judge", "qwen3.5-judge"); await sendAndWait(pi, ctx, judgePrompt("")); + if (cancelRequested) { cancelRequested = false; finalNotify(ctx, "⛔ Abgebrochen", `Nach Judge Runde ${round}`); return; } const judgeText = getLastAssistantText(ctx); verdict = parseVerdict(judgeText); if (verdict === "PASS" || verdict === "PASS WITH CONCERNS") { await tickTaskMdStatus(pi, ctx, "Review bestanden (PASS)"); - ctx.ui.setStatus("optimize", `✓ ${verdict} nach Runde ${round}`); + ctx.ui.setStatus("optimize", `${"●".repeat(round)} ✓ ${verdict} nach Runde ${round}/${maxRounds} — ShipIt…`); break; } // Loop-Erkennung: gleicher Blocker zweimal → manuell eingreifen const currentBlockers = parseBlockers(judgeText); if (currentBlockers && currentBlockers === lastBlockers) { - ctx.ui.setStatus("optimize", "⚠ Schleife: gleicher Blocker – manuelle Intervention nötig"); + ctx.ui.setStatus("optimize", `${prog} ⚠ Gleicher Blocker in Runde ${round} – manuelle Intervention nötig`); finalNotify(ctx, "⚠ Schleife", "Gleicher Blocker zweimal – manuelle Intervention nötig"); return; } lastBlockers = currentBlockers; if (round === maxRounds) { - ctx.ui.setStatus("optimize", `⚠ Max. ${maxRounds} Runden ohne PASS`); + ctx.ui.setStatus("optimize", `${"●".repeat(maxRounds)} ⚠ Max. ${maxRounds} Runden ohne PASS`); finalNotify(ctx, "⚠ Kein PASS", `${maxRounds} Runden ohne PASS – bitte /judge und /fix manuell`); return; } - // Fix-Phase - ctx.ui.setStatus("optimize", `Runde ${round}/${maxRounds}: Coder fixt…`); + // Fix-Phase: Blocker-Preview aus Judge-Bericht anzeigen + const blockerHint = currentBlockers + ? (currentBlockers.length > 50 ? currentBlockers.slice(0, 47) + "…" : currentBlockers) + : "Kritikpunkte aus Judge-Bericht"; + ctx.ui.setStatus("optimize", `${prog} Runde ${round}/${maxRounds}: Coder fixt — ${blockerHint}`); await switchModel(pi, ctx, "llama-cpp-coder", "qwen3.5-coder"); await sendAndWait(pi, ctx, fixPrompt("")); + if (cancelRequested) { cancelRequested = false; 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", "Finale ShipIt-Prüfung…"); + ctx.ui.setStatus("optimize", `${"●".repeat(maxRounds)}◉ ShipIt — SHIP oder NO-SHIP?…`); await switchModel(pi, ctx, "llama-cpp-judge", "qwen3.5-judge"); await sendAndWait(pi, ctx, shipitPrompt("")); @@ -739,6 +784,61 @@ export default function (pi: ExtensionAPI) { } }); + // ── Planungsmodus ──────────────────────────────────────────────────────── + + pi.registerCommand("plan", { + description: "Analysiert Auftrag, schmiedet Implementierungsplan in PLAN.md — macht keine Dateiänderungen. → qwen3.5-coder (:8001)", + handler: async function (args: string, ctx: ExtensionCommandContext) { + const task = (args || "").trim(); + if (!task) { + ctx.ui.notify("Benutzung: /plan ", "error"); + return; + } + await writeTaskMd(pi, ctx, task); + await switchModel(pi, ctx, "llama-cpp-coder", "qwen3.5-coder"); + ctx.ui.setStatus("plan", "Analysiere und plane (keine Dateiänderungen)…"); + pi.sendUserMessage(planPrompt(task)); + await ctx.waitForIdle(); + ctx.ui.setStatus("plan", ""); + finalNotify(ctx, "📋 Plan", "Analyse abgeschlossen — PLAN.md + Chat"); + } + }); + + pi.registerCommand("cancel", { + description: "Bricht laufenden Optimize-Loop nach dem aktuellen Schritt ab.", + handler: async function (_args: string, ctx: ExtensionCommandContext) { + cancelRequested = true; + ctx.ui.notify("Abbruch angefordert — wird nach aktuellem Schritt gestoppt", "warning"); + } + }); + + pi.registerCommand("discard", { + description: "Verwirft PLAN.md und setzt den Planungsstatus zurück.", + handler: async function (_args: string, ctx: ExtensionCommandContext) { + await pi.exec("bash", ["-c", "rm -f PLAN.md"], { cwd: ctx.cwd }); + ctx.ui.notify("PLAN.md gelöscht — Plan verworfen", "info"); + finalNotify(ctx, "🗑 Plan verworfen", "Neu starten mit /plan oder /coder"); + } + }); + + 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) { + await switchModel(pi, ctx, "llama-cpp-coder", "qwen3.5-coder"); + ctx.ui.setStatus("continue", "Analysiere unterbrochenen Prozess…"); + pi.sendUserMessage([ + "Ein Prozess wurde unterbrochen. Analysiere den aktuellen Stand und führe ihn sinnvoll fort:", + "1. Lies TASK.md für den Auftrag", + "2. Lies PLAN.md falls vorhanden (war ein Plan in Arbeit?)", + "3. Führe 'git log --oneline -5' aus um zu sehen was bereits committed wurde", + "4. Entscheide: Muss noch implementiert werden? Ist ein Review fällig? Müssen Fixes nachgezogen werden?", + "5. Fahre direkt mit dem nächsten sinnvollen Schritt fort — kein langer Bericht, einfach weitermachen.", + ].join("\n")); + await ctx.waitForIdle(); + ctx.ui.setStatus("continue", ""); + } + }); + // ── Projekt-Scaffolding ────────────────────────────────────────────────── pi.registerCommand("new_project", { diff --git a/start-coder.sh b/start-coder.sh index 70fd59b..21ef768 100755 --- a/start-coder.sh +++ b/start-coder.sh @@ -31,7 +31,7 @@ docker run -d \ "$IMAGE" \ -m "/hf_home/${MODEL_REL_PATH}" \ --alias "${MODEL_ALIAS}" \ - -c 131072 \ + -c 262144 \ -n 16384 \ --jinja \ --no-context-shift \ @@ -45,11 +45,11 @@ docker run -d \ -ngl 999 \ -fa on \ --kv-unified \ - --cache-type-k q8_0 \ - --cache-type-v q8_0 \ + --cache-type-k q4_0 \ + --cache-type-v q4_0 \ --batch-size 1024 \ --ubatch-size 512 \ - --parallel 2 \ + --parallel 1 \ --cont-batching \ --host 0.0.0.0 \ --port "$CONTAINER_PORT" diff --git a/start-judge.sh b/start-judge.sh index af20ed8..8076cbb 100755 --- a/start-judge.sh +++ b/start-judge.sh @@ -31,7 +31,7 @@ docker run -d \ "$IMAGE" \ -m "/hf_home/${MODEL_REL_PATH}" \ --alias "${MODEL_ALIAS}" \ - -c 131072 \ + -c 262144 \ -n 8192 \ --jinja \ --no-context-shift \ @@ -45,8 +45,8 @@ docker run -d \ -ngl 999 \ -fa on \ --kv-unified \ - --cache-type-k q8_0 \ - --cache-type-v q8_0 \ + --cache-type-k q4_0 \ + --cache-type-v q4_0 \ --batch-size 512 \ --ubatch-size 256 \ --parallel 1 \