/** * Tests for parseSummary (fix for #1360) * * Validates that false-positive summary matches (no sub-tags) are rejected * while real summaries — even with some missing fields — are still saved. */ import { describe, it, expect } from 'bun:test'; import { parseSummary } from '../../src/sdk/parser.js'; describe('parseSummary', () => { it('returns null when no tag present and coercion disabled', () => { expect(parseSummary('foo')).toBeNull(); }); it('returns null when no or tags present', () => { expect(parseSummary('Some plain text response without any XML tags')).toBeNull(); }); it('returns null when has no sub-tags (false positive — fix for #1360)', () => { // This is the bug: observation response accidentally contains some text expect(parseSummary('done some content here')).toBeNull(); }); it('returns null for bare with only plain text, no sub-tags', () => { expect(parseSummary('This session was productive.')).toBeNull(); }); it('returns summary when at least one sub-tag is present (respects maintainer note)', () => { const text = `Fix the bug`; const result = parseSummary(text); expect(result).not.toBeNull(); expect(result?.request).toBe('Fix the bug'); expect(result?.investigated).toBeNull(); expect(result?.learned).toBeNull(); }); it('returns full summary when all fields are present', () => { const text = ` Fix login bug Auth flow and JWT expiry Token was expiring too soon Extended token TTL to 24h Monitor error rates `; const result = parseSummary(text); expect(result).not.toBeNull(); expect(result?.request).toBe('Fix login bug'); expect(result?.investigated).toBe('Auth flow and JWT expiry'); expect(result?.learned).toBe('Token was expiring too soon'); expect(result?.completed).toBe('Extended token TTL to 24h'); expect(result?.next_steps).toBe('Monitor error rates'); }); it('returns null when skip_summary tag is present', () => { expect(parseSummary('')).toBeNull(); }); // Observation-to-summary coercion tests (#1633) it('coerces with content into a summary when coerceFromObservation=true (#1633)', () => { const result = parseSummary('foo', undefined, true); expect(result).not.toBeNull(); expect(result?.request).toBe('foo'); expect(result?.completed).toBe('foo'); }); it('coerces observation with narrative into summary with investigated field (#1633)', () => { const text = ` refactor UObjectArray refactored Removed local XXXX and migrated to new pattern `; const result = parseSummary(text, undefined, true); expect(result).not.toBeNull(); expect(result?.request).toBe('UObjectArray refactored'); expect(result?.investigated).toBe('Removed local XXXX and migrated to new pattern'); }); it('coerces observation with facts into summary with learned field (#1633)', () => { const text = ` discovery JWT token handling Tokens expire after 1 hour Refresh flow uses rotating keys `; const result = parseSummary(text, undefined, true); expect(result).not.toBeNull(); expect(result?.request).toBe('JWT token handling'); expect(result?.learned).toBe('Tokens expire after 1 hour; Refresh flow uses rotating keys'); }); it('coerces observation with subtitle into completed field (#1633)', () => { const text = ` config Database migration Added new index for performance `; const result = parseSummary(text, undefined, true); expect(result).not.toBeNull(); expect(result?.completed).toBe('Database migration — Added new index for performance'); }); it('returns null for empty observation even with coercion enabled (#1633)', () => { const text = `config`; expect(parseSummary(text, undefined, true)).toBeNull(); }); it('prefers tags over observation coercion when both present (#1633)', () => { const text = `obs title summary request`; const result = parseSummary(text, undefined, true); expect(result).not.toBeNull(); expect(result?.request).toBe('summary request'); }); it('falls back to observation coercion when matches but has empty sub-tags (#1633)', () => { // LLM wraps an empty summary around real observation content — without the // fallback, the empty-subtag guard (#1360) rejects the summary and we lose // the observation content, resurrecting the retry loop. const text = ` the real work what actually happened `; const result = parseSummary(text, undefined, true); expect(result).not.toBeNull(); expect(result?.request).toBe('the real work'); expect(result?.investigated).toBe('what actually happened'); }); it('empty with no observation content still returns null (coercion disabled)', () => { const text = ''; expect(parseSummary(text, undefined, true)).toBeNull(); }); it('skips empty leading observation blocks and coerces from the first populated one (#1633)', () => { const text = `discovery bugfix second block has content fixed the crash `; const result = parseSummary(text, undefined, true); expect(result).not.toBeNull(); expect(result?.request).toBe('second block has content'); expect(result?.investigated).toBe('fixed the crash'); }); });