feat(workflow): analyst→architect→planner→implementer⇄reviewer role pipeline

This commit is contained in:
2026-06-04 02:00:23 +04:00
parent 8b6eedcf87
commit a408a994e4
5 changed files with 250 additions and 0 deletions
@@ -0,0 +1,73 @@
package com.correx.infrastructure.workflow
import com.correx.core.artifacts.kind.ConfigArtifactKind
import com.correx.core.artifacts.kind.DefaultArtifactKindRegistry
import com.correx.core.artifacts.kind.JsonSchema
import com.correx.core.artifacts.kind.JsonSchemaProperty
import com.correx.core.events.types.StageId
import com.correx.core.transitions.conditions.ArtifactFieldEquals
import com.correx.core.transitions.conditions.FieldOperator
import org.junit.jupiter.api.Assumptions.assumeTrue
import org.junit.jupiter.api.Test
import java.nio.file.Path
import kotlin.io.path.exists
import kotlin.test.assertEquals
import kotlin.test.assertTrue
class RolePipelineWorkflowTest {
private fun llmKind(id: String) = ConfigArtifactKind(
id = id,
schema = JsonSchema(
type = "object",
properties = mapOf("verdict" to JsonSchemaProperty(type = "string")),
additionalProperties = true,
),
llmEmitted = true,
)
private val registry = DefaultArtifactKindRegistry().apply {
register(llmKind("analysis"))
register(llmKind("design"))
register(llmKind("impl_plan"))
register(llmKind("review_report"))
}
// Walk up from the working dir to the repo root so the test finds the shipped file
// regardless of which module dir Gradle runs it from.
private fun repoFile(rel: String): Path? {
var dir: Path? = Path.of(System.getProperty("user.dir")).toAbsolutePath()
while (dir != null) {
val candidate = dir.resolve(rel)
if (candidate.exists()) return candidate
dir = dir.parent
}
return null
}
@Test
fun `shipped role_pipeline toml parses with the full role graph`() {
val path = repoFile("examples/workflows/role_pipeline.toml")
assumeTrue(path != null, "role_pipeline.toml not found from ${System.getProperty("user.dir")}")
val graph = TomlWorkflowLoader(registry).load(path!!)
assertEquals(
setOf("analyst", "architect", "planner", "implementer", "reviewer"),
graph.stages.keys.map { it.value }.toSet(),
)
assertEquals(6, graph.transitions.size)
// Forward chaining via needs.
assertTrue(graph.stages[StageId("reviewer")]!!.needs.map { it.value }.containsAll(listOf("patch", "impl_plan")))
// Verdict-gated loop: approved → done, changes → implementer.
val approved = graph.transitions.first { it.to == StageId("done") }.condition as ArtifactFieldEquals
assertEquals(FieldOperator.EQ, approved.operator)
assertEquals("verdict", approved.field)
val loop = graph.transitions
.first { it.from == StageId("reviewer") && it.to == StageId("implementer") }
.condition as ArtifactFieldEquals
assertEquals(FieldOperator.NEQ, loop.operator)
}
}