diff --git a/core/journal/src/main/kotlin/com/correx/core/journal/DecisionJournalRenderer.kt b/core/journal/src/main/kotlin/com/correx/core/journal/DecisionJournalRenderer.kt index d1423d65..3d100186 100644 --- a/core/journal/src/main/kotlin/com/correx/core/journal/DecisionJournalRenderer.kt +++ b/core/journal/src/main/kotlin/com/correx/core/journal/DecisionJournalRenderer.kt @@ -3,12 +3,17 @@ package com.correx.core.journal import com.correx.core.journal.model.DecisionJournalState class DecisionJournalRenderer(private val keepLast: Int = 40) { - fun render(state: DecisionJournalState): String { - if (state.records.isEmpty()) return "" - val shown = state.records.takeLast(keepLast) + fun render(state: DecisionJournalState, summaryText: String? = null): String { + if (state.records.isEmpty() && summaryText == null) return "" return buildString { appendLine("## Decision history (chronological — ground truth)") - shown.forEach { appendLine("- [${it.kind}] ${it.summary}") } + if (summaryText != null) { + appendLine(summaryText) + if (state.lowSalienceOmittedCount > 0) { + appendLine("+${state.lowSalienceOmittedCount} routine transitions/retries omitted") + } + } + state.records.takeLast(keepLast).forEach { appendLine("- [${it.kind}] ${it.summary}") } }.trimEnd() } } diff --git a/core/journal/src/test/kotlin/com/correx/core/journal/DecisionJournalRendererTest.kt b/core/journal/src/test/kotlin/com/correx/core/journal/DecisionJournalRendererTest.kt new file mode 100644 index 00000000..41a9c51d --- /dev/null +++ b/core/journal/src/test/kotlin/com/correx/core/journal/DecisionJournalRendererTest.kt @@ -0,0 +1,80 @@ +package com.correx.core.journal + +import com.correx.core.journal.model.DecisionJournalState +import com.correx.core.journal.model.DecisionKind +import com.correx.core.journal.model.DecisionRecord +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test + +class DecisionJournalRendererTest { + + private val renderer = DecisionJournalRenderer() + + private fun record(seq: Long, kind: DecisionKind = DecisionKind.INTENT, summary: String = "summary-$seq") = + DecisionRecord(sequence = seq, kind = kind, summary = summary) + + @Test + fun `empty state with no summaryText returns empty string`() { + val state = DecisionJournalState() + assertEquals("", renderer.render(state)) + } + + @Test + fun `uncompacted state renders records without omitted line`() { + val state = DecisionJournalState( + records = listOf(record(1), record(2)), + ) + val result = renderer.render(state) + assertTrue(result.contains("- [INTENT] summary-1")) + assertTrue(result.contains("- [INTENT] summary-2")) + assertFalse(result.contains("omitted")) + } + + @Test + fun `summaryText appears before records`() { + val state = DecisionJournalState( + records = listOf(record(1)), + lowSalienceOmittedCount = 3, + ) + val result = renderer.render(state, summaryText = "Compaction summary here") + val lines = result.lines() + val headerIdx = lines.indexOfFirst { it.startsWith("## Decision history") } + val summaryIdx = lines.indexOfFirst { it == "Compaction summary here" } + val recordIdx = lines.indexOfFirst { it.startsWith("- [INTENT]") } + assertTrue(headerIdx >= 0) + assertTrue(summaryIdx > headerIdx) + assertTrue(recordIdx > summaryIdx) + } + + @Test + fun `count line appears when lowSalienceOmittedCount is positive`() { + val state = DecisionJournalState( + records = listOf(record(1)), + lowSalienceOmittedCount = 5, + ) + val result = renderer.render(state, summaryText = "Some summary") + assertTrue(result.contains("+5 routine transitions/retries omitted")) + } + + @Test + fun `no count line when lowSalienceOmittedCount is zero with summaryText`() { + val state = DecisionJournalState( + records = listOf(record(1)), + lowSalienceOmittedCount = 0, + ) + val result = renderer.render(state, summaryText = "Some summary") + assertFalse(result.contains("omitted")) + } + + @Test + fun `summaryText with empty records list renders header and summary`() { + val state = DecisionJournalState(records = emptyList(), lowSalienceOmittedCount = 2) + val result = renderer.render(state, summaryText = "Compacted summary") + assertTrue(result.contains("## Decision history")) + assertTrue(result.contains("Compacted summary")) + assertTrue(result.contains("+2 routine transitions/retries omitted")) + assertFalse(result.contains("- [")) + } +}