profileShare

rasmusjy / roundtable

Read-only snapshot

No repository description.

main default branch 181 files Expires Sep 13, 2026, 9:06 AM

Commit

test markdown export

commit 67e91a2

1 changed file with +99 and −0

added src/lib/export-markdown.test.ts +99 −0
@@ -0,0 +1,99 @@
1 +import { describe, expect, it } from 'vitest';
2 +import { emptyUsage, type DebateResult } from '@/core/types';
3 +import { debateToMarkdown } from './export-markdown';
4 +
5 +function makeResult(): DebateResult {
6 + const config = {
7 + question: 'Monolith or microservices?',
8 + models: ['openai/gpt-4o', 'anthropic/claude-3.5-sonnet'],
9 + chairmanModel: 'x-ai/grok-2',
10 + convergenceModel: 'google/gemini-2.0-flash-001',
11 + maxRounds: 2,
12 + convergenceThreshold: 85,
13 + temperature: 0.7,
14 + perModelTimeoutMs: 90_000,
15 + };
16 + const participants = [
17 + { id: 'p0', model: 'openai/gpt-4o', displayName: 'GPT 4o' },
18 + { id: 'p1', model: 'anthropic/claude-3.5-sonnet', displayName: 'Claude 3.5 Sonnet' },
19 + ];
20 + return {
21 + debateId: 'd1',
22 + config,
23 + participants,
24 + status: 'completed',
25 + initialAnswers: [
26 + { participantId: 'p0', model: 'openai/gpt-4o', round: 0, content: 'Monolith.', usage: emptyUsage(), latencyMs: 0 },
27 + { participantId: 'p1', model: 'anthropic/claude-3.5-sonnet', round: 0, content: 'Depends.', usage: emptyUsage(), latencyMs: 0 },
28 + ],
29 + rounds: [
30 + {
31 + round: 1,
32 + critiques: [
33 + {
34 + round: 1,
35 + reviewerParticipantId: 'p0',
36 + reviewerModel: 'openai/gpt-4o',
37 + reviews: [{ label: 'A', targetParticipantId: 'p1', weaknesses: ['vague'], strengths: [], score: 6, justification: 'meh' }],
38 + usage: emptyUsage(),
39 + latencyMs: 0,
40 + },
41 + ],
42 + revisions: [
43 + {
44 + round: 1,
45 + participantId: 'p1',
46 + model: 'anthropic/claude-3.5-sonnet',
47 + content: 'Monolith, with caveats.',
48 + changelog: { changed: true, summary: 'narrowed', bullets: ['a'] },
49 + usage: emptyUsage(),
50 + latencyMs: 0,
51 + },
52 + ],
53 + convergence: { round: 1, model: 'google/gemini-2.0-flash-001', score: 88, converged: true, disagreements: [], usage: emptyUsage(), latencyMs: 0 },
54 + },
55 + ],
56 + synthesis: {
57 + model: 'x-ai/grok-2',
58 + finalAnswer: 'Start with a modular monolith.',
59 + dissent: [{ topic: 'edge case', positions: [{ participantId: 'p0', model: 'openai/gpt-4o', position: 'no split' }] }],
60 + usage: emptyUsage(),
61 + latencyMs: 0,
62 + },
63 + failures: [],
64 + finalAnswers: [],
65 + totals: { costUsd: 0.02, promptTokens: 100, completionTokens: 200, rounds: 1, durationMs: 5000, costByModel: {} },
66 + };
67 +}
68 +
69 +describe('debateToMarkdown', () => {
70 + const md = debateToMarkdown(makeResult());
71 +
72 + it('includes the question and council', () => {
73 + expect(md).toContain('# Roundtable Deliberation Report');
74 + expect(md).toContain('Monolith or microservices?');
75 + expect(md).toContain('GPT 4o');
76 + expect(md).toContain('`x-ai/grok-2`');
77 + });
78 +
79 + it('includes the final answer and dissent', () => {
80 + expect(md).toContain('## Final Answer');
81 + expect(md).toContain('Start with a modular monolith.');
82 + expect(md).toContain('Dissent Report');
83 + expect(md).toContain('no split');
84 + });
85 +
86 + it('renders the round with a critique score table and revision', () => {
87 + expect(md).toContain('## Round 1');
88 + expect(md).toContain('Critique scores');
89 + expect(md).toContain('| GPT 4o |'); // reviewer row
90 + expect(md).toContain('Monolith, with caveats.');
91 + expect(md).toContain('Convergence score: 88/100');
92 + });
93 +
94 + it('renders round 0 answers', () => {
95 + expect(md).toContain('## Round 0');
96 + expect(md).toContain('Monolith.');
97 + expect(md).toContain('Depends.');
98 + });
99 +});