feat(journal): ProjectMemoryDistiller — pure repo-scoped decision distillation

This commit is contained in:
2026-06-04 01:00:49 +04:00
parent 440c96659d
commit d552148048
2 changed files with 50 additions and 0 deletions
@@ -0,0 +1,15 @@
package com.correx.core.journal
import com.correx.core.journal.model.DecisionJournalState
/**
* Distils a decision journal into durable, repo-scoped memory lines for persistence to the
* cross-session L3 store. Pure transform (no I/O) so `core:journal` stays dependent only on
* `core:events`; the actual L3 upsert/retrieve lives in the wiring layer that owns the store.
*
* Each line is namespaced by [repoRoot] so retrieval can attribute a decision to its project.
*/
class ProjectMemoryDistiller(private val keepLast: Int = 40) {
fun distill(state: DecisionJournalState, repoRoot: String): List<String> =
state.records.takeLast(keepLast).map { "[$repoRoot] [${it.kind}] ${it.summary}" }
}
@@ -0,0 +1,35 @@
import com.correx.core.journal.ProjectMemoryDistiller
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.assertTrue
import org.junit.jupiter.api.Test
class ProjectMemoryDistillerTest {
@Test
fun `distils records into repo-namespaced lines`() {
val state = DecisionJournalState(
records = listOf(
DecisionRecord(1, DecisionKind.STEERING, "User steering: use jwt"),
DecisionRecord(2, DecisionKind.TRANSITION, "Advanced plan → impl (t1)"),
),
)
val lines = ProjectMemoryDistiller().distill(state, "/home/me/repo")
assertEquals(2, lines.size)
assertEquals("[/home/me/repo] [STEERING] User steering: use jwt", lines[0])
assertTrue(lines[1].startsWith("[/home/me/repo] [TRANSITION]"))
}
@Test
fun `keepLast bounds the distilled output`() {
val records = (1..50).map { DecisionRecord(it.toLong(), DecisionKind.ARTIFACT, "v$it") }
val lines = ProjectMemoryDistiller(keepLast = 10).distill(DecisionJournalState(records), "/r")
assertEquals(10, lines.size)
assertTrue(lines.last().contains("v50"))
assertTrue(lines.first().contains("v41"))
}
}