diff --git a/src/agents/athena/findings-presenter.test.ts b/src/agents/athena/findings-presenter.test.ts new file mode 100644 index 00000000..3ac6a371 --- /dev/null +++ b/src/agents/athena/findings-presenter.test.ts @@ -0,0 +1,131 @@ +import { describe, expect, test } from "bun:test" +import type { SynthesisResult } from "./synthesis-types" +import { formatFindingsForUser } from "./findings-presenter" + +function createSynthesisResult(overrides?: Partial): SynthesisResult { + return { + question: "Review the Athena council outputs for actionable risks", + findings: [ + { + summary: "Validate configuration before execution", + details: "Missing guard clauses can allow invalid member configs.", + agreementLevel: "majority", + reportedBy: ["OpenAI", "Claude"], + assessment: { + agrees: true, + rationale: "This aligns with repeated failures observed in setup paths.", + }, + isFalsePositiveRisk: false, + }, + { + summary: "Retry strategy lacks upper bounds", + details: "Unbounded retries may cause runaway background tasks.", + agreementLevel: "solo", + reportedBy: ["Gemini"], + assessment: { + agrees: false, + rationale: "Current retry count is already constrained in most flows.", + }, + isFalsePositiveRisk: true, + }, + { + summary: "Preserve partial successes", + details: "Do not fail entire council run when one member errors.", + agreementLevel: "unanimous", + reportedBy: ["OpenAI", "Claude", "Gemini"], + assessment: { + agrees: true, + rationale: "This is required for resilient multi-model orchestration.", + }, + isFalsePositiveRisk: false, + }, + { + summary: "Reduce prompt token duplication", + details: "Duplicate context blocks increase cost without improving quality.", + agreementLevel: "minority", + reportedBy: ["Claude"], + assessment: { + agrees: true, + rationale: "Consolidation should lower cost while preserving intent.", + }, + isFalsePositiveRisk: false, + }, + ], + memberProvenance: [], + totalFindings: 4, + consensusCount: 2, + outlierCount: 1, + ...overrides, + } +} + +describe("formatFindingsForUser", () => { + //#given findings across all agreement levels + //#when formatFindingsForUser is called + //#then groups appear in deterministic order: unanimous, majority, minority, solo + test("groups findings by agreement level in required order", () => { + const result = createSynthesisResult() + + const output = formatFindingsForUser(result) + + const unanimousIndex = output.indexOf("## Unanimous Findings") + const majorityIndex = output.indexOf("## Majority Findings") + const minorityIndex = output.indexOf("## Minority Findings") + const soloIndex = output.indexOf("## Solo Findings") + + expect(unanimousIndex).toBeGreaterThan(-1) + expect(majorityIndex).toBeGreaterThan(unanimousIndex) + expect(minorityIndex).toBeGreaterThan(majorityIndex) + expect(soloIndex).toBeGreaterThan(minorityIndex) + }) + + //#given a finding with assessment details + //#when formatting is generated + //#then each finding includes summary, details, reported-by, and Athena rationale + test("renders finding body and Athena assessment rationale", () => { + const result = createSynthesisResult() + + const output = formatFindingsForUser(result) + + expect(output).toContain("Validate configuration before execution") + expect(output).toContain("Missing guard clauses can allow invalid member configs.") + expect(output).toContain("Reported by: OpenAI, Claude") + expect(output).toContain("Athena assessment: Agrees") + expect(output).toContain("Rationale: This aligns with repeated failures observed in setup paths.") + }) + + //#given a solo finding flagged as false-positive risk + //#when formatting is generated + //#then a visible warning marker is included + test("shows false-positive warning for risky solo findings", () => { + const result = createSynthesisResult() + + const output = formatFindingsForUser(result) + + expect(output).toContain("[False Positive Risk]") + expect(output).toContain("Retry strategy lacks upper bounds") + }) + + //#given no findings + //#when formatFindingsForUser is called + //#then output includes a graceful no-findings message + test("handles empty findings with a no-findings message", () => { + const result = createSynthesisResult({ findings: [], totalFindings: 0, consensusCount: 0, outlierCount: 0 }) + + const output = formatFindingsForUser(result) + + expect(output).toContain("No synthesized findings are available") + }) + + //#given a non-empty findings result + //#when formatting is generated + //#then output ends with an action recommendation section + test("includes a final action recommendation section", () => { + const result = createSynthesisResult() + + const output = formatFindingsForUser(result) + + expect(output.trimEnd()).toMatch(/## Action Recommendation[\s\S]*$/) + expect(output).toContain("Prioritize unanimous and majority findings") + }) +}) diff --git a/src/agents/athena/findings-presenter.ts b/src/agents/athena/findings-presenter.ts new file mode 100644 index 00000000..7ebf7e00 --- /dev/null +++ b/src/agents/athena/findings-presenter.ts @@ -0,0 +1,82 @@ +import type { SynthesisResult, SynthesizedFinding } from "./synthesis-types" +import type { AgreementLevel } from "./types" + +const AGREEMENT_ORDER: AgreementLevel[] = ["unanimous", "majority", "minority", "solo"] + +function toTitle(level: AgreementLevel): string { + return `${level.charAt(0).toUpperCase()}${level.slice(1)}` +} + +function formatAgreementLine(level: AgreementLevel, finding: SynthesizedFinding): string { + const memberCount = finding.reportedBy.length + + switch (level) { + case "unanimous": + return `${memberCount}/${memberCount} members agree` + case "majority": + return `${memberCount} members report this (majority)` + case "minority": + return `${memberCount} members report this (minority)` + case "solo": + return `${memberCount} member reported this` + } +} + +function formatFinding(level: AgreementLevel, finding: SynthesizedFinding): string { + const assessment = finding.assessment.agrees ? "Agrees" : "Disagrees" + const warning = level === "solo" && finding.isFalsePositiveRisk ? " [False Positive Risk]" : "" + + return [ + `### ${finding.summary}${warning}`, + `Details: ${finding.details}`, + `Reported by: ${finding.reportedBy.join(", ")}`, + `Agreement context: ${formatAgreementLine(level, finding)}`, + `Athena assessment: ${assessment}`, + `Rationale: ${finding.assessment.rationale}`, + ].join("\n") +} + +function formatActionRecommendation(result: SynthesisResult, groupedFindings: Map): string { + const counts = AGREEMENT_ORDER.map((level) => `${toTitle(level)}: ${groupedFindings.get(level)?.length ?? 0}`).join(" | ") + + return [ + "## Action Recommendation", + `Findings by agreement level: ${counts}`, + "Prioritize unanimous and majority findings for immediate execution,", + "then review minority findings, and manually validate solo findings before delegating changes.", + `Question context: ${result.question}`, + ].join("\n") +} + +export function formatFindingsForUser(result: SynthesisResult): string { + if (result.findings.length === 0) { + return [ + "# Synthesized Findings", + "No synthesized findings are available.", + "## Action Recommendation", + "Gather additional council responses or re-run synthesis before delegation.", + `Question context: ${result.question}`, + ].join("\n\n") + } + + const groupedFindings = new Map( + AGREEMENT_ORDER.map((level) => [ + level, + result.findings.filter((finding) => finding.agreementLevel === level), + ]), + ) + + const sections = AGREEMENT_ORDER.flatMap((level) => { + const findings = groupedFindings.get(level) ?? [] + if (findings.length === 0) { + return [] + } + + const firstFinding = findings[0] + const header = `## ${toTitle(level)} Findings (${formatAgreementLine(level, firstFinding)})` + const entries = findings.map((finding) => formatFinding(level, finding)).join("\n\n") + return [`${header}\n\n${entries}`] + }) + + return ["# Synthesized Findings", ...sections, formatActionRecommendation(result, groupedFindings)].join("\n\n") +}