UNPKG

@blundergoat/goat-flow

Version:

AI coding agent harness and local dashboard for Claude Code, OpenAI Codex, Google Antigravity, and GitHub Copilot - setup audits, guardrails, structured skills, deny hooks, and persistent learning loops.

425 lines (424 loc) 21.3 kB
"use strict"; /** * Setup and quality controller helpers for the dashboard Alpine app. * These functions are classic-script globals called by thin methods in app.ts. */ const DEFAULT_SETUP_COMMANDS = { test: "", lint: "", build: "", format: "", }; const DEFAULT_EXISTING_ARTIFACTS = { skills: false, instructionsRepoWide: false, instructionsPathScoped: false, lessons: false, footguns: false, config: false, }; const QUALITY_HISTORY_LOAD_DELAY_MS = 50; const SETUP_PROMPT_LOAD_DELAY_MS = 50; function dashboardAgentDisplayName(ctx, agentId) { return (ctx.supportedAgents.find((agent) => agent.id === agentId)?.name ?? agentId); } function dashboardSetupInstructionSurfaces(ctx) { const agent = ctx.supportedAgents.find((entry) => entry.id === ctx.setupSelectedAgent); return agent?.setupSurfaces.join(", ") ?? ctx.setupSelectedAgent; } function dashboardQualityModePreset(ctx, presetId) { return ctx.presets.find((preset) => preset.id === presetId) ?? null; } /** Reset quality-history rows and warnings before loading a new mode or project. */ function dashboardClearQualityHistory(ctx) { ctx.qualityHistoryRows = []; ctx.qualityHistoryLatest = null; ctx.qualityHistoryWarnings = []; } /** Build the read-only harness-engineering assessment prompt used by the Quality page. */ function dashboardHarnessQualityPrompt() { return [ "AI Harness Engineering Quality Assessment", "", "REPORTING-ONLY ASSESSMENT MODE. Do not edit tracked files. Do not use /goat-review or any goat skill as the wrapper for this assessment; this prompt is the full assessment contract. You may read files, run read-only validation commands, and write normal gitignored reporting/local-state artifacts if the runner requires them. In this contract, gitignored logs, scratchpad notes, critique snapshots, quality reports, and task-local state do not count as writes; do not report them as read-only violations.", "", "Assess whether the selected target project's agent harness is actually usable, not only structurally present. Focus on context loading, constraint safety, verification evidence, recovery paths, feedback-loop durability, and whether instructions distinguish the controlling goat-flow workspace from the selected target.", "", "Grounding commands to run or explicitly mark skipped: git status --short --untracked-files=all; node --import tsx src/cli/cli.ts audit . --harness --format json from the controlling workspace when applicable; node --import tsx src/cli/cli.ts stats . --check when the selected target is a goat-flow installation. Command output wins over prose.", "", "Read next: target instruction files, local agent settings/hooks, .goat-flow/config.yaml when present, .goat-flow/skill-docs/ and .goat-flow/skill-docs/playbooks/ when present, controlling-workspace harness code under src/cli/audit/harness/, and any dashboard terminal/runner context text that affects selected-target execution.", "", "Output sections: Harness Scorecard; Findings ordered by severity; Concern-by-concern analysis; False positive and false negative risks; Top 5 improvements; What was not verified. For each deterministic harness concern (Context, Constraints, Verification, Recovery, Feedback Loop), state what works, what fails or is weak, exact file or semantic-anchor evidence, and a verification command that would prove the fix.", "", "Do not treat a structural PASS as quality PASS. If a score or check claims completeness, verify what behavior it actually proves.", ].join("\n"); } function dashboardQualityModes(ctx) { const qualityCheck = dashboardQualityModePreset(ctx, "quality-check-goatflow"); const skillQuality = dashboardQualityModePreset(ctx, "skill-quality-test"); return [ { id: "agent-setup", label: "Agent Installation", desc: "Assess the active agent installation across accuracy, relevance, completeness, and friction.", source: "api", targetScope: "selected project and selected agent installation", }, { id: "process", label: "GOAT Flow Process", desc: "Review framework artifacts, instructions, references, hooks, and workflow policy.", source: "api", presetId: "quality-check-goatflow", targetScope: "controlling goat-flow workspace, plus selected target only when it is a goat-flow installation", prompt: qualityCheck?.prompt, }, { id: "harness", label: "Harness Engineering", desc: "Assess context, constraints, verification, recovery, and feedback-loop quality.", source: "api", targetScope: "selected target project harness, interpreted from the controlling workspace", prompt: dashboardHarnessQualityPrompt(), }, { id: "skills", label: "Skills", desc: "Pressure-test goat-flow skills with the RED/GREEN/REFACTOR quality protocol.", source: "api", presetId: "skill-quality-test", targetScope: "controlling goat-flow workspace skills and shared references", prompt: skillQuality?.prompt, }, ]; } function dashboardSelectedQualityModeMeta(ctx) { return (dashboardQualityModes(ctx).find((mode) => mode.id === ctx.selectedQualityModeId) ?? null); } /** Return the goat-flow controlling workspace path for framework-scoped quality modes. */ function dashboardQualityControllingWorkspace() { return window.__GOAT_FLOW_DEFAULT_PATH__ ?? "."; } /** Quote a value for the shell snippets embedded in generated quality-report prompts. */ function dashboardQualityShellQuote(value) { return `'${value.replace(/'/g, "'\\''")}'`; } function dashboardQualityReportProjectPath(ctx, mode) { if (mode.id === "process" || mode.id === "skills") { return dashboardQualityControllingWorkspace(); } return ctx.projectPath; } function dashboardQualityLaunchLabel(ctx) { const mode = dashboardSelectedQualityModeMeta(ctx); const modeLabel = mode ? mode.presetId ? (dashboardQualityModePreset(ctx, mode.presetId)?.name ?? mode.label) : mode.label : ctx.qualityAgent; return `Quality ${modeLabel} for ${dashboardAgentDisplayName(ctx, ctx.qualityAgent)} via ${dashboardAgentDisplayName(ctx, ctx.activeRunner)}`; } function dashboardQualityReportLogPrompt(ctx, mode) { const agent = ctx.qualityAgent; const projectPath = dashboardQualityReportProjectPath(ctx, mode); const agentJson = JSON.stringify(agent); const projectPathJson = JSON.stringify(projectPath); const modeJson = JSON.stringify(mode.id); const versionJson = JSON.stringify(window.__GOAT_FLOW_VERSION__ ?? "unknown"); const scopeJson = JSON.stringify(mode.id === "process" || mode.id === "skills" ? "framework-self" : "consumer"); const reportRootShell = dashboardQualityShellQuote(projectPath); const validatorRootShell = dashboardQualityShellQuote(dashboardQualityControllingWorkspace()); return [ "Quality report log:", `- Report owner project_path for this mode: ${projectPath}`, "- Write the final machine-readable report to the owner project's `.goat-flow/logs/quality/`. This path is gitignored and expected; do not write the JSON inline only.", "- Filename format: `YYYY-MM-DD-HHMM-<agent>-<rand5>.json`, where `<agent>` is the literal selected quality target shown below.", "- Derive the timestamp and random suffix from the shell at write time:", "```bash", `REPORT_ROOT=${reportRootShell}`, `VALIDATOR_ROOT=${validatorRootShell}`, 'STAMP="$(date +"%Y-%m-%d-%H%M")"', "RAND=\"$(LC_ALL=C tr -dc 'a-z0-9' </dev/urandom | head -c 5)\"", `FILE="$REPORT_ROOT/.goat-flow/logs/quality/\${STAMP}-${agent}-\${RAND}.json"`, 'mkdir -p "$REPORT_ROOT/.goat-flow/logs/quality"', "```", "- JSON body shape:", "```json", "{", ' "report_kind": "goat-flow-quality-report",', ` "goat_flow_version": ${versionJson},`, ` "agent": ${agentJson},`, ` "project_path": ${projectPathJson},`, ' "run_date": "YYYY-MM-DD",', ' "audit_status": "pass | fail | unavailable",', ` "scope": ${scopeJson},`, ` "rubric_version": ${versionJson},`, ` "quality_mode": ${modeJson},`, ' "scores": {', ' "setup": { "total": 0, "accuracy": 0, "relevance": 0, "completeness": 0, "friction": 0 },', ' "system": { "total": 0, "usefulness": 0, "signal_to_noise": 0, "adaptability": 0, "learnability": 0 }', " },", ' "findings": [', ' { "type": "setup_quality", "severity": "MAJOR", "file": ".goat-flow/architecture.md", "line": null, "summary": "One-line finding summary", "detail": "Why it matters", "evidence_quality": "OBSERVED", "evidence_method": "static-analysis", "delta_tag": "new" }', " ]", "}", "```", "- Use exact score axis values `0 | 5 | 10 | 15 | 20 | 25`; each total must equal its axis sum.", "- Allowed finding types: `setup_quality`, `skill_flaw`, `contradiction`, `false_path`, `content_quality`, `framework_flaw`.", "- Allowed severities: `BLOCKER`, `MAJOR`, `MINOR`. Allowed evidence methods: `runtime-probe`, `static-analysis`, `mixed`.", '- Use `delta_tag: "new"` unless the finding materially matches prior quality history for this same agent/mode; then use `persisted`.', "- Live review findings should cite `file` + semantic anchor after re-reading the cited file and anchor. Durable footguns, lessons, patterns, and decisions must use file paths plus semantic anchors rather than line numbers.", '- Validate before confirming: `(cd "$VALIDATOR_ROOT" && node --import tsx src/cli/cli.ts quality validate "$FILE")`.', '- Verify the file exists and is non-zero: `ls -la "$FILE"`.', `- End your response with: \`Wrote quality report to ${projectPath}/.goat-flow/logs/quality/<filename>.json\`.`, `- This log requirement applies to the ${mode.label} mode; do not skip it even when the prose assessment is complete.`, ].join("\n"); } function dashboardBuildQualityModePrompt(ctx, mode) { const prompt = mode.prompt?.trim(); if (!prompt) { return ""; } return [ prompt, "", "Quality mode scope:", `- Mode: ${mode.label}`, `- Controlling goat-flow workspace: ${window.__GOAT_FLOW_DEFAULT_PATH__ ?? "."}`, `- Selected target project: ${ctx.projectPath}`, `- Scope rule: ${mode.targetScope}`, "- Treat missing target .goat-flow files as normal unless this mode explicitly audits a goat-flow installation.", "- Keep this assessment read-only unless the user explicitly asks for edits.", `- Selected quality target agent: ${ctx.qualityAgent}`, "", dashboardQualityReportLogPrompt(ctx, mode), ].join("\n"); } /** Detect the selected project's stack and existing GOAT Flow setup state. */ async function dashboardDetectStack(ctx) { ctx.setupDetecting = true; try { const res = await dashboardFetch(`/api/setup/detect?path=${encodeURIComponent(ctx.projectPath)}`); const payload = readRecord(await res.json(), "Setup detection response"); const error = readErrorMessage(payload); if (error) { ctx.showToast(error, true); ctx.setupDetecting = false; return; } const commands = isRecord(payload.commands) ? payload.commands : {}; const agents = isRecord(payload.agents) ? payload.agents : {}; const existing = isRecord(payload.existing) ? payload.existing : {}; ctx.setupData.languages = readStringArray(payload.languages); ctx.setupData.frameworks = readStringArray(payload.frameworks); ctx.setupData.commands = { test: readString(commands.test), lint: readString(commands.lint), build: readString(commands.build), format: readString(commands.format), }; const defaultAgents = buildDefaultSetupAgents(ctx.supportedAgents, ctx.setupSelectedAgent); ctx.setupData.agents = Object.fromEntries(Object.keys(defaultAgents).map((agentId) => [ agentId, typeof agents[agentId] === "boolean" ? agents[agentId] : (defaultAgents[agentId] ?? false), ])); if (!Object.values(ctx.setupData.agents).some((v) => v)) { ctx.setupData.agents[ctx.setupSelectedAgent] = true; } ctx.setupData.existing = { skills: typeof existing.skills === "boolean" ? existing.skills : DEFAULT_EXISTING_ARTIFACTS.skills, instructionsRepoWide: typeof existing.instructionsRepoWide === "boolean" ? existing.instructionsRepoWide : DEFAULT_EXISTING_ARTIFACTS.instructionsRepoWide, instructionsPathScoped: typeof existing.instructionsPathScoped === "boolean" ? existing.instructionsPathScoped : DEFAULT_EXISTING_ARTIFACTS.instructionsPathScoped, lessons: typeof existing.lessons === "boolean" ? existing.lessons : DEFAULT_EXISTING_ARTIFACTS.lessons, footguns: typeof existing.footguns === "boolean" ? existing.footguns : DEFAULT_EXISTING_ARTIFACTS.footguns, config: typeof existing.config === "boolean" ? existing.config : DEFAULT_EXISTING_ARTIFACTS.config, }; ctx.setupData.nonGoatFlow = readStringArray(payload.nonGoatFlow); } catch (err) { const msg = err instanceof Error ? err.message : String(err); ctx.showToast(msg || "Detection failed", true); } ctx.setupDetecting = false; } /** Generate setup output for a specific target agent and selected project. */ async function dashboardGenerateSetupPromptForAgent(ctx, targetAgent, { force: shouldForce = false } = {}) { const requestProjectPath = ctx.projectPath; const agent = targetAgent; if (ctx._setupOutputProjectPath !== requestProjectPath) { ctx.setupOutputs = {}; ctx._setupOutputProjectPath = requestProjectPath; } if (!shouldForce && ctx.setupOutputs[agent]) return ctx.setupOutputs[agent]; const requestKey = `${requestProjectPath}\0${agent}`; ctx._setupPromptRequestKey = requestKey; ctx.setupGenerating = true; const isCurrentProject = () => ctx.projectPath === requestProjectPath; const isLatestRequest = () => ctx._setupPromptRequestKey === requestKey; const shouldSurfaceError = () => isLatestRequest() || !ctx.setupOutputs[agent]; try { const res = await dashboardFetch(`/api/setup?path=${encodeURIComponent(requestProjectPath)}&agent=${encodeURIComponent(agent)}`); const payload = readRecord(await res.json(), "Setup response"); if (!isCurrentProject()) return null; const error = readErrorMessage(payload); if (error) { if (shouldSurfaceError()) ctx.showToast(`${agent}: ${error}`, true); return null; } else { const output = readString(payload.output) || "No output generated."; if (isLatestRequest() || !ctx.setupOutputs[agent]) { ctx.setupOutputs[agent] = output; } return output; } } catch (err) { if (!isCurrentProject()) return null; const msg = err instanceof Error ? err.message : String(err); if (shouldSurfaceError()) ctx.showToast(msg || "Generation failed", true); return null; } finally { if (isLatestRequest()) { ctx.setupGenerating = false; ctx._setupPromptRequestKey = null; } } } /** Generate setup output for the agent selected in the setup view. */ async function dashboardGenerateSetupPrompt(ctx, { force: shouldForce = false } = {}) { await dashboardGenerateSetupPromptForAgent(ctx, ctx.setupSelectedAgent, { force: shouldForce, }); } /** Schedule setup prompt generation after setup detection gets a paint. */ function dashboardScheduleSetupPrompt(ctx) { if (ctx._setupPromptTimer !== null) { clearTimeout(ctx._setupPromptTimer); } ctx._setupPromptTimer = setTimeout(() => { ctx._setupPromptTimer = null; void ctx.generateSetupPrompt(); }, SETUP_PROMPT_LOAD_DELAY_MS); } /** Generate a quality prompt for the selected project and agent. */ async function dashboardGenerateQuality(ctx, { fast: useFastCache = false, fresh: includeFresh = false, } = {}) { ctx.qualityLoading = true; ctx.qualityResult = null; ctx.qualityCopyLabel = "Copy"; const requestModeId = ctx.selectedQualityModeId; const requestMode = dashboardSelectedQualityModeMeta(ctx); const requestProjectPath = requestMode ? dashboardQualityReportProjectPath(ctx, requestMode) : ctx.projectPath; const requestSelectedProjectPath = ctx.projectPath; const requestAgent = ctx.qualityAgent; const fastParam = useFastCache ? "&fast=true" : ""; const freshParam = includeFresh ? "&fresh=true" : ""; const isCurrentRequest = () => ctx.selectedQualityModeId === requestModeId && ctx.projectPath === requestSelectedProjectPath && ctx.qualityAgent === requestAgent; try { const res = await dashboardFetch(`/api/quality?path=${encodeURIComponent(requestProjectPath)}&agent=${encodeURIComponent(requestAgent)}&mode=${encodeURIComponent(requestModeId)}&target=${encodeURIComponent(requestSelectedProjectPath)}${fastParam}${freshParam}`); const payload = readRecord(await res.json(), "Quality response"); if (!isCurrentRequest()) return; const error = readErrorMessage(payload); if (error) { ctx.showToast(error, true); } else { ctx.qualityResult = readQualityResult(payload); } } catch (err) { if (!isCurrentRequest()) return; const msg = err instanceof Error ? err.message : String(err); ctx.showToast(msg || "Quality prompt generation failed", true); } if (isCurrentRequest()) ctx.qualityLoading = false; } /** Load persisted quality-history rows for the selected project and agent. */ async function dashboardGenerateQualityHistory(ctx) { ctx.qualityHistoryLoading = true; dashboardClearQualityHistory(ctx); const requestModeId = ctx.selectedQualityModeId; const requestMode = dashboardSelectedQualityModeMeta(ctx); const requestProjectPath = requestMode ? dashboardQualityReportProjectPath(ctx, requestMode) : ctx.projectPath; const requestSelectedProjectPath = ctx.projectPath; const requestAgent = ctx.qualityAgent; const isCurrentRequest = () => ctx.selectedQualityModeId === requestModeId && ctx.projectPath === requestSelectedProjectPath && ctx.qualityAgent === requestAgent; try { const res = await dashboardFetch(`/api/quality/history?path=${encodeURIComponent(requestProjectPath)}&agent=${encodeURIComponent(requestAgent)}&mode=${encodeURIComponent(requestModeId)}&limit=20`); const payload = readRecord(await res.json(), "Quality history response"); if (!isCurrentRequest()) return; const error = readErrorMessage(payload); if (error) { ctx.showToast(error, true); } else { ctx.qualityHistoryRows = Array.isArray(payload.rows) ? payload.rows .map((row) => readQualityHistoryRow(row)) .filter((row) => row !== null) : []; ctx.qualityHistoryLatest = readQualityHistoryLatest(payload.latest); ctx.qualityHistoryWarnings = readStringArray(payload.warnings); } } catch (err) { if (!isCurrentRequest()) return; const msg = err instanceof Error ? err.message : String(err); ctx.showToast(msg || "Quality history loading failed", true); } if (isCurrentRequest()) ctx.qualityHistoryLoading = false; } /** Schedule quality-history loading after the prompt path gets a paint. */ function dashboardScheduleQualityHistory(ctx) { if (ctx._qualityHistoryTimer !== null) { clearTimeout(ctx._qualityHistoryTimer); } ctx._qualityHistoryTimer = setTimeout(() => { ctx._qualityHistoryTimer = null; void ctx.generateQualityHistory(); }, QUALITY_HISTORY_LOAD_DELAY_MS); } /** Copy the current quality prompt to the clipboard. */ function dashboardCopyQuality(ctx) { if (!ctx.qualityResult?.prompt) return; ctx.copyText(ctx.qualityResult.prompt); ctx.qualityCopyLabel = "Copied!"; setTimeout(() => (ctx.qualityCopyLabel = "Copy"), 2000); }