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.

1,152 lines (1,133 loc) 52.8 kB
<div class="home-scroll" x-show="activeView === 'home'" x-cloak> <div class="home-page" x-data="{ concernKeys: ['context', 'constraints', 'verification', 'recovery', 'feedback_loop'], concernLabels: { context: 'Context', constraints: 'Constraints', verification: 'Verification', recovery: 'Recovery', feedback_loop: 'Feedback' }, setupScope() { return report?.scopes?.setup || { status: 'fail', checks: [] }; }, setupChecks() { return this.setupScope().checks || []; }, setupTotal() { return this.setupChecks().length; }, setupPassed() { return this.setupChecks().filter((check) => check.status === 'pass').length; }, setupComplete() { return this.setupTotal() > 0 && this.setupPassed() === this.setupTotal(); }, setupBlocked() { return !this.setupComplete(); }, setupPartial() { return this.setupPassed() > 0 && !this.setupComplete(); }, setupMissing() { return this.setupPassed() === 0; }, showPreviewAgents() { return this.agentScores().length === 0; }, activeSessions() { return currentProjectSessions.filter((session) => session.status === 'active'); }, primarySession() { return this.activeSessions()[0] || null; }, sessionLabel(session) { if (!session) return 'session'; return sessionTitleFor(session); }, sessionAge(session) { if (!session) return '0m'; const seconds = typeof session.age === 'number' ? session.age : Math.max(0, Math.floor((Date.now() - new Date(session.createdAt).getTime()) / 1000)); const minutes = Math.max(0, Math.floor(seconds / 60)); const hours = Math.floor(minutes / 60); if (hours > 0) return hours + 'h ' + (minutes % 60) + 'm'; return minutes + 'm'; }, openSession(session) { if (!session) return; if (isSessionBoundLocally(session.id)) { activeSessionId = session.id; activeView = 'workspace'; workspacePanel = 'terminal'; return; } openServerSession(session); }, endPrimarySession() { const session = this.primarySession(); if (session) endServerSession(session.id); }, auditAgeHours() { if (!lastAuditTime) return 0; return Math.max(0, Math.floor((Date.now() - new Date(lastAuditTime).getTime()) / 3600000)); }, auditAgeText() { const prefix = auditCached ? 'cached - ' : 'audited '; return prefix + formatAuditAge(lastAuditTime); }, auditStale() { return Boolean(auditCached && this.auditAgeHours() > 24); }, agentScores() { return report?.agentScores || []; }, agentColumnCount() { return Math.min(Math.max(this.homeAgents().length, 1), 4); }, homeAgents() { if (!this.showPreviewAgents()) return this.agentScores(); return supportedAgents.map((agent) => ({ preview: true, id: agent.id, name: agent.name })); }, agentDisplayName(agent) { return agent?.name || agent?.id || 'Agent'; }, agentScore(agent) { if (agent?.preview) return null; const harnessChecks = agent?.harness?.checks || []; if (harnessChecks.length > 0) { const scored = harnessChecks.filter((check) => check.status !== 'skipped'); if (scored.length === 0) return 100; const passed = scored.filter((check) => check.status === 'pass').length; return Math.round((passed / scored.length) * 100); } const agentChecks = agent?.agent?.checks || []; if (agentChecks.length > 0) { const scored = agentChecks.filter((check) => check.status !== 'skipped'); if (scored.length === 0) return 100; const passed = scored.filter((check) => check.status === 'pass').length; return Math.round((passed / scored.length) * 100); } return null; }, gradeFor(score) { if (score === null || Number.isNaN(score)) return '-'; if (score >= 90) return 'A'; if (score >= 80) return 'B'; if (score >= 70) return 'C'; if (score >= 60) return 'D'; return 'F'; }, toneForScore(score) { if (score === null || Number.isNaN(score)) return 'na'; if (score >= 80) return 'ok'; if (score >= 70) return 'warn'; return 'bad'; }, agentCardClass(agent) { if (agent?.preview) return 'disabled'; const tone = this.toneForScore(this.agentScore(agent)); return tone === 'ok' ? '' : tone; }, concern(agent, key) { return agent?.concerns?.[key] || { status: 'fail', score: 0, findings: [], recommendations: [], howToFix: [] }; }, concernScore(agent, key) { if (agent?.preview) return null; const item = this.concern(agent, key); if (typeof item.score === 'number') return Math.round(item.score); return item.status === 'pass' ? 100 : 0; }, concernWidth(agent, key) { const score = this.concernScore(agent, key); return score === null ? '0%' : Math.max(6, score) + '%'; }, concernPctText(agent, key) { const score = this.concernScore(agent, key); return score === null ? '-' : String(score); }, concernToneClass(agent, key) { return this.toneForScore(this.concernScore(agent, key)); }, expandedAgentBreakdowns: {}, agentAllConcernsPassing(agent) { if (agent?.preview || !agent?.concerns) return false; return this.concernKeys.every((key) => { const item = this.concern(agent, key); return item.status === 'pass' && this.concernScore(agent, key) >= 100; }); }, agentBreakdownOpen(agent) { return !this.agentAllConcernsPassing(agent) || this.expandedAgentBreakdowns[agent.id] === true; }, toggleAgentBreakdown(agent) { if (!agent?.id) return; this.expandedAgentBreakdowns[agent.id] = !this.expandedAgentBreakdowns[agent.id]; }, recommendationSummary(agent) { if (agent?.preview) return 'Skills, hooks, instruction file'; const recs = Object.values(agent?.concerns || {}).reduce((total, concern) => total + (concern.recommendations || []).length, 0); const integrity = this.integrityFailCount(agent); const warnings = this.scoreOnlyWarningCount(agent); if (recs === 0 && integrity === 0 && warnings === 0) return 'All checks passing'; if (integrity > 0) return integrity + ' integrity issue' + (integrity === 1 ? '' : 's'); if (warnings > 0) return warnings + ' score warning' + (warnings === 1 ? '' : 's'); return recs + ' recommendation' + (recs === 1 ? '' : 's'); }, scoreOnlyWarningCount(agent) { return (agent?.harness?.checks || []).filter((check) => check.status === 'fail' && check.impact === 'score-only').length; }, integrityFailCount(agent) { return Object.values(agent?.concerns || {}).reduce((total, concern) => total + (concern.integrityFail || 0), 0); }, setupCheckPass(id) { return this.setupChecks().some((check) => check.id === id && check.status === 'pass'); }, allAgentsPassCheck(id) { const agents = this.agentScores(); return agents.length > 0 && agents.every((agent) => (agent.agent?.checks || []).some((check) => check.id === id && check.status === 'pass') ); }, failureText(check) { return check?.failure?.message || check?.failure?.howToFix || ''; }, setupCheckDetail(id) { const check = this.setupChecks().find((item) => item.id === id && item.status === 'fail'); return this.failureText(check); }, agentCheckDetail(id) { const agents = this.agentScores(); const active = agents.find((agent) => agent.id === activeRunner); const ordered = active ? [active, ...agents.filter((agent) => agent.id !== activeRunner)] : agents; const check = ordered .flatMap((agent) => agent.agent?.checks || []) .find((item) => item.id === id && item.status === 'fail'); return this.failureText(check); }, verificationGateDetail() { return [ this.setupCheckDetail('config-version'), this.setupCheckDetail('config-parses'), this.agentCheckDetail('agent-settings') ].find((detail) => detail) || ''; }, installChecks() { return [ { label: 'Agent instruction files present', ok: this.allAgentsPassCheck('agent-instruction'), detail: this.agentCheckDetail('agent-instruction') }, { label: 'Agent skills installed and current', ok: this.allAgentsPassCheck('agent-skills'), detail: this.agentCheckDetail('agent-skills') }, { label: 'Agent guardrails hooks enabled', ok: this.allAgentsPassCheck('agent-guardrails'), detail: this.agentCheckDetail('agent-guardrails') }, { label: 'Footguns and lessons tracked', ok: this.setupCheckPass('footguns') && this.setupCheckPass('lessons'), detail: this.setupCheckDetail('footguns') || this.setupCheckDetail('lessons') }, { label: 'Verification gates configured', ok: this.setupCheckPass('config-parses') && this.setupCheckPass('config-version') && this.allAgentsPassCheck('agent-settings'), detail: this.verificationGateDetail() } ]; }, installChecklistScore() { const checks = this.installChecks(); if (checks.length === 0) return null; const passed = checks.filter((check) => check.ok).length; return Math.round((passed / checks.length) * 100); }, installCheckIcon(check) { return check.ok ? '&#10003;' : '&#10005;'; }, installPillTone() { if (this.setupComplete()) return 'ok'; return this.setupPartial() ? 'warn' : 'bad'; }, installPillValue() { if (this.setupComplete()) return 'Passing'; return this.setupPartial() ? 'Partial' : 'Not installed'; }, installPillDetail() { if (this.setupTotal() === 0) return 'audit components'; return this.setupPassed() + ' of ' + this.setupTotal() + ' components'; }, harnessAverage() { const scores = this.agentScores().map((agent) => this.agentScore(agent)).filter((score) => score !== null && !Number.isNaN(score)); if (scores.length === 0) return null; return Math.round(scores.reduce((total, score) => total + score, 0) / scores.length); }, readinessScore() { const scores = []; const installScore = this.installChecklistScore(); if (installScore !== null && !Number.isNaN(installScore)) scores.push(installScore); const harnessScore = this.harnessAverage(); if (this.setupComplete() && harnessScore !== null && !Number.isNaN(harnessScore)) scores.push(harnessScore); if (scores.length === 0) return null; return Math.round(scores.reduce((total, score) => total + score, 0) / scores.length); }, ringTone() { return this.toneForScore(this.readinessScore()); }, ringColor() { const tone = this.ringTone(); if (tone === 'ok') return 'var(--status-pass)'; if (tone === 'warn') return 'var(--status-waiting)'; if (tone === 'bad') return 'var(--status-danger)'; return 'rgba(148, 163, 184, 0.25)'; }, ringStyle() { const value = this.readinessScore(); return { '--ring-color': this.ringColor(), '--ring-value': value === null ? '0%' : value + '%' }; }, ringLabel() { return 'READINESS'; }, ringNumber() { const value = this.readinessScore(); return value === null ? '--' : String(value); }, ringShowPct() { return this.readinessScore() !== null; }, ringBreakdown() { const installScore = this.installChecklistScore(); const harnessScore = this.setupComplete() ? this.harnessAverage() : null; const fmt = (value) => value === null || Number.isNaN(value) ? '--' : value + '%'; return 'Install ' + fmt(installScore) + ' / Harness ' + fmt(harnessScore); }, atAGrade() { return this.agentScores().filter((agent) => this.agentScore(agent) >= 90 && !this.harnessAgentFailed(agent)).length; }, harnessBlockingFailures() { return this.agentScores().filter((agent) => this.harnessAgentFailed(agent)); }, widestGap() { const agents = this.agentScores(); if (agents.length === 0) return 'pending'; const gaps = this.concernKeys.map((key) => { const values = agents.map((agent) => this.concernScore(agent, key)).filter((score) => score !== null); const avg = values.length === 0 ? 0 : values.reduce((total, score) => total + score, 0) / values.length; return { key, avg }; }).sort((a, b) => a.avg - b.avg); const gap = gaps[0]; return gap && gap.avg < 90 ? this.concernLabel(gap.key) : 'none'; }, concernLabel(key) { if (key === 'verification' && this.setupBlocked()) return 'Verification support'; return this.concernLabels[key] || key; }, sectionMeta() { if (this.showPreviewAgents()) return 'preview - audit will populate after install'; if (this.setupBlocked()) return 'setup incomplete - raw details only'; const total = this.agentScores().length; const failing = this.harnessBlockingFailures().length; const gap = this.widestGap(); const gapText = gap !== 'none' && gap !== 'pending' ? ' - widest gap is ' + gap : ''; if (failing > 0) return failing + ' of ' + total + ' agents need fixes' + gapText + ' - click for details'; return this.atAGrade() + ' of ' + total + ' at A' + gapText + ' - click for details'; }, harnessPillTone() { if (this.setupBlocked() || this.agentScores().length === 0) return 'na'; if (this.harnessBlockingFailures().length > 0) return 'bad'; return this.toneForScore(this.harnessAverage()); }, harnessPillValue() { if (this.setupBlocked()) return 'Setup first'; if (this.harnessBlockingFailures().length > 0) return 'Needs work'; const avg = this.harnessAverage(); if (avg === null) return 'N/A'; return avg >= 90 ? 'Passing' : 'Needs work'; }, harnessPillDetail() { if (this.setupBlocked()) return 'finish setup before trusting harness'; const total = this.agentScores().length; if (total === 0) return 'run audit after setup'; const failing = this.harnessBlockingFailures().length; const gap = this.widestGap(); const gapText = gap !== 'none' && gap !== 'pending' ? ' - ' + gap + ' low' : ''; if (failing > 0) return failing + ' of ' + total + ' agents have failing checks' + gapText; return this.atAGrade() + ' of ' + total + ' agents at A' + gapText; }, learningSummary() { return report?.learningLoop || null; }, learningIndexEntries() { const entries = this.learningSummary()?.indexes; return Array.isArray(entries) ? entries : []; }, learningIndexRows() { return this.learningIndexEntries().filter((entry) => entry.state !== 'no-bucket'); }, learningIndexCount(state) { return this.learningIndexEntries().filter((entry) => entry.state === state).length; }, learningIndexAvailable() { return this.learningIndexRows().length > 0; }, learningIndexButtonDisabled() { return this.setupBlocked() || this.indexRegenerating || !this.learningIndexAvailable(); }, learningIndexButtonLabel() { if (this.indexRegenerating) return 'Regenerating...'; if (this.setupBlocked()) return 'Setup first'; if (!this.learningIndexAvailable()) return 'No indexes'; return 'Regenerate index'; }, learningIndexStateLabel(state) { if (state === 'fresh') return 'fresh'; if (state === 'stale') return 'stale'; if (state === 'missing') return 'missing'; return 'not installed'; }, learningLoopReady() { if (this.setupBlocked()) return false; const summary = this.learningSummary(); return Boolean(summary && summary.status !== 'unavailable'); }, learningLoopNaText() { if (this.setupMissing()) return 'No learning-loop records yet.'; if (this.setupBlocked()) return 'Finish setup to unlock learning-loop health.'; return 'No learning-loop files found for this project.'; }, learningBucketMax() { const counts = this.learningIndexRows().map((entry) => entry.entryCount || 0); return Math.max(1, ...counts); }, learningBarWidth(entry) { const count = entry.entryCount || 0; if (count <= 0) return '0%'; return Math.max(5, Math.round((count / this.learningBucketMax()) * 100)) + '%'; }, learningBarTone(entry) { if (entry.state === 'fresh') return 'ok'; if (entry.state === 'stale') return 'warn'; return 'na'; }, learningLoopStatusDetail() { const summary = this.learningSummary(); if (!summary) return ''; const total = this.learningIndexRows().reduce((acc, entry) => acc + (entry.entryCount || 0), 0); const parts = [total + ' active entries']; if (summary.staleCount > 0) parts.push(summary.staleCount + ' stale'); if (summary.oversizedCount > 0) parts.push(summary.oversizedCount + ' oversized'); if (summary.invalidLineRefCount > 0) parts.push(summary.invalidLineRefCount + ' bad refs'); const indexStale = this.learningIndexCount('stale'); const indexMissing = this.learningIndexCount('missing'); if (indexStale > 0) parts.push(indexStale + ' stale index' + (indexStale === 1 ? '' : 'es')); if (indexMissing > 0) parts.push(indexMissing + ' missing index' + (indexMissing === 1 ? '' : 'es')); return parts.join(', '); }, learningPillTone() { if (this.setupBlocked()) return 'na'; const summary = this.learningSummary(); if (!summary || summary.status === 'unavailable') return 'na'; if (this.learningIndexCount('stale') > 0 || this.learningIndexCount('missing') > 0) return 'warn'; return summary.status === 'fresh' ? 'ok' : 'warn'; }, learningPillValue() { if (this.setupBlocked()) return 'Setup first'; const summary = this.learningSummary(); if (!summary || summary.status === 'unavailable') return 'N/A'; if (this.learningIndexCount('stale') > 0) return 'Index stale'; if (this.learningIndexCount('missing') > 0) return 'Index missing'; return summary.status === 'fresh' ? 'Fresh' : 'Needs review'; }, learningPillDetail() { if (this.setupBlocked()) return 'created by setup'; const summary = this.learningSummary(); if (!summary || summary.status === 'unavailable') return 'no learning-loop files'; const stale = summary.staleCount || 0; const oversized = summary.oversizedCount || 0; const fg = typeof summary.footgunCount === 'number' ? summary.footgunCount : null; const ls = typeof summary.lessonCount === 'number' ? summary.lessonCount : null; const parts = fg !== null && ls !== null ? [fg + ' footguns', ls + ' lessons'] : [summary.recordCount + ' records']; if (stale > 0) parts.push(stale + ' stale'); if (oversized > 0) parts.push(oversized + ' oversized'); if (summary.invalidLineRefCount > 0) parts.push(summary.invalidLineRefCount + ' bad refs'); const indexStale = this.learningIndexCount('stale'); const indexMissing = this.learningIndexCount('missing'); if (indexStale > 0) parts.push(indexStale + ' stale index' + (indexStale === 1 ? '' : 'es')); if (indexMissing > 0) parts.push(indexMissing + ' missing index' + (indexMissing === 1 ? '' : 'es')); return parts.join(', '); }, learningOldest() { const summary = this.learningSummary(); return summary?.oldestLastReviewed || null; }, learningTopBuckets() { const summary = this.learningSummary(); return summary?.topBucketsNeedingAction || []; }, qualityLatestDate() { if (!homeQualityLatest?.date) return null; const time = homeQualityLatest.time || '0000'; const hour = time.slice(0, 2) || '00'; const minute = time.slice(2, 4) || '00'; return new Date(homeQualityLatest.date + 'T' + hour + ':' + minute + ':00'); }, qualityPillTone() { if (this.setupBlocked()) return 'na'; if (homeQualityLoading) return 'na'; if (!homeQualityLatest) return 'warn'; if (homeQualityLatest.blockerCount > 0) return 'bad'; if (homeQualityLatest.majorCount > 0) return 'warn'; return 'ok'; }, qualityPillValue() { if (this.setupBlocked()) return 'Setup first'; if (homeQualityLoading) return 'Loading'; if (!homeQualityLatest) return 'N/A'; if (homeQualityLatest.blockerCount > 0) return 'Blockers'; if (homeQualityLatest.majorCount > 0) return 'Attention'; return 'Recent'; }, qualityPillDetail() { if (this.setupBlocked()) return 'finish setup before quality'; if (homeQualityLoading) return '--'; if (!homeQualityLatest) return 'no saved assessment'; const date = this.qualityLatestDate(); const age = date ? formatAuditAge(date) : 'recently'; if (homeQualityLatest.blockerCount > 0) return age + ', ' + homeQualityLatest.blockerCount + ' blocker' + (homeQualityLatest.blockerCount === 1 ? '' : 's'); if (homeQualityLatest.majorCount > 0) return age + ', ' + homeQualityLatest.majorCount + ' major'; return age + ', 0 blockers'; }, nextPriority() { if (!this.setupComplete()) return 'setup'; const harnessFail = this.agentScores().some((agent) => this.harnessAgentFailed(agent)); if (harnessFail) return 'harness'; const concernFail = this.agentScores().some((agent) => Object.values(agent.concerns || {}).some((item) => item?.status === 'fail') ); const qualityFail = Boolean((homeQualityLatest?.blockerCount || 0) > 0 || (homeQualityLatest?.majorCount || 0) > 0); if (concernFail || qualityFail || !homeQualityLatest) return 'completeness'; return 'healthy'; }, failingAgent() { const scores = this.agentScores(); const failing = scores.find((agent) => agent.agent?.status === 'fail' || agent.harness?.status === 'fail' || this.integrityFailCount(agent) > 0 || Object.values(agent.concerns || {}).some((item) => item?.status === 'fail') ); return failing?.id || activeRunner; }, harnessAgentFailed(agent) { return agent.agent?.status === 'fail' || agent.harness?.status === 'fail' || this.integrityFailCount(agent) > 0; }, failingHarnessAgent() { const failing = this.agentScores().find((agent) => this.harnessAgentFailed(agent)); return failing?.id || activeRunner; }, nextEyebrow() { const priority = this.nextPriority(); if (priority === 'setup') return this.setupPartial() ? 'FIX FIRST' : 'GET STARTED'; if (priority === 'harness') return 'FIX FIRST'; if (priority === 'completeness') return 'RECOMMENDED'; return 'NEXT ACTION'; }, nextTitle() { const priority = this.nextPriority(); if (priority === 'setup') return this.setupPartial() ? 'Finish goat-flow setup' : 'Install goat-flow for ' + agentName(activeRunner); if (priority === 'harness') return 'Fix the failing harness checks'; if (priority === 'completeness') return 'Run a fresh quality assessment'; return 'Run a skill or pick up active session'; }, nextDetail() { const priority = this.nextPriority(); if (priority === 'setup') { if (this.setupPartial()) return 'This project has ' + this.setupPassed() + ' of ' + this.setupTotal() + ' setup components. Finish setup before trusting harness and quality signals.'; return 'Creates config, skills, hooks, lessons, and instruction files for this project.'; } if (priority === 'harness') return 'Use the current audit findings to repair instruction, skill, and integrity gaps.'; if (priority === 'completeness') return 'Refresh quality history and turn the largest stale findings into concrete next work.'; return 'The Home checks are healthy. Use the review preset to inspect the next implementation change.'; }, nextActionCommand() { const priority = this.nextPriority(); if (priority === 'setup') return 'Generated setup prompt for ' + agentName(activeRunner); if (priority === 'harness') return '$ npx goat-flow audit . --harness --agent ' + this.failingHarnessAgent(); if (priority === 'completeness') return '$ npx goat-flow quality . --agent ' + activeRunner; return null; }, nextPrimaryLabel() { const priority = this.nextPriority(); if (priority === 'setup') return this.setupPartial() ? 'Fix with ' + agentName(activeRunner) : (terminalAvailable ? 'Run with ' + agentName(activeRunner) : 'Open Setup'); if (priority === 'harness') return 'Fix with ' + agentName(activeRunner); if (priority === 'completeness') return 'Run assessment'; return 'Explore codebase'; }, nextSecondaryLabel() { const priority = this.nextPriority(); if (priority === 'setup') return 'Setup wizard'; if (priority === 'harness') return 'Open Setup'; if (priority === 'completeness') return 'View prompts'; return 'View prompts'; }, async runPrimaryAction() { const priority = this.nextPriority(); if (priority === 'setup') { if (terminalAvailable) { const setupTarget = activeRunner; const setupPrompt = await generateSetupPromptForAgent(setupTarget); if (!setupPrompt) return; await launchPreset(setupPrompt, activeRunner, 'Setup ' + agentName(setupTarget) + ' via ' + agentName(activeRunner), { cwdPath: projectPath, targetPath: projectPath }); activeView = 'workspace'; workspacePanel = 'terminal'; return; } activeView = 'setup'; detectStack(); return; } if (priority === 'harness') { launchPreset(this.harnessFixPrompt(), activeRunner, 'Harness fix ' + agentName(this.failingHarnessAgent())); activeView = 'workspace'; workspacePanel = 'terminal'; return; } if (priority === 'completeness') { qualityAgent = activeRunner; activeView = 'quality'; generateQuality({ fast: true }); generateQualityHistory(); return; } const preset = presets.find((item) => item.id === 'explore'); if (preset) launchPreset(preset.prompt, activeRunner, preset.label, { presetId: preset.id }); else launchInTerminal('/goat', activeRunner, { promptLabel: 'Explore codebase', cwdPath: projectPath, targetPath: projectPath }); activeView = 'workspace'; workspacePanel = 'terminal'; }, runSecondaryAction() { const priority = this.nextPriority(); if (priority === 'setup' || priority === 'harness') { activeView = 'setup'; detectStack(); return; } activeView = 'prompts'; }, formatConcernSummary(agent, key) { const item = this.concern(agent, key); const findings = item.findings || []; if (key === 'verification' && this.setupBlocked()) return 'Setup incomplete; verification evidence is support-only until setup finishes.'; if (item.status === 'pass' && this.concernScore(agent, key) < 100 && findings.length > 0) return findings[0]; if (item.status === 'pass') return 'No active findings'; if (findings.length > 0) return findings[0]; return 'Concern needs attention'; }, formatConcernFix(agent, key) { const item = this.concern(agent, key); const rec = (item.recommendations || [])[0]; const fix = (item.howToFix || [])[0]; if (rec && fix) return rec + ' Fix: ' + fix; return rec || fix || 'No fix detail supplied'; }, checkBadge(check) { const status = check.displayStatus || check.status; if (status === 'pass') return 'OK'; if (status === 'warn') return 'Warn'; if (status === 'info') return 'Info'; if (status === 'skipped') return 'Skip'; return 'Fix'; }, checkBadgeClass(check) { return check.displayStatus || check.status; }, checkImpactDetail(check) { const parts = []; if (check.impact === 'scope-fail') parts.push('fails audit status'); else if (check.impact === 'score-only') parts.push('score-only signal'); else parts.push('no failing impact'); if (check.type === 'metric') parts.push('metric evidence'); if (check.acknowledged) parts.push('acknowledged advisory'); if (check.evidenceKind === 'structural') parts.push('structural smoke'); if (check.assurance === 'limited') parts.push('limited assurance'); return parts.join(' · '); }, detailList(agent, scope) { return (agent?.[scope]?.checks || []).map((check) => ({ id: check.id, name: check.name, status: check.status, displayStatus: check.displayStatus, impact: check.impact, type: check.type, acknowledged: check.acknowledged, evidenceKind: check.evidenceKind, assurance: check.assurance, detail: (check.failure?.message || check.failure?.howToFix || '') ? (check.failure?.message || check.failure?.howToFix) + ' - ' + this.checkImpactDetail(check) : this.checkImpactDetail(check) })); }, enforcementRows(agent) { return agent?.enforcement?.capabilities || []; }, enforcementBadge(row) { if (row.status === 'hard') return 'Hard'; if (row.status === 'limited') return 'Lim'; if (row.status === 'soft') return 'Soft'; if (row.status === 'missing') return 'Miss'; return 'Unk'; }, enforcementBadgeClass(row) { if (row.status === 'hard') return 'pass'; if (row.status === 'missing') return 'fail'; if (row.status === 'unknown') return 'skipped'; return 'warn'; }, harnessFixPrompt() { const scores = this.agentScores(); if (scores.length === 0) { return [ '/goat-plan re-run goat-flow audit for ' + activeRunner, '', 'Context:', '- Project: ' + projectPath, '- Target agent: ' + activeRunner, '- No agent audit scores are available in the dashboard report.', '', 'Goal:', 'Run a fresh goat-flow audit and inspect the failing harness checks before editing.', '', 'Constraints:', '- Do not guess which agent or scope is failing.', '- Re-run `goat-flow audit . --harness --agent ' + activeRunner + '` and report exact output before proposing fixes.' ].join('\n'); } const target = this.failingHarnessAgent(); const agent = scores.find((item) => item.id === target) || scores[0]; const findings = []; if (agent) { for (const check of (agent.agent?.checks || [])) { if (check.status === 'fail') findings.push('agent/' + check.id + ': ' + (check.failure?.message || check.failure?.howToFix || 'check failed')); } for (const check of (agent.harness?.checks || [])) { if (check.status === 'fail' && check.type !== 'metric' && !check.acknowledged) findings.push('harness/' + check.id + ': ' + (check.failure?.message || check.failure?.howToFix || 'check failed')); } for (const [key, concern] of Object.entries(agent.concerns || {})) { if (concern.status === 'fail') findings.push(key + ': ' + ((concern.findings || []).slice(0, 2).join('; ') || 'check failed')); } } return [ '/goat-plan fix goat-flow harness findings for ' + target, '', 'Context:', '- Project: ' + projectPath, '- Target agent: ' + target, '- Latest audit age: ' + this.auditAgeText(), '- Findings:', findings.length ? findings.map((item) => ' - ' + item).join('\n') : ' - Re-run audit and inspect failing harness checks.', '', 'Goal:', 'Repair the failing goat-flow harness checks while preserving existing project behaviour.', '', 'Constraints:', '- Read relevant instruction, skill, lesson, footgun, and pattern files before edits.', '- Keep changes minimal and evidence-backed.', '- Do not invent incidents, lessons, or footgun evidence.', '- Update the milestone/checklist only for tasks actually completed.', '- Verify with the dashboard audit/typecheck commands and paste exact pass/fail output.' ].join('\n'); } }" > <div class="home-empty" x-show="!report"> <div class="home-empty-mark">GF</div> <h2>No audit results yet</h2> <p> Run an audit to populate setup status, agent health, learning-loop freshness, and the next action for this project. </p> <button class="gf-btn gf-btn-primary gf-btn-md home-primary" @click="runAudit()" :disabled="auditing" > <span x-text="auditing ? 'Running...' : 'Run Audit'"></span> </button> </div> <div x-show="report"> <div class="sessions-strip" x-show="activeSessions().length > 0"> <div class="sessions-strip-main"> <span class="gf-status-dot gf-status-running" aria-label="Running session" >●</span > <span class="session-count" x-text="activeSessions().length + ' active session' + (activeSessions().length === 1 ? '' : 's')" ></span> <span class="session-meta" x-show="primarySession()" x-text="agentName(primarySession()?.runner || activeRunner) + ' - ' + sessionLabel(primarySession()) + ' - ' + sessionAge(primarySession())" ></span> </div> <span class="spacer"></span> <button class="gf-btn gf-btn-secondary gf-btn-sm" @click="openSession(primarySession())" > Resume </button> <button class="gf-btn gf-btn-danger gf-btn-sm" @click="endPrimarySession()" x-show="primarySession()" > End </button> </div> <section class="rollup" :class="{ stale: auditStale() }"> <div class="rollup-top"> <div> <div class="rollup-kicker" x-show="setupMissing()"> Home readiness </div> <div class="rollup-heading"> <h1 class="rollup-name" x-text="setupMissing() ? 'Install goat-flow for this project' : projectName" ></h1> <span class="rollup-meta" x-show="!setupMissing()" :class="{ warn: auditStale() }" x-text="auditAgeText()" ></span> </div> <p class="rollup-subtitle" x-show="setupMissing()" x-text="'One guided setup unlocks agent health, lessons, and quality tracking.'" ></p> </div> <div class="rollup-actions"> <span class="rollup-meta" x-show="setupMissing()" :class="{ warn: auditStale() }" x-text="auditAgeText()" ></span> <button class="gf-btn gf-btn-secondary gf-btn-sm" :class="{ 'btn-warn': auditStale() }" @click="runAudit(true)" :disabled="auditing" > <span x-text="auditing ? 'Auditing...' : (auditStale() ? 'Re-audit now' : 'Re-audit')" ></span> </button> </div> </div> <div class="pills"> <div class="pill" :class="installPillTone()"> <div class="pill-head"> <span class="pill-dot" :class="installPillTone()"></span> <span class="pill-label">INSTALL</span> </div> <div class="pill-value" :class="installPillTone()" x-text="installPillValue()" ></div> <div class="pill-detail" x-text="installPillDetail()"></div> </div> <div class="pill" :class="harnessPillTone()"> <div class="pill-head"> <span class="pill-dot" :class="harnessPillTone()"></span> <span class="pill-label">HARNESS</span> </div> <div class="pill-value" :class="harnessPillTone()" x-text="harnessPillValue()" ></div> <div class="pill-detail" x-text="harnessPillDetail()"></div> </div> <div class="pill" :class="learningPillTone()"> <div class="pill-head"> <span class="pill-dot" :class="learningPillTone()"></span> <span class="pill-label">LEARNING LOOP</span> </div> <div class="pill-value" :class="learningPillTone()" x-text="learningPillValue()" ></div> <div class="pill-detail" x-text="learningPillDetail()"></div> </div> <div class="pill" :class="qualityPillTone()"> <div class="pill-head"> <span class="pill-dot" :class="qualityPillTone()"></span> <span class="pill-label" x-text="'QUALITY - ' + agentName(activeRunner)" ></span> </div> <div class="pill-value" :class="qualityPillTone()" x-text="qualityPillValue()" ></div> <div class="pill-detail" x-text="qualityPillDetail()"></div> </div> </div> </section> <section class="next-action" :class="nextPriority() === 'healthy' ? 'priority-ok' : (nextPriority() === 'completeness' ? 'priority-warn' : 'priority-bad')" > <div class="next-action-body"> <div class="next-action-eyebrow" x-text="nextEyebrow()"></div> <h2 class="next-action-title" x-text="nextTitle()"></h2> <p class="next-action-detail" x-text="nextDetail()"></p> <div class="next-action-cmd" x-show="nextActionCommand()" x-text="nextActionCommand()" ></div> </div> <div class="next-action-cta"> <button class="gf-btn gf-btn-secondary gf-btn-md" @click="runSecondaryAction()" > <span x-text="nextSecondaryLabel()"></span> </button> <button class="gf-btn gf-btn-primary gf-btn-md home-primary" @click="runPrimaryAction()" > <span x-text="nextPrimaryLabel()"></span> </button> </div> </section> <div class="section-head"> <span class="section-title" x-text="showPreviewAgents() ? 'What you get after setup' : (setupBlocked() ? 'AI harness detail' : 'AI harness health')" ></span> <span class="section-meta" x-text="sectionMeta()"></span> </div> <section class="agent-grid" :style="{ '--agent-columns': String(agentColumnCount()) }" > <template x-for="agent in homeAgents()" :key="agent.id"> <article class="agent-card" :class="[agentCardClass(agent), { 'agent-card-selected': !agent.preview && auditDetailAgent === agent.id }]" > <div class="agent-card-button" role="button" tabindex="0" :aria-expanded="String(!agent.preview && auditDetailAgent === agent.id)" :aria-controls="'detail-' + agent.id" @click="if (!agent.preview) auditDetailAgent = auditDetailAgent === agent.id ? null : agent.id" @keydown.enter="if (!agent.preview) auditDetailAgent = auditDetailAgent === agent.id ? null : agent.id" @keydown.space.prevent="if (!agent.preview) auditDetailAgent = auditDetailAgent === agent.id ? null : agent.id" > <div class="agent-card-head"> <span class="agent-name" x-text="agentDisplayName(agent)" ></span> <span class="agent-grade" :class="toneForScore(agentScore(agent))" > <span class="grade-letter" x-text="gradeFor(agentScore(agent))" ></span> <span class="grade-pct" :class="toneForScore(agentScore(agent))" x-text="agentScore(agent) === null ? 'preview' : agentScore(agent) + '%'" ></span> </span> </div> <div class="agent-summary" x-text="recommendationSummary(agent)" ></div> <button x-show="agentAllConcernsPassing(agent) && !agentBreakdownOpen(agent)" class="agent-all-pass" type="button" @click.stop="toggleAgentBreakdown(agent)" @keydown.enter.stop="toggleAgentBreakdown(agent)" @keydown.space.prevent.stop="toggleAgentBreakdown(agent)" > ✓ All checks passing </button> <div class="concern-list" x-show="agentBreakdownOpen(agent)"> <template x-for="key in concernKeys" :key="key"> <div class="concern-row" :aria-label="concernLabel(key) + ' ' + concernPctText(agent, key) + '%'" > <span class="concern-label" x-text="concernLabel(key)" ></span> <div class="concern-bar"> <div class="concern-fill" :class="concernToneClass(agent, key)" :style="{ width: concernWidth(agent, key) }" ></div> </div> <span class="concern-pct" :class="concernToneClass(agent, key)" x-text="concernPctText(agent, key)" ></span> </div> </template> <button x-show="agentAllConcernsPassing(agent)" class="gf-btn gf-btn-sm gf-btn-text" type="button" style="margin-top: 4px" @click.stop="toggleAgentBreakdown(agent)" > Collapse </button> </div> </div> </article> </template> </section> <div class="home-detail" x-show="auditDetailAgent && agentScores().length > 0" x-transition:enter="transition ease-out duration-200" x-transition:enter-start="opacity-0 -translate-y-2" x-transition:enter-end="opacity-100 translate-y-0" x-transition:leave="transition ease-in duration-150" x-transition:leave-start="opacity-100 translate-y-0" x-transition:leave-end="opacity-0 -translate-y-2" @keydown.escape.window="auditDetailAgent = null" :id="'detail-' + auditDetailAgent" > <template x-if="auditDetailAgent"> <template x-for="agent in agentScores().filter(a => a.id === auditDetailAgent)" :key="agent.id" > <div> <div class="detail-header"> <div class="detail-header-left"> <span class="agent-name" x-text="agentDisplayName(agent)" ></span> <span class="agent-grade" :class="toneForScore(agentScore(agent))" > <span class="grade-letter" x-text="gradeFor(agentScore(agent))" ></span> <span class="grade-pct" :class="toneForScore(agentScore(agent))" x-text="agentScore(agent) + '%'" ></span> </span> </div> <button class="detail-close" @click="auditDetailAgent = null" aria-label="Close detail panel" > &times; </button> </div> <div class="detail-grid"> <div class="detail-card"> <h4>Agent Setup</h4> <template x-for="check in detailList(agent, 'agent')" :key="check.id" > <div class="detail-row"> <span class="detail-status" :class="checkBadgeClass(check)" x-text="checkBadge(check)" ></span> <div> <strong x-text="check.name"></strong> <p x-text="check.detail || 'No detail supplied'"></p> </div> </div> </template> </div> <div class="detail-card"> <h4>Harness Checks</h4> <template x-for="check in detailList(agent, 'harness')" :key="check.id" > <div class="detail-row"> <span class="detail-status" :class="checkBadgeClass(check)" x-text="checkBadge(check)" ></span> <div> <strong x-text="check.name"></strong> <p x-text="check.detail"></p> </div> </div> </template> </div> <div class="detail-card"> <h4>Enforcement Matrix</h4> <template x-for="row in enforcementRows(agent)" :key="row.id"> <div class="detail-row"> <span class="detail-status" :class="enforcementBadgeClass(row)" x-text="enforcementBadge(row)" ></span> <div> <strong x-text="row.label"></strong> <p x-text="row.summary"></p> </div> </div> </template> </div> <div class="detail-card"> <h4>Concern Detail</h4> <template x-for="key in concernKeys" :key="key"> <div class="detail-row"> <span class="detail-status" :class="concern(agent, key).status" x-text="concern(agent, key).status === 'pass' ? 'OK' : 'Fix'" ></span> <div> <strong x-text="concernLabel(key)"></strong> <p x-text="formatConcernSummary(agent, key)"></p> <p class="detail-fix" x-text="formatConcernFix(agent, key)" ></p> </div> </div> </template> </div> </div> </div> </template> </template> </div> <section class="lower-row"> <article class="panel install-panel"> <div class="panel-head"> <span class="panel-title">Install state</span> <button class="panel-cta" x-show="!setupMissing()" @click="activeView = 'setup'; detectStack()" > Setup </button> </div> <div class="install-body"> <div class="install-checks"> <template x-for="check in installChecks()" :key="check.label"> <div class="install-check" :class="{ dim: !check.ok }"> <span class="install-check-icon" :class="{ ok: check.ok, bad: !check.ok }" x-html="installCheckIcon(check)" ></span> <span class="install-check-copy"> <span x-text="check.label"></span> <span class="install-check-detail" x-show="!check.ok && check.detail" x-text="check.detail" ></span> </span> </div> </template> </div> <div class="ring-wrap"> <div class="ring-chart" :style="ringStyle()"> <span class="ring-value" :style="{ color: ringColor() }"> <span x-text="ringNumber()"></span ><span class="ring-pct" x-show="ringShowPct()">%</span> </span> </div> <span class="ring-label" x-text="ringLabel()"></span> <span class="ring-breakdown" x-text="ringBreakdown()"></span> </div> </div> </article> <article class="panel loop-panel"> <div class="panel-head"> <span class="panel-title">Learning loop</span> <button type="button" class="gf-btn gf-btn-sm gf-btn-secondary