chore: init pi_coder repository
Pi agent extension, model config, and LLaMA server startup scripts for the coder/judge workflow (ports 8001/8002).
This commit is contained in:
commit
4074e10c1a
6 changed files with 1075 additions and 0 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
*.bak
|
||||
node_modules/
|
||||
.DS_Store
|
||||
19
install.sh
Executable file
19
install.sh
Executable file
|
|
@ -0,0 +1,19 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
REPO="$(cd "$(dirname "$0")" && pwd)"
|
||||
|
||||
# Extension symlink
|
||||
mkdir -p ~/.pi/agent/extensions
|
||||
ln -sf "$REPO/pi-coder-judge-extension.ts" ~/.pi/agent/extensions/pi-coder-judge-extension.ts
|
||||
echo "Symlink: ~/.pi/agent/extensions/pi-coder-judge-extension.ts -> $REPO/pi-coder-judge-extension.ts"
|
||||
|
||||
# models.json symlink (Backup wenn reguläre Datei vorhanden)
|
||||
if [ -f ~/.pi/agent/models.json ] && [ ! -L ~/.pi/agent/models.json ]; then
|
||||
cp ~/.pi/agent/models.json ~/.pi/agent/models.json.bak
|
||||
echo "Backup: ~/.pi/agent/models.json.bak"
|
||||
fi
|
||||
ln -sf "$REPO/models.json" ~/.pi/agent/models.json
|
||||
echo "Symlink: ~/.pi/agent/models.json -> $REPO/models.json"
|
||||
|
||||
echo ""
|
||||
echo "Fertig. Bitte /reload in pi agent ausführen."
|
||||
111
models.json
Normal file
111
models.json
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
{
|
||||
"providers": {
|
||||
"ollama": {
|
||||
"baseUrl": "http://localhost:11434/v1",
|
||||
"api": "openai-completions",
|
||||
"apiKey": "ollama",
|
||||
"compat": {
|
||||
"supportsDeveloperRole": false,
|
||||
"supportsReasoningEffort": false
|
||||
},
|
||||
"models": [
|
||||
{ "id": "qwen2.5-coder:7b", "name": "Qwen2.5 Coder 7B (schnell)" },
|
||||
{ "id": "qwen3-coder-30b-gpu:latest", "name": "Qwen3 Coder 30B GPU (Standard)" },
|
||||
{ "id": "mistral-small3.2:24b", "name": "Mistral Small 3.2 24B" },
|
||||
{ "id": "deepseek-r1:32b", "name": "DeepSeek R1 32B (Reasoning)" }
|
||||
]
|
||||
},
|
||||
|
||||
"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-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": 131072,
|
||||
"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": 65536,
|
||||
"maxTokens": 8192,
|
||||
"cost": {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"cacheRead": 0,
|
||||
"cacheWrite": 0
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
"openrouter": {
|
||||
"models": [
|
||||
{ "id": "qwen/qwen3-235b-a22b:free", "name": "Qwen3 235B (Free)" },
|
||||
{ "id": "deepseek/deepseek-r1:free", "name": "DeepSeek R1 (Free)" },
|
||||
{ "id": "google/gemini-2.5-pro-exp-03-25:free", "name": "Gemini 2.5 Pro (Free)" },
|
||||
{ "id": "meta-llama/llama-4-maverick:free", "name": "Llama 4 Maverick (Free)" },
|
||||
{ "id": "microsoft/phi-4:free", "name": "Phi-4 (Free)" },
|
||||
{ "id": "qwen/qwen-2.5-coder-32b-instruct", "name": "Qwen2.5 Coder 32B (günstig)" },
|
||||
{ "id": "deepseek/deepseek-r1", "name": "DeepSeek R1 Full (Reasoning)" },
|
||||
{ "id": "qwen/qwen3-235b-a22b", "name": "Qwen3 235B Full" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
762
pi-coder-judge-extension.ts
Normal file
762
pi-coder-judge-extension.ts
Normal file
|
|
@ -0,0 +1,762 @@
|
|||
// pi-coder-judge-extension.ts
|
||||
// Automatisierter Coder-Judge-Fix-Workflow für AI-Coding-Assistenten
|
||||
// Modelle: qwen3.5-coder (Port 8001), qwen3.5-judge (Port 8002)
|
||||
|
||||
import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
||||
import { Type } from "typebox";
|
||||
|
||||
// ── Prompt-Bausteine ────────────────────────────────────────────────────────
|
||||
|
||||
function coderKickoff(task: string): string {
|
||||
return [
|
||||
"Du bist der Coding-Agent.",
|
||||
"Lies TASK.md für die vollständige Aufgabenbeschreibung.",
|
||||
"Halte dich strikt an die dort beschriebenen Anforderungen.",
|
||||
"Arbeite sorgfältig, konkret und produktionsorientiert.",
|
||||
"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 — nicht mehrere edit-Aufrufe.",
|
||||
"Fallback: Datei komplett neu schreiben wenn apply_patch nicht möglich.",
|
||||
"",
|
||||
"Git-Pflichten:",
|
||||
"- Falls noch kein git-Repository existiert, initialisiere es mit 'git init'.",
|
||||
"- Führe nach der Implementierung einen Commit durch: git add -A && git commit -m 'feat: ...'",
|
||||
"",
|
||||
"Führe nach der Implementierung passende Tests oder Checks aus.",
|
||||
"Melde knapp, was du geändert hast, welche Risiken bleiben und welche Tests du ausgeführt hast.",
|
||||
"",
|
||||
"Auftrag:",
|
||||
task
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function judgePrompt(extra: string): string {
|
||||
const suffix = extra?.trim() ? "\n\nZusätzlicher Fokus des Users:\n" + extra.trim() : "";
|
||||
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.",
|
||||
"Arbeite reproduzierbar und konkret.",
|
||||
"",
|
||||
"Pflichten:",
|
||||
"0. Lies TASK.md und prüfe, ob alle dort beschriebenen Anforderungen vollständig umgesetzt sind.",
|
||||
"1. Sieh dir den letzten Commit an: 'git log -1 --stat' und 'git show HEAD'.",
|
||||
"2. Führe relevante Tests, Linter oder Startchecks aus.",
|
||||
"3. Versuche Fehler aktiv zu finden.",
|
||||
"4. Bewerte Korrektheit, Robustheit, Fehlerbehandlung, Sicherheit, Logging, Wartbarkeit und Produktionsreife.",
|
||||
"5. Wenn etwas fehlt, sage es klar und direkt.",
|
||||
"",
|
||||
"Ausgabeformat:",
|
||||
"- Urteil: PASS | PASS WITH CONCERNS | FAIL",
|
||||
"- Blocker",
|
||||
"- Major",
|
||||
"- Minor",
|
||||
"- Fehlende Tests",
|
||||
"- Produktionsrisiken",
|
||||
"- Konkrete Fix-Aufträge an den Coder",
|
||||
"",
|
||||
"Wenn du etwas behauptest, nenne die Datei, den Befehl, den Test oder den Reproduktionshinweis."
|
||||
].join("\n") + suffix;
|
||||
}
|
||||
|
||||
function fixPrompt(extra: string): string {
|
||||
const suffix = extra?.trim() ? "\n\nZusätzlicher User-Hinweis:\n" + extra.trim() : "";
|
||||
return [
|
||||
"Wechsle in den Reparaturmodus.",
|
||||
"Lies TASK.md als Referenz — stelle sicher, dass nach den Fixes alle Anforderungen erfüllt bleiben.",
|
||||
"Nutze den letzten Judge-Bericht als verbindliche Aufgabenliste.",
|
||||
"Behebe zuerst Blocker, dann Major, dann Minor.",
|
||||
"Lies jede betroffene Datei unmittelbar vor dem Editieren erneut ein.",
|
||||
"Wenn du mehrere Stellen in derselben Datei änderst: Erzeuge einen unified diff und nutze das apply_patch-Tool — nicht mehrere edit-Aufrufe.",
|
||||
"Fallback: Datei komplett neu schreiben wenn apply_patch nicht möglich.",
|
||||
"Führe nach den Fixes passende Tests aus.",
|
||||
"Führe danach einen Commit durch: git add -A && git commit -m 'fix: ...'",
|
||||
"Wenn ein Punkt nicht sinnvoll umsetzbar ist, begründe das präzise.",
|
||||
"Liefere am Ende nur:",
|
||||
"- Was geändert wurde",
|
||||
"- Welche Judge-Punkte geschlossen wurden",
|
||||
"- Welche Punkte offen bleiben",
|
||||
"- Welche Tests ausgeführt wurden"
|
||||
].join("\n") + suffix;
|
||||
}
|
||||
|
||||
function shipitPrompt(extra: string): string {
|
||||
return [
|
||||
"Führe die finale Freigabeprüfung durch.",
|
||||
"Lies TASK.md und prüfe, ob alle dort beschriebenen Anforderungen im finalen Stand enthalten sind.",
|
||||
"Lies die relevanten geänderten Dateien und 'git log --oneline -10'.",
|
||||
"Führe sinnvolle Tests, Linter und Startchecks aus.",
|
||||
"Beurteile, ob der Stand produktionsreif ist.",
|
||||
"",
|
||||
"Ausgabeformat:",
|
||||
"- Urteil: SHIP | NO-SHIP",
|
||||
"- Letzte Blocker",
|
||||
"- Restrisiken",
|
||||
"- Empfohlene Sofortmaßnahmen vor Deployment"
|
||||
].join("\n") + (extra?.trim() ? "\n\nZusätzlicher Fokus:\n" + extra.trim() : "");
|
||||
}
|
||||
|
||||
function patchPrompt(change: string): string {
|
||||
return [
|
||||
"Du machst eine kleine, gezielte Codeänderung. Nichts weiter.",
|
||||
"Ändere AUSSCHLIESSLICH das Folgende:",
|
||||
change,
|
||||
"",
|
||||
"Regeln:",
|
||||
"- Kein Refactoring, keine weiteren Verbesserungen, keine Umbenennungen",
|
||||
"- Lies die 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",
|
||||
"- Fallback: Datei komplett neu schreiben",
|
||||
"- Finde die betroffene Stelle direkt (grep oder gezielte Datei-Suche)",
|
||||
"- Ändere nur die notwendigen Zeilen",
|
||||
"- Prüfe danach nur: Kompiliert/startet es noch?",
|
||||
"- Commit: git add -A && git commit -m 'fix: <ein Satz>'",
|
||||
"- Melde: Datei, Zeile(n), was geändert wurde. Fertig."
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function quickCheckPrompt(what: string): string {
|
||||
const focus = what?.trim() ? "\n\nZu prüfende Änderung:\n" + what.trim() : "";
|
||||
return [
|
||||
"Schnelle Prüfung einer kleinen Codeänderung.",
|
||||
"Lies 'git show HEAD' um zu sehen was geändert wurde.",
|
||||
"Prüfe NUR:",
|
||||
"- Ist die Änderung korrekt umgesetzt?",
|
||||
"- Gibt es offensichtliche Fehler oder Randfälle die übersehen wurden?",
|
||||
"- Kompiliert/startet der Code?",
|
||||
"",
|
||||
"Ausgabeformat (kurz):",
|
||||
"- Urteil: OK | PROBLEM",
|
||||
"- Falls PROBLEM: konkret was falsch ist und wie zu fixen",
|
||||
"",
|
||||
"Kein vollständiger Review, keine Stilkritik, kein Refactoring-Vorschlag."
|
||||
].join("\n") + focus;
|
||||
}
|
||||
|
||||
function commentCodePrompt(): string {
|
||||
return [
|
||||
"Lies TASK.md und alle Quelldateien des Projekts.",
|
||||
"Füge wartungsfreundliche Kommentare ein, die Entwicklern ohne Vorkenntnis helfen, den Code zu verstehen und zu warten.",
|
||||
"",
|
||||
"Regeln:",
|
||||
"- Kommentiere das WARUM, nicht das WAS (kein 'x += 1 // increment x')",
|
||||
"- Erkläre nicht-offensichtliche Algorithmen, Randfälle und Design-Entscheidungen",
|
||||
"- Füge Modul-/Datei-Level-Kommentare ein, die den Gesamtzweck der Datei erklären",
|
||||
"- Keine trivialen Kommentare, die nur den Code wiederholen",
|
||||
"- Sprache der Kommentare: Deutsch",
|
||||
"",
|
||||
"Wenn du mehrere Stellen in derselben Datei kommentierst: Erzeuge einen unified diff und nutze das apply_patch-Tool — nicht mehrere edit-Aufrufe.",
|
||||
"Fallback: Datei komplett neu schreiben wenn apply_patch nicht möglich.",
|
||||
"",
|
||||
"Führe danach einen Build/Test aus um sicherzustellen, dass die Kommentare nichts kaputt gemacht haben.",
|
||||
"Melde welche Dateien du kommentiert hast."
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function readmeMdPrompt(): string {
|
||||
return [
|
||||
"Lies TASK.md und alle Quelldateien des Projekts.",
|
||||
"Schreibe oder aktualisiere README.md aus Entwicklerperspektive.",
|
||||
"",
|
||||
"Pflichtabschnitte:",
|
||||
"- Projektbeschreibung (Was macht das Programm? Warum?)",
|
||||
"- Voraussetzungen (Dependencies, Toolchain)",
|
||||
"- Installation und Build",
|
||||
"- Verwendung (alle Kommandozeilenoptionen und Flags)",
|
||||
"- Beispiele mit konkreter Ausgabe",
|
||||
"- Projektstruktur (Dateien und ihre Aufgabe)",
|
||||
"",
|
||||
"Halte es technisch präzise und korrekt. Sprache: Deutsch."
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function bedienungsanleitungPrompt(): string {
|
||||
return [
|
||||
"Lies TASK.md und README.md.",
|
||||
"Schreibe oder aktualisiere BEDIENUNGSANLEITUNG.md aus Endnutzer-Perspektive.",
|
||||
"Setze kein Entwicklerwissen voraus — die Zielgruppe sind normale Anwender.",
|
||||
"",
|
||||
"Pflichtabschnitte:",
|
||||
"- Zweck des Programms (was kann der Nutzer damit tun?)",
|
||||
"- Installation für Endnutzer (Schritt für Schritt)",
|
||||
"- Erste Schritte / Schnellstart",
|
||||
"- Alle Optionen mit verständlicher Erklärung und Beispielen",
|
||||
"- Typische Anwendungsfälle",
|
||||
"- Fehlermeldungen und ihre Lösung",
|
||||
"",
|
||||
"Sprache: Deutsch. Einfach, klar, ohne Jargon."
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function commentCodePromptIncremental(files: string[]): string {
|
||||
const fileList = files.map(f => ` - ${f}`).join("\n");
|
||||
return [
|
||||
"Kommentiere NUR die folgenden Quelldateien — sie haben sich seit dem letzten Kommentar-Update geändert:",
|
||||
fileList,
|
||||
"Alle anderen Dateien haben bereits aktuelle Kommentare — lass sie vollständig unberührt.",
|
||||
"",
|
||||
"Regeln:",
|
||||
"- Kommentiere das WARUM, nicht das WAS (kein 'x += 1 // increment x')",
|
||||
"- Erkläre nicht-offensichtliche Algorithmen, Randfälle und Design-Entscheidungen",
|
||||
"- Füge Modul-/Datei-Level-Kommentare ein, die den Gesamtzweck der Datei erklären",
|
||||
"- Keine trivialen Kommentare, die nur den Code wiederholen",
|
||||
"- Sprache der Kommentare: Deutsch",
|
||||
"",
|
||||
"Wenn du mehrere Stellen in derselben Datei kommentierst: Erzeuge einen unified diff und nutze das apply_patch-Tool — nicht mehrere edit-Aufrufe.",
|
||||
"Fallback: Datei komplett neu schreiben wenn apply_patch nicht möglich.",
|
||||
"",
|
||||
"Führe danach einen Build/Test aus um sicherzustellen, dass die Kommentare nichts kaputt gemacht haben.",
|
||||
"Melde welche Dateien du kommentiert hast."
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function readmeMdPromptIncremental(files: string[]): string {
|
||||
const fileList = files.map(f => ` - ${f}`).join("\n");
|
||||
return [
|
||||
"Folgende Quelldateien haben sich seit dem letzten README-Update geändert:",
|
||||
fileList,
|
||||
"",
|
||||
"Prüfe: Haben diese Änderungen Auswirkungen auf Installation, Verwendung, Optionen oder Projektstruktur?",
|
||||
"- Falls JA: Lies README.md und aktualisiere NUR die betroffenen Abschnitte.",
|
||||
"- Falls NEIN: Antworte nur mit dem Satz: 'README.md ist aktuell – keine Änderung nötig.'",
|
||||
"",
|
||||
"Wenn du mehrere Abschnitte in README.md aktualisierst: Erzeuge einen unified diff und nutze das apply_patch-Tool.",
|
||||
"Halte es technisch präzise und korrekt. Sprache: Deutsch."
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function bedienungsanleitungPromptIncremental(files: string[]): string {
|
||||
const fileList = files.map(f => ` - ${f}`).join("\n");
|
||||
return [
|
||||
"Folgende Quelldateien haben sich seit dem letzten Bedienungsanleitung-Update geändert:",
|
||||
fileList,
|
||||
"",
|
||||
"Prüfe: Haben diese Änderungen Auswirkungen auf die Benutzung des Programms durch Endnutzer?",
|
||||
"(z.B. neue Optionen, geändertes Verhalten, neue Fehlermeldungen)",
|
||||
"- Falls JA: Lies BEDIENUNGSANLEITUNG.md und aktualisiere NUR die betroffenen Abschnitte.",
|
||||
"- Falls NEIN: Antworte nur mit dem Satz: 'BEDIENUNGSANLEITUNG.md ist aktuell – keine Änderung nötig.'",
|
||||
"",
|
||||
"Wenn du mehrere Abschnitte in BEDIENUNGSANLEITUNG.md aktualisierst: Erzeuge einen unified diff und nutze das apply_patch-Tool.",
|
||||
"Sprache: Deutsch. Einfach, klar, ohne Jargon."
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
// ── Hilfsfunktionen ─────────────────────────────────────────────────────────
|
||||
|
||||
// Legt TASK.md neu an oder hängt einen Zusatzauftrag an.
|
||||
async function writeTaskMd(
|
||||
pi: ExtensionAPI,
|
||||
ctx: ExtensionCommandContext,
|
||||
task: string
|
||||
): Promise<void> {
|
||||
const check = await pi.exec("bash", ["-c", "test -f TASK.md && echo exists"], { cwd: ctx.cwd });
|
||||
const exists = check.stdout.trim() === "exists";
|
||||
|
||||
let content: string;
|
||||
if (exists) {
|
||||
content = [
|
||||
"",
|
||||
"---",
|
||||
"",
|
||||
"## Zusatzauftrag",
|
||||
"",
|
||||
new Date().toISOString(),
|
||||
"",
|
||||
task,
|
||||
"",
|
||||
"## Status",
|
||||
"- [ ] Implementierung",
|
||||
"- [ ] Review bestanden (PASS)",
|
||||
"- [ ] Produktionsreif (SHIP)",
|
||||
].join("\n") + "\n";
|
||||
} else {
|
||||
content = [
|
||||
"# Aufgabe",
|
||||
"",
|
||||
task,
|
||||
"",
|
||||
"## Erstellt",
|
||||
new Date().toISOString(),
|
||||
"",
|
||||
"## Status",
|
||||
"- [ ] Implementierung",
|
||||
"- [ ] Review bestanden (PASS)",
|
||||
"- [ ] Produktionsreif (SHIP)",
|
||||
].join("\n") + "\n";
|
||||
}
|
||||
|
||||
const redirect = exists ? ">>" : ">";
|
||||
await pi.exec("bash", ["-c", `printf "%s" "$1" ${redirect} TASK.md`, "_", content], { cwd: ctx.cwd });
|
||||
ctx.ui.notify(exists ? "TASK.md erweitert" : "TASK.md angelegt", "info");
|
||||
}
|
||||
|
||||
// Hakt einen Status-Eintrag in TASK.md ab.
|
||||
// label: exakt wie in der Checkbox, z.B. "Implementierung"
|
||||
async function tickTaskMdStatus(
|
||||
pi: ExtensionAPI,
|
||||
ctx: ExtensionCommandContext,
|
||||
label: string
|
||||
): Promise<void> {
|
||||
const check = await pi.exec("bash", ["-c", "test -f TASK.md && echo exists"], { cwd: ctx.cwd });
|
||||
if (check.stdout.trim() !== "exists") return;
|
||||
// Python übernimmt den String-Ersatz — kein Shell-Escaping-Problem
|
||||
await pi.exec(
|
||||
"python3",
|
||||
["-c",
|
||||
"import sys; f=open('TASK.md','r'); c=f.read(); f.close(); " +
|
||||
"c=c.replace('- [ ] '+sys.argv[1], '- [x] '+sys.argv[1]); " +
|
||||
"f=open('TASK.md','w'); f.write(c); f.close()",
|
||||
label
|
||||
],
|
||||
{ cwd: ctx.cwd }
|
||||
);
|
||||
}
|
||||
|
||||
async function switchModel(
|
||||
pi: ExtensionAPI,
|
||||
ctx: ExtensionCommandContext,
|
||||
provider: string,
|
||||
modelId: string
|
||||
): Promise<void> {
|
||||
const model = ctx.modelRegistry.find(provider, modelId);
|
||||
if (!model) {
|
||||
ctx.ui.notify(`Modell ${provider}/${modelId} nicht gefunden`, "error");
|
||||
return;
|
||||
}
|
||||
const ok = await pi.setModel(model);
|
||||
if (!ok) ctx.ui.notify(`Kein API-Key für ${modelId}`, "warning");
|
||||
}
|
||||
|
||||
// Sendet eine Nachricht und wartet bis der Agent fertig ist.
|
||||
// Erst idle abwarten, dann als followUp einstellen — verhindert "Agent is already processing".
|
||||
async function sendAndWait(
|
||||
pi: ExtensionAPI,
|
||||
ctx: ExtensionCommandContext,
|
||||
content: string
|
||||
): Promise<void> {
|
||||
await ctx.waitForIdle();
|
||||
pi.sendUserMessage(content, { deliverAs: "followUp" });
|
||||
await new Promise(r => setTimeout(r, 400));
|
||||
await ctx.waitForIdle();
|
||||
}
|
||||
|
||||
// Liest den Text der letzten Assistenten-Antwort aus dem Session-Branch.
|
||||
function getLastAssistantText(ctx: ExtensionCommandContext): string {
|
||||
const entries = ctx.sessionManager.getBranch();
|
||||
for (let i = entries.length - 1; i >= 0; i--) {
|
||||
const entry = entries[i];
|
||||
if (entry.type === "message") {
|
||||
const msg = (entry as any).message;
|
||||
if (msg?.role === "assistant" && Array.isArray(msg.content)) {
|
||||
return msg.content
|
||||
.filter((c: any) => c.type === "text")
|
||||
.map((c: any) => c.text as string)
|
||||
.join("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
// Extrahiert das Urteil aus einer Judge-Antwort.
|
||||
function parseVerdict(text: string): string {
|
||||
const m = text.match(/Urteil:\s*(PASS WITH CONCERNS|PASS|FAIL)/i);
|
||||
return m ? m[1].toUpperCase() : "";
|
||||
}
|
||||
|
||||
// Extrahiert den Blocker-Abschnitt für die Loop-Erkennung.
|
||||
function parseBlockers(text: string): string {
|
||||
const m = text.match(/[-–*]\s*Blocker[:\n]([\s\S]*?)(?:\n[-–*]\s*Major|\n[-–*]\s*Minor|$)/i);
|
||||
return m ? m[1].trim() : "";
|
||||
}
|
||||
|
||||
// Gibt geänderte Quelldateien seit einem Git-Tag zurück.
|
||||
// null = Tag existiert nicht (erster Lauf) → alles verarbeiten
|
||||
// [] = nichts geändert → Phase überspringen
|
||||
// [...] = nur diese Dateien verarbeiten
|
||||
async function getFilesSinceTag(
|
||||
pi: ExtensionAPI,
|
||||
ctx: ExtensionCommandContext,
|
||||
tagName: string
|
||||
): Promise<string[] | null> {
|
||||
const tagCheck = await pi.exec("bash", ["-c", `git tag -l "${tagName}"`], { cwd: ctx.cwd });
|
||||
if (!tagCheck.stdout.trim()) return null;
|
||||
|
||||
const diff = await pi.exec(
|
||||
"bash",
|
||||
["-c", `git diff "${tagName}" --name-only 2>/dev/null`],
|
||||
{ cwd: ctx.cwd }
|
||||
);
|
||||
|
||||
return diff.stdout.trim()
|
||||
.split("\n")
|
||||
.filter(f =>
|
||||
f.length > 0 &&
|
||||
!f.endsWith(".md") &&
|
||||
!f.endsWith(".lock") &&
|
||||
!f.endsWith(".toml") &&
|
||||
!f.startsWith("target/") &&
|
||||
!f.endsWith(".gitignore")
|
||||
);
|
||||
}
|
||||
|
||||
// Dokumentations-Phase: inkrementell via Git-Tags, nur geänderte Dateien werden verarbeitet.
|
||||
// Wird von /update_doku und /optimize --with-doku genutzt.
|
||||
async function runUpdateDoku(pi: ExtensionAPI, ctx: ExtensionCommandContext): Promise<void> {
|
||||
await switchModel(pi, ctx, "llama-cpp-coder", "qwen3.5-coder");
|
||||
|
||||
// Phase 1: Code-Kommentare
|
||||
const commentFiles = await getFilesSinceTag(pi, ctx, "docs-last-commented");
|
||||
if (commentFiles === null) {
|
||||
ctx.ui.setStatus("update_doku", "1/3: Code wird kommentiert (alle Dateien)…");
|
||||
await sendAndWait(pi, ctx, commentCodePrompt());
|
||||
} else if (commentFiles.length === 0) {
|
||||
ctx.ui.notify("Code-Kommentare: keine Änderungen seit letztem Lauf – übersprungen.", "info");
|
||||
} else {
|
||||
ctx.ui.setStatus("update_doku", `1/3: Code wird kommentiert (${commentFiles.length} Datei(en))…`);
|
||||
await sendAndWait(pi, ctx, commentCodePromptIncremental(commentFiles));
|
||||
}
|
||||
await pi.exec("bash", ["-c", "git tag -f docs-last-commented"], { cwd: ctx.cwd });
|
||||
|
||||
// Phase 2: README.md
|
||||
const readmeFiles = await getFilesSinceTag(pi, ctx, "docs-last-readme");
|
||||
if (readmeFiles === null) {
|
||||
ctx.ui.setStatus("update_doku", "2/3: README.md wird geschrieben…");
|
||||
await sendAndWait(pi, ctx, readmeMdPrompt());
|
||||
} else if (readmeFiles.length === 0) {
|
||||
ctx.ui.notify("README.md: keine Änderungen seit letztem Lauf – übersprungen.", "info");
|
||||
} else {
|
||||
ctx.ui.setStatus("update_doku", `2/3: README.md wird geprüft (${readmeFiles.length} Datei(en) geändert)…`);
|
||||
await sendAndWait(pi, ctx, readmeMdPromptIncremental(readmeFiles));
|
||||
}
|
||||
await pi.exec("bash", ["-c", "git tag -f docs-last-readme"], { cwd: ctx.cwd });
|
||||
|
||||
// Phase 3: BEDIENUNGSANLEITUNG.md
|
||||
const bedFiles = await getFilesSinceTag(pi, ctx, "docs-last-bedienungsanleitung");
|
||||
if (bedFiles === null) {
|
||||
ctx.ui.setStatus("update_doku", "3/3: BEDIENUNGSANLEITUNG.md wird geschrieben…");
|
||||
await sendAndWait(pi, ctx, bedienungsanleitungPrompt());
|
||||
} else if (bedFiles.length === 0) {
|
||||
ctx.ui.notify("BEDIENUNGSANLEITUNG.md: keine Änderungen seit letztem Lauf – übersprungen.", "info");
|
||||
} else {
|
||||
ctx.ui.setStatus("update_doku", `3/3: BEDIENUNGSANLEITUNG.md wird geprüft (${bedFiles.length} Datei(en) geändert)…`);
|
||||
await sendAndWait(pi, ctx, bedienungsanleitungPromptIncremental(bedFiles));
|
||||
}
|
||||
await pi.exec("bash", ["-c", "git tag -f docs-last-bedienungsanleitung"], { cwd: ctx.cwd });
|
||||
|
||||
// Abschließender Dokumentations-Commit
|
||||
await pi.exec(
|
||||
"bash",
|
||||
["-c", "git add -A && git commit -m 'docs: update comments, README, BEDIENUNGSANLEITUNG' || true"],
|
||||
{ cwd: ctx.cwd }
|
||||
);
|
||||
|
||||
// TASK.md: Produktionsreif abhaken
|
||||
await tickTaskMdStatus(pi, ctx, "Produktionsreif (SHIP)");
|
||||
|
||||
ctx.ui.setStatus("update_doku", "✓ Dokumentation abgeschlossen");
|
||||
ctx.ui.notify("Dokumentations-Phase abgeschlossen. Commit angelegt.", "info");
|
||||
}
|
||||
|
||||
// ── Extension ────────────────────────────────────────────────────────────────
|
||||
|
||||
export default function (pi: ExtensionAPI) {
|
||||
pi.on("session_start", async function (_event, ctx) {
|
||||
ctx.ui.setWidget("coder-judge", [
|
||||
"Workflow: /coder <auftrag> | /judge | /fix | /shipit",
|
||||
"Auto-Loop: /optimize <auftrag> [--rounds N] [--with-doku]",
|
||||
"Kleine Änderung: /patch <änderung> → /quick_check [was]",
|
||||
"Finale Doku: /update_doku (nach SHIP – Kommentare + README + Bedienungsanleitung)",
|
||||
"Neues Projekt: /new_project <pfad>",
|
||||
"Modell wird automatisch gewechselt (Coder→:8001, Judge→:8002)"
|
||||
]);
|
||||
});
|
||||
|
||||
// ── Robustes edit: Bottom-up-Reordering via tool_call-Hook ─────────────
|
||||
// Behebt "edits[n] doesn't match": Mehrere Edits auf dieselbe Datei werden
|
||||
// von hinten nach vorne sortiert, damit frühere Edits spätere Positionen nicht verschieben.
|
||||
|
||||
pi.on("tool_call", async function (event, ctx) {
|
||||
if (event.toolName !== "edit") return;
|
||||
|
||||
const input = event.input as {
|
||||
path: string;
|
||||
edits: Array<{ oldText: string; newText: string }>;
|
||||
};
|
||||
|
||||
if (!input?.edits || input.edits.length <= 1) return;
|
||||
|
||||
const readResult = await pi.exec(
|
||||
"bash",
|
||||
["-c", `cat "$1"`, "_", input.path],
|
||||
{ cwd: ctx.cwd }
|
||||
);
|
||||
if (readResult.code !== 0) return;
|
||||
|
||||
const content = readResult.stdout;
|
||||
|
||||
const positioned = input.edits.map(edit => ({
|
||||
edit,
|
||||
idx: content.indexOf(edit.oldText)
|
||||
}));
|
||||
|
||||
// Nicht gefundene Einträge (idx === -1) ans Ende — sie schlagen sowieso fehl
|
||||
positioned.sort((a, b) => b.idx - a.idx);
|
||||
|
||||
input.edits.splice(0, input.edits.length, ...positioned.map(p => p.edit));
|
||||
});
|
||||
|
||||
// ── Manuelle Kommandos ───────────────────────────────────────────────────
|
||||
|
||||
pi.registerCommand("coder", {
|
||||
description: "Legt TASK.md an, startet Implementierung → qwen3.5-coder (:8001).",
|
||||
handler: async function (args: string, ctx: ExtensionCommandContext) {
|
||||
const task = (args || "").trim();
|
||||
if (!task) {
|
||||
ctx.ui.notify("Benutzung: /coder <auftrag>", "error");
|
||||
return;
|
||||
}
|
||||
await writeTaskMd(pi, ctx, task);
|
||||
await switchModel(pi, ctx, "llama-cpp-coder", "qwen3.5-coder");
|
||||
pi.sendUserMessage(coderKickoff(task));
|
||||
}
|
||||
});
|
||||
|
||||
pi.registerCommand("judge", {
|
||||
description: "Review gegen TASK.md + git show HEAD → qwen3.5-judge (:8002).",
|
||||
handler: async function (args: string, ctx: ExtensionCommandContext) {
|
||||
await switchModel(pi, ctx, "llama-cpp-judge", "qwen3.5-judge");
|
||||
pi.sendUserMessage(judgePrompt(args || ""));
|
||||
}
|
||||
});
|
||||
|
||||
pi.registerCommand("fix", {
|
||||
description: "Fixt Judge-Kritik, committet Ergebnis → qwen3.5-coder (:8001).",
|
||||
handler: async function (args: string, ctx: ExtensionCommandContext) {
|
||||
await switchModel(pi, ctx, "llama-cpp-coder", "qwen3.5-coder");
|
||||
pi.sendUserMessage(fixPrompt(args || ""));
|
||||
}
|
||||
});
|
||||
|
||||
pi.registerCommand("shipit", {
|
||||
description: "Finale Freigabe gegen TASK.md + git log → qwen3.5-judge (:8002).",
|
||||
handler: async function (args: string, ctx: ExtensionCommandContext) {
|
||||
await switchModel(pi, ctx, "llama-cpp-judge", "qwen3.5-judge");
|
||||
pi.sendUserMessage(shipitPrompt(args || ""));
|
||||
}
|
||||
});
|
||||
|
||||
// ── Automatische Optimierungsschleife ────────────────────────────────────
|
||||
|
||||
pi.registerCommand("optimize", {
|
||||
description: "Coder→Judge→Fix-Schleife bis PASS + optional Doku. /optimize <auftrag> [--rounds N] [--with-doku]",
|
||||
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 withDoku = /--with-doku/.test(args || "");
|
||||
const task = (args || "")
|
||||
.replace(/--rounds\s+\d+/, "")
|
||||
.replace(/--with-doku/, "")
|
||||
.trim();
|
||||
|
||||
if (!task) {
|
||||
ctx.ui.notify("Benutzung: /optimize <auftrag> [--rounds N] [--with-doku]", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
// TASK.md anlegen
|
||||
await writeTaskMd(pi, ctx, task);
|
||||
|
||||
ctx.ui.setStatus("optimize", `Starte Optimierung (max ${maxRounds} Runden)…`);
|
||||
|
||||
// Phase 1: Initiale Implementierung
|
||||
ctx.ui.setStatus("optimize", "Phase 1: Coder implementiert…");
|
||||
await switchModel(pi, ctx, "llama-cpp-coder", "qwen3.5-coder");
|
||||
await sendAndWait(pi, ctx, coderKickoff(task));
|
||||
await tickTaskMdStatus(pi, ctx, "Implementierung");
|
||||
|
||||
let lastBlockers = "";
|
||||
let verdict = "";
|
||||
|
||||
// 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…`);
|
||||
await switchModel(pi, ctx, "llama-cpp-judge", "qwen3.5-judge");
|
||||
await sendAndWait(pi, ctx, judgePrompt(""));
|
||||
|
||||
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.notify(`Optimierung abgeschlossen: ${verdict} nach ${round} Runde(n)`, "info");
|
||||
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.notify(
|
||||
"Derselbe Blocker tritt erneut auf – Schleife abgebrochen. Bitte manuell prüfen.",
|
||||
"warning"
|
||||
);
|
||||
return;
|
||||
}
|
||||
lastBlockers = currentBlockers;
|
||||
|
||||
if (round === maxRounds) {
|
||||
ctx.ui.setStatus("optimize", `⚠ Max. ${maxRounds} Runden ohne PASS`);
|
||||
ctx.ui.notify(`${maxRounds} Runden durchlaufen ohne PASS. Bitte manuell prüfen.`, "warning");
|
||||
return;
|
||||
}
|
||||
|
||||
// Fix-Phase
|
||||
ctx.ui.setStatus("optimize", `Runde ${round}/${maxRounds}: Coder fixt…`);
|
||||
await switchModel(pi, ctx, "llama-cpp-coder", "qwen3.5-coder");
|
||||
await sendAndWait(pi, ctx, fixPrompt(""));
|
||||
}
|
||||
|
||||
// Finale ShipIt-Prüfung nur bei PASS
|
||||
if (verdict === "PASS" || verdict === "PASS WITH CONCERNS") {
|
||||
ctx.ui.setStatus("optimize", "Finale ShipIt-Prüfung…");
|
||||
await switchModel(pi, ctx, "llama-cpp-judge", "qwen3.5-judge");
|
||||
await sendAndWait(pi, ctx, shipitPrompt(""));
|
||||
|
||||
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");
|
||||
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");
|
||||
} else {
|
||||
ctx.ui.setStatus("optimize", "ShipIt abgeschlossen");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// ── Schlanke Kommandos für kleine Änderungen ─────────────────────────────
|
||||
|
||||
pi.registerCommand("patch", {
|
||||
description: "Gezielte Minimaländerung ohne vollständigen Review → qwen3.5-coder (:8001).",
|
||||
handler: async function (args: string, ctx: ExtensionCommandContext) {
|
||||
const change = (args || "").trim();
|
||||
if (!change) {
|
||||
ctx.ui.notify("Benutzung: /patch <beschreibung der änderung>", "error");
|
||||
return;
|
||||
}
|
||||
await switchModel(pi, ctx, "llama-cpp-coder", "qwen3.5-coder");
|
||||
pi.sendUserMessage(patchPrompt(change));
|
||||
}
|
||||
});
|
||||
|
||||
pi.registerCommand("quick_check", {
|
||||
description: "Schnelle Prüfung der letzten Änderung (OK/PROBLEM) → qwen3.5-judge (:8002).",
|
||||
handler: async function (args: string, ctx: ExtensionCommandContext) {
|
||||
await switchModel(pi, ctx, "llama-cpp-judge", "qwen3.5-judge");
|
||||
pi.sendUserMessage(quickCheckPrompt(args || ""));
|
||||
}
|
||||
});
|
||||
|
||||
// ── Dokumentations-Phase ─────────────────────────────────────────────────
|
||||
|
||||
pi.registerCommand("update_doku", {
|
||||
description: "Code kommentieren + README.md + BEDIENUNGSANLEITUNG.md + git commit → qwen3.5-coder (:8001).",
|
||||
handler: async function (_args: string, ctx: ExtensionCommandContext) {
|
||||
await runUpdateDoku(pi, ctx);
|
||||
}
|
||||
});
|
||||
|
||||
// ── Robustes Editieren via GNU patch ─────────────────────────────────────
|
||||
|
||||
pi.registerTool({
|
||||
name: "apply_patch",
|
||||
label: "Patch anwenden",
|
||||
description: [
|
||||
"Wendet einen unified diff (git-Format) auf Dateien an.",
|
||||
"Zuverlässiger als das edit-Tool bei mehrfachen Änderungen an derselben Datei.",
|
||||
"Format: --- a/pfad/datei +++ b/pfad/datei @@ -n,m +n,m @@ ...",
|
||||
"Verwende dieses Tool wenn du mehrere Stellen in einer Datei änderst."
|
||||
].join(" "),
|
||||
parameters: Type.Object({
|
||||
patch: Type.String({
|
||||
description: "Unified diff im git-Format mit --- a/... und +++ b/... Headern."
|
||||
}),
|
||||
}),
|
||||
async execute(_id, params, _signal, _onUpdate, ctx) {
|
||||
const tmpFile = `/tmp/pi_patch_${Date.now()}.diff`;
|
||||
await pi.exec(
|
||||
"bash",
|
||||
["-c", `printf "%s" "$1" > "${tmpFile}"`, "_", params.patch],
|
||||
{ cwd: ctx.cwd }
|
||||
);
|
||||
// -p1 entfernt führende a/ b/ Präfixe (git-Standard)
|
||||
const result = await pi.exec(
|
||||
"bash",
|
||||
["-c", `patch -p1 < "${tmpFile}"; rm -f "${tmpFile}"`],
|
||||
{ cwd: ctx.cwd }
|
||||
);
|
||||
if (result.code !== 0) {
|
||||
return {
|
||||
content: [{ type: "text", text: `Patch fehlgeschlagen:\n${result.stderr}\n${result.stdout}` }],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
return { content: [{ type: "text", text: result.stdout || "Patch erfolgreich angewendet." }] };
|
||||
}
|
||||
});
|
||||
|
||||
// ── Projekt-Scaffolding ──────────────────────────────────────────────────
|
||||
|
||||
pi.registerCommand("new_project", {
|
||||
description: "Legt Projektverzeichnis an + git init + .gitignore. /new_project <pfad>",
|
||||
handler: async function (args: string, ctx: ExtensionCommandContext) {
|
||||
const rawPath = (args || "").trim();
|
||||
if (!rawPath) {
|
||||
ctx.ui.notify("Benutzung: /new_project <pfad>", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
// ~ expandieren
|
||||
const projectPath = rawPath.startsWith("~/")
|
||||
? rawPath.replace("~/", (process.env.HOME || "") + "/")
|
||||
: rawPath;
|
||||
|
||||
// Verzeichnis anlegen
|
||||
const mkResult = await pi.exec("bash", ["-c", 'mkdir -p "$1"', "_", projectPath], { cwd: ctx.cwd });
|
||||
if (mkResult.code !== 0) {
|
||||
ctx.ui.notify(`Fehler: ${mkResult.stderr}`, "error");
|
||||
return;
|
||||
}
|
||||
|
||||
// git init (nur wenn noch kein Repo vorhanden)
|
||||
const gitCheck = await pi.exec("bash", ["-c", "test -d .git && echo exists"], { cwd: projectPath });
|
||||
if (gitCheck.stdout.trim() !== "exists") {
|
||||
await pi.exec("bash", ["-c", "git init"], { cwd: projectPath });
|
||||
}
|
||||
|
||||
// .gitignore anlegen
|
||||
const gitignore = "target/\n*.o\n*.d\n*.swp\n.env\n.DS_Store\n";
|
||||
await pi.exec("bash", ["-c", 'printf "%s" "$1" > .gitignore', "_", gitignore], { cwd: projectPath });
|
||||
await pi.exec(
|
||||
"bash",
|
||||
["-c", "git add .gitignore && git commit -m 'chore: init project' || true"],
|
||||
{ cwd: projectPath }
|
||||
);
|
||||
|
||||
ctx.ui.notify(`Projekt angelegt: ${projectPath}`, "info");
|
||||
ctx.ui.notify(
|
||||
`⚠ Pi läuft noch in: ${ctx.cwd} — Session-Verzeichnis kann nicht gewechselt werden.\n` +
|
||||
`Neues Projekt starten: cd ${projectPath} && pi`,
|
||||
"warning"
|
||||
);
|
||||
ctx.ui.setStatus("new_project", `Neues Projekt → cd ${projectPath} && pi`);
|
||||
}
|
||||
});
|
||||
}
|
||||
90
start-coder.sh
Executable file
90
start-coder.sh
Executable file
|
|
@ -0,0 +1,90 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
HF_HOME="${HF_HOME:-/home/dschlueter/nvme2n1p7_home/huggingface}"
|
||||
MODEL_REL_PATH="models/qwen3/Qwen3.6-27B-Uncensored-HauhauCS-Aggressive-IQ4_XS.gguf"
|
||||
IMAGE="ghcr.io/ggml-org/llama.cpp:server-cuda"
|
||||
CONTAINER_NAME="qwen36-27b-coder"
|
||||
HOST_PORT=8001
|
||||
CONTAINER_PORT=8000
|
||||
MODEL_ALIAS="qwen3.5-coder"
|
||||
|
||||
echo "[*] Verwende HF_HOME = $HF_HOME"
|
||||
if [ ! -f "$HF_HOME/$MODEL_REL_PATH" ]; then
|
||||
echo "[!] Modell-Datei nicht gefunden: $HF_HOME/$MODEL_REL_PATH" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}\$"; then
|
||||
echo "[*] Stoppe existierenden Container $CONTAINER_NAME ..."
|
||||
docker rm -f "$CONTAINER_NAME" >/dev/null 2>&1 || true
|
||||
fi
|
||||
|
||||
echo "[*] Starte llama.cpp-Server für Coder ..."
|
||||
docker run -d \
|
||||
--gpus '"device=1,2"' \
|
||||
--name "$CONTAINER_NAME" \
|
||||
--restart unless-stopped \
|
||||
-e HF_HOME="/hf_home" \
|
||||
-v "$HF_HOME:/hf_home:ro" \
|
||||
-p "${HOST_PORT}:${CONTAINER_PORT}" \
|
||||
"$IMAGE" \
|
||||
-m "/hf_home/${MODEL_REL_PATH}" \
|
||||
--alias "${MODEL_ALIAS}" \
|
||||
-c 131072 \
|
||||
-n 16384 \
|
||||
--jinja \
|
||||
--no-context-shift \
|
||||
--temp 0.2 \
|
||||
--top-p 0.95 \
|
||||
--top-k 40 \
|
||||
--min-p 0.01 \
|
||||
--repeat-penalty 1.05 \
|
||||
--main-gpu 0 \
|
||||
--tensor-split 0.5,0.5 \
|
||||
-ngl 999 \
|
||||
-fa on \
|
||||
--kv-unified \
|
||||
--cache-type-k q8_0 \
|
||||
--cache-type-v q8_0 \
|
||||
--batch-size 1024 \
|
||||
--ubatch-size 512 \
|
||||
--parallel 2 \
|
||||
--cont-batching \
|
||||
--host 0.0.0.0 \
|
||||
--port "$CONTAINER_PORT"
|
||||
|
||||
echo "[*] Warte auf HTTP ..."
|
||||
HTTP_READY=0
|
||||
for i in {1..90}; do
|
||||
if curl -s "http://localhost:${HOST_PORT}/health" >/dev/null 2>&1 || \
|
||||
curl -s "http://localhost:${HOST_PORT}/v1/models" >/dev/null 2>&1; then
|
||||
HTTP_READY=1
|
||||
break
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
|
||||
if [ "$HTTP_READY" -ne 1 ]; then
|
||||
echo "[!] HTTP-Server wurde nicht rechtzeitig erreichbar." >&2
|
||||
docker logs --tail 200 "$CONTAINER_NAME" || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[*] Teste Chat-Completion ..."
|
||||
curl -s -X POST "http://localhost:${HOST_PORT}/v1/chat/completions" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{
|
||||
\"model\": \"${MODEL_ALIAS}\",
|
||||
\"messages\": [
|
||||
{ \"role\": \"system\", \"content\": \"Du bist ein präziser Coding-Assistent.\" },
|
||||
{ \"role\": \"user\", \"content\": \"Antworte nur mit dem Wort: bereit\" }
|
||||
],
|
||||
\"max_tokens\": 8,
|
||||
\"temperature\": 0.0,
|
||||
\"stream\": false
|
||||
}"
|
||||
|
||||
echo
|
||||
echo "[*] Server bereit auf http://0.0.0.0:${HOST_PORT}"
|
||||
echo "[*] Stoppen mit: docker rm -f ${CONTAINER_NAME}"
|
||||
90
start-judge.sh
Executable file
90
start-judge.sh
Executable file
|
|
@ -0,0 +1,90 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
HF_HOME="${HF_HOME:-/home/dschlueter/nvme2n1p7_home/huggingface}"
|
||||
MODEL_REL_PATH="models/qwen3/Qwen3.6-27B-Uncensored-HauhauCS-Aggressive-IQ4_XS.gguf"
|
||||
IMAGE="ghcr.io/ggml-org/llama.cpp:server-cuda"
|
||||
CONTAINER_NAME="qwen36-27b-judge"
|
||||
HOST_PORT=8002
|
||||
CONTAINER_PORT=8000
|
||||
MODEL_ALIAS="qwen3.5-judge"
|
||||
|
||||
echo "[*] Verwende HF_HOME = $HF_HOME"
|
||||
if [ ! -f "$HF_HOME/$MODEL_REL_PATH" ]; then
|
||||
echo "[!] Modell-Datei nicht gefunden: $HF_HOME/$MODEL_REL_PATH" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}\$"; then
|
||||
echo "[*] Stoppe existierenden Container $CONTAINER_NAME ..."
|
||||
docker rm -f "$CONTAINER_NAME" >/dev/null 2>&1 || true
|
||||
fi
|
||||
|
||||
echo "[*] Starte llama.cpp-Server für Judge ..."
|
||||
docker run -d \
|
||||
--gpus '"device=1,2"' \
|
||||
--name "$CONTAINER_NAME" \
|
||||
--restart unless-stopped \
|
||||
-e HF_HOME="/hf_home" \
|
||||
-v "$HF_HOME:/hf_home:ro" \
|
||||
-p "${HOST_PORT}:${CONTAINER_PORT}" \
|
||||
"$IMAGE" \
|
||||
-m "/hf_home/${MODEL_REL_PATH}" \
|
||||
--alias "${MODEL_ALIAS}" \
|
||||
-c 65536 \
|
||||
-n 8192 \
|
||||
--jinja \
|
||||
--no-context-shift \
|
||||
--temp 0.1 \
|
||||
--top-p 0.9 \
|
||||
--top-k 40 \
|
||||
--min-p 0.01 \
|
||||
--repeat-penalty 1.05 \
|
||||
--main-gpu 0 \
|
||||
--tensor-split 0.5,0.5 \
|
||||
-ngl 999 \
|
||||
-fa on \
|
||||
--kv-unified \
|
||||
--cache-type-k q8_0 \
|
||||
--cache-type-v q8_0 \
|
||||
--batch-size 512 \
|
||||
--ubatch-size 256 \
|
||||
--parallel 1 \
|
||||
--cont-batching \
|
||||
--host 0.0.0.0 \
|
||||
--port "$CONTAINER_PORT"
|
||||
|
||||
echo "[*] Warte auf HTTP ..."
|
||||
HTTP_READY=0
|
||||
for i in {1..90}; do
|
||||
if curl -s "http://localhost:${HOST_PORT}/health" >/dev/null 2>&1 || \
|
||||
curl -s "http://localhost:${HOST_PORT}/v1/models" >/dev/null 2>&1; then
|
||||
HTTP_READY=1
|
||||
break
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
|
||||
if [ "$HTTP_READY" -ne 1 ]; then
|
||||
echo "[!] HTTP-Server wurde nicht rechtzeitig erreichbar." >&2
|
||||
docker logs --tail 200 "$CONTAINER_NAME" || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[*] Teste Judge-Endpoint ..."
|
||||
curl -s -X POST "http://localhost:${HOST_PORT}/v1/chat/completions" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{
|
||||
\"model\": \"${MODEL_ALIAS}\",
|
||||
\"messages\": [
|
||||
{ \"role\": \"system\", \"content\": \"Du bist ein strenger Code-Reviewer.\" },
|
||||
{ \"role\": \"user\", \"content\": \"Antworte nur mit dem Wort: bereit\" }
|
||||
],
|
||||
\"max_tokens\": 8,
|
||||
\"temperature\": 0.0,
|
||||
\"stream\": false
|
||||
}"
|
||||
|
||||
echo
|
||||
echo "[*] Server bereit auf http://0.0.0.0:${HOST_PORT}"
|
||||
echo "[*] Stoppen mit: docker rm -f ${CONTAINER_NAME}"
|
||||
Loading…
Add table
Add a link
Reference in a new issue