@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.
214 lines • 8.69 kB
JavaScript
import { runAudit } from "../audit/audit.js";
import { loadConfig } from "../config/reader.js";
import { redactEvidenceText } from "../evidence/redaction.js";
import { createFS } from "../facts/fs.js";
import { extractSharedFacts } from "../facts/shared/index.js";
import { findLatestQualityReport } from "../quality/history.js";
import { QUALITY_MODES } from "../quality/schema.js";
import { composeQuality } from "../prompt/compose-quality.js";
import { KNOWN_AGENT_LIST, VALID_AGENTS, VALID_QUALITY_MODES, } from "./dashboard-route-types.js";
import { buildLatestQualitySummary, buildQualityAuditCacheKey, } from "./dashboard-reporting.js";
/** Parse the quality history limit. Invalid input (non-numeric, zero, negative)
* falls back to the default so callers can't bypass the cap with ?limit=0. */
function parseQualityHistoryLimit(param) {
if (param === null)
return 20;
const parsed = Number.parseInt(param, 10);
if (!Number.isFinite(parsed) || parsed <= 0)
return 20;
return Math.min(parsed, 100);
}
/** Parse a dashboard quality-mode filter. */
function parseQualityModeParam(param) {
if (param === null)
return null;
return VALID_QUALITY_MODES.has(param) ? param : null;
}
function qualityHistoryEntryMatchesFilters(entry, agent, qualityMode) {
if (agent !== null && entry.agent !== agent)
return false;
if (qualityMode === null)
return true;
return (entry.report.quality_mode ?? "agent-setup") === qualityMode;
}
function readQualityHistoryFilters(ctx, url, res) {
const agentParam = url.searchParams.get("agent");
const agent = agentParam && VALID_AGENTS.has(agentParam) ? agentParam : null;
if (agentParam && !agent) {
ctx.jsonResponse(res, 400, {
error: `quality history agent must be one of: ${KNOWN_AGENT_LIST}`,
});
return null;
}
const modeParam = url.searchParams.get("mode");
const qualityMode = parseQualityModeParam(modeParam);
if (modeParam && !qualityMode) {
ctx.jsonResponse(res, 400, {
error: `quality history mode must be one of: ${QUALITY_MODES.join(", ")}`,
});
return null;
}
return {
agent,
limit: parseQualityHistoryLimit(url.searchParams.get("limit")),
qualityMode,
};
}
function parseQualityRequestParams(ctx, url, res) {
const agentParam = url.searchParams.get("agent");
if (!agentParam || !VALID_AGENTS.has(agentParam)) {
ctx.jsonResponse(res, 400, {
error: `quality requires --agent. Valid: ${KNOWN_AGENT_LIST}`,
});
return null;
}
const modeParam = url.searchParams.get("mode");
if (modeParam && !VALID_QUALITY_MODES.has(modeParam)) {
ctx.jsonResponse(res, 400, {
error: `quality mode must be one of: ${QUALITY_MODES.join(", ")}`,
});
return null;
}
return {
agent: agentParam,
qualityMode: parseQualityModeParam(modeParam) ?? "agent-setup",
includeFresh: url.searchParams.get("fresh") === "true",
shouldUseFastCache: url.searchParams.get("fast") === "true",
};
}
function readQualityAuditCache(ctx, projectPath, agent, fresh) {
if (fresh)
return null;
const cached = ctx.qualityAuditCache.get(buildQualityAuditCacheKey(projectPath, agent));
if (!cached)
return null;
if (Date.now() - cached.cachedAt >= 10_000) {
ctx.qualityAuditCache.delete(buildQualityAuditCacheKey(projectPath, agent));
return null;
}
return cached.report;
}
function writeQualityAuditCache(ctx, projectPath, agent, report) {
ctx.qualityAuditCache.set(buildQualityAuditCacheKey(projectPath, agent), {
report,
cachedAt: Date.now(),
});
}
function getOrRunQualityAudit(ctx, projectPath, agent, { cacheOnly = false, fresh = false, } = {}) {
const cached = readQualityAuditCache(ctx, projectPath, agent, fresh);
if (cached !== null) {
return { report: cached, cacheStatus: "hit" };
}
const cacheStatus = fresh ? "bypass" : "miss";
if (cacheOnly)
return { report: null, cacheStatus };
try {
const fs = createFS(projectPath);
const report = runAudit(fs, projectPath, {
agentFilter: agent,
harness: true,
});
writeQualityAuditCache(ctx, projectPath, agent, report);
return { report, cacheStatus };
}
catch {
return { report: null, cacheStatus };
}
}
/** Generate a quality prompt and reports path/audit failures as JSON. */
function handleQualityRequest(ctx, url, res) {
if (url.pathname !== "/api/quality")
return false;
const params = parseQualityRequestParams(ctx, url, res);
if (params === null)
return true;
try {
const projectPath = ctx.validatedPath(url.searchParams.get("path"), "project-read");
const selectedProjectPath = ctx.validatedPath(url.searchParams.get("target"), "project-read");
const fs = createFS(projectPath);
const sharedFacts = extractSharedFacts(fs, loadConfig(projectPath, fs));
const audit = getOrRunQualityAudit(ctx, projectPath, params.agent, {
cacheOnly: params.shouldUseFastCache,
fresh: params.includeFresh,
});
const auditReport = audit.report;
const { entry: priorReport } = findLatestQualityReport(projectPath, params.agent, params.qualityMode);
const result = composeQuality({
agent: params.agent,
projectPath,
auditReport,
auditUnavailableReason: audit.report === null && params.shouldUseFastCache
? "fast-cache-only"
: undefined,
priorReport,
qualityMode: params.qualityMode,
selectedProjectPath,
sharedFacts,
});
ctx.recordDashboardEvent(projectPath, "quality.prompt", {
agent: params.agent,
quality_mode: params.qualityMode,
audit_status: auditReport?.status ?? "unavailable",
prompt: redactEvidenceText("quality prompt", result.prompt),
});
ctx.jsonResponse(res, 200, {
...result,
auditCacheStatus: audit.cacheStatus,
});
}
catch (err) {
ctx.jsonResponse(res, ctx.responseStatusForError(err, 500), {
error: err instanceof Error ? err.message : String(err),
});
}
return true;
}
/** Return persisted quality-history rows and latest trend summary for dashboard UI rendering. */
async function handleQualityHistoryRequest(ctx, url, res) {
if (url.pathname !== "/api/quality/history")
return false;
const filters = readQualityHistoryFilters(ctx, url, res);
if (filters === null)
return true;
try {
const projectPath = ctx.validatedPath(url.searchParams.get("path"), "project-read");
const { buildQualityHistoryRows, loadQualityHistoryWindow } = await import("../quality/history.js");
const history = loadQualityHistoryWindow(projectPath, {
agent: filters.agent,
limit: filters.limit,
qualityMode: filters.qualityMode,
});
const rows = buildQualityHistoryRows(history.entries, {
agent: filters.agent,
limit: filters.limit,
qualityMode: filters.qualityMode,
});
const latestEntry = history.entries.find((entry) => qualityHistoryEntryMatchesFilters(entry, filters.agent, filters.qualityMode)) ?? null;
ctx.jsonResponse(res, 200, {
rows,
latest: buildLatestQualitySummary(latestEntry),
warnings: history.warnings,
});
}
catch (err) {
ctx.jsonResponse(res, ctx.responseStatusForError(err, 500), {
error: err instanceof Error ? err.message : String(err),
});
}
return true;
}
/**
* Bind the quality handlers to one server's request context so each closure shares the path validator,
* the per-server audit cache, and the evidence recorder.
*
* @param ctx - per-server dashboard route context with path validation, the quality audit cache, and IO hooks
* @returns the quality-prompt and quality-history handlers; each resolves true once it has answered a
* matching request, or false to let another handler claim the URL
*/
export function createQualityRouteHandlers(ctx) {
return {
handleQualityRequest: (url, res) => handleQualityRequest(ctx, url, res),
handleQualityHistoryRequest: (url, res) => handleQualityHistoryRequest(ctx, url, res),
};
}
//# sourceMappingURL=dashboard-quality-routes.js.map