From 8d67f5b03e31a948d357c3564088d3dd2b7b00a7 Mon Sep 17 00:00:00 2001 From: kami Date: Thu, 4 Jun 2026 00:56:50 +0400 Subject: [PATCH] =?UTF-8?q?feat(workflow):=20sample=20review=5Freport=20sc?= =?UTF-8?q?hema=20+=20implementer=E2=86=94reviewer=20loop?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/sample-config.toml | 4 + docs/schemas/review_report.json | 15 +++ examples/workflows/review_loop.toml | 58 +++++++++++ .../workflow/ReviewLoopWorkflowTest.kt | 96 +++++++++++++++++++ 4 files changed, 173 insertions(+) create mode 100644 docs/schemas/review_report.json create mode 100644 examples/workflows/review_loop.toml create mode 100644 infrastructure/workflow/src/test/kotlin/com/correx/infrastructure/workflow/ReviewLoopWorkflowTest.kt diff --git a/docs/sample-config.toml b/docs/sample-config.toml index bcc440a9..88d6796b 100644 --- a/docs/sample-config.toml +++ b/docs/sample-config.toml @@ -117,3 +117,7 @@ backend = "in_memory" # or "turbovec" # id = "review_report" # schema_path = "schemas/review_report.json" # llm_emitted = true +# +# The bundled examples/workflows/review_loop.toml uses this kind: the reviewer emits a +# review_report whose `verdict` field gates the implementer↔reviewer loop via +# artifact_field_equals transitions. See docs/schemas/review_report.json for the schema. diff --git a/docs/schemas/review_report.json b/docs/schemas/review_report.json new file mode 100644 index 00000000..8faa0a79 --- /dev/null +++ b/docs/schemas/review_report.json @@ -0,0 +1,15 @@ +{ + "type": "object", + "properties": { + "verdict": { + "type": "string", + "description": "approved | changes_requested" + }, + "notes": { + "type": "string", + "description": "reviewer rationale and requested changes" + } + }, + "required": ["verdict"], + "additionalProperties": true +} diff --git a/examples/workflows/review_loop.toml b/examples/workflows/review_loop.toml new file mode 100644 index 00000000..002029af --- /dev/null +++ b/examples/workflows/review_loop.toml @@ -0,0 +1,58 @@ +# Implementer ↔ reviewer refinement loop. +# +# The reviewer emits a validated `review_report` artifact carrying a `verdict` field. +# The loop exits to `done` when verdict == "approved" and loops back to `implement` +# otherwise (verdict != "approved"). The runtime refinement guard caps the +# review→implement cycle at `implement.max_retries` iterations and then escalates. +# +# Requires a `review_report` artifact kind declared in config: +# [[artifacts]] +# id = "review_report" +# schema_path = "schemas/review_report.json" +# llm_emitted = true + +id = "review_loop" +start = "implement" + +[[stages]] +id = "implement" +prompt = "prompts/implement.md" +produces = [{ name = "patch", kind = "file_written" }] +allowed_tools = ["file_write"] +token_budget = 8192 +max_retries = 3 + +[[stages]] +id = "review" +prompt = "prompts/review.md" +needs = ["patch"] +produces = [{ name = "review_report", kind = "review_report" }] +token_budget = 8192 +max_retries = 3 + +[[transitions]] +id = "implement-to-review" +from = "implement" +to = "review" +condition_type = "artifact_validated" +condition_artifact_id = "patch" + +[[transitions]] +id = "review-approved" +from = "review" +to = "done" +condition_type = "artifact_field_equals" +condition_artifact_id = "review_report" +condition_field = "verdict" +condition_value = "approved" +condition_operator = "eq" + +[[transitions]] +id = "review-changes-requested" +from = "review" +to = "implement" +condition_type = "artifact_field_equals" +condition_artifact_id = "review_report" +condition_field = "verdict" +condition_value = "approved" +condition_operator = "neq" diff --git a/infrastructure/workflow/src/test/kotlin/com/correx/infrastructure/workflow/ReviewLoopWorkflowTest.kt b/infrastructure/workflow/src/test/kotlin/com/correx/infrastructure/workflow/ReviewLoopWorkflowTest.kt new file mode 100644 index 00000000..0172cc3e --- /dev/null +++ b/infrastructure/workflow/src/test/kotlin/com/correx/infrastructure/workflow/ReviewLoopWorkflowTest.kt @@ -0,0 +1,96 @@ +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.Test +import java.nio.file.Files +import kotlin.io.path.writeText +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class ReviewLoopWorkflowTest { + + private val registry = DefaultArtifactKindRegistry().apply { + register( + ConfigArtifactKind( + id = "review_report", + schema = JsonSchema( + type = "object", + properties = mapOf("verdict" to JsonSchemaProperty(type = "string")), + required = listOf("verdict"), + additionalProperties = true, + ), + llmEmitted = true, + ), + ) + } + private val loader = TomlWorkflowLoader(registry) + + private val toml = """ + id = "review_loop" + start = "implement" + + [[stages]] + id = "implement" + prompt = "prompts/implement.md" + produces = [{ name = "patch", kind = "file_written" }] + allowed_tools = ["file_write"] + max_retries = 3 + + [[stages]] + id = "review" + prompt = "prompts/review.md" + needs = ["patch"] + produces = [{ name = "review_report", kind = "review_report" }] + max_retries = 3 + + [[transitions]] + id = "implement-to-review" + from = "implement" + to = "review" + condition_type = "artifact_validated" + condition_artifact_id = "patch" + + [[transitions]] + id = "review-approved" + from = "review" + to = "done" + condition_type = "artifact_field_equals" + condition_artifact_id = "review_report" + condition_field = "verdict" + condition_value = "approved" + condition_operator = "eq" + + [[transitions]] + id = "review-changes-requested" + from = "review" + to = "implement" + condition_type = "artifact_field_equals" + condition_artifact_id = "review_report" + condition_field = "verdict" + condition_value = "approved" + condition_operator = "neq" + """.trimIndent() + + @Test + fun `review loop parses with verdict-gated edges`() { + val path = Files.createTempFile("review_loop", ".toml").also { it.writeText(toml) } + val graph = loader.load(path) + + val review = graph.stages[StageId("review")]!! + assertTrue(review.produces.any { it.name.value == "review_report" && it.kind.llmEmitted }) + + val approved = graph.transitions.first { it.to == StageId("done") }.condition as ArtifactFieldEquals + assertEquals("verdict", approved.field) + assertEquals("approved", approved.value) + assertEquals(FieldOperator.EQ, approved.operator) + + val changes = graph.transitions.first { it.to == StageId("implement") }.condition as ArtifactFieldEquals + assertEquals(FieldOperator.NEQ, changes.operator) + } +}