initial impl
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
|
||||
from agentci.domain.models import (
|
||||
AgentResult,
|
||||
Job,
|
||||
JobKind,
|
||||
ReviewFinding,
|
||||
ReviewReport,
|
||||
ReviewSeverity,
|
||||
Workflow,
|
||||
WorkflowKind,
|
||||
)
|
||||
from agentci.workflows.code_review import CodeReviewLoop
|
||||
|
||||
|
||||
def serious_report() -> ReviewReport:
|
||||
return ReviewReport(
|
||||
summary="Needs work",
|
||||
findings=[
|
||||
ReviewFinding(
|
||||
severity=ReviewSeverity.MAJOR,
|
||||
title="Missing check",
|
||||
detail="A check is absent.",
|
||||
recommendation="Add it.",
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
class FakeCodex:
|
||||
def __init__(self, reports: list[ReviewReport]) -> None:
|
||||
self.reports = iter(reports)
|
||||
self.reviews = 0
|
||||
self.revisions = 0
|
||||
|
||||
async def start(self, **_kwargs):
|
||||
self.reviews += 1
|
||||
return "reviewer", next(self.reports)
|
||||
|
||||
async def resume(self, **kwargs):
|
||||
if kwargs["result_type"] is ReviewReport:
|
||||
self.reviews += 1
|
||||
return next(self.reports)
|
||||
self.revisions += 1
|
||||
return AgentResult(summary_markdown=f"revision {self.revisions}", tests=[])
|
||||
|
||||
|
||||
class FakeStorage:
|
||||
async def update_job(self, *_args, **_kwargs):
|
||||
return None
|
||||
|
||||
async def update_workflow(self, *_args, **_kwargs):
|
||||
return None
|
||||
|
||||
|
||||
class FakePrompts:
|
||||
def render(self, name, **_kwargs):
|
||||
return name
|
||||
|
||||
|
||||
def objects(rounds: int, reports: list[ReviewReport]):
|
||||
codex = FakeCodex(reports)
|
||||
settings = SimpleNamespace(
|
||||
implement_review_rounds=rounds,
|
||||
implement_model="model",
|
||||
implement_reasoning="high",
|
||||
)
|
||||
deps = SimpleNamespace(
|
||||
settings=settings,
|
||||
codex=codex,
|
||||
storage=FakeStorage(),
|
||||
prompts=FakePrompts(),
|
||||
)
|
||||
workflow = Workflow(
|
||||
id="flow",
|
||||
kind=WorkflowKind.IMPLEMENT,
|
||||
repo_owner="org",
|
||||
repo_name="repo",
|
||||
issue_number=1,
|
||||
workspace_path=Path("."),
|
||||
base_sha="abc",
|
||||
primary_session_id="primary",
|
||||
)
|
||||
job = Job(
|
||||
id="job",
|
||||
kind=JobKind.IMPLEMENT,
|
||||
target_key="target",
|
||||
repo_owner="org",
|
||||
repo_name="repo",
|
||||
issue_number=1,
|
||||
pr_number=None,
|
||||
requester="alice",
|
||||
message="",
|
||||
comment_id=1,
|
||||
)
|
||||
return CodeReviewLoop(deps), codex, workflow, job # type: ignore[arg-type]
|
||||
|
||||
|
||||
async def test_stops_after_clean_second_review() -> None:
|
||||
clean = ReviewReport(summary="Ready", findings=[])
|
||||
loop, codex, workflow, job = objects(4, [serious_report(), clean])
|
||||
_, report = await loop.run(
|
||||
job, workflow, "context", AgentResult(summary_markdown="initial", tests=[])
|
||||
)
|
||||
assert not report.has_serious_findings
|
||||
assert codex.reviews == 2
|
||||
assert codex.revisions == 1
|
||||
|
||||
|
||||
async def test_does_not_make_unreviewed_final_revision() -> None:
|
||||
loop, codex, workflow, job = objects(
|
||||
3, [serious_report(), serious_report(), serious_report()]
|
||||
)
|
||||
_, report = await loop.run(
|
||||
job, workflow, "context", AgentResult(summary_markdown="initial", tests=[])
|
||||
)
|
||||
assert report.has_serious_findings
|
||||
assert codex.reviews == 3
|
||||
assert codex.revisions == 2
|
||||
|
||||
Reference in New Issue
Block a user