diff --git a/apps/server/src/main/kotlin/com/correx/apps/server/ServerModule.kt b/apps/server/src/main/kotlin/com/correx/apps/server/ServerModule.kt index 1952aab6..a098d258 100644 --- a/apps/server/src/main/kotlin/com/correx/apps/server/ServerModule.kt +++ b/apps/server/src/main/kotlin/com/correx/apps/server/ServerModule.kt @@ -41,6 +41,7 @@ import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import org.slf4j.LoggerFactory private val log = LoggerFactory.getLogger(ServerModule::class.java) @@ -220,35 +221,7 @@ class ServerModule( ), ) } - // Bind the per-repo project profile (curated .correx/project.toml) as an event so - // stages and replay read the recorded snapshot, never the live file (invariants #8/#9). - val workspaceRoot = runCatching { - sessionRepository.getSession(sessionId).state.boundWorkspace?.workspaceRoot - }.getOrNull() ?: projectMemory?.repoRoot() - workspaceRoot?.let { root -> - val projectProfile = ProjectProfileLoader.load(root) - if (!projectProfile.isEmpty()) { - eventStore.append( - NewEvent( - metadata = EventMetadata( - eventId = EventId(java.util.UUID.randomUUID().toString()), - sessionId = sessionId, - timestamp = Clock.System.now(), - schemaVersion = 1, - causationId = null, - correlationId = null, - ), - payload = ProjectProfileBoundEvent( - sessionId = sessionId, - workspaceRoot = root, - about = projectProfile.about, - conventions = projectProfile.conventions, - commands = projectProfile.commands, - ), - ), - ) - } - } + bindProjectProfile(sessionId) runCatching { orchestrator.run(sessionId, graph, sessionConfig) // After a successful freestyle planning run, lock the plan and execute phase 2. @@ -290,6 +263,38 @@ class ServerModule( } } + /** + * Bind the per-repo project profile (curated .correx/project.toml) as an event so stages, + * router chat triage, and replay read the recorded snapshot, never the live file + * (invariants #8/#9). Shared by the workflow path and the chat-session path. + */ + suspend fun bindProjectProfile(sessionId: SessionId) { + val workspaceRoot = runCatching { + sessionRepository.getSession(sessionId).state.boundWorkspace?.workspaceRoot + }.getOrNull() ?: projectMemory?.repoRoot() ?: return + val projectProfile = withContext(Dispatchers.IO) { ProjectProfileLoader.load(workspaceRoot) } + if (projectProfile.isEmpty()) return + eventStore.append( + NewEvent( + metadata = EventMetadata( + eventId = EventId(java.util.UUID.randomUUID().toString()), + sessionId = sessionId, + timestamp = Clock.System.now(), + schemaVersion = 1, + causationId = null, + correlationId = null, + ), + payload = ProjectProfileBoundEvent( + sessionId = sessionId, + workspaceRoot = workspaceRoot, + about = projectProfile.about, + conventions = projectProfile.conventions, + commands = projectProfile.commands, + ), + ), + ) + } + private fun launchSessionResume(sessionId: SessionId, graph: com.correx.core.transitions.graph.WorkflowGraph) { activeSessionJobs.computeIfAbsent(sessionId) { moduleScope.launch { diff --git a/apps/server/src/main/kotlin/com/correx/apps/server/ws/GlobalStreamHandler.kt b/apps/server/src/main/kotlin/com/correx/apps/server/ws/GlobalStreamHandler.kt index df499216..af657546 100644 --- a/apps/server/src/main/kotlin/com/correx/apps/server/ws/GlobalStreamHandler.kt +++ b/apps/server/src/main/kotlin/com/correx/apps/server/ws/GlobalStreamHandler.kt @@ -379,6 +379,11 @@ class GlobalStreamHandler(private val module: ServerModule) { payload = ChatSessionStartedEvent(sessionId = sessionId), )) + // Bind the per-repo project profile so router triage can cite conventions/commands. + // Parity with launchSessionRun — chat sessions previously never bound a profile. + runCatching { module.bindProjectProfile(sessionId) } + .onFailure { log.warn("project profile binding failed for chat session={}: {}", sessionId.value, it.message) } + runCatching { module.routerFacade.onUserInput(sessionId, msg.text) }.onFailure {