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.

489 lines 18.7 kB
/** * Parser implementation for quality report schema validation. */ import { isAbsolute } from "node:path"; import { KNOWN_AGENT_IDS } from "../agents/registry.js"; import { QUALITY_AUDIT_STATUSES, QUALITY_DELTA_TAGS, QUALITY_EVIDENCE_METHODS, QUALITY_EVIDENCE_QUALITIES, QUALITY_FINDING_SEVERITIES, QUALITY_FINDING_TYPES, QUALITY_MODES, QUALITY_REPORT_KIND, QUALITY_SCOPES, QUALITY_SCORE_VALUES, } from "./schema-types.js"; /** Check whether parsed JSON is an object before inspecting schema keys. */ function isRecord(candidate) { return (typeof candidate === "object" && candidate !== null && !Array.isArray(candidate)); } /** Reject unknown keys. */ function rejectUnknownKeys(value, allowedKeys, path) { const unknown = Object.keys(value).filter((key) => !allowedKeys.includes(key)); if (unknown.length === 0) return null; return `${path} has unknown key(s): ${unknown.join(", ")}`; } /** Expect string. */ function expectString(value, path) { if (typeof value !== "string") { return { ok: false, error: `${path} must be a string` }; } return { ok: true, value }; } /** Expect non empty string. */ function expectNonEmptyString(value, path) { const parsed = expectString(value, path); if (!parsed.ok) return parsed; if (parsed.value.trim().length === 0) { return { ok: false, error: `${path} must not be empty` }; } return { ok: true, value: parsed.value }; } /** Expect enum value. */ function expectEnumValue(value, path, values) { if (typeof value !== "string" || !values.includes(value)) { return { ok: false, error: `${path} must be one of: ${values.join(", ")}`, }; } return { ok: true, value: value }; } /** Expect nullable string. */ function expectNullableString(value, path) { if (value === null) return { ok: true, value: null }; const parsed = expectNonEmptyString(value, path); if (!parsed.ok) return parsed; return { ok: true, value: parsed.value }; } /** Expect nullable positive integer. */ function expectNullablePositiveInteger(value, path) { if (value === null) return { ok: true, value: null }; if (!Number.isInteger(value) || Number(value) <= 0) { return { ok: false, error: `${path} must be a positive integer or null` }; } return { ok: true, value: Number(value) }; } /** Expect optional non-empty string. */ function expectOptionalNonEmptyString(value, path) { if (value === undefined) return { ok: true, value: undefined }; const parsed = expectNonEmptyString(value, path); if (!parsed.ok) return parsed; return { ok: true, value: parsed.value }; } /** Expect optional non-negative integer. */ function expectOptionalNonNegativeInteger(value, path) { if (value === undefined) return { ok: true, value: undefined }; if (!Number.isInteger(value) || Number(value) < 0) { return { ok: false, error: `${path} must be a non-negative integer` }; } return { ok: true, value: Number(value) }; } /** Expect axis score. */ function expectAxisScore(value, path) { if (!Number.isInteger(value) || !QUALITY_SCORE_VALUES.includes(Number(value))) { return { ok: false, error: `${path} must be one of: ${QUALITY_SCORE_VALUES.join(", ")}`, }; } return { ok: true, value: Number(value) }; } /** Expect score total. */ function expectScoreTotal(value, path) { if (!Number.isInteger(value) || Number(value) < 0 || Number(value) > 100) { return { ok: false, error: `${path} must be an integer between 0 and 100` }; } return { ok: true, value: Number(value) }; } /** Parse the setup scores. */ function parseSetupScores(raw, path) { if (!isRecord(raw)) return { ok: false, error: `${path} must be an object` }; const unknownKeyError = rejectUnknownKeys(raw, ["total", "accuracy", "relevance", "completeness", "friction"], path); if (unknownKeyError) return { ok: false, error: unknownKeyError }; const total = expectScoreTotal(raw.total, `${path}.total`); if (!total.ok) return total; const accuracy = expectAxisScore(raw.accuracy, `${path}.accuracy`); if (!accuracy.ok) return accuracy; const relevance = expectAxisScore(raw.relevance, `${path}.relevance`); if (!relevance.ok) return relevance; const completeness = expectAxisScore(raw.completeness, `${path}.completeness`); if (!completeness.ok) return completeness; const friction = expectAxisScore(raw.friction, `${path}.friction`); if (!friction.ok) return friction; const sum = accuracy.value + relevance.value + completeness.value + friction.value; if (sum !== total.value) { return { ok: false, error: `${path} axis scores must sum exactly to total`, }; } return { ok: true, scores: { total: total.value, accuracy: accuracy.value, relevance: relevance.value, completeness: completeness.value, friction: friction.value, }, }; } /** Parse the system scores. */ function parseSystemScores(raw, path) { if (!isRecord(raw)) return { ok: false, error: `${path} must be an object` }; const unknownKeyError = rejectUnknownKeys(raw, ["total", "usefulness", "signal_to_noise", "adaptability", "learnability"], path); if (unknownKeyError) return { ok: false, error: unknownKeyError }; const total = expectScoreTotal(raw.total, `${path}.total`); if (!total.ok) return total; const usefulness = expectAxisScore(raw.usefulness, `${path}.usefulness`); if (!usefulness.ok) return usefulness; const signalToNoise = expectAxisScore(raw.signal_to_noise, `${path}.signal_to_noise`); if (!signalToNoise.ok) return signalToNoise; const adaptability = expectAxisScore(raw.adaptability, `${path}.adaptability`); if (!adaptability.ok) return adaptability; const learnability = expectAxisScore(raw.learnability, `${path}.learnability`); if (!learnability.ok) return learnability; const sum = usefulness.value + signalToNoise.value + adaptability.value + learnability.value; if (sum !== total.value) { return { ok: false, error: `${path} axis scores must sum exactly to total`, }; } return { ok: true, scores: { total: total.value, usefulness: usefulness.value, signal_to_noise: signalToNoise.value, adaptability: adaptability.value, learnability: learnability.value, }, }; } /** Parse the scores. */ function parseScores(raw, path) { if (!isRecord(raw)) return { ok: false, error: `${path} must be an object` }; const unknownKeyError = rejectUnknownKeys(raw, ["setup", "system"], path); if (unknownKeyError) return { ok: false, error: unknownKeyError }; const setup = parseSetupScores(raw.setup, `${path}.setup`); if (!setup.ok) return setup; const system = parseSystemScores(raw.system, `${path}.system`); if (!system.ok) return system; return { ok: true, scores: { setup: setup.scores, system: system.scores, }, }; } /** Parse one quality finding payload. */ // eslint-disable-next-line complexity -- intentional because finding validation stays explicit so every rejected field gets a precise path-specific error. function parseFinding(raw, index, options) { const path = `findings[${index}]`; if (!isRecord(raw)) return { ok: false, error: `${path} must be an object` }; const allowedKeys = [ "type", "severity", "file", "line", "summary", "detail", "evidence_quality", "evidence_method", "evidence_command", "evidence_exit_code", "evidence_summary", "evidence_warning_count", "evidence_excerpt", "delta_tag", ]; const unknownKeyError = rejectUnknownKeys(raw, allowedKeys, path); if (unknownKeyError) return { ok: false, error: unknownKeyError }; if (Object.hasOwn(raw, "id")) { return { ok: false, error: `${path}.id is not allowed in agent-emitted reports`, }; } const type = expectEnumValue(raw.type, `${path}.type`, QUALITY_FINDING_TYPES); if (!type.ok) return type; const severity = expectEnumValue(raw.severity, `${path}.severity`, QUALITY_FINDING_SEVERITIES); if (!severity.ok) return severity; const file = expectNullableString(raw.file ?? null, `${path}.file`); if (!file.ok) return file; const line = expectNullablePositiveInteger(raw.line ?? null, `${path}.line`); if (!line.ok) return line; const summary = expectNonEmptyString(raw.summary, `${path}.summary`); if (!summary.ok) return summary; if (summary.value.length > 200) { return { ok: false, error: `${path}.summary must be 200 characters or fewer`, }; } const detail = expectNonEmptyString(raw.detail, `${path}.detail`); if (!detail.ok) return detail; const evidenceQuality = expectEnumValue(raw.evidence_quality, `${path}.evidence_quality`, QUALITY_EVIDENCE_QUALITIES); if (!evidenceQuality.ok) return evidenceQuality; let evidenceMethod = "static-analysis"; if (options.requireCurrentFields === true && !Object.hasOwn(raw, "evidence_method")) { return { ok: false, error: `${path}.evidence_method is required for current quality reports`, }; } // evidence_method: optional on legacy reports, defaulted to "static-analysis". // Required on current emissions by quality validate. if (Object.hasOwn(raw, "evidence_method")) { const parsedMethod = expectEnumValue(raw.evidence_method, `${path}.evidence_method`, QUALITY_EVIDENCE_METHODS); if (!parsedMethod.ok) return parsedMethod; evidenceMethod = parsedMethod.value; } const evidenceCommand = expectOptionalNonEmptyString(raw.evidence_command, `${path}.evidence_command`); if (!evidenceCommand.ok) return evidenceCommand; const evidenceExitCode = expectOptionalNonNegativeInteger(raw.evidence_exit_code, `${path}.evidence_exit_code`); if (!evidenceExitCode.ok) return evidenceExitCode; const evidenceSummary = expectOptionalNonEmptyString(raw.evidence_summary, `${path}.evidence_summary`); if (!evidenceSummary.ok) return evidenceSummary; const evidenceWarningCount = expectOptionalNonNegativeInteger(raw.evidence_warning_count, `${path}.evidence_warning_count`); if (!evidenceWarningCount.ok) return evidenceWarningCount; const evidenceExcerpt = expectOptionalNonEmptyString(raw.evidence_excerpt, `${path}.evidence_excerpt`); if (!evidenceExcerpt.ok) return evidenceExcerpt; const deltaTagRaw = Object.hasOwn(raw, "delta_tag") ? raw.delta_tag : null; let deltaTag = null; if (deltaTagRaw !== null) { const parsedDeltaTag = expectEnumValue(deltaTagRaw, `${path}.delta_tag`, QUALITY_DELTA_TAGS); if (!parsedDeltaTag.ok) return parsedDeltaTag; deltaTag = parsedDeltaTag.value; } const findingBase = { type: type.value, severity: severity.value, file: file.value, line: line.value, summary: summary.value, detail: detail.value, evidence_quality: evidenceQuality.value, evidence_method: evidenceMethod, ...(evidenceCommand.value !== undefined ? { evidence_command: evidenceCommand.value } : {}), ...(evidenceExitCode.value !== undefined ? { evidence_exit_code: evidenceExitCode.value } : {}), ...(evidenceSummary.value !== undefined ? { evidence_summary: evidenceSummary.value } : {}), ...(evidenceWarningCount.value !== undefined ? { evidence_warning_count: evidenceWarningCount.value } : {}), ...(evidenceExcerpt.value !== undefined ? { evidence_excerpt: evidenceExcerpt.value } : {}), delta_tag: deltaTag, }; return { ok: true, finding: findingBase }; } /** Parse a current agent-emitted quality report. */ // eslint-disable-next-line complexity -- intentional because report validation stays fully expanded so schema errors name the exact failing field. function parseReportInternal(raw, options = {}) { if (!isRecord(raw)) { return { ok: false, error: "quality report must be an object" }; } const unknownKeyError = rejectUnknownKeys(raw, [ "report_kind", "goat_flow_version", "agent", "project_path", "run_date", "audit_status", "scope", "rubric_version", "quality_mode", "prior_report_id", "scores", "findings", ], "report"); if (unknownKeyError) return { ok: false, error: unknownKeyError }; if (raw.report_kind !== QUALITY_REPORT_KIND) { return { ok: false, error: `report.report_kind must equal "${QUALITY_REPORT_KIND}"`, }; } const version = expectNonEmptyString(raw.goat_flow_version, "report.goat_flow_version"); if (!version.ok) return version; const agent = expectEnumValue(raw.agent, "report.agent", KNOWN_AGENT_IDS); if (!agent.ok) return agent; const projectPath = expectNonEmptyString(raw.project_path, "report.project_path"); if (!projectPath.ok) return projectPath; if (!isAbsolute(projectPath.value)) { return { ok: false, error: "report.project_path must be an absolute path", }; } const runDate = expectNonEmptyString(raw.run_date, "report.run_date"); if (!runDate.ok) return runDate; if (!/^\d{4}-\d{2}-\d{2}$/.test(runDate.value)) { return { ok: false, error: "report.run_date must be YYYY-MM-DD" }; } const auditStatus = expectEnumValue(raw.audit_status, "report.audit_status", QUALITY_AUDIT_STATUSES); if (!auditStatus.ok) return auditStatus; let scope; if (options.requireCurrentFields === true && !Object.hasOwn(raw, "scope")) { return { ok: false, error: "report.scope is required for current quality reports", }; } // scope: optional on legacy reports, enum-validated when present. if (Object.hasOwn(raw, "scope")) { const parsedScope = expectEnumValue(raw.scope, "report.scope", QUALITY_SCOPES); if (!parsedScope.ok) return parsedScope; scope = parsedScope.value; } let rubricVersion; if (options.requireCurrentFields === true && !Object.hasOwn(raw, "rubric_version")) { return { ok: false, error: "report.rubric_version is required for current quality reports", }; } // rubric_version: optional on legacy reports, non-empty string when present. if (Object.hasOwn(raw, "rubric_version")) { const parsedRubric = expectNonEmptyString(raw.rubric_version, "report.rubric_version"); if (!parsedRubric.ok) return parsedRubric; rubricVersion = parsedRubric.value; } let qualityMode; if (options.requireCurrentFields === true && !Object.hasOwn(raw, "quality_mode")) { return { ok: false, error: "report.quality_mode is required for current quality reports", }; } // quality_mode: optional on legacy reports, enum-validated when present. if (Object.hasOwn(raw, "quality_mode")) { const parsedQualityMode = expectEnumValue(raw.quality_mode, "report.quality_mode", QUALITY_MODES); if (!parsedQualityMode.ok) return parsedQualityMode; qualityMode = parsedQualityMode.value; } let priorReportId; if (Object.hasOwn(raw, "prior_report_id")) { const parsedPriorReportId = expectNullableString(raw.prior_report_id, "report.prior_report_id"); if (!parsedPriorReportId.ok) return parsedPriorReportId; priorReportId = parsedPriorReportId.value; } const scores = parseScores(raw.scores, "report.scores"); if (!scores.ok) return scores; if (!Array.isArray(raw.findings)) { return { ok: false, error: "report.findings must be an array" }; } const findings = []; for (const [index, item] of raw.findings.entries()) { const parsedFinding = parseFinding(item, index, options); if (!parsedFinding.ok) return parsedFinding; findings.push(parsedFinding.finding); } if (options.requireCurrentFields && typeof priorReportId === "string") { const nullDeltaIndex = findings.findIndex((f) => f.delta_tag === null); if (nullDeltaIndex !== -1) { return { ok: false, error: `findings[${nullDeltaIndex}].delta_tag must be "new" or "persisted" when prior_report_id is set`, }; } } const reportBase = { report_kind: QUALITY_REPORT_KIND, goat_flow_version: version.value, agent: agent.value, project_path: projectPath.value, run_date: runDate.value, audit_status: auditStatus.value, ...(scope !== undefined ? { scope } : {}), ...(rubricVersion !== undefined ? { rubric_version: rubricVersion } : {}), ...(qualityMode !== undefined ? { quality_mode: qualityMode } : {}), ...(priorReportId !== undefined ? { prior_report_id: priorReportId } : {}), scores: scores.scores, }; return { ok: true, report: { ...reportBase, findings, }, }; } /** * Parse a current agent-emitted quality report. * * @param raw - Unknown JSON value to validate. * @param options - Optional strictness override for legacy compatibility. * @returns Parsed quality report or a path-specific schema error. */ export function parseQualityReport(raw, options = { requireCurrentFields: true }) { const result = parseReportInternal(raw, options); if (!result.ok) return result; return { ok: true, report: result.report }; } //# sourceMappingURL=schema-parser.js.map