feat(journal): compaction-aware DecisionJournalRenderer
This commit is contained in:
@@ -3,12 +3,17 @@ package com.correx.core.journal
|
|||||||
import com.correx.core.journal.model.DecisionJournalState
|
import com.correx.core.journal.model.DecisionJournalState
|
||||||
|
|
||||||
class DecisionJournalRenderer(private val keepLast: Int = 40) {
|
class DecisionJournalRenderer(private val keepLast: Int = 40) {
|
||||||
fun render(state: DecisionJournalState): String {
|
fun render(state: DecisionJournalState, summaryText: String? = null): String {
|
||||||
if (state.records.isEmpty()) return ""
|
if (state.records.isEmpty() && summaryText == null) return ""
|
||||||
val shown = state.records.takeLast(keepLast)
|
|
||||||
return buildString {
|
return buildString {
|
||||||
appendLine("## Decision history (chronological — ground truth)")
|
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()
|
}.trimEnd()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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("- ["))
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user