refactor: Single-GPU-Betrieb + System-Prompt-Rollen + /optimize-Bugfixes
Single-GPU-Umstellung: - models.json auf ein Modell qwen3.5-single kollabiert; tote Provider (llama-cpp, llama-cpp-coder, llama-cpp-judge) entfernt - Coder/Judge-Rolle via before_agent_start-Hook (Persona als System-Prompt) statt Modellwechsel; switchModel -> ensureSingleModel + selectRole - Dual-GPU-Skripte entfernt (start-coder/judge/servers.sh); stop/status auf Single-Container reduziert /optimize-Bugfixes: - Crash behoben: ctx.ui.select positional aufrufen (verursachte "Cannot read properties of undefined (reading 'length')" bei SHIP/version) - PASS WITH CONCERNS loest jetzt eine Coder-Fix-Runde aus; nur sauberes PASS gilt als bestanden (--approve-concerns bleibt Opt-out) - Quick-Judge abgeschafft: immer voller Judge ab Runde 1 - Loop-Erkennung greift auch ohne Blocker-Abschnitt Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
3cf9ef928b
commit
ca85c45a39
5 changed files with 129 additions and 273 deletions
91
models.json
91
models.json
|
|
@ -16,28 +16,6 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
"llama-cpp": {
|
|
||||||
"baseUrl": "http://127.0.0.1:8000/v1",
|
|
||||||
"api": "openai-completions",
|
|
||||||
"apiKey": "none",
|
|
||||||
"compat": {
|
|
||||||
"supportsDeveloperRole": false,
|
|
||||||
"supportsReasoningEffort": false,
|
|
||||||
"maxTokensField": "max_tokens",
|
|
||||||
"thinkingFormat": "qwen-chat-template"
|
|
||||||
},
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"id": "qwen35b-uncensored",
|
|
||||||
"name": "Qwen3.6 35B Uncensored (llama.cpp :8000)"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "qwen35b-moe-tools",
|
|
||||||
"name": "Qwen3.6 35B MoE Tools (llama.cpp :8000)"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
"llama-cpp-single": {
|
"llama-cpp-single": {
|
||||||
"baseUrl": "http://127.0.0.1:8001/v1",
|
"baseUrl": "http://127.0.0.1:8001/v1",
|
||||||
"api": "openai-completions",
|
"api": "openai-completions",
|
||||||
|
|
@ -50,78 +28,13 @@
|
||||||
},
|
},
|
||||||
"models": [
|
"models": [
|
||||||
{
|
{
|
||||||
"id": "qwen3.5-coder",
|
"id": "qwen3.5-single",
|
||||||
"name": "Qwen3.6 27B Single-Server Coder (llama.cpp :8001)",
|
"name": "Qwen3.6 27B Single-Server (llama.cpp :8001)",
|
||||||
"reasoning": true,
|
"reasoning": true,
|
||||||
"input": ["text"],
|
"input": ["text"],
|
||||||
"contextWindow": 262144,
|
"contextWindow": 262144,
|
||||||
"maxTokens": 16384,
|
"maxTokens": 16384,
|
||||||
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
|
"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 }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
"llama-cpp-coder": {
|
|
||||||
"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 Coder (llama.cpp :8001)",
|
|
||||||
"reasoning": true,
|
|
||||||
"input": ["text"],
|
|
||||||
"contextWindow": 262144,
|
|
||||||
"maxTokens": 16384,
|
|
||||||
"cost": {
|
|
||||||
"input": 0,
|
|
||||||
"output": 0,
|
|
||||||
"cacheRead": 0,
|
|
||||||
"cacheWrite": 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
"llama-cpp-judge": {
|
|
||||||
"baseUrl": "http://127.0.0.1:8002/v1",
|
|
||||||
"api": "openai-completions",
|
|
||||||
"apiKey": "none",
|
|
||||||
"compat": {
|
|
||||||
"supportsDeveloperRole": false,
|
|
||||||
"supportsReasoningEffort": false,
|
|
||||||
"maxTokensField": "max_tokens",
|
|
||||||
"thinkingFormat": "qwen-chat-template"
|
|
||||||
},
|
|
||||||
"models": [
|
|
||||||
{
|
|
||||||
"id": "qwen3.5-judge",
|
|
||||||
"name": "Qwen3.6 27B Judge (llama.cpp :8002)",
|
|
||||||
"reasoning": true,
|
|
||||||
"input": ["text"],
|
|
||||||
"contextWindow": 262144,
|
|
||||||
"maxTokens": 16384,
|
|
||||||
"cost": {
|
|
||||||
"input": 0,
|
|
||||||
"output": 0,
|
|
||||||
"cacheRead": 0,
|
|
||||||
"cacheWrite": 0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,37 @@
|
||||||
// pi-coder-judge-extension.ts
|
// pi-coder-judge-extension.ts
|
||||||
// Automatisierter Coder-Judge-Fix-Workflow für AI-Coding-Assistenten
|
// Automatisierter Coder-Judge-Fix-Workflow für AI-Coding-Assistenten
|
||||||
// Modelle: qwen3.5-coder + qwen3.5-judge auf Single-Server (Port 8001)
|
// Ein Modell (qwen3.5-single) auf Single-Server (Port 8001); Rollen Coder/Judge via System-Prompt
|
||||||
|
|
||||||
import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
||||||
import { Type } from "typebox";
|
import { Type } from "typebox";
|
||||||
|
|
||||||
|
// ── Persona-System-Prompts (Rollenumschaltung Coder/Judge) ───────────────────
|
||||||
|
// Diese beschreiben die STABILE Rolle und werden im before_agent_start-Hook an den
|
||||||
|
// Basis-System-Prompt angehängt — abhängig von currentRole. Bewusst neutral gehalten
|
||||||
|
// (keine Git-Commit-/Edit-Pflicht), damit sie auch für /plan (Nur-Lesen) gültig bleiben.
|
||||||
|
// Task-spezifische Anweisungen (committen, Ausgabeformat, Auftrag) stehen weiter in den
|
||||||
|
// jeweiligen User-Prompts unten.
|
||||||
|
function coderPersona(): string {
|
||||||
|
return [
|
||||||
|
"Du bist der Coding-Agent in einem Coder-Judge-Workflow.",
|
||||||
|
"Arbeite sorgfältig, konkret und produktionsorientiert; bevorzuge klare, einfache Lösungen.",
|
||||||
|
"Lies jede Datei unmittelbar vor dem Editieren neu ein.",
|
||||||
|
"Wenn du mehrere Stellen in derselben Datei änderst: Erzeuge einen unified diff und nutze das apply_patch-Tool statt mehrerer edit-Aufrufe. Fallback: Datei komplett neu schreiben.",
|
||||||
|
].join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
function judgePersona(): string {
|
||||||
|
return [
|
||||||
|
"Du bist ein pingeliger, skeptischer Senior-Reviewer und QA-Ingenieur in einem Coder-Judge-Workflow.",
|
||||||
|
"Deine Aufgabe ist NICHT, nett zu sein, sondern Fehler, Risiken, Randfälle und Produktionsprobleme aktiv zu finden.",
|
||||||
|
"Arbeite reproduzierbar und konkret: Wenn du etwas behauptest, nenne die Datei, den Befehl, den Test oder den Reproduktionshinweis.",
|
||||||
|
].join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
// ── Prompt-Bausteine ────────────────────────────────────────────────────────
|
// ── Prompt-Bausteine ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
function coderKickoff(task: string): string {
|
function coderKickoff(task: string): string {
|
||||||
return [
|
return [
|
||||||
"Du bist der Coding-Agent.",
|
|
||||||
"Lies TASK.md für die vollständige Aufgabenbeschreibung.",
|
"Lies TASK.md für die vollständige Aufgabenbeschreibung.",
|
||||||
"Halte dich strikt an die dort beschriebenen Anforderungen.",
|
"Halte dich strikt an die dort beschriebenen Anforderungen.",
|
||||||
"Arbeite sorgfältig, konkret und produktionsorientiert.",
|
"Arbeite sorgfältig, konkret und produktionsorientiert.",
|
||||||
|
|
@ -32,7 +54,6 @@ function coderKickoff(task: string): string {
|
||||||
function judgePrompt(extra: string): string {
|
function judgePrompt(extra: string): string {
|
||||||
const suffix = extra?.trim() ? "\n\nZusätzlicher Fokus des Users:\n" + extra.trim() : "";
|
const suffix = extra?.trim() ? "\n\nZusätzlicher Fokus des Users:\n" + extra.trim() : "";
|
||||||
return [
|
return [
|
||||||
"Du bist ein pingeliger, skeptischer Senior-Reviewer und QA-Ingenieur.",
|
|
||||||
"Deine Aufgabe ist NICHT, nett zu sein, sondern Fehler, Risiken, Randfälle und Produktionsprobleme zu finden.",
|
"Deine Aufgabe ist NICHT, nett zu sein, sondern Fehler, Risiken, Randfälle und Produktionsprobleme zu finden.",
|
||||||
"Arbeite reproduzierbar und konkret.",
|
"Arbeite reproduzierbar und konkret.",
|
||||||
"",
|
"",
|
||||||
|
|
@ -57,59 +78,11 @@ function judgePrompt(extra: string): string {
|
||||||
].join("\n") + suffix;
|
].join("\n") + suffix;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Kompakter Ersteindruck-Prompt für Runde 1: kein TASK.md, nur Diff-Review.
|
|
||||||
// Reduziert Inference-Zeit wenn der Code offensichtlich gut ist.
|
|
||||||
// Bei FAIL → Runde 2 mit vollem judgePrompt() für detaillierte Analyse.
|
|
||||||
function quickJudgePrompt(extra: string): string {
|
|
||||||
const suffix = extra?.trim() ? "\n\nZusätzlicher Fokus des Users:\n" + extra.trim() : "";
|
|
||||||
return [
|
|
||||||
"Schneller Code-Review — erster Eindruck.",
|
|
||||||
"Du bist ein skeptischer Senior-Reviewer. Sei direkt und knapp.",
|
|
||||||
"",
|
|
||||||
"1. Sieh dir 'git show HEAD' an.",
|
|
||||||
"2. Führe relevante Tests aus, falls vorhanden.",
|
|
||||||
"3. Gibt es offensichtliche Blocker? (Bugs, fehlende Fehlerbehandlung, Sicherheitslücken, kaputte Imports)",
|
|
||||||
"4. Wenn alles offensichtlich in Ordnung ist: PASS.",
|
|
||||||
"5. Bei Zweifeln oder Lücken: FAIL — konkrete Blocker benennen.",
|
|
||||||
"",
|
|
||||||
"Ausgabeformat (kompakt):",
|
|
||||||
"- Urteil: PASS | PASS WITH CONCERNS | FAIL",
|
|
||||||
"- Blocker (falls vorhanden)",
|
|
||||||
"- Konkrete Fix-Aufträge (falls FAIL)"
|
|
||||||
].join("\n") + suffix;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Quick-Variante für Runde 1 mit bereits vorliegendem Test-Output.
|
|
||||||
function quickJudgeWithTestsPrompt(testOutput: string, extra: string): string {
|
|
||||||
const suffix = extra?.trim() ? "\n\nZusätzlicher Fokus des Users:\n" + extra.trim() : "";
|
|
||||||
return [
|
|
||||||
"Schneller Code-Review — erster Eindruck.",
|
|
||||||
"Du bist ein skeptischer Senior-Reviewer. Sei direkt und knapp.",
|
|
||||||
"",
|
|
||||||
"Die Test-Suite wurde bereits extern ausgeführt. Führe KEINE weiteren Tests aus.",
|
|
||||||
"",
|
|
||||||
"1. Sieh dir 'git show HEAD' an.",
|
|
||||||
"2. Analysiere das folgende Test-Ergebnis:",
|
|
||||||
"```",
|
|
||||||
testOutput,
|
|
||||||
"```",
|
|
||||||
"3. Gibt es offensichtliche Blocker? (Test-Failures, Bugs, Sicherheitslücken)",
|
|
||||||
"4. Wenn alles offensichtlich in Ordnung ist: PASS.",
|
|
||||||
"5. Bei Zweifeln: FAIL — konkrete Blocker benennen.",
|
|
||||||
"",
|
|
||||||
"Ausgabeformat (kompakt):",
|
|
||||||
"- Urteil: PASS | PASS WITH CONCERNS | FAIL",
|
|
||||||
"- Blocker (falls vorhanden)",
|
|
||||||
"- Konkrete Fix-Aufträge (falls FAIL)"
|
|
||||||
].join("\n") + suffix;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wie judgePrompt, aber Tests werden NICHT vom Judge ausgeführt —
|
// Wie judgePrompt, aber Tests werden NICHT vom Judge ausgeführt —
|
||||||
// die Extension hat sie bereits extern gestartet und übergibt den Output.
|
// die Extension hat sie bereits extern gestartet und übergibt den Output.
|
||||||
function judgeWithTestsPrompt(testOutput: string, extra: string): string {
|
function judgeWithTestsPrompt(testOutput: string, extra: string): string {
|
||||||
const suffix = extra?.trim() ? "\n\nZusätzlicher Fokus des Users:\n" + extra.trim() : "";
|
const suffix = extra?.trim() ? "\n\nZusätzlicher Fokus des Users:\n" + extra.trim() : "";
|
||||||
return [
|
return [
|
||||||
"Du bist ein pingeliger, skeptischer Senior-Reviewer und QA-Ingenieur.",
|
|
||||||
"Deine Aufgabe ist NICHT, nett zu sein, sondern Fehler, Risiken, Randfälle und Produktionsprobleme zu finden.",
|
"Deine Aufgabe ist NICHT, nett zu sein, sondern Fehler, Risiken, Randfälle und Produktionsprobleme zu finden.",
|
||||||
"",
|
"",
|
||||||
"Die Test-Suite wurde bereits extern ausgeführt. Das Ergebnis steht unten.",
|
"Die Test-Suite wurde bereits extern ausgeführt. Das Ergebnis steht unten.",
|
||||||
|
|
@ -420,25 +393,43 @@ async function tickTaskMdStatus(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function switchModel(
|
// Single-Server: genau ein Modell (qwen3.5-single auf :8001) für beide Rollen.
|
||||||
|
// Der Rollenwechsel Coder/Judge erfolgt NICHT mehr über den Modellwechsel, sondern
|
||||||
|
// über den System-Prompt (siehe before_agent_start-Hook + currentRole). Diese Funktion
|
||||||
|
// stellt nur sicher, dass das eine Single-Modell aktiv ist; nach dem ersten Aufruf
|
||||||
|
// ist der Wechsel via currentModelKey gecacht und damit ein No-op.
|
||||||
|
const SINGLE_PROVIDER = "llama-cpp-single";
|
||||||
|
const SINGLE_MODEL_ID = "qwen3.5-single";
|
||||||
|
|
||||||
|
async function ensureSingleModel(
|
||||||
pi: ExtensionAPI,
|
pi: ExtensionAPI,
|
||||||
ctx: ExtensionCommandContext,
|
ctx: ExtensionCommandContext
|
||||||
provider: string,
|
|
||||||
modelId: string
|
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const key = `${provider}/${modelId}`;
|
const key = `${SINGLE_PROVIDER}/${SINGLE_MODEL_ID}`;
|
||||||
if (key === currentModelKey) return true;
|
if (key === currentModelKey) return true;
|
||||||
const model = ctx.modelRegistry.find(provider, modelId);
|
const model = ctx.modelRegistry.find(SINGLE_PROVIDER, SINGLE_MODEL_ID);
|
||||||
if (!model) {
|
if (!model) {
|
||||||
ctx.ui.notify(`Modell ${provider}/${modelId} nicht gefunden`, "error");
|
ctx.ui.notify(`Modell ${SINGLE_PROVIDER}/${SINGLE_MODEL_ID} nicht gefunden`, "error");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const ok = await pi.setModel(model);
|
const ok = await pi.setModel(model);
|
||||||
if (ok !== false) currentModelKey = key;
|
if (ok !== false) currentModelKey = key;
|
||||||
if (!ok) ctx.ui.notify(`Kein API-Key für ${modelId}`, "warning");
|
if (!ok) ctx.ui.notify(`Kein API-Key für ${SINGLE_MODEL_ID}`, "warning");
|
||||||
return ok !== false;
|
return ok !== false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Setzt die aktive Rolle (steuert den per before_agent_start injizierten System-Prompt)
|
||||||
|
// und stellt das Single-Modell sicher. Ersetzt das frühere switchModel(): da beide Rollen
|
||||||
|
// dasselbe Modell nutzen, ist der einzige echte Effekt das Setzen von currentRole.
|
||||||
|
async function selectRole(
|
||||||
|
pi: ExtensionAPI,
|
||||||
|
ctx: ExtensionCommandContext,
|
||||||
|
role: "coder" | "judge"
|
||||||
|
): Promise<boolean> {
|
||||||
|
currentRole = role;
|
||||||
|
return ensureSingleModel(pi, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
// Sendet eine Nachricht und wartet bis der Agent fertig ist.
|
// Sendet eine Nachricht und wartet bis der Agent fertig ist.
|
||||||
// Retry-Schleife fängt "Agent is already processing" ab — tritt auf wenn
|
// Retry-Schleife fängt "Agent is already processing" ab — tritt auf wenn
|
||||||
// waitForIdle() zu früh zurückkehrt (Race Condition im pi-Agent).
|
// waitForIdle() zu früh zurückkehrt (Race Condition im pi-Agent).
|
||||||
|
|
@ -644,7 +635,7 @@ async function getFilesSinceTag(
|
||||||
// Dokumentations-Phase: inkrementell via Git-Tags, nur geänderte Dateien werden verarbeitet.
|
// Dokumentations-Phase: inkrementell via Git-Tags, nur geänderte Dateien werden verarbeitet.
|
||||||
// Wird von /update_doku und /optimize --with-doku genutzt.
|
// Wird von /update_doku und /optimize --with-doku genutzt.
|
||||||
async function runUpdateDoku(pi: ExtensionAPI, ctx: ExtensionCommandContext): Promise<void> {
|
async function runUpdateDoku(pi: ExtensionAPI, ctx: ExtensionCommandContext): Promise<void> {
|
||||||
if (!await switchModel(pi, ctx, "llama-cpp-single", "qwen3.5-coder")) {
|
if (!await selectRole(pi, ctx, "coder")) {
|
||||||
ctx.ui.notify("Coder-Modell nicht verfügbar — Dokumentations-Phase abgebrochen", "error");
|
ctx.ui.notify("Coder-Modell nicht verfügbar — Dokumentations-Phase abgebrochen", "error");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -761,10 +752,10 @@ async function detectVersionFile(
|
||||||
): Promise<"package.json" | "Cargo.toml" | "pyproject.toml" | "VERSION" | null> {
|
): Promise<"package.json" | "Cargo.toml" | "pyproject.toml" | "VERSION" | null> {
|
||||||
for (const f of ["package.json", "Cargo.toml", "pyproject.toml"]) {
|
for (const f of ["package.json", "Cargo.toml", "pyproject.toml"]) {
|
||||||
const r = await pi.exec("bash", ["-c", `test -f ${f}`], { cwd: ctx.cwd });
|
const r = await pi.exec("bash", ["-c", `test -f ${f}`], { cwd: ctx.cwd });
|
||||||
if (r.exitCode === 0) return f as "package.json" | "Cargo.toml" | "pyproject.toml";
|
if (r.code === 0) return f as "package.json" | "Cargo.toml" | "pyproject.toml";
|
||||||
}
|
}
|
||||||
const r = await pi.exec("bash", ["-c", "test -f VERSION"], { cwd: ctx.cwd });
|
const r = await pi.exec("bash", ["-c", "test -f VERSION"], { cwd: ctx.cwd });
|
||||||
return r.exitCode === 0 ? "VERSION" : null;
|
return r.code === 0 ? "VERSION" : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Schreibt die neue Version in die Manifest-Datei und erstellt einen chore-Commit.
|
// Schreibt die neue Version in die Manifest-Datei und erstellt einen chore-Commit.
|
||||||
|
|
@ -796,7 +787,7 @@ async function applyVersionBump(
|
||||||
async function runVersionBump(pi: ExtensionAPI, ctx: ExtensionCommandContext): Promise<void> {
|
async function runVersionBump(pi: ExtensionAPI, ctx: ExtensionCommandContext): Promise<void> {
|
||||||
// Early exit wenn kein git-Repo vorhanden
|
// Early exit wenn kein git-Repo vorhanden
|
||||||
const gitCheck = await pi.exec("bash", ["-c", "git rev-parse --is-inside-work-tree 2>/dev/null"], { cwd: ctx.cwd });
|
const gitCheck = await pi.exec("bash", ["-c", "git rev-parse --is-inside-work-tree 2>/dev/null"], { cwd: ctx.cwd });
|
||||||
if (gitCheck.exitCode !== 0) return;
|
if (gitCheck.code !== 0) return;
|
||||||
|
|
||||||
const current = await getCurrentVersion(pi, ctx);
|
const current = await getCurrentVersion(pi, ctx);
|
||||||
const tag = current ? `v${current[0]}.${current[1]}.${current[2]}` : undefined;
|
const tag = current ? `v${current[0]}.${current[1]}.${current[2]}` : undefined;
|
||||||
|
|
@ -813,13 +804,12 @@ async function runVersionBump(pi: ExtensionAPI, ctx: ExtensionCommandContext): P
|
||||||
t => `${t} → ${versions[t]}${t === recommended ? " (empfohlen)" : ""}`
|
t => `${t} → ${versions[t]}${t === recommended ? " (empfohlen)" : ""}`
|
||||||
);
|
);
|
||||||
|
|
||||||
const choice = await ctx.ui.select({
|
// ctx.ui.select ist positional: select(title, options[], opts?). Die Erklärung muss in den
|
||||||
title: "Version",
|
// Titel — ExtensionUIDialogOptions kennt kein message-Feld.
|
||||||
message: current
|
const titleLine = current
|
||||||
? `Aktuelle Version: ${tag}. Commits seit letztem Tag: ${bump}-Bump erkannt.`
|
? `Version — aktuell ${tag}, ${bump}-Bump erkannt`
|
||||||
: "Noch kein Versions-Tag vorhanden.",
|
: "Version — noch kein Versions-Tag vorhanden";
|
||||||
options: [...labels, "Überspringen"],
|
const choice = await ctx.ui.select(titleLine, [...labels, "Überspringen"]);
|
||||||
});
|
|
||||||
|
|
||||||
if (!choice || choice.startsWith("Überspringen")) return;
|
if (!choice || choice.startsWith("Überspringen")) return;
|
||||||
|
|
||||||
|
|
@ -833,7 +823,7 @@ async function runVersionBump(pi: ExtensionAPI, ctx: ExtensionCommandContext): P
|
||||||
}
|
}
|
||||||
|
|
||||||
const tagResult = await pi.exec("bash", ["-c", `git tag ${newTag}`], { cwd: ctx.cwd });
|
const tagResult = await pi.exec("bash", ["-c", `git tag ${newTag}`], { cwd: ctx.cwd });
|
||||||
if (tagResult.exitCode !== 0) {
|
if (tagResult.code !== 0) {
|
||||||
ctx.ui.notify(`Tag ${newTag} existiert bereits — manuell löschen mit: git tag -d ${newTag}`, "error");
|
ctx.ui.notify(`Tag ${newTag} existiert bereits — manuell löschen mit: git tag -d ${newTag}`, "error");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -883,7 +873,10 @@ function finalNotify(
|
||||||
// ── Extension ────────────────────────────────────────────────────────────────
|
// ── Extension ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
let cancelRequested = false;
|
let cancelRequested = false;
|
||||||
let currentModelKey = ""; // Cache für switchModel() — verhindert redundante setModel()-Aufrufe
|
let currentModelKey = ""; // Cache für ensureSingleModel() — verhindert redundante setModel()-Aufrufe
|
||||||
|
// Aktive Rolle für die Single-Server-Architektur: steuert, welcher Persona-System-Prompt
|
||||||
|
// im before_agent_start-Hook injiziert wird. Wird ausschließlich über selectRole() gesetzt.
|
||||||
|
let currentRole: "coder" | "judge" = "coder";
|
||||||
let interactivePauseActive = false;
|
let interactivePauseActive = false;
|
||||||
let interactiveContinueRequested = false;
|
let interactiveContinueRequested = false;
|
||||||
let interactivePauseTask = "";
|
let interactivePauseTask = "";
|
||||||
|
|
@ -952,6 +945,17 @@ export default function (pi: ExtensionAPI) {
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ── Rollenumschaltung via System-Prompt ──────────────────────────────────
|
||||||
|
// Single-Server: ein Modell, zwei Rollen. Vor jedem Agent-Lauf wird die passende
|
||||||
|
// Persona (Coder/Judge, gesteuert über currentRole) an den fertig zusammengebauten
|
||||||
|
// Basis-System-Prompt ANGEHÄNGT — nicht ersetzt, damit Pi-Basis (Tools, cwd, Umgebung)
|
||||||
|
// erhalten bleibt. So unterscheiden sich Coder- und Judge-Turns auf System-Prompt-Ebene,
|
||||||
|
// ohne Server- oder Modellwechsel.
|
||||||
|
pi.on("before_agent_start", function (event) {
|
||||||
|
const persona = currentRole === "judge" ? judgePersona() : coderPersona();
|
||||||
|
return { systemPrompt: event.systemPrompt + "\n\n" + persona };
|
||||||
|
});
|
||||||
|
|
||||||
// ── Live-Aktivitätsstatus ────────────────────────────────────────────────
|
// ── Live-Aktivitätsstatus ────────────────────────────────────────────────
|
||||||
// turn_start: Working-Text auf aktuellen Command-Kontext setzen
|
// turn_start: Working-Text auf aktuellen Command-Kontext setzen
|
||||||
pi.on("turn_start", function (_event, ctx) {
|
pi.on("turn_start", function (_event, ctx) {
|
||||||
|
|
@ -1013,7 +1017,7 @@ export default function (pi: ExtensionAPI) {
|
||||||
// ── Manuelle Kommandos ───────────────────────────────────────────────────
|
// ── Manuelle Kommandos ───────────────────────────────────────────────────
|
||||||
|
|
||||||
pi.registerCommand("coder", {
|
pi.registerCommand("coder", {
|
||||||
description: "Implementiert <auftrag> ohne Review-Loop → qwen3.5-coder (:8001).",
|
description: "Implementiert <auftrag> ohne Review-Loop → Coder-Rolle (:8001).",
|
||||||
handler: async function (args: string, ctx: ExtensionCommandContext) {
|
handler: async function (args: string, ctx: ExtensionCommandContext) {
|
||||||
const task = (args || "").trim();
|
const task = (args || "").trim();
|
||||||
if (!task) {
|
if (!task) {
|
||||||
|
|
@ -1025,46 +1029,46 @@ export default function (pi: ExtensionAPI) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await writeTaskMd(pi, ctx, task);
|
await writeTaskMd(pi, ctx, task);
|
||||||
await switchModel(pi, ctx, "llama-cpp-single", "qwen3.5-coder");
|
await selectRole(pi, ctx, "coder");
|
||||||
currentActivity = "Coder implementiert…";
|
currentActivity = "Coder implementiert…";
|
||||||
await sendAndWait(pi, ctx, coderKickoff(task));
|
await sendAndWait(pi, ctx, coderKickoff(task));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
pi.registerCommand("judge", {
|
pi.registerCommand("judge", {
|
||||||
description: "Review gegen TASK.md + git show HEAD → qwen3.5-judge (:8001).",
|
description: "Review gegen TASK.md + git show HEAD → Judge-Rolle (:8001).",
|
||||||
handler: async function (args: string, ctx: ExtensionCommandContext) {
|
handler: async function (args: string, ctx: ExtensionCommandContext) {
|
||||||
if (!await waitUntilModelReady(pi, ctx, 8001, "qwen3.5-single")) {
|
if (!await waitUntilModelReady(pi, ctx, 8001, "qwen3.5-single")) {
|
||||||
ctx.ui.notify("Server nicht bereit (Port 8001) — start-single.sh ausführen", "error");
|
ctx.ui.notify("Server nicht bereit (Port 8001) — start-single.sh ausführen", "error");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await switchModel(pi, ctx, "llama-cpp-single", "qwen3.5-judge");
|
await selectRole(pi, ctx, "judge");
|
||||||
currentActivity = "Judge reviewt…";
|
currentActivity = "Judge reviewt…";
|
||||||
await sendAndWait(pi, ctx, judgePrompt(args || ""));
|
await sendAndWait(pi, ctx, judgePrompt(args || ""));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
pi.registerCommand("fix", {
|
pi.registerCommand("fix", {
|
||||||
description: "Fixt Judge-Kritik, committet Ergebnis → qwen3.5-coder (:8001).",
|
description: "Fixt Judge-Kritik, committet Ergebnis → Coder-Rolle (:8001).",
|
||||||
handler: async function (args: string, ctx: ExtensionCommandContext) {
|
handler: async function (args: string, ctx: ExtensionCommandContext) {
|
||||||
if (!await waitUntilModelReady(pi, ctx, 8001, "qwen3.5-single")) {
|
if (!await waitUntilModelReady(pi, ctx, 8001, "qwen3.5-single")) {
|
||||||
ctx.ui.notify("Server nicht bereit (Port 8001) — start-single.sh ausführen", "error");
|
ctx.ui.notify("Server nicht bereit (Port 8001) — start-single.sh ausführen", "error");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await switchModel(pi, ctx, "llama-cpp-single", "qwen3.5-coder");
|
await selectRole(pi, ctx, "coder");
|
||||||
currentActivity = "Coder fixt Judge-Kritik…";
|
currentActivity = "Coder fixt Judge-Kritik…";
|
||||||
await sendAndWait(pi, ctx, fixPrompt(args || ""));
|
await sendAndWait(pi, ctx, fixPrompt(args || ""));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
pi.registerCommand("shipit", {
|
pi.registerCommand("shipit", {
|
||||||
description: "Finale Freigabe gegen TASK.md + git log → qwen3.5-judge (:8001).",
|
description: "Finale Freigabe gegen TASK.md + git log → Judge-Rolle (:8001).",
|
||||||
handler: async function (args: string, ctx: ExtensionCommandContext) {
|
handler: async function (args: string, ctx: ExtensionCommandContext) {
|
||||||
if (!await waitUntilModelReady(pi, ctx, 8001, "qwen3.5-single")) {
|
if (!await waitUntilModelReady(pi, ctx, 8001, "qwen3.5-single")) {
|
||||||
ctx.ui.notify("Server nicht bereit (Port 8001) — start-single.sh ausführen", "error");
|
ctx.ui.notify("Server nicht bereit (Port 8001) — start-single.sh ausführen", "error");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await switchModel(pi, ctx, "llama-cpp-single", "qwen3.5-judge");
|
await selectRole(pi, ctx, "judge");
|
||||||
ctx.ui.notify("Judge prüft finale Freigabe — Ergebnis erscheint im Chat (SHIP / NO-SHIP)", "info");
|
ctx.ui.notify("Judge prüft finale Freigabe — Ergebnis erscheint im Chat (SHIP / NO-SHIP)", "info");
|
||||||
currentActivity = "Judge: finale Freigabe…";
|
currentActivity = "Judge: finale Freigabe…";
|
||||||
await sendAndWait(pi, ctx, shipitPrompt(args || ""));
|
await sendAndWait(pi, ctx, shipitPrompt(args || ""));
|
||||||
|
|
@ -1136,7 +1140,7 @@ export default function (pi: ExtensionAPI) {
|
||||||
ctx.ui.setStatus("optimize", `Starte Optimierung (max ${maxRounds} Runden)…`);
|
ctx.ui.setStatus("optimize", `Starte Optimierung (max ${maxRounds} Runden)…`);
|
||||||
const taskPreview = task.length > 55 ? task.slice(0, 52) + "…" : task;
|
const taskPreview = task.length > 55 ? task.slice(0, 52) + "…" : task;
|
||||||
ctx.ui.setStatus("optimize", `◉ Coder liest Anforderungen + implementiert: ${taskPreview}`);
|
ctx.ui.setStatus("optimize", `◉ Coder liest Anforderungen + implementiert: ${taskPreview}`);
|
||||||
if (!await switchModel(pi, ctx, "llama-cpp-single", "qwen3.5-coder")) {
|
if (!await selectRole(pi, ctx, "coder")) {
|
||||||
finalNotify(ctx, "⛔ Modell-Fehler", "Modell (llama-cpp-single) nicht verfügbar");
|
finalNotify(ctx, "⛔ Modell-Fehler", "Modell (llama-cpp-single) nicht verfügbar");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -1189,32 +1193,27 @@ export default function (pi: ExtensionAPI) {
|
||||||
// Schleife: Judge → (PASS? fertig : Fix → nächste Runde)
|
// Schleife: Judge → (PASS? fertig : Fix → nächste Runde)
|
||||||
for (let round = 1; round <= maxRounds; round++) {
|
for (let round = 1; round <= maxRounds; round++) {
|
||||||
const prog = "●".repeat(round - 1) + "◉" + "○".repeat(maxRounds - round);
|
const prog = "●".repeat(round - 1) + "◉" + "○".repeat(maxRounds - round);
|
||||||
if (!await switchModel(pi, ctx, "llama-cpp-single", "qwen3.5-judge")) {
|
if (!await selectRole(pi, ctx, "judge")) {
|
||||||
finalNotify(ctx, "⛔ Modell-Fehler", "Modell (llama-cpp-single) nicht verfügbar");
|
finalNotify(ctx, "⛔ Modell-Fehler", "Modell (llama-cpp-single) nicht verfügbar");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Runde 1 ohne --continue: Quick-Judge (kein TASK.md, kürzerer Prompt).
|
// Immer der vollständige Judge — auch in Runde 1. Findet Beanstandungen sofort
|
||||||
// Bei FAIL folgt Runde 2 mit vollem judgePrompt für detaillierte Analyse.
|
// (kein nachsichtiger Quick-Check, der triviale Fälle vorschnell durchwinkt).
|
||||||
const useQuickJudge = round === 1 && !continueMode;
|
|
||||||
if (autoTestCmds.length > 0) {
|
if (autoTestCmds.length > 0) {
|
||||||
const label = autoTestCmds.length === 1
|
const label = autoTestCmds.length === 1
|
||||||
? autoTestCmds[0].split(" ")[0]
|
? autoTestCmds[0].split(" ")[0]
|
||||||
: `${autoTestCmds.length} Suiten parallel`;
|
: `${autoTestCmds.length} Suiten parallel`;
|
||||||
ctx.ui.setStatus("optimize", `${prog} Runde ${round}/${maxRounds}: Tests laufen (${label}, max. ${testTimeout}s)…`);
|
ctx.ui.setStatus("optimize", `${prog} Runde ${round}/${maxRounds}: Tests laufen (${label}, max. ${testTimeout}s)…`);
|
||||||
const testOutput = await runTestsParallel(pi, ctx, autoTestCmds, testTimeout);
|
const testOutput = await runTestsParallel(pi, ctx, autoTestCmds, testTimeout);
|
||||||
const judgeLabel = useQuickJudge ? "Quick-Check" : "Judge analysiert";
|
currentActivity = `Judge Runde ${round}/${maxRounds}…`;
|
||||||
currentActivity = `Judge Runde ${round}/${maxRounds}${useQuickJudge ? " (Quick)" : ""}…`;
|
startPhaseTimer(ctx, "optimize", `${prog} Runde ${round}/${maxRounds}: Judge analysiert Test-Ergebnis`);
|
||||||
startPhaseTimer(ctx, "optimize", `${prog} Runde ${round}/${maxRounds}: ${judgeLabel} Test-Ergebnis`);
|
await sendAndWait(pi, ctx, judgeWithTestsPrompt(testOutput, ""));
|
||||||
await sendAndWait(pi, ctx, useQuickJudge
|
|
||||||
? quickJudgeWithTestsPrompt(testOutput, "")
|
|
||||||
: judgeWithTestsPrompt(testOutput, ""));
|
|
||||||
stopPhaseTimer();
|
stopPhaseTimer();
|
||||||
} else {
|
} else {
|
||||||
const judgeLabel = useQuickJudge ? "Quick-Check" : "Judge — TASK.md + letzter Commit + Tests";
|
currentActivity = `Judge Runde ${round}/${maxRounds}…`;
|
||||||
currentActivity = `Judge Runde ${round}/${maxRounds}${useQuickJudge ? " (Quick)" : ""}…`;
|
startPhaseTimer(ctx, "optimize", `${prog} Runde ${round}/${maxRounds}: Judge — TASK.md + letzter Commit + Tests`);
|
||||||
startPhaseTimer(ctx, "optimize", `${prog} Runde ${round}/${maxRounds}: ${judgeLabel}`);
|
await sendAndWait(pi, ctx, judgePrompt(""));
|
||||||
await sendAndWait(pi, ctx, useQuickJudge ? quickJudgePrompt("") : judgePrompt(""));
|
|
||||||
stopPhaseTimer();
|
stopPhaseTimer();
|
||||||
}
|
}
|
||||||
if (cancelRequested) { finalNotify(ctx, "⛔ Abgebrochen", `Nach Judge Runde ${round}`); return; }
|
if (cancelRequested) { finalNotify(ctx, "⛔ Abgebrochen", `Nach Judge Runde ${round}`); return; }
|
||||||
|
|
@ -1222,16 +1221,22 @@ export default function (pi: ExtensionAPI) {
|
||||||
const judgeText = getLastAssistantText(ctx);
|
const judgeText = getLastAssistantText(ctx);
|
||||||
verdict = parseVerdict(judgeText);
|
verdict = parseVerdict(judgeText);
|
||||||
|
|
||||||
if (verdict === "PASS" || verdict === "PASS WITH CONCERNS") {
|
// Bestanden ist nur sauberes PASS. "PASS WITH CONCERNS" gilt nur mit --approve-concerns
|
||||||
|
// als bestanden — sonst fällt es (wie FAIL) in die Fix-Phase, damit der Coder die
|
||||||
|
// gefundenen Beanstandungen behebt.
|
||||||
|
const passed = verdict === "PASS" || (verdict === "PASS WITH CONCERNS" && approveConcerns);
|
||||||
|
if (passed) {
|
||||||
await tickTaskMdStatus(pi, ctx, "Review bestanden (PASS)");
|
await tickTaskMdStatus(pi, ctx, "Review bestanden (PASS)");
|
||||||
const nextStep = interactive ? "warte auf /continue…" : "ShipIt…";
|
const nextStep = interactive ? "warte auf /continue…" : "ShipIt…";
|
||||||
ctx.ui.setStatus("optimize", `${"●".repeat(round)} ✓ ${verdict} nach Runde ${round}/${maxRounds} — ${nextStep}`);
|
ctx.ui.setStatus("optimize", `${"●".repeat(round)} ✓ ${verdict} nach Runde ${round}/${maxRounds} — ${nextStep}`);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loop-Erkennung: gleicher Blocker zweimal → manuell eingreifen.
|
// Loop-Erkennung: gleiche Beanstandung zweimal → manuell eingreifen.
|
||||||
|
// Fallback auf den gesamten Judge-Text, wenn kein Blocker-Abschnitt vorhanden ist
|
||||||
|
// (z.B. reine Minor-Concerns) — sonst würde die Erkennung dort nie greifen.
|
||||||
// Normalisierung verhindert False-Negatives durch minimale Formulierungsunterschiede.
|
// Normalisierung verhindert False-Negatives durch minimale Formulierungsunterschiede.
|
||||||
const currentBlockers = parseBlockers(judgeText);
|
const currentBlockers = parseBlockers(judgeText) || judgeText;
|
||||||
if (currentBlockers && normalizeForComparison(currentBlockers) === normalizeForComparison(lastBlockers)) {
|
if (currentBlockers && normalizeForComparison(currentBlockers) === normalizeForComparison(lastBlockers)) {
|
||||||
ctx.ui.setStatus("optimize", `${prog} ⚠ Gleicher Blocker in Runde ${round} – 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");
|
finalNotify(ctx, "⚠ Schleife", "Gleicher Blocker zweimal – manuelle Intervention nötig");
|
||||||
|
|
@ -1253,7 +1258,7 @@ export default function (pi: ExtensionAPI) {
|
||||||
const blockerHint = currentBlockers
|
const blockerHint = currentBlockers
|
||||||
? (currentBlockers.length > 50 ? currentBlockers.slice(0, 47) + "…" : currentBlockers)
|
? (currentBlockers.length > 50 ? currentBlockers.slice(0, 47) + "…" : currentBlockers)
|
||||||
: "Kritikpunkte aus Judge-Bericht";
|
: "Kritikpunkte aus Judge-Bericht";
|
||||||
if (!await switchModel(pi, ctx, "llama-cpp-single", "qwen3.5-coder")) {
|
if (!await selectRole(pi, ctx, "coder")) {
|
||||||
finalNotify(ctx, "⛔ Modell-Fehler", "Modell (llama-cpp-single) nicht verfügbar");
|
finalNotify(ctx, "⛔ Modell-Fehler", "Modell (llama-cpp-single) nicht verfügbar");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -1297,7 +1302,7 @@ export default function (pi: ExtensionAPI) {
|
||||||
: interactivePauseTask;
|
: interactivePauseTask;
|
||||||
ctx.ui.setStatus("optimize", `◉ Coder implementiert Zusatzauftrag: ${addPreview}`);
|
ctx.ui.setStatus("optimize", `◉ Coder implementiert Zusatzauftrag: ${addPreview}`);
|
||||||
await writeTaskMd(pi, ctx, interactivePauseTask);
|
await writeTaskMd(pi, ctx, interactivePauseTask);
|
||||||
if (!await switchModel(pi, ctx, "llama-cpp-single", "qwen3.5-coder")) {
|
if (!await selectRole(pi, ctx, "coder")) {
|
||||||
finalNotify(ctx, "⛔ Modell-Fehler", "Coder-Modell nicht verfügbar");
|
finalNotify(ctx, "⛔ Modell-Fehler", "Coder-Modell nicht verfügbar");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -1314,9 +1319,9 @@ export default function (pi: ExtensionAPI) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finaler SHIP-Schritt: klares PASS → direkt SHIP ohne zweiten Inference-Aufruf.
|
// Finaler SHIP-Schritt: nur erreichbar mit sauberem PASS (oder PASS WITH CONCERNS +
|
||||||
// "PASS WITH CONCERNS" + --approve-concerns → direkt SHIP (ShipIt-Runde überspringen).
|
// --approve-concerns; beides via break aus der Schleife). PASS WITH CONCERNS ohne Flag
|
||||||
// "PASS WITH CONCERNS" ohne Flag → ShipIt-Runde als finale Abwägung.
|
// führt zu Coder-Fixes (Änderung 3) und kommt nicht mehr hierher.
|
||||||
if (verdict === "PASS" || (verdict === "PASS WITH CONCERNS" && approveConcerns)) {
|
if (verdict === "PASS" || (verdict === "PASS WITH CONCERNS" && approveConcerns)) {
|
||||||
ctx.ui.setStatus("optimize", "🚀 SHIP – produktionsreif");
|
ctx.ui.setStatus("optimize", "🚀 SHIP – produktionsreif");
|
||||||
await autoCommitIfDirty(pi, ctx);
|
await autoCommitIfDirty(pi, ctx);
|
||||||
|
|
@ -1328,38 +1333,6 @@ export default function (pi: ExtensionAPI) {
|
||||||
} else {
|
} 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 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-single", "qwen3.5-judge")) {
|
|
||||||
finalNotify(ctx, "⛔ Modell-Fehler", "Modell (llama-cpp-single) nicht verfügbar");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
currentActivity = "Judge: finale Freigabe…";
|
|
||||||
startPhaseTimer(ctx, "optimize", `${"●".repeat(maxRounds)}◉ ShipIt — finale Freigabe`);
|
|
||||||
await sendAndWait(pi, ctx, shipitPrompt(""));
|
|
||||||
stopPhaseTimer();
|
|
||||||
|
|
||||||
const shipText = getLastAssistantText(ctx);
|
|
||||||
const shipVerdict = shipText.match(/Urteil:\s*(SHIP|NO-SHIP)/i)?.[1]?.toUpperCase() ?? "";
|
|
||||||
|
|
||||||
if (shipVerdict === "SHIP") {
|
|
||||||
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 (shipVerdict === "NO-SHIP") {
|
|
||||||
ctx.ui.setStatus("optimize", "⛔ NO-SHIP – noch nicht bereit");
|
|
||||||
finalNotify(ctx, "⛔ NO-SHIP", "Noch Blocker offen – bitte /judge und /fix manuell");
|
|
||||||
} else {
|
|
||||||
ctx.ui.setStatus("optimize", "ShipIt abgeschlossen");
|
|
||||||
finalNotify(ctx, "ShipIt", "Kein klares Urteil – Antwort im Chat prüfen");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
finalNotify(ctx, "⛔ Fehler", String(e?.message ?? e));
|
finalNotify(ctx, "⛔ Fehler", String(e?.message ?? e));
|
||||||
|
|
@ -1368,6 +1341,9 @@ export default function (pi: ExtensionAPI) {
|
||||||
// Sicherstellen dass keine Zustandsvariable in späteren /optimize-Aufruf leckt
|
// Sicherstellen dass keine Zustandsvariable in späteren /optimize-Aufruf leckt
|
||||||
cancelRequested = false;
|
cancelRequested = false;
|
||||||
currentModelKey = "";
|
currentModelKey = "";
|
||||||
|
// Rolle auf den Standard (coder) zurücksetzen, damit freier Chat nach einem
|
||||||
|
// Lauf nicht versehentlich die Judge-Persona behält (before_agent_start-Hook).
|
||||||
|
currentRole = "coder";
|
||||||
interactivePauseActive = false;
|
interactivePauseActive = false;
|
||||||
interactiveContinueRequested = false;
|
interactiveContinueRequested = false;
|
||||||
interactivePauseTask = "";
|
interactivePauseTask = "";
|
||||||
|
|
@ -1378,7 +1354,7 @@ export default function (pi: ExtensionAPI) {
|
||||||
// ── Schlanke Kommandos für kleine Änderungen ─────────────────────────────
|
// ── Schlanke Kommandos für kleine Änderungen ─────────────────────────────
|
||||||
|
|
||||||
pi.registerCommand("patch", {
|
pi.registerCommand("patch", {
|
||||||
description: "Gezielte Minimaländerung ohne Refactoring, committet → qwen3.5-coder (:8001).",
|
description: "Gezielte Minimaländerung ohne Refactoring, committet → Coder-Rolle (:8001).",
|
||||||
handler: async function (args: string, ctx: ExtensionCommandContext) {
|
handler: async function (args: string, ctx: ExtensionCommandContext) {
|
||||||
const change = (args || "").trim();
|
const change = (args || "").trim();
|
||||||
if (!change) {
|
if (!change) {
|
||||||
|
|
@ -1389,20 +1365,20 @@ export default function (pi: ExtensionAPI) {
|
||||||
ctx.ui.notify("Server nicht bereit (Port 8001) — start-single.sh ausführen", "error");
|
ctx.ui.notify("Server nicht bereit (Port 8001) — start-single.sh ausführen", "error");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await switchModel(pi, ctx, "llama-cpp-single", "qwen3.5-coder");
|
await selectRole(pi, ctx, "coder");
|
||||||
currentActivity = "Coder patcht…";
|
currentActivity = "Coder patcht…";
|
||||||
await sendAndWait(pi, ctx, patchPrompt(change));
|
await sendAndWait(pi, ctx, patchPrompt(change));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
pi.registerCommand("quick_check", {
|
pi.registerCommand("quick_check", {
|
||||||
description: "Schnelle OK/PROBLEM-Prüfung einer kleinen Codeänderung → qwen3.5-judge (:8001).",
|
description: "Schnelle OK/PROBLEM-Prüfung einer kleinen Codeänderung → Judge-Rolle (:8001).",
|
||||||
handler: async function (args: string, ctx: ExtensionCommandContext) {
|
handler: async function (args: string, ctx: ExtensionCommandContext) {
|
||||||
if (!await waitUntilModelReady(pi, ctx, 8001, "qwen3.5-single")) {
|
if (!await waitUntilModelReady(pi, ctx, 8001, "qwen3.5-single")) {
|
||||||
ctx.ui.notify("Server nicht bereit (Port 8001) — start-single.sh ausführen", "error");
|
ctx.ui.notify("Server nicht bereit (Port 8001) — start-single.sh ausführen", "error");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await switchModel(pi, ctx, "llama-cpp-single", "qwen3.5-judge");
|
await selectRole(pi, ctx, "judge");
|
||||||
currentActivity = "Judge: Schnellcheck…";
|
currentActivity = "Judge: Schnellcheck…";
|
||||||
await sendAndWait(pi, ctx, quickCheckPrompt(args || ""));
|
await sendAndWait(pi, ctx, quickCheckPrompt(args || ""));
|
||||||
}
|
}
|
||||||
|
|
@ -1459,7 +1435,7 @@ export default function (pi: ExtensionAPI) {
|
||||||
// ── Planungsmodus ────────────────────────────────────────────────────────
|
// ── Planungsmodus ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
pi.registerCommand("plan", {
|
pi.registerCommand("plan", {
|
||||||
description: "Erstellt Implementierungsplan in PLAN.md ohne Dateiänderungen → qwen3.5-coder.",
|
description: "Erstellt Implementierungsplan in PLAN.md ohne Dateiänderungen → Coder-Rolle.",
|
||||||
handler: async function (args: string, ctx: ExtensionCommandContext) {
|
handler: async function (args: string, ctx: ExtensionCommandContext) {
|
||||||
const task = (args || "").trim();
|
const task = (args || "").trim();
|
||||||
if (!task) {
|
if (!task) {
|
||||||
|
|
@ -1471,7 +1447,7 @@ export default function (pi: ExtensionAPI) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await writeTaskMd(pi, ctx, task);
|
await writeTaskMd(pi, ctx, task);
|
||||||
await switchModel(pi, ctx, "llama-cpp-single", "qwen3.5-coder");
|
await selectRole(pi, ctx, "coder");
|
||||||
ctx.ui.setStatus("plan", "Analysiere und plane (keine Dateiänderungen)…");
|
ctx.ui.setStatus("plan", "Analysiere und plane (keine Dateiänderungen)…");
|
||||||
currentActivity = "Coder plant (kein Code)…";
|
currentActivity = "Coder plant (kein Code)…";
|
||||||
await sendAndWait(pi, ctx, planPrompt(task));
|
await sendAndWait(pi, ctx, planPrompt(task));
|
||||||
|
|
@ -1552,7 +1528,7 @@ export default function (pi: ExtensionAPI) {
|
||||||
ctx.ui.notify("Server nicht bereit (Port 8001) — start-single.sh ausführen", "error");
|
ctx.ui.notify("Server nicht bereit (Port 8001) — start-single.sh ausführen", "error");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await switchModel(pi, ctx, "llama-cpp-single", "qwen3.5-coder");
|
await selectRole(pi, ctx, "coder");
|
||||||
ctx.ui.setStatus("continue", "Analysiere unterbrochenen Prozess…");
|
ctx.ui.setStatus("continue", "Analysiere unterbrochenen Prozess…");
|
||||||
currentActivity = "Coder analysiert Stand…";
|
currentActivity = "Coder analysiert Stand…";
|
||||||
await sendAndWait(pi, ctx, [
|
await sendAndWait(pi, ctx, [
|
||||||
|
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
||||||
LOG_CODER=$(mktemp /tmp/coder_XXXXXX.log)
|
|
||||||
LOG_JUDGE=$(mktemp /tmp/judge_XXXXXX.log)
|
|
||||||
|
|
||||||
echo "[*] Starte beide Server parallel ..."
|
|
||||||
bash "$SCRIPT_DIR/start-coder.sh" > "$LOG_CODER" 2>&1 &
|
|
||||||
PID_CODER=$!
|
|
||||||
bash "$SCRIPT_DIR/start-judge.sh" > "$LOG_JUDGE" 2>&1 &
|
|
||||||
PID_JUDGE=$!
|
|
||||||
|
|
||||||
wait_result() {
|
|
||||||
local PID="$1" NAME="$2" LOG="$3"
|
|
||||||
if wait "$PID"; then
|
|
||||||
echo "[✓] $NAME bereit"
|
|
||||||
else
|
|
||||||
echo "[✗] $NAME fehlgeschlagen — Log:"
|
|
||||||
cat "$LOG"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
RC=0
|
|
||||||
wait_result "$PID_CODER" "Coder (:8001)" "$LOG_CODER" || RC=1
|
|
||||||
wait_result "$PID_JUDGE" "Judge (:8002)" "$LOG_JUDGE" || RC=1
|
|
||||||
|
|
||||||
rm -f "$LOG_CODER" "$LOG_JUDGE"
|
|
||||||
exit $RC
|
|
||||||
|
|
@ -30,5 +30,4 @@ check_server() {
|
||||||
}
|
}
|
||||||
|
|
||||||
echo "=== LLaMA-Server Status ==="
|
echo "=== LLaMA-Server Status ==="
|
||||||
check_server "qwen36-27b-coder" 8001 "qwen3.5-coder"
|
check_server "qwen36-27b-single" 8001 "qwen3.5-single"
|
||||||
check_server "qwen36-27b-judge" 8002 "qwen3.5-judge"
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,12 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
CODER="qwen36-27b-coder"
|
# Single-GPU-Architektur: nur noch ein Container (Coder + Judge in einem).
|
||||||
JUDGE="qwen36-27b-judge"
|
SINGLE="qwen36-27b-single"
|
||||||
|
|
||||||
for NAME in "$CODER" "$JUDGE"; do
|
if docker ps -a --format '{{.Names}}' | grep -q "^${SINGLE}\$"; then
|
||||||
if docker ps -a --format '{{.Names}}' | grep -q "^${NAME}\$"; then
|
docker rm -f "$SINGLE" >/dev/null
|
||||||
docker rm -f "$NAME" >/dev/null
|
echo "[*] Gestoppt: $SINGLE"
|
||||||
echo "[*] Gestoppt: $NAME"
|
|
||||||
else
|
else
|
||||||
echo "[-] Nicht gefunden: $NAME"
|
echo "[-] Nicht gefunden: $SINGLE"
|
||||||
fi
|
fi
|
||||||
done
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue