@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
HTML
<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 ? '✓' : '✕'; },
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"
>
×
</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