Add code_review phase with approval gate, reviewer≠implementer enforcement, and structured CODE_REVIEW.md
CI / build (push) Has been cancelled
CI / build (push) Has been cancelled
- Insert code_review phase between implement and bug_find - Approval gate: code_review:awaiting_approval → code_review:approved - Read-only phase — no edits, no fixes, no returning to implement - Reviewer≠implementer: .state.implementer tracking + --claim enforcement - Structured CODE_REVIEW.md: spec compliance, design conformance, quality scorecard, items found (severity/category/location/resolution), test coverage - Updated status.py (10 data structures), dashboard (4 files), prompts (3 files), agent routing, tests (6 new test classes, 19 new tests)
This commit is contained in:
+154
-3
@@ -375,14 +375,14 @@ class TestInferStateFromArtifacts:
|
||||
state = (task_dir / ".state").read_text().strip()
|
||||
assert state == "research"
|
||||
|
||||
def test_implementation_only_infers_bug_find(self, tmp_project):
|
||||
def test_implementation_only_infers_code_review(self, tmp_project):
|
||||
task_dir = tmp_project / ".automaton" / "tasks" / "impl-only"
|
||||
task_dir.mkdir()
|
||||
(task_dir / "IMPLEMENTATION.md").write_text("# Impl")
|
||||
out, code = _run_status(["--upgrade", "--task", "impl-only"], tmp_project)
|
||||
assert code == 0
|
||||
state = (task_dir / ".state").read_text().strip()
|
||||
assert state == "bug_find"
|
||||
assert state == "code_review"
|
||||
|
||||
def test_bug_report_only_infers_adversarial_bug_find(self, tmp_project):
|
||||
task_dir = tmp_project / ".automaton" / "tasks" / "bug-only"
|
||||
@@ -421,4 +421,155 @@ class TestValidateFolderCorruptedState:
|
||||
(task_dir / "SPEC.md").write_text("# Spec")
|
||||
out, code = _run_status(["--validate-folder", "--task", "ws-test"], tmp_project)
|
||||
assert code == 1
|
||||
assert "corrupted" in out.lower()
|
||||
assert "corrupted" in out.lower()
|
||||
|
||||
|
||||
class TestCodeReviewPhase:
|
||||
def test_transition_implement_to_code_review(self, tmp_project):
|
||||
task_dir = _create_task(tmp_project, "cr-trans", "implement")
|
||||
(task_dir / "IMPLEMENTATION.md").write_text("# Impl")
|
||||
out, code = _run_status(["--task", "cr-trans", "--transition", "code_review"], tmp_project)
|
||||
assert code == 0
|
||||
assert "Transitioned" in out
|
||||
assert (task_dir / ".state").read_text().strip() == "code_review"
|
||||
|
||||
def test_transition_code_review_to_awaiting_approval(self, tmp_project):
|
||||
task_dir = _create_task(tmp_project, "cr-await", "code_review")
|
||||
(task_dir / "IMPLEMENTATION.md").write_text("# Impl")
|
||||
(task_dir / "CODE_REVIEW.md").write_text("# Code Review")
|
||||
out, code = _run_status(["--task", "cr-await", "--transition", "code_review:awaiting_approval"], tmp_project)
|
||||
assert code == 0
|
||||
assert (task_dir / ".state").read_text().strip() == "code_review:awaiting_approval"
|
||||
|
||||
def test_code_review_approval_gate_blocks_skip(self, tmp_project):
|
||||
task_dir = _create_task(tmp_project, "cr-approval-block", "code_review:awaiting_approval")
|
||||
out, code = _run_status(["--task", "cr-approval-block", "--transition", "bug_find"], tmp_project)
|
||||
assert code == 1
|
||||
assert "code_review:approved" in out
|
||||
|
||||
def test_code_review_approved_to_bug_find(self, tmp_project):
|
||||
task_dir = _create_task(tmp_project, "cr-approved", "code_review:approved")
|
||||
(task_dir / "CODE_REVIEW.md").write_text("# Code Review")
|
||||
out, code = _run_status(["--task", "cr-approved", "--transition", "bug_find"], tmp_project)
|
||||
assert code == 0
|
||||
assert (task_dir / ".state").read_text().strip() == "bug_find"
|
||||
|
||||
def test_code_review_approvable(self, tmp_project):
|
||||
task_dir = _create_task(tmp_project, "cr-approvable", "code_review:awaiting_approval")
|
||||
out, code = _run_status(["--approve", "--task", "cr-approvable"], tmp_project)
|
||||
assert code == 0
|
||||
assert "Approved" in out
|
||||
assert (task_dir / ".state").read_text().strip() == "code_review:approved"
|
||||
|
||||
def test_code_review_approve_records_log(self, tmp_project):
|
||||
task_dir = _create_task(tmp_project, "cr-log", "code_review:awaiting_approval")
|
||||
_run_status(["--approve", "--task", "cr-log"], tmp_project)
|
||||
approvals = (task_dir / ".state.approvals").read_text()
|
||||
assert "code_review:approved" in approvals
|
||||
|
||||
def test_code_review_not_editable(self, tmp_project):
|
||||
_create_task(tmp_project, "cr-noedit", "code_review")
|
||||
out, code = _run_status(["--can-edit", "--task", "cr-noedit"], tmp_project)
|
||||
assert code == 1
|
||||
assert "DENIED" in out
|
||||
|
||||
def test_code_review_forbids_bug_report_artifact(self, tmp_project):
|
||||
task_dir = _create_task(tmp_project, "cr-forbidden", "code_review")
|
||||
(task_dir / "BUG_REPORT.md").write_text("# Bugs")
|
||||
out, code = _run_status(["--validate-folder", "--task", "cr-forbidden"], tmp_project)
|
||||
assert code == 1
|
||||
assert "BUG_REPORT.md" in out
|
||||
|
||||
|
||||
class TestImplementerTracking:
|
||||
def test_transition_records_implementer_when_lock_exists(self, tmp_project):
|
||||
task_dir = _create_task(tmp_project, "impl-track", "implement")
|
||||
(task_dir / "IMPLEMENTATION.md").write_text("# Impl")
|
||||
lock_file = task_dir / ".state.lock"
|
||||
from datetime import datetime, timedelta, timezone
|
||||
now = datetime.now(timezone.utc)
|
||||
expires = now + timedelta(minutes=30)
|
||||
lock_file.write_text(f"agent: builder-agent\nphase: implement\nclaimed: {now.isoformat()}\nexpires: {expires.isoformat()}\n")
|
||||
out, code = _run_status(["--task", "impl-track", "--transition", "code_review"], tmp_project)
|
||||
assert code == 0
|
||||
impl_file = task_dir / ".state.implementer"
|
||||
assert impl_file.exists()
|
||||
assert impl_file.read_text().strip() == "builder-agent"
|
||||
|
||||
def test_no_lock_does_not_block_transition(self, tmp_project):
|
||||
task_dir = _create_task(tmp_project, "impl-nolock", "implement")
|
||||
(task_dir / "IMPLEMENTATION.md").write_text("# Impl")
|
||||
out, code = _run_status(["--task", "impl-nolock", "--transition", "code_review"], tmp_project)
|
||||
assert code == 0
|
||||
assert not (task_dir / ".state.implementer").exists()
|
||||
|
||||
|
||||
class TestReviewerEnforcement:
|
||||
def test_same_implementer_cannot_claim_code_review(self, tmp_project):
|
||||
task_dir = _create_task(tmp_project, "same-reviewer", "code_review")
|
||||
(task_dir / ".state.implementer").write_text("builder-agent\n")
|
||||
agent_file = tmp_project / ".automaton" / ".agent.md"
|
||||
agent_file.write_text("""## Agent Configuration
|
||||
Mode: multi-agent
|
||||
Agents:
|
||||
- id: builder-agent
|
||||
phases: [code_review, implement]
|
||||
Lock timeout: 30m
|
||||
""")
|
||||
out, code = _run_status(["--claim", "--task", "same-reviewer", "--agent", "builder-agent"], tmp_project)
|
||||
assert code == 1
|
||||
assert "cannot claim" in out
|
||||
|
||||
def test_different_reviewer_can_claim_code_review(self, tmp_project):
|
||||
task_dir = _create_task(tmp_project, "diff-reviewer", "code_review")
|
||||
(task_dir / ".state.implementer").write_text("builder-agent\n")
|
||||
agent_file = tmp_project / ".automaton" / ".agent.md"
|
||||
agent_file.write_text("""## Agent Configuration
|
||||
Mode: multi-agent
|
||||
Agents:
|
||||
- id: reviewer-agent
|
||||
phases: [code_review]
|
||||
Lock timeout: 30m
|
||||
""")
|
||||
out, code = _run_status(["--claim", "--task", "diff-reviewer", "--agent", "reviewer-agent"], tmp_project)
|
||||
assert code == 0
|
||||
assert "Claimed" in out
|
||||
|
||||
|
||||
class TestCodeReviewInference:
|
||||
def test_code_review_artifact_infers_bug_find(self, tmp_project):
|
||||
task_dir = tmp_project / ".automaton" / "tasks" / "cr-infer"
|
||||
task_dir.mkdir()
|
||||
(task_dir / "CODE_REVIEW.md").write_text("# Review")
|
||||
out, code = _run_status(["--upgrade", "--task", "cr-infer"], tmp_project)
|
||||
assert code == 0
|
||||
state = (task_dir / ".state").read_text().strip()
|
||||
assert state == "bug_find"
|
||||
|
||||
def test_impl_and_code_review_infers_bug_find(self, tmp_project):
|
||||
task_dir = tmp_project / ".automaton" / "tasks" / "cr-plus-impl"
|
||||
task_dir.mkdir()
|
||||
(task_dir / "IMPLEMENTATION.md").write_text("# Impl")
|
||||
(task_dir / "CODE_REVIEW.md").write_text("# Review")
|
||||
out, code = _run_status(["--upgrade", "--task", "cr-plus-impl"], tmp_project)
|
||||
assert code == 0
|
||||
state = (task_dir / ".state").read_text().strip()
|
||||
assert state == "bug_find"
|
||||
|
||||
|
||||
class TestCodeReviewShowTask:
|
||||
def test_show_task_code_review(self, tmp_project):
|
||||
_create_task(tmp_project, "cr-show", "code_review")
|
||||
out, code = _run_status(["--task", "cr-show"], tmp_project)
|
||||
assert code == 0
|
||||
assert "Phase: code_review" in out
|
||||
assert "Next artifact needed: CODE_REVIEW.md" in out
|
||||
|
||||
|
||||
class TestCodeReviewListStates:
|
||||
def test_list_states_includes_code_review(self, tmp_project):
|
||||
out, code = _run_status(["--list-states"], tmp_project)
|
||||
assert code == 0
|
||||
assert "code_review * requires approval" in out
|
||||
assert "code_review:awaiting_approval" in out
|
||||
assert "code_review:approved" in out
|
||||
Reference in New Issue
Block a user