Make plan-review a review-fix-verify loop
This commit is contained in:
@@ -53,7 +53,7 @@ agents:
|
||||
# 방법 1: 프리셋 사용 (사용자가 pipeline YAML 직접 작성할 필요 없음)
|
||||
pipeline: preset:simple # "A 생성 → B 리뷰" (기본값)
|
||||
# pipeline: preset:cross-review # "둘 다 생성 → 서로 리뷰"
|
||||
# pipeline: preset:plan-review # "구현 전 문서/기획 검토"
|
||||
# pipeline: preset:plan-review # "구현 전 문서 리뷰 → 수정 → 재검증 반복"
|
||||
# pipeline: preset:coding-review-fix # "초기 코딩 1회 → 리뷰/수정 반복"
|
||||
|
||||
# 방법 2: 직접 커스텀 (고급 사용자용)
|
||||
@@ -77,7 +77,7 @@ pipeline: preset:simple # "A 생성 → B 리뷰" (기본값)
|
||||
|--------|------|-------------------|
|
||||
| `simple` | A 코딩 → B 리뷰 | coding(agent1) → review(agent2) |
|
||||
| `cross-review` | 둘 다 코딩, 서로 리뷰 | coding_a → coding_b → review_of_b(agent_a) → review_of_a(agent_b) |
|
||||
| `plan-review` | 구현 전 문서 검토 | parallel plan_review_* → senior_review(optional) |
|
||||
| `plan-review` | 구현 전 문서 리뷰/수정/재검증 반복 | plan_review_* → aggregate_review → plan_fix → verify |
|
||||
| `coding-review-fix` | 초기 코딩 후 리뷰/수정 반복 | initial_coding(coding) → review_fix(review* → aggregate → coding → verify) |
|
||||
|
||||
프리셋은 내부적으로 적절한 pipeline steps + context_override를 자동 구성한다. agents에 정의된 순서대로 agent1, agent2가 배정된다. 프리셋이 불충분하면 직접 steps를 작성할 수 있다.
|
||||
@@ -185,3 +185,6 @@ final-report.md 생성
|
||||
--reviewer-effort high \
|
||||
--senior-effort xhigh \
|
||||
--max-iter 10
|
||||
|
||||
|
||||
cross-eval run --plan /Users/chungyeong/Desktop/Dev/cross-eval/UX_IMPROVEMENT_PLAN.md --coder claude --reviewer claude --senior claude --model sonnet --preset coding-review-fix --lang ko --max-iter 1
|
||||
|
||||
@@ -112,7 +112,7 @@ pipeline: preset:simple
|
||||
|--------|------|
|
||||
| `simple` | Agent A가 코딩, Agent B가 리뷰 (기본값) |
|
||||
| `cross-review` | 둘 다 코딩, 서로 교차 리뷰 |
|
||||
| `plan-review` | 구현 전 기획서/체크리스트/참고문서를 검토하고 필요시 현재 코드베이스와의 정합성도 확인 |
|
||||
| `plan-review` | 구현 전 기획서/체크리스트/참고문서를 검토하고 문서를 수정한 뒤 재검증까지 반복 |
|
||||
| `review-only` | 기존 코드만 감사 용도로 검토 |
|
||||
| `review-fix` | 리뷰 결과를 취합한 뒤 자동 수정과 재검증까지 반복 |
|
||||
| `coding-review-fix` | 초기 코딩 1회 후 리뷰 결과를 취합해 자동 수정과 재검증을 반복 |
|
||||
@@ -120,6 +120,6 @@ pipeline: preset:simple
|
||||
```bash
|
||||
# 초기화 옵션
|
||||
cross-eval init --preset cross-review # 교차 리뷰 프리셋
|
||||
cross-eval init --preset plan-review # 구현 전 문서 검토 프리셋
|
||||
cross-eval init --preset plan-review # 문서 리뷰/수정/재검증 프리셋
|
||||
cross-eval init --lang en # 영어 템플릿
|
||||
```
|
||||
|
||||
@@ -205,7 +205,7 @@ def main(argv: list[str] | None = None) -> int:
|
||||
],
|
||||
help=(
|
||||
"파이프라인 종류 (기본: simple). "
|
||||
"simple=코딩+리뷰, cross-review=교차리뷰, plan-review=문서기획검토, "
|
||||
"simple=코딩+리뷰, cross-review=교차리뷰, plan-review=문서리뷰수정재검증, "
|
||||
"review-only=리뷰만, review-fix=리뷰수렴+자동수정, "
|
||||
"coding-review-fix=초기코딩후리뷰수렴"
|
||||
),
|
||||
@@ -291,8 +291,8 @@ def main(argv: list[str] | None = None) -> int:
|
||||
" │ coding- │ 3단계 파이프라인: │\n"
|
||||
" │ review-fix │ 초기 코딩 1회 → 리뷰 취합 → 수정 → 재검증 반복 │\n"
|
||||
" ├──────────────┼─────────────────────────────────────────────────────┤\n"
|
||||
" │ plan-review │ 구현 전 기획서/체크리스트/문서를 검토 │\n"
|
||||
" │ │ 필요하면 현재 코드베이스와의 정합성도 점검 │\n"
|
||||
" │ plan-review │ 구현 전 기획서/체크리스트/문서를 검토하고 │\n"
|
||||
" │ │ 수정한 뒤 시니어가 재검증할 때까지 반복 │\n"
|
||||
" ├──────────────┼─────────────────────────────────────────────────────┤\n"
|
||||
" │ review-only │ 코드 작성 없이 Reviewer N명이 기존 코드만 검토 │\n"
|
||||
" │ │ (이미 작성된 코드의 품질 감사용) │\n"
|
||||
@@ -341,9 +341,9 @@ def main(argv: list[str] | None = None) -> int:
|
||||
" cross-eval run --plan plan.md --preset review-only \\\n"
|
||||
" --reviewer claude --reviewer codex\n"
|
||||
"\n"
|
||||
" 구현 전 문서/기획 검토 (plan-review):\n"
|
||||
" 문서 리뷰 + 수정 + 재검증 반복 (plan-review):\n"
|
||||
" cross-eval run --plan plan.md --preset plan-review \\\n"
|
||||
" --reviewer claude --reviewer codex\n"
|
||||
" --coder codex --reviewer codex\n"
|
||||
"\n"
|
||||
" 모델 변경:\n"
|
||||
" cross-eval run --plan plan.md --model sonnet\n"
|
||||
@@ -563,7 +563,7 @@ _PRESET_DESCRIPTIONS = {
|
||||
"simple": "코딩 + 리뷰 (가장 기본)",
|
||||
"review-fix": "리뷰 → 취합 → 수정 → 재검증 반복",
|
||||
"coding-review-fix": "초기 코딩 + 리뷰 수렴 반복",
|
||||
"plan-review": "구현 전 기획서/문서 검토",
|
||||
"plan-review": "문서 리뷰 → 수정 → 재검증 반복",
|
||||
"review-only": "기존 코드만 리뷰 (코딩 없음)",
|
||||
"cross-review": "2명이 각각 구현 후 교차 리뷰",
|
||||
}
|
||||
@@ -929,7 +929,7 @@ def cmd_run(args: argparse.Namespace) -> int:
|
||||
elif preset in PIPELINE_PRESETS:
|
||||
config.pipeline = PIPELINE_PRESETS[preset](coders, reviewers, seniors)
|
||||
config.phases = []
|
||||
if preset in {"plan-review", "review-only"} and args.max_iter is None and args.min_iter is None:
|
||||
if preset == "review-only" and args.max_iter is None and args.min_iter is None:
|
||||
config.max_iterations = 1
|
||||
|
||||
sync_phased_iterations(config)
|
||||
|
||||
@@ -31,7 +31,7 @@ DEFAULT_ROLE_REASONING_EFFORTS = {
|
||||
"reviewer": "medium",
|
||||
"senior": "high",
|
||||
}
|
||||
FIX_STYLE_PRESETS = {"review-fix", "coding-review-fix"}
|
||||
FIX_STYLE_PRESETS = {"plan-review", "review-fix", "coding-review-fix"}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -296,7 +296,11 @@ def _default_seniors_for_preset(
|
||||
"""Infer a default senior agent for presets that benefit from adjudication."""
|
||||
if not (
|
||||
isinstance(pipeline_raw, str)
|
||||
and pipeline_raw in {"preset:review-fix", "preset:coding-review-fix"}
|
||||
and pipeline_raw in {
|
||||
"preset:plan-review",
|
||||
"preset:review-fix",
|
||||
"preset:coding-review-fix",
|
||||
}
|
||||
and reviewers
|
||||
):
|
||||
return []
|
||||
|
||||
@@ -472,12 +472,58 @@ PLAN_REVIEW_TEMPLATE_KO = """\
|
||||
그렇지 않으면: VERDICT: FAIL
|
||||
"""
|
||||
|
||||
PLAN_FIX_TEMPLATE = """\
|
||||
You are tasked with revising planning documents based on adjudicated review feedback.
|
||||
|
||||
## Artifact References
|
||||
{artifact_references}
|
||||
|
||||
## Current Review Feedback
|
||||
{feedback}
|
||||
|
||||
## Instructions
|
||||
1. Read the referenced plan/checklist/docs/review artifacts directly from disk.
|
||||
2. Update the planning package itself: the plan, checklist, and reference documents as needed.
|
||||
3. Do NOT write or modify production code. Only revise planning artifacts.
|
||||
4. Address ONLY the confirmed planning issues from the current review feedback.
|
||||
5. If feedback marks any item as DISMISSED or false positive, leave it unchanged.
|
||||
6. Make the smallest document changes that resolve ambiguity, omissions, scope creep, or repository compatibility issues.
|
||||
7. Keep the plan, checklist, and supporting docs internally consistent after your edits.
|
||||
8. After editing, briefly summarize what you changed and any blocker that still needs human input.
|
||||
"""
|
||||
|
||||
PLAN_FIX_TEMPLATE_KO = """\
|
||||
당신은 시니어 리뷰 결과를 바탕으로 기획 문서를 수정하는 담당자입니다.
|
||||
|
||||
## 참조 아티팩트
|
||||
{artifact_references}
|
||||
|
||||
## 현재 리뷰 피드백
|
||||
{feedback}
|
||||
|
||||
## 지침
|
||||
1. 참조된 plan/checklist/docs/review markdown를 직접 읽으세요.
|
||||
2. 수정 대상은 기획 패키지 자체입니다. 필요에 따라 기획서, 체크리스트, 참고 문서를 수정하세요.
|
||||
3. 프로덕션 코드를 작성하거나 수정하지 마세요. 기획 문서만 고치세요.
|
||||
4. 현재 리뷰 피드백에서 확정된 기획 이슈만 해결하세요.
|
||||
5. DISMISSED 또는 오탐으로 정리된 항목은 건드리지 마세요.
|
||||
6. 모호성, 누락, 과도한 범위, 저장소 정합성 문제를 해소하는 최소한의 문서 수정만 하세요.
|
||||
7. 수정 후에도 기획서, 체크리스트, 참고 문서가 서로 모순되지 않게 유지하세요.
|
||||
8. 수정이 끝나면 무엇을 바꿨는지와 아직 사람 판단이 필요한 blocker가 있는지 짧게 정리하세요.
|
||||
"""
|
||||
|
||||
AGGREGATE_REVIEW_TEMPLATE = """\
|
||||
You are adjudicating multiple review results and turning them into an actionable decision.
|
||||
|
||||
## Artifact References
|
||||
{artifact_references}
|
||||
|
||||
## Candidate Artifact Under Review
|
||||
{candidate_outputs}
|
||||
|
||||
## Reviewer Findings Bundle
|
||||
{reviews_bundle}
|
||||
|
||||
## Previous Issue Tracker
|
||||
{previous_senior_tracker}
|
||||
|
||||
@@ -486,19 +532,19 @@ You are adjudicating multiple review results and turning them into an actionable
|
||||
|
||||
## Instructions
|
||||
Read the referenced plan/checklist/docs/review artifacts directly from disk. \
|
||||
Explore the project directory and the referenced git commit/diff to confirm the \
|
||||
current codebase state. Use the execution evidence above to verify claims against \
|
||||
Inspect the repository and referenced artifacts only as needed to confirm the \
|
||||
current target state. Use the execution evidence above to verify claims against \
|
||||
actual command outputs, artifact paths, and exit codes. Then:
|
||||
1. Deduplicate overlapping issues across reviewers.
|
||||
2. Resolve disagreements explicitly.
|
||||
3. Keep only issues supported by the plan, checklist, code, or reviewer evidence.
|
||||
3. Keep only issues supported by the plan, checklist, reference docs, repository state, or reviewer evidence.
|
||||
4. When evidence is mixed, explain what was confirmed, what was dismissed, and what still needs follow-up.
|
||||
5. Produce a prioritized action list for the coder.
|
||||
5. Produce a prioritized action list for the implementer/editor.
|
||||
6. Maintain the Issue Tracker table across iterations (carry forward unresolved issues).
|
||||
7. If no confirmed issue remains, output VERDICT: PASS.
|
||||
8. If issues exist that the coder can fix, output VERDICT: FAIL.
|
||||
8. If issues exist that the implementer/editor can fix, output VERDICT: FAIL.
|
||||
9. If issues require human intervention (ambiguous requirements, architecture decisions, \
|
||||
external dependency problems, or the same issue persists after 2+ fix attempts), \
|
||||
external dependency problems, or the same issue persists after 2+ attempts), \
|
||||
output VERDICT: ESCALATE.
|
||||
|
||||
## Output Format
|
||||
@@ -512,8 +558,8 @@ output VERDICT: ESCALATE.
|
||||
(Write "None" if nothing was dismissed.)
|
||||
|
||||
### Action Items
|
||||
1. Concrete fix the coder should make
|
||||
2. Concrete fix the coder should make
|
||||
1. Concrete fix the implementer/editor should make
|
||||
2. Concrete fix the implementer/editor should make
|
||||
|
||||
## Issue Tracker
|
||||
|
||||
@@ -536,6 +582,12 @@ AGGREGATE_REVIEW_TEMPLATE_KO = """\
|
||||
## 참조 아티팩트
|
||||
{artifact_references}
|
||||
|
||||
## 현재 검토 대상
|
||||
{candidate_outputs}
|
||||
|
||||
## 리뷰 결과 묶음
|
||||
{reviews_bundle}
|
||||
|
||||
## 이전 이슈 트래커
|
||||
{previous_senior_tracker}
|
||||
|
||||
@@ -543,17 +595,17 @@ AGGREGATE_REVIEW_TEMPLATE_KO = """\
|
||||
{execution_evidence}
|
||||
|
||||
## 지침
|
||||
참조된 plan/checklist/docs/review markdown와 git 상태를 직접 읽어 현재 코드베이스 상태를 확인한 뒤, \
|
||||
참조된 plan/checklist/docs/review markdown와 저장소 상태를 직접 읽어 현재 검토 대상의 상태를 확인한 뒤, \
|
||||
위 실행 증거를 활용하여 에이전트의 주장을 실제 명령어 출력, 아티팩트 경로, 종료 코드로 검증하세요. \
|
||||
그런 다음 아래를 수행하세요.
|
||||
1. 리뷰어들 사이에 중복되는 이슈를 합치세요.
|
||||
2. 의견 충돌은 명시적으로 정리하세요.
|
||||
3. 기획서, 체크리스트, 코드, 리뷰 근거로 뒷받침되는 이슈만 남기세요.
|
||||
3. 기획서, 체크리스트, 참고 문서, 저장소 상태, 리뷰 근거로 뒷받침되는 이슈만 남기세요.
|
||||
4. 근거가 엇갈리면 무엇이 확정이고 무엇이 기각 또는 추가확인 대상인지 분명히 적으세요.
|
||||
5. coder가 바로 수정할 수 있는 우선순위 액션 아이템을 만드세요.
|
||||
5. 수정 담당자가 바로 처리할 수 있는 우선순위 액션 아이템을 만드세요.
|
||||
6. 이슈 트래커 테이블을 반복 간에 유지하세요 (미해결 이슈를 이월).
|
||||
7. 확정된 이슈가 없으면 VERDICT: PASS 를 출력하세요.
|
||||
8. coder가 수정 가능한 이슈가 있으면 VERDICT: FAIL 을 출력하세요.
|
||||
8. 수정 담당자가 해결 가능한 이슈가 있으면 VERDICT: FAIL 을 출력하세요.
|
||||
9. 사람의 개입이 필요한 이슈(모호한 요구사항, 아키텍처 결정, 외부 의존성 문제, \
|
||||
동일 이슈가 2회 이상 해결 실패)가 있으면 VERDICT: ESCALATE 를 출력하세요.
|
||||
|
||||
@@ -568,8 +620,8 @@ AGGREGATE_REVIEW_TEMPLATE_KO = """\
|
||||
(기각된 항목이 없으면 "없음"이라고 작성하세요.)
|
||||
|
||||
### 액션 아이템
|
||||
1. coder가 수정해야 할 구체적인 작업
|
||||
2. coder가 수정해야 할 구체적인 작업
|
||||
1. 수정 담당자가 처리해야 할 구체적인 작업
|
||||
2. 수정 담당자가 처리해야 할 구체적인 작업
|
||||
|
||||
## 이슈 트래커
|
||||
|
||||
@@ -592,6 +644,7 @@ DEFAULT_TEMPLATES: dict[str, dict[str, str]] = {
|
||||
"coding": CODING_TEMPLATE,
|
||||
"review": REVIEW_TEMPLATE,
|
||||
"plan-review": PLAN_REVIEW_TEMPLATE,
|
||||
"plan-fix": PLAN_FIX_TEMPLATE,
|
||||
"review-only": REVIEW_ONLY_TEMPLATE,
|
||||
"aggregate-review": AGGREGATE_REVIEW_TEMPLATE,
|
||||
},
|
||||
@@ -599,6 +652,7 @@ DEFAULT_TEMPLATES: dict[str, dict[str, str]] = {
|
||||
"coding": CODING_TEMPLATE_KO,
|
||||
"review": REVIEW_TEMPLATE_KO,
|
||||
"plan-review": PLAN_REVIEW_TEMPLATE_KO,
|
||||
"plan-fix": PLAN_FIX_TEMPLATE_KO,
|
||||
"review-only": REVIEW_ONLY_TEMPLATE_KO,
|
||||
"aggregate-review": AGGREGATE_REVIEW_TEMPLATE_KO,
|
||||
},
|
||||
@@ -843,56 +897,75 @@ def _build_review_only_preset(
|
||||
def _build_plan_review_preset(
|
||||
coders: list[str], reviewers: list[str], seniors: list[str],
|
||||
) -> list[StepConfig]:
|
||||
"""Plan-review: reviewers audit planning docs before implementation."""
|
||||
"""Plan-review: review planning docs, revise them, then verify in a loop."""
|
||||
if not coders:
|
||||
raise ValueError("'plan-review' preset requires at least 1 coder")
|
||||
if not reviewers:
|
||||
raise ValueError("'plan-review' preset requires at least 1 reviewer")
|
||||
|
||||
if len(reviewers) == 1 and not seniors:
|
||||
return [
|
||||
review_steps: list[StepConfig] = []
|
||||
if len(reviewers) == 1:
|
||||
review_steps.append(
|
||||
StepConfig(
|
||||
name="plan_review",
|
||||
agent=reviewers[0],
|
||||
role="review",
|
||||
prompt_template="default:plan-review",
|
||||
output_key="plan_review_result",
|
||||
verdict=True,
|
||||
),
|
||||
]
|
||||
|
||||
steps: list[StepConfig] = []
|
||||
)
|
||||
review_step_names = ["plan_review"]
|
||||
review_output_keys = ["plan_review_result"]
|
||||
else:
|
||||
reviewer_keys = _unique_safe_keys(reviewers)
|
||||
for reviewer, rk in zip(reviewers, reviewer_keys):
|
||||
steps.append(
|
||||
review_steps.append(
|
||||
StepConfig(
|
||||
name=f"plan_review_{rk}",
|
||||
agent=reviewer,
|
||||
role="review",
|
||||
prompt_template="default:plan-review",
|
||||
output_key=f"plan_review_{rk}",
|
||||
verdict=not seniors,
|
||||
parallel=True,
|
||||
),
|
||||
)
|
||||
if seniors:
|
||||
step_names = [f"plan_review_{rk}" for rk in reviewer_keys]
|
||||
output_keys = [f"plan_review_{rk}" for rk in reviewer_keys]
|
||||
steps.append(
|
||||
review_step_names = [f"plan_review_{rk}" for rk in reviewer_keys]
|
||||
review_output_keys = [f"plan_review_{rk}" for rk in reviewer_keys]
|
||||
|
||||
fix_coder = coders[0]
|
||||
senior_agent = seniors[0] if seniors else reviewers[0]
|
||||
|
||||
return review_steps + [
|
||||
StepConfig(
|
||||
name="senior_review",
|
||||
agent=seniors[0],
|
||||
name="aggregate_review",
|
||||
agent=senior_agent,
|
||||
role="review",
|
||||
prompt_template="default:aggregate-review",
|
||||
output_key="senior_review_result",
|
||||
verdict=True,
|
||||
output_key="aggregate_review",
|
||||
context_override={
|
||||
"candidate_outputs": "Planning documents under review (plan/checklist/reference docs).",
|
||||
"candidate_outputs": "Current planning package under review (plan/checklist/reference docs).",
|
||||
"reviews_bundle": _build_named_bundle(
|
||||
reviewers, step_names, output_keys, "Review",
|
||||
reviewers, review_step_names, review_output_keys, "Review",
|
||||
),
|
||||
},
|
||||
),
|
||||
)
|
||||
return steps
|
||||
StepConfig(
|
||||
name="plan_fix",
|
||||
agent=fix_coder,
|
||||
role="coding",
|
||||
prompt_template="default:plan-fix",
|
||||
output_key="plan_fix_output",
|
||||
context_override={"feedback": "{aggregate_review}"},
|
||||
),
|
||||
StepConfig(
|
||||
name="verify",
|
||||
agent=senior_agent,
|
||||
role="review",
|
||||
prompt_template="default:plan-review",
|
||||
output_key="verify_result",
|
||||
verdict=True,
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
def _build_review_fix_preset(
|
||||
|
||||
@@ -42,6 +42,8 @@ from cross_eval.prompts import (
|
||||
REVIEW_TEMPLATE_KO,
|
||||
PLAN_REVIEW_TEMPLATE,
|
||||
PLAN_REVIEW_TEMPLATE_KO,
|
||||
PLAN_FIX_TEMPLATE,
|
||||
PLAN_FIX_TEMPLATE_KO,
|
||||
REVIEW_ONLY_TEMPLATE,
|
||||
REVIEW_ONLY_TEMPLATE_KO,
|
||||
AGGREGATE_REVIEW_TEMPLATE,
|
||||
@@ -310,7 +312,23 @@ class BuiltinAgentConfigTest(unittest.TestCase):
|
||||
self.assertIn("Repeated Aggregate Findings", report)
|
||||
self.assertIn("same as iteration 3", report)
|
||||
|
||||
def test_review_fix_defaults_senior_from_reviewer_family(self) -> None:
|
||||
def test_fix_and_plan_presets_default_senior_from_reviewer_family(self) -> None:
|
||||
self.assertEqual(
|
||||
_default_seniors_for_preset(
|
||||
"preset:plan-review",
|
||||
["codex-reviewer"],
|
||||
BUILTIN_AGENTS,
|
||||
),
|
||||
["codex-senior"],
|
||||
)
|
||||
self.assertEqual(
|
||||
_default_seniors_for_preset(
|
||||
"preset:plan-review",
|
||||
["claude-reviewer"],
|
||||
BUILTIN_AGENTS,
|
||||
),
|
||||
["claude-senior"],
|
||||
)
|
||||
self.assertEqual(
|
||||
_default_seniors_for_preset(
|
||||
"preset:review-fix",
|
||||
@@ -421,23 +439,49 @@ class BuiltinAgentConfigTest(unittest.TestCase):
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
[step.output_key for step in steps],
|
||||
[step.output_key for step in steps[:2]],
|
||||
["plan_review_codex_reviewer", "plan_review_codex_reviewer_2"],
|
||||
)
|
||||
|
||||
def test_plan_review_with_senior_adds_aggregate_step(self) -> None:
|
||||
def test_plan_review_builds_review_fix_verify_loop(self) -> None:
|
||||
steps = _build_plan_review_preset(
|
||||
["codex-coder"],
|
||||
["claude-reviewer", "codex-reviewer"],
|
||||
["claude-senior"],
|
||||
)
|
||||
|
||||
self.assertEqual(steps[-1].name, "senior_review")
|
||||
self.assertEqual(steps[-1].agent, "claude-senior")
|
||||
self.assertTrue(steps[-1].verdict)
|
||||
self.assertEqual(
|
||||
[step.name for step in steps],
|
||||
[
|
||||
"plan_review_claude_reviewer",
|
||||
"plan_review_codex_reviewer",
|
||||
"aggregate_review",
|
||||
"plan_fix",
|
||||
"verify",
|
||||
],
|
||||
)
|
||||
self.assertEqual(steps[2].agent, "claude-senior")
|
||||
self.assertEqual(steps[3].agent, "codex-coder")
|
||||
self.assertEqual(steps[4].agent, "claude-senior")
|
||||
self.assertTrue(steps[4].verdict)
|
||||
self.assertFalse(steps[0].verdict)
|
||||
self.assertFalse(steps[1].verdict)
|
||||
|
||||
def test_plan_review_single_reviewer_uses_default_loop_steps(self) -> None:
|
||||
steps = _build_plan_review_preset(
|
||||
["codex-coder"],
|
||||
["codex-reviewer"],
|
||||
[],
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
[step.name for step in steps],
|
||||
["plan_review", "aggregate_review", "plan_fix", "verify"],
|
||||
)
|
||||
self.assertEqual(steps[1].agent, "codex-reviewer")
|
||||
self.assertEqual(steps[2].prompt_template, "default:plan-fix")
|
||||
self.assertTrue(steps[3].verdict)
|
||||
|
||||
def test_cross_review_duplicate_coders_get_unique_step_keys(self) -> None:
|
||||
steps = _build_cross_review_preset(
|
||||
["codex-coder", "codex-coder"],
|
||||
@@ -576,6 +620,8 @@ class PromptTemplateTest(unittest.TestCase):
|
||||
"""Coding templates should tell coder to ignore DISMISSED items."""
|
||||
self.assertIn("DISMISSED", CODING_TEMPLATE)
|
||||
self.assertIn("DISMISSED", CODING_TEMPLATE_KO)
|
||||
self.assertIn("DISMISSED", PLAN_FIX_TEMPLATE)
|
||||
self.assertIn("DISMISSED", PLAN_FIX_TEMPLATE_KO)
|
||||
|
||||
def test_aggregate_templates_dismissed_structure(self) -> None:
|
||||
"""Aggregate templates should use [False positive] / [Already fixed] tags."""
|
||||
@@ -583,6 +629,10 @@ class PromptTemplateTest(unittest.TestCase):
|
||||
self.assertIn("[Already fixed]", AGGREGATE_REVIEW_TEMPLATE)
|
||||
self.assertIn("[오탐]", AGGREGATE_REVIEW_TEMPLATE_KO)
|
||||
self.assertIn("[수정 완료]", AGGREGATE_REVIEW_TEMPLATE_KO)
|
||||
self.assertIn("{candidate_outputs}", AGGREGATE_REVIEW_TEMPLATE)
|
||||
self.assertIn("{reviews_bundle}", AGGREGATE_REVIEW_TEMPLATE)
|
||||
self.assertIn("{candidate_outputs}", AGGREGATE_REVIEW_TEMPLATE_KO)
|
||||
self.assertIn("{reviews_bundle}", AGGREGATE_REVIEW_TEMPLATE_KO)
|
||||
|
||||
|
||||
class ReviewMetricsParsingTest(unittest.TestCase):
|
||||
@@ -1033,6 +1083,34 @@ class FixPresetBehaviorTest(unittest.TestCase):
|
||||
self.assertTrue(captured["agentic"])
|
||||
self.assertEqual(captured["phase_max"], 3)
|
||||
|
||||
def test_run_preset_plan_review_auto_enables_agentic_without_flag(self) -> None:
|
||||
captured: dict[str, object] = {}
|
||||
|
||||
def _fake_run_pipeline(config, **kwargs):
|
||||
captured["preset"] = config.preset_name
|
||||
captured["agentic"] = config.agents[config.coders[0]].agentic
|
||||
captured["seniors"] = list(config.seniors)
|
||||
captured["steps"] = [step.name for step in config.pipeline]
|
||||
captured["max_iter"] = config.max_iterations
|
||||
return PipelineResult(
|
||||
iterations=[],
|
||||
final_verdict="PASS",
|
||||
run_dir=Path(".cross-eval/output"),
|
||||
)
|
||||
|
||||
with patch("cross_eval.pipeline.run_pipeline", side_effect=_fake_run_pipeline):
|
||||
exit_code = main(["run", "--preset", "plan-review", "--dry-run"])
|
||||
|
||||
self.assertEqual(exit_code, 0)
|
||||
self.assertEqual(captured["preset"], "plan-review")
|
||||
self.assertTrue(captured["agentic"])
|
||||
self.assertEqual(captured["seniors"], ["claude-senior"])
|
||||
self.assertEqual(
|
||||
captured["steps"],
|
||||
["plan_review", "aggregate_review", "plan_fix", "verify"],
|
||||
)
|
||||
self.assertEqual(captured["max_iter"], 3)
|
||||
|
||||
def test_run_senior_model_override_applies_only_to_seniors(self) -> None:
|
||||
captured: dict[str, list[str]] = {}
|
||||
|
||||
|
||||
@@ -13,7 +13,11 @@ from cross_eval.models import (
|
||||
StepConfig,
|
||||
)
|
||||
from cross_eval.pipeline import run_pipeline
|
||||
from cross_eval.prompts import _build_review_fix_preset, _build_simple_preset
|
||||
from cross_eval.prompts import (
|
||||
_build_plan_review_preset,
|
||||
_build_review_fix_preset,
|
||||
_build_simple_preset,
|
||||
)
|
||||
|
||||
|
||||
def _make_mock_agent(outputs: list[str]):
|
||||
@@ -262,6 +266,60 @@ class TestPhasedPipelineEscalateBreaksPhase(unittest.TestCase):
|
||||
self.assertTrue(len(result.escalated_issues) > 0)
|
||||
|
||||
|
||||
class TestPlanReviewPipelineLoopsUntilVerifyPass(unittest.TestCase):
|
||||
"""Document plan-review should revise docs and re-verify across iterations."""
|
||||
|
||||
def test_plan_review_fail_then_pass(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
coders = ["claude-coder"]
|
||||
reviewers = ["claude-reviewer"]
|
||||
seniors = ["claude-senior"]
|
||||
steps = _build_plan_review_preset(coders, reviewers, seniors)
|
||||
|
||||
config = PipelineConfig(
|
||||
output_dir=Path(tmpdir),
|
||||
max_iterations=4,
|
||||
min_iterations=1,
|
||||
language="en",
|
||||
inputs={
|
||||
"plan": "Test plan",
|
||||
"checklist": "Test checklist",
|
||||
"docs": "Reference docs",
|
||||
},
|
||||
agents=dict(BUILTIN_AGENTS),
|
||||
coders=coders,
|
||||
reviewers=reviewers,
|
||||
seniors=seniors,
|
||||
pipeline=steps,
|
||||
preset_name="plan-review",
|
||||
)
|
||||
|
||||
mock = _make_step_mock({
|
||||
"plan_review": [
|
||||
"Requirements are ambiguous\n\nVERDICT: FAIL",
|
||||
"Looks aligned\n\nVERDICT: PASS",
|
||||
],
|
||||
"aggregate_review": [
|
||||
"### Confirmed Issues\n- Clarify acceptance criteria\n\n"
|
||||
"### Action Items\n1. Tighten the checklist\n\nVERDICT: FAIL",
|
||||
"### Confirmed Issues\nNone\n\n"
|
||||
"### Dismissed Findings\nNone\n\n"
|
||||
"### Action Items\n1. No document changes needed\n\nVERDICT: PASS",
|
||||
],
|
||||
"plan_fix": ["Updated plan and checklist", "No-op"],
|
||||
"verify": [
|
||||
"Still missing edge-case criteria\n\nVERDICT: FAIL",
|
||||
"Planning package is now implementable\n\nVERDICT: PASS",
|
||||
],
|
||||
})
|
||||
|
||||
with patch("cross_eval.pipeline.invoke_agent", side_effect=mock):
|
||||
result = run_pipeline(config)
|
||||
|
||||
self.assertEqual(result.final_verdict, "PASS")
|
||||
self.assertEqual(len(result.iterations), 2)
|
||||
|
||||
|
||||
class TestAutoEscalateFiresWithoutSenior(unittest.TestCase):
|
||||
"""Test 6: simple pipeline without senior, same FAIL feedback 3 times -> auto-escalate."""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user