/** * v0.36.1.0 (T16) — recall morning pulse calibration footer tests. * * Pure formatter tests. No engine, no LLM. * * Tests cover: * - cold-brain branch: null profile → empty string * - insufficient resolved (< 5) → empty string * - happy path: section header + Brier + patterns * - abandoned threads section: optional, formatted, capped at 5 * - trend note maps Brier ranges to conversational copy * - patterns capped at 4 * - column alignment on abandoned threads */ import { describe, test, expect } from 'bun:test'; import { buildRecallCalibrationFooter } from '../src/core/calibration/recall-footer.ts'; import type { CalibrationProfileRow } from '../src/commands/calibration.ts'; function buildProfile(opts: Partial = {}): CalibrationProfileRow { return { id: 1, source_id: 'default', holder: 'garry', wave_version: 'v0.36.1.0', generated_at: '2026-05-17T00:00:00Z', published: false, total_resolved: 12, brier: 0.18, accuracy: 0.6, partial_rate: 0.1, grade_completion: 1.0, pattern_statements: ['Right on early-stage tactics, late on macro by 18 months.'], active_bias_tags: ['over-confident-geography'], voice_gate_passed: true, voice_gate_attempts: 1, model_id: 'claude-sonnet-4-6', ...opts, }; } describe('buildRecallCalibrationFooter — cold-brain branch', () => { test('null profile → empty string', () => { expect(buildRecallCalibrationFooter({ profile: null })).toBe(''); }); test('insufficient resolved (< 5) → empty string', () => { expect(buildRecallCalibrationFooter({ profile: buildProfile({ total_resolved: 3 }) })).toBe( '', ); }); test('zero resolved → empty string', () => { expect(buildRecallCalibrationFooter({ profile: buildProfile({ total_resolved: 0 }) })).toBe( '', ); }); }); describe('buildRecallCalibrationFooter — happy path', () => { test('emits header + Brier + pattern block', () => { const out = buildRecallCalibrationFooter({ profile: buildProfile() }); expect(out).toContain('Calibration this quarter:'); expect(out).toContain('Brier 0.18'); expect(out).toContain('Right on early-stage tactics'); }); test('Brier line includes trend note ("solid")', () => { expect(buildRecallCalibrationFooter({ profile: buildProfile({ brier: 0.18 }) })).toContain( '(solid)', ); }); test('Brier near-baseline range', () => { expect( buildRecallCalibrationFooter({ profile: buildProfile({ brier: 0.24 }) }), ).toContain('(near baseline)'); }); test('Brier worse than baseline → review hint', () => { expect(buildRecallCalibrationFooter({ profile: buildProfile({ brier: 0.32 }) })).toContain( 'worse than always-50%', ); }); test('Brier strong calibration', () => { expect( buildRecallCalibrationFooter({ profile: buildProfile({ brier: 0.08 }) }), ).toContain('(strong calibration)'); }); test('omits Brier line when brier=null (resolved correct+incorrect = 0)', () => { const out = buildRecallCalibrationFooter({ profile: buildProfile({ brier: null }) }); expect(out).not.toContain('Brier null'); expect(out).toContain('Calibration this quarter:'); // header still emitted }); test('caps at 4 pattern statements', () => { const out = buildRecallCalibrationFooter({ profile: buildProfile({ pattern_statements: ['a', 'b', 'c', 'd', 'e', 'f'] }), }); const aIdx = out.indexOf(' a\n'); expect(out).toContain(' a'); expect(out).toContain(' d'); // 'e' and 'f' should NOT appear as standalone bullets. const lines = out.split('\n'); const bullets = lines.filter(l => /^ {2}[abcdef]$/.test(l)); expect(bullets.length).toBe(4); void aIdx; }); }); describe('buildRecallCalibrationFooter — abandoned threads', () => { test('omits the abandoned-threads section when no threads passed', () => { const out = buildRecallCalibrationFooter({ profile: buildProfile() }); expect(out).not.toContain('Threads you opened'); }); test('emits section + rows when threads provided', () => { const out = buildRecallCalibrationFooter({ profile: buildProfile(), abandonedThreads: [ { claim: 'AI search platform differentiation', monthsSilent: 17 }, { claim: 'International expansion playbook', monthsSilent: 12 }, ], }); expect(out).toContain('Threads you opened and never came back to:'); expect(out).toContain('AI search platform differentiation'); expect(out).toContain('17 months silent'); expect(out).toContain('International expansion playbook'); expect(out).toContain('12 months silent'); }); test('caps at 5 abandoned threads', () => { const threads = Array.from({ length: 8 }, (_, i) => ({ claim: `thread ${i}`, monthsSilent: 12 + i, })); const out = buildRecallCalibrationFooter({ profile: buildProfile(), abandonedThreads: threads }); expect(out).toContain('thread 0'); expect(out).toContain('thread 4'); expect(out).not.toContain('thread 5'); }); test('truncates long claim text with ellipsis', () => { const longClaim = 'x'.repeat(100); const out = buildRecallCalibrationFooter({ profile: buildProfile(), abandonedThreads: [{ claim: longClaim, monthsSilent: 12 }], threadColumnWidth: 30, }); expect(out).toContain('x'.repeat(29) + '…'); }); });