const rateCard = { "GPT-5.4": { input: 62.5, cached: 6.25, output: 375 }, "GPT-5.4-Mini": { input: 18.75, cached: 1.875, output: 113 }, "GPT-5.3-Codex": { input: 43.75, cached: 4.375, output: 350 }, "GPT-5.2-Codex": { input: 43.75, cached: 4.375, output: 350 }, }; const days = [ { title: "Inventar und Ziele", mode: "Scout", output: "Repo-Liste, Top-20 Task-Kandidaten, Risiko-Matrix", items: ["Codex Usage Panel pruefen", "beste Repos nach Impact sortieren", "keine Endlosjobs starten"], }, { title: "Harness-Benchmark", mode: "Scout", output: "Messbare Eval-Suite fuer Codex, Hermes, OpenCode", items: ["identische Tasksheets", "Zeit, Kosten, Fehlerklasse erfassen", "ein Scoreboard schreiben"], }, { title: "Testgenerator-Welle", mode: "Build", output: "Tests fuer riskante Module und fehlende Regressionen", items: ["erst failing tests", "dann Fixes", "Coverage-Luecken dokumentieren"], }, { title: "Refactor-Welle", mode: "Build", output: "kleinere APIs, weniger Duplication, bessere Grenzen", items: ["keine Big-Bang-Refactors", "jede Aenderung testbar", "Diffs nach Modul trennen"], }, { title: "Frontend-Finish", mode: "Ship", output: "visuell fertige lokale Tools mit Screenshot-Check", items: ["mobile und desktop pruefen", "Textueberlauf fixen", "leere States bauen"], }, { title: "Doku und Runbooks", mode: "Ship", output: "README, Setup, Troubleshooting, Agent-Prompts", items: ["nur operative Doku", "keine Markdown-Flut", "Prompts als wiederverwendbare Vorlagen"], }, { title: "Review-Tag", mode: "Verify", output: "Codex-Reviews, Security-Checks, CI-Fixes", items: ["codex review nutzen", "Findings zuerst", "nur echte Risiken fixen"], }, { title: "Final Sweep", mode: "Verify", output: "Archiv, offene Risiken, naechster Monatsplan", items: ["Zombie-Sessions schliessen", "Artefakte verlinken", "Lessons in Harness-Guide uebernehmen"], }, ]; const prompts = [ { title: "Deep Repo Audit", command: `codex exec --full-auto -m gpt-5.4 -C /home/smlflg/Projekte/DEIN_REPO \\ "Lies das Repo, finde die 10 hoechsten Hebel fuer Tests, Sicherheit und Wartbarkeit. Aendere noch nichts. Gib eine priorisierte, belegte Liste mit Dateien und konkreten naechsten Commands aus."`, }, { title: "Harness Eval Run", command: `codex exec --full-auto -m gpt-5.4 -C /home/smlflg/Projekte/HarnessEcosystem \\ "Baue eine kleine reproduzierbare Eval fuer Codex-Harness-Verhalten: Tasksheet, Messkriterien, Beispieltask, Ergebnis-Tabelle. Halte die Aenderung eng und teste lokal."`, }, { title: "Test-First Fix", command: `codex exec --full-auto -m gpt-5.4 -c model_reasoning_effort="high" -C /home/smlflg/Projekte/DEIN_REPO \\ "Finde eine reale, riskante Bugklasse. Schreibe zuerst einen failing Test, implementiere den kleinsten Fix, fuehre relevante Tests aus und erklaere den Diff."`, }, { title: "Codex Review", command: `codex review --base main "Priorisiere Bugs, Regressionen, Security-Risiken und fehlende Tests. Keine Stil-Nitpicks ohne Risiko."`, }, { title: "OpenAI Docs MCP Check", command: `codex exec --search -C /home/smlflg/Projekte/DEIN_REPO \\ "Nutze openaiDeveloperDocs, pruefe ob unsere OpenAI-API-Nutzung aktuell ist, und schlage gezielte Code-Aenderungen vor. Nur offizielle OpenAI-Doku verwenden."`, }, { title: "Finish Pass", command: `codex exec --full-auto -m gpt-5.4 -C /home/smlflg/Projekte/DEIN_REPO \\ "Mache aus diesem lokalen Tool ein fertiges Artefakt: README, Startanleitung, leere States, Fehlertexte, kleine UI-Politur und abschliessende Verifikation."`, }, ]; const lanes = ["Scout", "Build", "Verify", "Ship"]; const defaultTasks = [ { id: "seed-mcp-docs", title: "Codex MCP Docs in bestehende OpenAI-Projekte einbauen", lane: "Scout", model: "GPT-5.4" }, { id: "seed-promptgarage-eval", title: "PromptGarage: Eval-DB pruefen und Judge-Runs automatisieren", lane: "Build", model: "GPT-5.3-Codex" }, { id: "seed-token-diaet", title: "HarnessEcosystem: TOKEN_DIAET mit neuen Codex-Rate-Card-Daten aktualisieren", lane: "Ship", model: "GPT-5.4" }, { id: "seed-static-sites", title: "Alle privaten Static-Sites auf mobile Textueberlaeufe pruefen", lane: "Verify", model: "GPT-5.4-Mini" }, ]; const formatter = new Intl.NumberFormat("de-DE", { maximumFractionDigits: 0 }); const preciseFormatter = new Intl.NumberFormat("de-DE", { maximumFractionDigits: 2 }); let taskState = loadTasks(); function escapeHtml(value) { return String(value) .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """) .replaceAll("'", "'"); } function createId() { if (globalThis.crypto?.randomUUID) return crypto.randomUUID(); return `task-${Date.now()}-${Math.random().toString(16).slice(2)}`; } function normalizeTask(task) { const lane = lanes.includes(task?.lane) ? task.lane : "Scout"; const model = Object.hasOwn(rateCard, task?.model) ? task.model : "GPT-5.4"; return { id: String(task?.id || createId()), title: String(task?.title || "Unbenannter Task").slice(0, 240), lane, model, }; } function loadTasks() { try { const saved = localStorage.getItem("codex-max-harness-tasks"); if (!saved) return defaultTasks.map(normalizeTask); const parsed = JSON.parse(saved); return Array.isArray(parsed) ? parsed.map(normalizeTask) : defaultTasks.map(normalizeTask); } catch { return defaultTasks.map(normalizeTask); } } function numberFrom(id) { return Number(document.getElementById(id).value || 0); } function renderTimeline() { document.getElementById("timeline").innerHTML = days .map( (day, index) => `
Tag ${index + 1} ยท ${escapeHtml(day.mode)}

${escapeHtml(day.title)}

Output: ${escapeHtml(day.output)}

`, ) .join(""); } function renderModels() { const select = document.getElementById("modelSelect"); select.innerHTML = Object.keys(rateCard) .map((model) => ``) .join(""); } function updateBudget() { const remaining = numberFrom("remainingCredits"); const daysLeft = Math.max(1, numberFrom("remainingDays")); const buffer = Math.min(40, Math.max(0, numberFrom("bufferPercent"))) / 100; const blocks = Math.max(1, numberFrom("blocksPerDay")); const usable = remaining * (1 - buffer); const daily = usable / daysLeft; const block = daily / blocks; document.getElementById("usableCredits").textContent = formatter.format(usable); document.getElementById("dailyCredits").textContent = formatter.format(daily); document.getElementById("blockCredits").textContent = formatter.format(block); } function updateEstimate() { const selected = document.getElementById("modelSelect").value; const rate = rateCard[selected]; const input = numberFrom("inputTokens") * rate.input; const cached = numberFrom("cachedTokens") * rate.cached; const output = numberFrom("outputTokens") * rate.output; const multiplier = document.getElementById("fastMode").checked ? 2 : 1; const total = (input + cached + output) * multiplier; document.getElementById("estimatedCredits").textContent = preciseFormatter.format(total); document.getElementById("estimateDetail").textContent = `${selected}: Input ${rate.input}, Cached ${rate.cached}, Output ${rate.output} Credits pro 1M Tokens`; } function getTasks() { return taskState; } function saveTasks(tasks) { taskState = tasks.map(normalizeTask); try { localStorage.setItem("codex-max-harness-tasks", JSON.stringify(taskState)); } catch { // Keep the in-memory board working when file:// or private mode blocks storage. } } function moveTask(id, direction) { const tasks = getTasks(); const task = tasks.find((item) => item.id === id); if (!task) return; const index = lanes.indexOf(task.lane); const nextIndex = Math.min(lanes.length - 1, Math.max(0, index + direction)); task.lane = lanes[nextIndex]; saveTasks(tasks); renderKanban(); } function deleteTask(id) { saveTasks(getTasks().filter((task) => task.id !== id)); renderKanban(); } function renderKanban() { const tasks = getTasks(); document.getElementById("kanban").innerHTML = lanes .map((lane) => { const laneTasks = tasks.filter((task) => task.lane === lane); return `

${escapeHtml(lane)}${laneTasks.length}

${laneTasks .map( (task) => `
${escapeHtml(task.title)}
${escapeHtml(task.model)} ${escapeHtml(task.lane)}
`, ) .join("")}
`; }) .join(""); } function renderPrompts() { document.getElementById("promptGrid").innerHTML = prompts .map( (prompt, index) => `

${escapeHtml(prompt.title)}

${escapeHtml(prompt.command)}
`, ) .join(""); } function fallbackCopy(text) { const area = document.createElement("textarea"); area.value = text; area.setAttribute("readonly", ""); area.style.position = "fixed"; area.style.left = "-9999px"; document.body.append(area); area.select(); try { return document.execCommand("copy"); } finally { area.remove(); } } async function copyText(text) { try { if (navigator.clipboard?.writeText) { await navigator.clipboard.writeText(text); return true; } } catch { // Fall through to the textarea fallback for file:// contexts. } return fallbackCopy(text); } function attachEvents() { document.querySelectorAll("#budgetForm input").forEach((input) => { input.addEventListener("input", updateBudget); }); document.querySelectorAll("#costForm input, #costForm select").forEach((input) => { input.addEventListener("input", updateEstimate); input.addEventListener("change", updateEstimate); }); document.getElementById("taskForm").addEventListener("submit", (event) => { event.preventDefault(); const title = document.getElementById("taskTitle").value.trim(); if (!title) return; const tasks = getTasks(); tasks.push({ id: createId(), title, lane: document.getElementById("taskLane").value, model: document.getElementById("taskModel").value, }); saveTasks(tasks); event.currentTarget.reset(); renderKanban(); }); document.getElementById("kanban").addEventListener("click", (event) => { const moveButton = event.target.closest("[data-task-move]"); if (moveButton) { moveTask(moveButton.dataset.taskMove, Number(moveButton.dataset.direction)); return; } const deleteButton = event.target.closest("[data-task-delete]"); if (deleteButton) { deleteTask(deleteButton.dataset.taskDelete); } }); document.getElementById("promptGrid").addEventListener("click", async (event) => { const button = event.target.closest("[data-copy]"); if (!button) return; const prompt = prompts[Number(button.dataset.copy)]; const copied = await copyText(prompt.command); button.textContent = copied ? "Kopiert" : "Nicht kopiert"; window.setTimeout(() => { button.textContent = "Kopieren"; }, 1200); }); } renderTimeline(); renderModels(); renderKanban(); renderPrompts(); attachEvents(); updateBudget(); updateEstimate();