Make plan-review a review-fix-verify loop
This commit is contained in:
@@ -53,7 +53,7 @@ agents:
|
|||||||
# 방법 1: 프리셋 사용 (사용자가 pipeline YAML 직접 작성할 필요 없음)
|
# 방법 1: 프리셋 사용 (사용자가 pipeline YAML 직접 작성할 필요 없음)
|
||||||
pipeline: preset:simple # "A 생성 → B 리뷰" (기본값)
|
pipeline: preset:simple # "A 생성 → B 리뷰" (기본값)
|
||||||
# pipeline: preset:cross-review # "둘 다 생성 → 서로 리뷰"
|
# pipeline: preset:cross-review # "둘 다 생성 → 서로 리뷰"
|
||||||
# pipeline: preset:plan-review # "구현 전 문서/기획 검토"
|
# pipeline: preset:plan-review # "구현 전 문서 리뷰 → 수정 → 재검증 반복"
|
||||||
# pipeline: preset:coding-review-fix # "초기 코딩 1회 → 리뷰/수정 반복"
|
# pipeline: preset:coding-review-fix # "초기 코딩 1회 → 리뷰/수정 반복"
|
||||||
|
|
||||||
# 방법 2: 직접 커스텀 (고급 사용자용)
|
# 방법 2: 직접 커스텀 (고급 사용자용)
|
||||||
@@ -77,7 +77,7 @@ pipeline: preset:simple # "A 생성 → B 리뷰" (기본값)
|
|||||||
|--------|------|-------------------|
|
|--------|------|-------------------|
|
||||||
| `simple` | A 코딩 → B 리뷰 | coding(agent1) → review(agent2) |
|
| `simple` | A 코딩 → B 리뷰 | coding(agent1) → review(agent2) |
|
||||||
| `cross-review` | 둘 다 코딩, 서로 리뷰 | coding_a → coding_b → review_of_b(agent_a) → review_of_a(agent_b) |
|
| `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) |
|
| `coding-review-fix` | 초기 코딩 후 리뷰/수정 반복 | initial_coding(coding) → review_fix(review* → aggregate → coding → verify) |
|
||||||
|
|
||||||
프리셋은 내부적으로 적절한 pipeline steps + context_override를 자동 구성한다. agents에 정의된 순서대로 agent1, agent2가 배정된다. 프리셋이 불충분하면 직접 steps를 작성할 수 있다.
|
프리셋은 내부적으로 적절한 pipeline steps + context_override를 자동 구성한다. agents에 정의된 순서대로 agent1, agent2가 배정된다. 프리셋이 불충분하면 직접 steps를 작성할 수 있다.
|
||||||
@@ -185,3 +185,6 @@ final-report.md 생성
|
|||||||
--reviewer-effort high \
|
--reviewer-effort high \
|
||||||
--senior-effort xhigh \
|
--senior-effort xhigh \
|
||||||
--max-iter 10
|
--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가 리뷰 (기본값) |
|
| `simple` | Agent A가 코딩, Agent B가 리뷰 (기본값) |
|
||||||
| `cross-review` | 둘 다 코딩, 서로 교차 리뷰 |
|
| `cross-review` | 둘 다 코딩, 서로 교차 리뷰 |
|
||||||
| `plan-review` | 구현 전 기획서/체크리스트/참고문서를 검토하고 필요시 현재 코드베이스와의 정합성도 확인 |
|
| `plan-review` | 구현 전 기획서/체크리스트/참고문서를 검토하고 문서를 수정한 뒤 재검증까지 반복 |
|
||||||
| `review-only` | 기존 코드만 감사 용도로 검토 |
|
| `review-only` | 기존 코드만 감사 용도로 검토 |
|
||||||
| `review-fix` | 리뷰 결과를 취합한 뒤 자동 수정과 재검증까지 반복 |
|
| `review-fix` | 리뷰 결과를 취합한 뒤 자동 수정과 재검증까지 반복 |
|
||||||
| `coding-review-fix` | 초기 코딩 1회 후 리뷰 결과를 취합해 자동 수정과 재검증을 반복 |
|
| `coding-review-fix` | 초기 코딩 1회 후 리뷰 결과를 취합해 자동 수정과 재검증을 반복 |
|
||||||
@@ -120,6 +120,6 @@ pipeline: preset:simple
|
|||||||
```bash
|
```bash
|
||||||
# 초기화 옵션
|
# 초기화 옵션
|
||||||
cross-eval init --preset cross-review # 교차 리뷰 프리셋
|
cross-eval init --preset cross-review # 교차 리뷰 프리셋
|
||||||
cross-eval init --preset plan-review # 구현 전 문서 검토 프리셋
|
cross-eval init --preset plan-review # 문서 리뷰/수정/재검증 프리셋
|
||||||
cross-eval init --lang en # 영어 템플릿
|
cross-eval init --lang en # 영어 템플릿
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -205,7 +205,7 @@ def main(argv: list[str] | None = None) -> int:
|
|||||||
],
|
],
|
||||||
help=(
|
help=(
|
||||||
"파이프라인 종류 (기본: simple). "
|
"파이프라인 종류 (기본: simple). "
|
||||||
"simple=코딩+리뷰, cross-review=교차리뷰, plan-review=문서기획검토, "
|
"simple=코딩+리뷰, cross-review=교차리뷰, plan-review=문서리뷰수정재검증, "
|
||||||
"review-only=리뷰만, review-fix=리뷰수렴+자동수정, "
|
"review-only=리뷰만, review-fix=리뷰수렴+자동수정, "
|
||||||
"coding-review-fix=초기코딩후리뷰수렴"
|
"coding-review-fix=초기코딩후리뷰수렴"
|
||||||
),
|
),
|
||||||
@@ -291,8 +291,8 @@ def main(argv: list[str] | None = None) -> int:
|
|||||||
" │ coding- │ 3단계 파이프라인: │\n"
|
" │ coding- │ 3단계 파이프라인: │\n"
|
||||||
" │ review-fix │ 초기 코딩 1회 → 리뷰 취합 → 수정 → 재검증 반복 │\n"
|
" │ review-fix │ 초기 코딩 1회 → 리뷰 취합 → 수정 → 재검증 반복 │\n"
|
||||||
" ├──────────────┼─────────────────────────────────────────────────────┤\n"
|
" ├──────────────┼─────────────────────────────────────────────────────┤\n"
|
||||||
" │ plan-review │ 구현 전 기획서/체크리스트/문서를 검토 │\n"
|
" │ plan-review │ 구현 전 기획서/체크리스트/문서를 검토하고 │\n"
|
||||||
" │ │ 필요하면 현재 코드베이스와의 정합성도 점검 │\n"
|
" │ │ 수정한 뒤 시니어가 재검증할 때까지 반복 │\n"
|
||||||
" ├──────────────┼─────────────────────────────────────────────────────┤\n"
|
" ├──────────────┼─────────────────────────────────────────────────────┤\n"
|
||||||
" │ review-only │ 코드 작성 없이 Reviewer N명이 기존 코드만 검토 │\n"
|
" │ review-only │ 코드 작성 없이 Reviewer N명이 기존 코드만 검토 │\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"
|
" cross-eval run --plan plan.md --preset review-only \\\n"
|
||||||
" --reviewer claude --reviewer codex\n"
|
" --reviewer claude --reviewer codex\n"
|
||||||
"\n"
|
"\n"
|
||||||
" 구현 전 문서/기획 검토 (plan-review):\n"
|
" 문서 리뷰 + 수정 + 재검증 반복 (plan-review):\n"
|
||||||
" cross-eval run --plan plan.md --preset 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"
|
||||||
" 모델 변경:\n"
|
" 모델 변경:\n"
|
||||||
" cross-eval run --plan plan.md --model sonnet\n"
|
" cross-eval run --plan plan.md --model sonnet\n"
|
||||||
@@ -563,7 +563,7 @@ _PRESET_DESCRIPTIONS = {
|
|||||||
"simple": "코딩 + 리뷰 (가장 기본)",
|
"simple": "코딩 + 리뷰 (가장 기본)",
|
||||||
"review-fix": "리뷰 → 취합 → 수정 → 재검증 반복",
|
"review-fix": "리뷰 → 취합 → 수정 → 재검증 반복",
|
||||||
"coding-review-fix": "초기 코딩 + 리뷰 수렴 반복",
|
"coding-review-fix": "초기 코딩 + 리뷰 수렴 반복",
|
||||||
"plan-review": "구현 전 기획서/문서 검토",
|
"plan-review": "문서 리뷰 → 수정 → 재검증 반복",
|
||||||
"review-only": "기존 코드만 리뷰 (코딩 없음)",
|
"review-only": "기존 코드만 리뷰 (코딩 없음)",
|
||||||
"cross-review": "2명이 각각 구현 후 교차 리뷰",
|
"cross-review": "2명이 각각 구현 후 교차 리뷰",
|
||||||
}
|
}
|
||||||
@@ -929,7 +929,7 @@ def cmd_run(args: argparse.Namespace) -> int:
|
|||||||
elif preset in PIPELINE_PRESETS:
|
elif preset in PIPELINE_PRESETS:
|
||||||
config.pipeline = PIPELINE_PRESETS[preset](coders, reviewers, seniors)
|
config.pipeline = PIPELINE_PRESETS[preset](coders, reviewers, seniors)
|
||||||
config.phases = []
|
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
|
config.max_iterations = 1
|
||||||
|
|
||||||
sync_phased_iterations(config)
|
sync_phased_iterations(config)
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ DEFAULT_ROLE_REASONING_EFFORTS = {
|
|||||||
"reviewer": "medium",
|
"reviewer": "medium",
|
||||||
"senior": "high",
|
"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."""
|
"""Infer a default senior agent for presets that benefit from adjudication."""
|
||||||
if not (
|
if not (
|
||||||
isinstance(pipeline_raw, str)
|
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
|
and reviewers
|
||||||
):
|
):
|
||||||
return []
|
return []
|
||||||
|
|||||||
@@ -472,12 +472,58 @@ PLAN_REVIEW_TEMPLATE_KO = """\
|
|||||||
그렇지 않으면: VERDICT: FAIL
|
그렇지 않으면: 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 = """\
|
AGGREGATE_REVIEW_TEMPLATE = """\
|
||||||
You are adjudicating multiple review results and turning them into an actionable decision.
|
You are adjudicating multiple review results and turning them into an actionable decision.
|
||||||
|
|
||||||
## Artifact References
|
## Artifact References
|
||||||
{artifact_references}
|
{artifact_references}
|
||||||
|
|
||||||
|
## Candidate Artifact Under Review
|
||||||
|
{candidate_outputs}
|
||||||
|
|
||||||
|
## Reviewer Findings Bundle
|
||||||
|
{reviews_bundle}
|
||||||
|
|
||||||
## Previous Issue Tracker
|
## Previous Issue Tracker
|
||||||
{previous_senior_tracker}
|
{previous_senior_tracker}
|
||||||
|
|
||||||
@@ -486,19 +532,19 @@ You are adjudicating multiple review results and turning them into an actionable
|
|||||||
|
|
||||||
## Instructions
|
## Instructions
|
||||||
Read the referenced plan/checklist/docs/review artifacts directly from disk. \
|
Read the referenced plan/checklist/docs/review artifacts directly from disk. \
|
||||||
Explore the project directory and the referenced git commit/diff to confirm the \
|
Inspect the repository and referenced artifacts only as needed to confirm the \
|
||||||
current codebase state. Use the execution evidence above to verify claims against \
|
current target state. Use the execution evidence above to verify claims against \
|
||||||
actual command outputs, artifact paths, and exit codes. Then:
|
actual command outputs, artifact paths, and exit codes. Then:
|
||||||
1. Deduplicate overlapping issues across reviewers.
|
1. Deduplicate overlapping issues across reviewers.
|
||||||
2. Resolve disagreements explicitly.
|
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.
|
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).
|
6. Maintain the Issue Tracker table across iterations (carry forward unresolved issues).
|
||||||
7. If no confirmed issue remains, output VERDICT: PASS.
|
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, \
|
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 VERDICT: ESCALATE.
|
||||||
|
|
||||||
## Output Format
|
## Output Format
|
||||||
@@ -512,8 +558,8 @@ output VERDICT: ESCALATE.
|
|||||||
(Write "None" if nothing was dismissed.)
|
(Write "None" if nothing was dismissed.)
|
||||||
|
|
||||||
### Action Items
|
### Action Items
|
||||||
1. Concrete fix the coder should make
|
1. Concrete fix the implementer/editor should make
|
||||||
2. Concrete fix the coder should make
|
2. Concrete fix the implementer/editor should make
|
||||||
|
|
||||||
## Issue Tracker
|
## Issue Tracker
|
||||||
|
|
||||||
@@ -536,6 +582,12 @@ AGGREGATE_REVIEW_TEMPLATE_KO = """\
|
|||||||
## 참조 아티팩트
|
## 참조 아티팩트
|
||||||
{artifact_references}
|
{artifact_references}
|
||||||
|
|
||||||
|
## 현재 검토 대상
|
||||||
|
{candidate_outputs}
|
||||||
|
|
||||||
|
## 리뷰 결과 묶음
|
||||||
|
{reviews_bundle}
|
||||||
|
|
||||||
## 이전 이슈 트래커
|
## 이전 이슈 트래커
|
||||||
{previous_senior_tracker}
|
{previous_senior_tracker}
|
||||||
|
|
||||||
@@ -543,17 +595,17 @@ AGGREGATE_REVIEW_TEMPLATE_KO = """\
|
|||||||
{execution_evidence}
|
{execution_evidence}
|
||||||
|
|
||||||
## 지침
|
## 지침
|
||||||
참조된 plan/checklist/docs/review markdown와 git 상태를 직접 읽어 현재 코드베이스 상태를 확인한 뒤, \
|
참조된 plan/checklist/docs/review markdown와 저장소 상태를 직접 읽어 현재 검토 대상의 상태를 확인한 뒤, \
|
||||||
위 실행 증거를 활용하여 에이전트의 주장을 실제 명령어 출력, 아티팩트 경로, 종료 코드로 검증하세요. \
|
위 실행 증거를 활용하여 에이전트의 주장을 실제 명령어 출력, 아티팩트 경로, 종료 코드로 검증하세요. \
|
||||||
그런 다음 아래를 수행하세요.
|
그런 다음 아래를 수행하세요.
|
||||||
1. 리뷰어들 사이에 중복되는 이슈를 합치세요.
|
1. 리뷰어들 사이에 중복되는 이슈를 합치세요.
|
||||||
2. 의견 충돌은 명시적으로 정리하세요.
|
2. 의견 충돌은 명시적으로 정리하세요.
|
||||||
3. 기획서, 체크리스트, 코드, 리뷰 근거로 뒷받침되는 이슈만 남기세요.
|
3. 기획서, 체크리스트, 참고 문서, 저장소 상태, 리뷰 근거로 뒷받침되는 이슈만 남기세요.
|
||||||
4. 근거가 엇갈리면 무엇이 확정이고 무엇이 기각 또는 추가확인 대상인지 분명히 적으세요.
|
4. 근거가 엇갈리면 무엇이 확정이고 무엇이 기각 또는 추가확인 대상인지 분명히 적으세요.
|
||||||
5. coder가 바로 수정할 수 있는 우선순위 액션 아이템을 만드세요.
|
5. 수정 담당자가 바로 처리할 수 있는 우선순위 액션 아이템을 만드세요.
|
||||||
6. 이슈 트래커 테이블을 반복 간에 유지하세요 (미해결 이슈를 이월).
|
6. 이슈 트래커 테이블을 반복 간에 유지하세요 (미해결 이슈를 이월).
|
||||||
7. 확정된 이슈가 없으면 VERDICT: PASS 를 출력하세요.
|
7. 확정된 이슈가 없으면 VERDICT: PASS 를 출력하세요.
|
||||||
8. coder가 수정 가능한 이슈가 있으면 VERDICT: FAIL 을 출력하세요.
|
8. 수정 담당자가 해결 가능한 이슈가 있으면 VERDICT: FAIL 을 출력하세요.
|
||||||
9. 사람의 개입이 필요한 이슈(모호한 요구사항, 아키텍처 결정, 외부 의존성 문제, \
|
9. 사람의 개입이 필요한 이슈(모호한 요구사항, 아키텍처 결정, 외부 의존성 문제, \
|
||||||
동일 이슈가 2회 이상 해결 실패)가 있으면 VERDICT: ESCALATE 를 출력하세요.
|
동일 이슈가 2회 이상 해결 실패)가 있으면 VERDICT: ESCALATE 를 출력하세요.
|
||||||
|
|
||||||
@@ -568,8 +620,8 @@ AGGREGATE_REVIEW_TEMPLATE_KO = """\
|
|||||||
(기각된 항목이 없으면 "없음"이라고 작성하세요.)
|
(기각된 항목이 없으면 "없음"이라고 작성하세요.)
|
||||||
|
|
||||||
### 액션 아이템
|
### 액션 아이템
|
||||||
1. coder가 수정해야 할 구체적인 작업
|
1. 수정 담당자가 처리해야 할 구체적인 작업
|
||||||
2. coder가 수정해야 할 구체적인 작업
|
2. 수정 담당자가 처리해야 할 구체적인 작업
|
||||||
|
|
||||||
## 이슈 트래커
|
## 이슈 트래커
|
||||||
|
|
||||||
@@ -592,6 +644,7 @@ DEFAULT_TEMPLATES: dict[str, dict[str, str]] = {
|
|||||||
"coding": CODING_TEMPLATE,
|
"coding": CODING_TEMPLATE,
|
||||||
"review": REVIEW_TEMPLATE,
|
"review": REVIEW_TEMPLATE,
|
||||||
"plan-review": PLAN_REVIEW_TEMPLATE,
|
"plan-review": PLAN_REVIEW_TEMPLATE,
|
||||||
|
"plan-fix": PLAN_FIX_TEMPLATE,
|
||||||
"review-only": REVIEW_ONLY_TEMPLATE,
|
"review-only": REVIEW_ONLY_TEMPLATE,
|
||||||
"aggregate-review": AGGREGATE_REVIEW_TEMPLATE,
|
"aggregate-review": AGGREGATE_REVIEW_TEMPLATE,
|
||||||
},
|
},
|
||||||
@@ -599,6 +652,7 @@ DEFAULT_TEMPLATES: dict[str, dict[str, str]] = {
|
|||||||
"coding": CODING_TEMPLATE_KO,
|
"coding": CODING_TEMPLATE_KO,
|
||||||
"review": REVIEW_TEMPLATE_KO,
|
"review": REVIEW_TEMPLATE_KO,
|
||||||
"plan-review": PLAN_REVIEW_TEMPLATE_KO,
|
"plan-review": PLAN_REVIEW_TEMPLATE_KO,
|
||||||
|
"plan-fix": PLAN_FIX_TEMPLATE_KO,
|
||||||
"review-only": REVIEW_ONLY_TEMPLATE_KO,
|
"review-only": REVIEW_ONLY_TEMPLATE_KO,
|
||||||
"aggregate-review": AGGREGATE_REVIEW_TEMPLATE_KO,
|
"aggregate-review": AGGREGATE_REVIEW_TEMPLATE_KO,
|
||||||
},
|
},
|
||||||
@@ -843,56 +897,75 @@ def _build_review_only_preset(
|
|||||||
def _build_plan_review_preset(
|
def _build_plan_review_preset(
|
||||||
coders: list[str], reviewers: list[str], seniors: list[str],
|
coders: list[str], reviewers: list[str], seniors: list[str],
|
||||||
) -> list[StepConfig]:
|
) -> 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:
|
if not reviewers:
|
||||||
raise ValueError("'plan-review' preset requires at least 1 reviewer")
|
raise ValueError("'plan-review' preset requires at least 1 reviewer")
|
||||||
|
|
||||||
if len(reviewers) == 1 and not seniors:
|
review_steps: list[StepConfig] = []
|
||||||
return [
|
if len(reviewers) == 1:
|
||||||
|
review_steps.append(
|
||||||
StepConfig(
|
StepConfig(
|
||||||
name="plan_review",
|
name="plan_review",
|
||||||
agent=reviewers[0],
|
agent=reviewers[0],
|
||||||
role="review",
|
role="review",
|
||||||
prompt_template="default:plan-review",
|
prompt_template="default:plan-review",
|
||||||
output_key="plan_review_result",
|
output_key="plan_review_result",
|
||||||
verdict=True,
|
|
||||||
),
|
),
|
||||||
]
|
)
|
||||||
|
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):
|
||||||
|
review_steps.append(
|
||||||
|
StepConfig(
|
||||||
|
name=f"plan_review_{rk}",
|
||||||
|
agent=reviewer,
|
||||||
|
role="review",
|
||||||
|
prompt_template="default:plan-review",
|
||||||
|
output_key=f"plan_review_{rk}",
|
||||||
|
parallel=True,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
review_step_names = [f"plan_review_{rk}" for rk in reviewer_keys]
|
||||||
|
review_output_keys = [f"plan_review_{rk}" for rk in reviewer_keys]
|
||||||
|
|
||||||
steps: list[StepConfig] = []
|
fix_coder = coders[0]
|
||||||
reviewer_keys = _unique_safe_keys(reviewers)
|
senior_agent = seniors[0] if seniors else reviewers[0]
|
||||||
for reviewer, rk in zip(reviewers, reviewer_keys):
|
|
||||||
steps.append(
|
return review_steps + [
|
||||||
StepConfig(
|
StepConfig(
|
||||||
name=f"plan_review_{rk}",
|
name="aggregate_review",
|
||||||
agent=reviewer,
|
agent=senior_agent,
|
||||||
role="review",
|
role="review",
|
||||||
prompt_template="default:plan-review",
|
prompt_template="default:aggregate-review",
|
||||||
output_key=f"plan_review_{rk}",
|
output_key="aggregate_review",
|
||||||
verdict=not seniors,
|
context_override={
|
||||||
parallel=True,
|
"candidate_outputs": "Current planning package under review (plan/checklist/reference docs).",
|
||||||
),
|
"reviews_bundle": _build_named_bundle(
|
||||||
)
|
reviewers, review_step_names, review_output_keys, "Review",
|
||||||
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(
|
StepConfig(
|
||||||
StepConfig(
|
name="plan_fix",
|
||||||
name="senior_review",
|
agent=fix_coder,
|
||||||
agent=seniors[0],
|
role="coding",
|
||||||
role="review",
|
prompt_template="default:plan-fix",
|
||||||
prompt_template="default:aggregate-review",
|
output_key="plan_fix_output",
|
||||||
output_key="senior_review_result",
|
context_override={"feedback": "{aggregate_review}"},
|
||||||
verdict=True,
|
),
|
||||||
context_override={
|
StepConfig(
|
||||||
"candidate_outputs": "Planning documents under review (plan/checklist/reference docs).",
|
name="verify",
|
||||||
"reviews_bundle": _build_named_bundle(
|
agent=senior_agent,
|
||||||
reviewers, step_names, output_keys, "Review",
|
role="review",
|
||||||
),
|
prompt_template="default:plan-review",
|
||||||
},
|
output_key="verify_result",
|
||||||
),
|
verdict=True,
|
||||||
)
|
),
|
||||||
return steps
|
]
|
||||||
|
|
||||||
|
|
||||||
def _build_review_fix_preset(
|
def _build_review_fix_preset(
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ from cross_eval.prompts import (
|
|||||||
REVIEW_TEMPLATE_KO,
|
REVIEW_TEMPLATE_KO,
|
||||||
PLAN_REVIEW_TEMPLATE,
|
PLAN_REVIEW_TEMPLATE,
|
||||||
PLAN_REVIEW_TEMPLATE_KO,
|
PLAN_REVIEW_TEMPLATE_KO,
|
||||||
|
PLAN_FIX_TEMPLATE,
|
||||||
|
PLAN_FIX_TEMPLATE_KO,
|
||||||
REVIEW_ONLY_TEMPLATE,
|
REVIEW_ONLY_TEMPLATE,
|
||||||
REVIEW_ONLY_TEMPLATE_KO,
|
REVIEW_ONLY_TEMPLATE_KO,
|
||||||
AGGREGATE_REVIEW_TEMPLATE,
|
AGGREGATE_REVIEW_TEMPLATE,
|
||||||
@@ -310,7 +312,23 @@ class BuiltinAgentConfigTest(unittest.TestCase):
|
|||||||
self.assertIn("Repeated Aggregate Findings", report)
|
self.assertIn("Repeated Aggregate Findings", report)
|
||||||
self.assertIn("same as iteration 3", 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(
|
self.assertEqual(
|
||||||
_default_seniors_for_preset(
|
_default_seniors_for_preset(
|
||||||
"preset:review-fix",
|
"preset:review-fix",
|
||||||
@@ -421,23 +439,49 @@ class BuiltinAgentConfigTest(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(
|
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"],
|
["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(
|
steps = _build_plan_review_preset(
|
||||||
["codex-coder"],
|
["codex-coder"],
|
||||||
["claude-reviewer", "codex-reviewer"],
|
["claude-reviewer", "codex-reviewer"],
|
||||||
["claude-senior"],
|
["claude-senior"],
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(steps[-1].name, "senior_review")
|
self.assertEqual(
|
||||||
self.assertEqual(steps[-1].agent, "claude-senior")
|
[step.name for step in steps],
|
||||||
self.assertTrue(steps[-1].verdict)
|
[
|
||||||
|
"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[0].verdict)
|
||||||
self.assertFalse(steps[1].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:
|
def test_cross_review_duplicate_coders_get_unique_step_keys(self) -> None:
|
||||||
steps = _build_cross_review_preset(
|
steps = _build_cross_review_preset(
|
||||||
["codex-coder", "codex-coder"],
|
["codex-coder", "codex-coder"],
|
||||||
@@ -576,6 +620,8 @@ class PromptTemplateTest(unittest.TestCase):
|
|||||||
"""Coding templates should tell coder to ignore DISMISSED items."""
|
"""Coding templates should tell coder to ignore DISMISSED items."""
|
||||||
self.assertIn("DISMISSED", CODING_TEMPLATE)
|
self.assertIn("DISMISSED", CODING_TEMPLATE)
|
||||||
self.assertIn("DISMISSED", CODING_TEMPLATE_KO)
|
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:
|
def test_aggregate_templates_dismissed_structure(self) -> None:
|
||||||
"""Aggregate templates should use [False positive] / [Already fixed] tags."""
|
"""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("[Already fixed]", AGGREGATE_REVIEW_TEMPLATE)
|
||||||
self.assertIn("[오탐]", AGGREGATE_REVIEW_TEMPLATE_KO)
|
self.assertIn("[오탐]", AGGREGATE_REVIEW_TEMPLATE_KO)
|
||||||
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):
|
class ReviewMetricsParsingTest(unittest.TestCase):
|
||||||
@@ -1033,6 +1083,34 @@ class FixPresetBehaviorTest(unittest.TestCase):
|
|||||||
self.assertTrue(captured["agentic"])
|
self.assertTrue(captured["agentic"])
|
||||||
self.assertEqual(captured["phase_max"], 3)
|
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:
|
def test_run_senior_model_override_applies_only_to_seniors(self) -> None:
|
||||||
captured: dict[str, list[str]] = {}
|
captured: dict[str, list[str]] = {}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,11 @@ from cross_eval.models import (
|
|||||||
StepConfig,
|
StepConfig,
|
||||||
)
|
)
|
||||||
from cross_eval.pipeline import run_pipeline
|
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]):
|
def _make_mock_agent(outputs: list[str]):
|
||||||
@@ -262,6 +266,60 @@ class TestPhasedPipelineEscalateBreaksPhase(unittest.TestCase):
|
|||||||
self.assertTrue(len(result.escalated_issues) > 0)
|
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):
|
class TestAutoEscalateFiresWithoutSenior(unittest.TestCase):
|
||||||
"""Test 6: simple pipeline without senior, same FAIL feedback 3 times -> auto-escalate."""
|
"""Test 6: simple pipeline without senior, same FAIL feedback 3 times -> auto-escalate."""
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user