feat(kernel): RepoKnowledgeRetriever port + relevance-retrieved repo context with recency fallback

This commit is contained in:
2026-06-11 13:39:57 +04:00
parent 9d38a89c88
commit 7a9e060a55
7 changed files with 111 additions and 8 deletions
@@ -4,6 +4,7 @@ package com.correx.core.kernel.orchestration
// and to stay under detekt's function-count threshold (same pattern as isBackEdge).
import com.correx.core.artifacts.kind.ArtifactKind
import com.correx.core.events.events.RepoKnowledgeHit
import com.correx.core.context.model.ContextEntry
import com.correx.core.context.model.ContextLayer
import com.correx.core.context.model.EntryRole
@@ -66,6 +67,22 @@ fun buildArtifactKindVocabularyEntry(kinds: Collection<ArtifactKind>): ContextEn
)
}
fun buildRelevantFilesEntry(hits: List<RepoKnowledgeHit>): ContextEntry {
val content = buildString {
append("## Relevant files (retrieved by semantic similarity)\n")
hits.forEach { append("- ").append(it.text).append("\n") }
}.trimEnd()
return ContextEntry(
id = ContextEntryId(UUID.randomUUID().toString()),
layer = ContextLayer.L3,
content = content,
sourceType = "relevantFiles",
sourceId = "repo-knowledge",
tokenEstimate = content.length / 4,
role = EntryRole.SYSTEM,
)
}
fun buildProjectProfileEntry(profile: BoundProjectProfile): ContextEntry {
val content = buildString {
append("## Project profile\n")
@@ -57,7 +57,8 @@ class DefaultSessionOrchestrator(
private val decisionJournalRepository: DefaultDecisionJournalRepository,
private val compactionService: JournalCompactionService? = null,
artifactKindRegistry: ArtifactKindRegistry? = null,
) : SessionOrchestrator(repositories, engines, artifactStore, decisionJournalRepository, artifactKindRegistry = artifactKindRegistry), ApprovalGateway {
repoKnowledgeRetriever: RepoKnowledgeRetriever? = null,
) : SessionOrchestrator(repositories, engines, artifactStore, decisionJournalRepository, artifactKindRegistry = artifactKindRegistry, repoKnowledgeRetriever = repoKnowledgeRetriever), ApprovalGateway {
override val tokenizer: Tokenizer? = tokenizer
override val cancellations: ConcurrentHashMap<SessionId, AtomicBoolean> =
ConcurrentHashMap<SessionId, AtomicBoolean>()
@@ -0,0 +1,8 @@
package com.correx.core.kernel.orchestration
import com.correx.core.events.events.RepoKnowledgeHit
import com.correx.core.events.types.SessionId
interface RepoKnowledgeRetriever {
suspend fun retrieve(sessionId: SessionId, query: String, k: Int): List<RepoKnowledgeHit>
}
@@ -35,6 +35,7 @@ import com.correx.core.events.events.ArtifactValidatingEvent
import com.correx.core.events.events.EventMetadata
import com.correx.core.events.events.EventPayload
import com.correx.core.events.events.InferenceCompletedEvent
import com.correx.core.events.events.RepoKnowledgeRetrievedEvent
import com.correx.core.events.events.RepoMapComputedEvent
import com.correx.core.events.events.InferenceFailedEvent
import com.correx.core.events.events.InferenceStartedEvent
@@ -155,6 +156,7 @@ abstract class SessionOrchestrator(
private val decisionJournalRepository: DefaultDecisionJournalRepository,
private val decisionJournalRenderer: DecisionJournalRenderer = DecisionJournalRenderer(),
private val artifactKindRegistry: ArtifactKindRegistry? = null,
private val repoKnowledgeRetriever: RepoKnowledgeRetriever? = null,
) {
private val log = LoggerFactory.getLogger(this::class.java)
private val eventStore: EventStore = repositories.eventStore
@@ -349,7 +351,7 @@ abstract class SessionOrchestrator(
role = EntryRole.SYSTEM,
),
)
val repoMapEntries = buildRepoMapEntries(sessionId)
val repoMapEntries = buildContextualRepoEntries(sessionId, stageId, stageConfig)
val sessionEvents = eventStore.read(sessionId)
val criticIds = criticArtifactIds(sessionEvents, graph, stageId)
val criticFrom = sessionEvents
@@ -1024,6 +1026,30 @@ abstract class SessionOrchestrator(
)
}
private suspend fun buildContextualRepoEntries(
sessionId: SessionId,
stageId: StageId,
stageConfig: StageConfig,
): List<ContextEntry> {
val recorded = eventStore.read(sessionId)
.mapNotNull { it.payload as? RepoKnowledgeRetrievedEvent }
.lastOrNull { it.stageId == stageId }
if (recorded != null) return listOf(buildRelevantFilesEntry(recorded.hits))
val retriever = repoKnowledgeRetriever ?: return buildRepoMapEntries(sessionId)
val query = (stageConfig.metadata["promptInline"] ?: stageConfig.metadata["prompt"] ?: stageId.value)
.trim().ifBlank { stageId.value }
val hits = runCatching { retriever.retrieve(sessionId, query, REPO_MAP_INJECT_TOP_K) }
.getOrElse { e ->
if (e is CancellationException) throw e
log.warn("repo-knowledge retrieval failed for stage {}: {}", stageId.value, e.message)
emptyList()
}
if (hits.isEmpty()) return buildRepoMapEntries(sessionId)
emit(sessionId, RepoKnowledgeRetrievedEvent(sessionId, stageId, query, hits))
return listOf(buildRelevantFilesEntry(hits))
}
private suspend fun buildNeedsArtifactEntries(
sessionId: SessionId,
stageConfig: StageConfig,