fix(kernel): deterministic repo-map floor + 0.0-hit filter + closest-path suggestion

RepoMapComputed recorded per session (in-memory scan cache keeps the walk-once
guarantee); zero-score embedder hits filtered before reaching the agent, falling
back to the deterministic repo map; REFERENCE_EXISTS surfaces a closest-path hint
via closestPathByFilename. Breaks the hallucinated-package-path read loop even
with a noop embedder.
This commit is contained in:
2026-07-03 13:15:54 +04:00
parent 2698971082
commit a0b3a1865a
3 changed files with 164 additions and 34 deletions
@@ -4,6 +4,7 @@ import com.correx.core.config.ProjectConfig
import com.correx.core.events.events.EventMetadata
import com.correx.core.events.events.NewEvent
import com.correx.core.events.events.RepoMapComputedEvent
import com.correx.core.events.events.RepoMapEntry
import com.correx.core.events.events.SteeringNoteAddedEvent
import com.correx.core.events.events.WorkspaceStateObservedEvent
import com.correx.core.events.stores.EventStore
@@ -44,6 +45,11 @@ class ProjectMemoryService(
private val indexer: RepoMapIndexerPort = RepoMapIndexer(),
private val workspaceStateProbe: WorkspaceStateProbe = RealWorkspaceStateProbe(),
) {
// repoRoot+stateKey -> scanned entries, so a second session on the same workspace state reuses
// the walk instead of re-scanning the FS (preserves the one-walk-per-state guarantee while still
// emitting a per-session RepoMapComputedEvent). Per-process; cold after a restart (one walk then).
private val entryCache = java.util.concurrent.ConcurrentHashMap<String, List<RepoMapEntry>>()
private fun tag(repoRoot: String) = "project:$repoRoot"
/** Resolved repo-root key: configured [ProjectConfig.root], else the working dir. */
@@ -59,12 +65,17 @@ class ProjectMemoryService(
val stateKey = eventStore.read(sessionId)
.mapNotNull { it.payload as? WorkspaceStateObservedEvent }
.lastOrNull()?.stateKey
if (stateKey != null && l3MemoryStore.existsByTurnIdPrefix("repomap:$repoRoot:$stateKey")) {
log.debug("repo-map already indexed for {}@{} — skipping", repoRoot, stateKey)
return
}
runCatching {
val entries = indexer.index(java.nio.file.Path.of(repoRoot))
// Record the map into THIS session's log unconditionally. buildRepoMapEntries and replay
// are session-scoped (invariant #9), so the old walk-once-and-skip left every session after
// the first with no deterministic repo grounding. To keep the walk-once guarantee we reuse
// the scanned entries from a per-stateKey in-memory cache (no re-walk), and only the
// expensive L3 embed/store below stays guarded by the persistent L3 presence check.
val cacheKey = stateKey?.let { "$repoRoot::$it" }
val entries = cacheKey?.let { entryCache[it] }
?: indexer.index(java.nio.file.Path.of(repoRoot)).also {
if (cacheKey != null && it.isNotEmpty()) entryCache[cacheKey] = it
}
if (entries.isEmpty()) return
eventStore.append(
NewEvent(
@@ -85,33 +96,47 @@ class ProjectMemoryService(
),
),
)
// Trailing ':' delimiter so a repoRoot prefix can't collide with a longer sibling
// (/repo vs /repo2) under the retriever's startsWith filter.
val tag = if (stateKey != null) "repomap:$repoRoot:$stateKey" else "repomap:$repoRoot:"
entries.forEach { entry ->
runCatching {
val text = entry.path + if (entry.symbols.isEmpty()) "" else ": ${entry.symbols.joinToString(", ")}"
l3MemoryStore.store(
L3MemoryEntry(
id = UUID.randomUUID().toString(),
sessionId = sessionId,
turnId = tag,
text = text,
vector = embedder.embed(text),
timestampMs = Clock.System.now().toEpochMilliseconds(),
),
)
}.exceptionOrNull()?.let { e ->
if (e is CancellationException) throw e
log.warn("repo-map embedding failed for {}: {}", entry.path, e.message)
}
}
embedRepoMapIntoL3(sessionId, repoRoot, stateKey, entries)
}.exceptionOrNull()?.let { e ->
if (e is CancellationException) throw e
log.warn("repo-map indexing failed for {}: {}", repoRoot, e.message)
}
}
/** Embed the scanned repo-map entries into L3 once per workspace state (skips if already stored). */
private suspend fun embedRepoMapIntoL3(
sessionId: SessionId,
repoRoot: String,
stateKey: String?,
entries: List<RepoMapEntry>,
) {
if (stateKey != null && l3MemoryStore.existsByTurnIdPrefix("repomap:$repoRoot:$stateKey")) {
log.debug("repo-map already embedded for {}@{} — skipping L3 store", repoRoot, stateKey)
return
}
// Trailing ':' delimiter so a repoRoot prefix can't collide with a longer sibling
// (/repo vs /repo2) under the retriever's startsWith filter.
val tag = if (stateKey != null) "repomap:$repoRoot:$stateKey" else "repomap:$repoRoot:"
entries.forEach { entry ->
runCatching {
val text = entry.path + if (entry.symbols.isEmpty()) "" else ": ${entry.symbols.joinToString(", ")}"
l3MemoryStore.store(
L3MemoryEntry(
id = UUID.randomUUID().toString(),
sessionId = sessionId,
turnId = tag,
text = text,
vector = embedder.embed(text),
timestampMs = Clock.System.now().toEpochMilliseconds(),
),
)
}.exceptionOrNull()?.let { e ->
if (e is CancellationException) throw e
log.warn("repo-map embedding failed for {}: {}", entry.path, e.message)
}
}
}
/**
* Observe the workspace content state and record it as a [WorkspaceStateObservedEvent]
* (invariant #9: environment observations are recorded as events, never re-observed).