feat(05-01): add synthesized findings presenter
- Format synthesis findings by agreement level for user-facing output - Add BDD tests for ordering, warning flags, empty state, and recommendations
This commit is contained in:
parent
b1f43e8113
commit
665499a40d
131
src/agents/athena/findings-presenter.test.ts
Normal file
131
src/agents/athena/findings-presenter.test.ts
Normal file
@ -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>): 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")
|
||||
})
|
||||
})
|
||||
82
src/agents/athena/findings-presenter.ts
Normal file
82
src/agents/athena/findings-presenter.ts
Normal file
@ -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<AgreementLevel, SynthesizedFinding[]>): 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<AgreementLevel, SynthesizedFinding[]>(
|
||||
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")
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user