fix(agent-ops): 구현 체크리스트 완료 판정을 완화한다

This commit is contained in:
toki 2026-07-26 12:11:57 +09:00
parent 8d782e8fc3
commit c6ea3bdc87
3 changed files with 19 additions and 43 deletions

View file

@ -99,7 +99,7 @@ Keep control prompts in English, insert absolute paths only, and do not expand t
Never ask a worker, self-check, or review model to create, edit, or summarize `WORK_LOG.md`.
Do not treat Pi self-check exit code `0` as success by itself. Set `selfcheck_done=true` only when the implementation-completion table, `구현 체크리스트`, `계획 대비 변경 사항`, `주요 설계 결정`, `검증 결과`, and final CODE_REVIEW synchronization item in `CODE_REVIEW_PATH` all contain real content. If any field is empty, retry with the same prompt. After 10 consecutive incomplete results, block that task and continue draining independent work.
Do not treat Pi self-check exit code `0` as success by itself. Set `selfcheck_done=true` only when `## 구현 체크리스트` in `CODE_REVIEW_PATH` contains at least one Markdown list checkbox and every `[...]` checkbox value has at least one non-whitespace character. Accept any non-empty value, including `x`, `v`, and `✅`. Do not inspect `## 구현 항목별 완료 여부`, `계획 대비 변경 사항`, `주요 설계 결정`, `검증 결과`, or final CODE_REVIEW synchronization text. If the checklist condition fails, retry with the same prompt. After 10 consecutive incomplete results, block that task and continue draining independent work.
For Pi recovery attempts, pass only `Read {PLAN_PATH}. Continue.` without a locator explanation. For other CLI escalation attempts, pass `Continue from {LOCATOR_PATH}. Check the saved context and current workspace. Final in Korean.` Preserve the collaboration prohibition and next-state-materialization sentence in official-review escalation and recovery prompts. Do not ask the model to write a separate handoff summary.

View file

@ -38,7 +38,9 @@ VERDICT_BLOCK_RE = re.compile(
PLAN_IDENTITY_RE = re.compile(
r"<!--\s+task=(?P<task>\S+)\s+plan=(?P<plan>\d+)\s+tag=(?P<tag>\S+)\s+-->"
)
FINAL_CHECK_RE = re.compile(r"^-\s+\[[xX]\]\s+CODE_REVIEW-\*-G\?\?\.md", re.MULTILINE)
IMPLEMENTATION_CHECKBOX_RE = re.compile(
r"^-\s+\[([^\]\r\n]*)\]", re.MULTILINE
)
WORK_LOG_NAME = "WORK_LOG.md"
WORK_LOG_ARCHIVE_RE = re.compile(r"^work_log_(\d+)\.log$")
AGENT_PROCESS_MARKER_ENV = "IOP_AGENT_TASK_EXECUTION_ID"
@ -1459,8 +1461,6 @@ def task_stage(task: Task, state: dict[str, Any]) -> str:
if task_requires_selfcheck(task, state) and not state.get("selfcheck_done"):
return "selfcheck"
return "review"
if task.review and task.review.exists() and FINAL_CHECK_RE.search(text):
return "review"
return "worker"
@ -1477,23 +1477,11 @@ def implementation_review_errors(task: Task) -> list[str]:
if task.review is None or not task.review.is_file():
return ["CODE_REVIEW 파일 없음"]
text = task.review.read_text(encoding="utf-8", errors="replace")
errors: list[str] = []
checklist = markdown_section(text, "구현 체크리스트")
if not checklist or re.search(r"^-\s+\[\s\]", checklist, re.MULTILINE):
errors.append("구현 체크리스트 미완료")
completion = markdown_section(text, "구현 항목별 완료 여부")
if not completion or re.search(r"\|\s*\[\s\]\s*\|", completion):
errors.append("구현 항목 완료 여부 미작성")
for heading in ("계획 대비 변경 사항", "주요 설계 결정"):
body = markdown_section(text, heading)
if not body or re.fullmatch(r"_[^_]*_", body, re.DOTALL):
errors.append(f"{heading} 미작성")
verification = markdown_section(text, "검증 결과")
if not verification or re.search(r"_미실행(?:[^_]*)_", verification):
errors.append("검증 결과 미작성")
if not FINAL_CHECK_RE.search(text):
errors.append("CODE_REVIEW 동기화 체크 미완료")
return errors
checkbox_values = IMPLEMENTATION_CHECKBOX_RE.findall(checklist)
if not checkbox_values or any(not value.strip() for value in checkbox_values):
return ["구현 체크리스트 미완료"]
return []
def classify_failure_with_evidence(output: str) -> tuple[str, str | None]:

View file

@ -254,14 +254,14 @@ class TaskStageTest(unittest.TestCase):
task.recovery = True
self.assertEqual(dispatch.task_stage(task, {}), "blocked")
def test_completed_implementation_checklist_starts_review(self):
def test_completed_implementation_checklist_does_not_bypass_worker(self):
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
task = self.make_task(
root,
"- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 채운다.\n",
)
self.assertEqual(dispatch.task_stage(task, {}), "review")
self.assertEqual(dispatch.task_stage(task, {}), "worker")
def test_pi_worker_success_requires_selfcheck_before_review(self):
with tempfile.TemporaryDirectory() as temporary:
@ -329,41 +329,29 @@ class TaskStageTest(unittest.TestCase):
self.assertIs(created, semaphores["pi:laguna-s:2.1"])
self.assertEqual(created._value, 64)
def test_selfcheck_requires_all_implementation_review_fields(self):
def test_selfcheck_requires_nonempty_checklist_values(self):
with tempfile.TemporaryDirectory() as temporary:
task = self.make_task(
Path(temporary),
"## 구현 항목별 완료 여부\n\n"
"| 항목 | 완료 여부 |\n|---|---|\n| TEST-1 | [ ] |\n\n"
"## 구현 체크리스트\n\n- [ ] TEST-1\n"
"- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 채운다.\n\n"
"## 계획 대비 변경 사항\n\n_미작성_\n\n"
"## 주요 설계 결정\n\n_미작성_\n\n"
"## 검증 결과\n\n결과:\n_미실행_\n",
"## 구현 체크리스트\n\n- [ ] TEST-1\n",
)
self.assertEqual(
dispatch.implementation_review_errors(task),
[
"구현 체크리스트 미완료",
"구현 항목 완료 여부 미작성",
"계획 대비 변경 사항 미작성",
"주요 설계 결정 미작성",
"검증 결과 미작성",
"CODE_REVIEW 동기화 체크 미완료",
],
["구현 체크리스트 미완료"],
)
def test_selfcheck_accepts_filled_implementation_review_fields(self):
def test_selfcheck_accepts_any_nonempty_checklist_values(self):
with tempfile.TemporaryDirectory() as temporary:
task = self.make_task(
Path(temporary),
"## 구현 항목별 완료 여부\n\n"
"| 항목 | 완료 여부 |\n|---|---|\n| TEST-1 | [x] |\n\n"
"## 구현 체크리스트\n\n- [x] TEST-1\n"
"- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 채운다.\n\n"
"## 계획 대비 변경 사항\n\n계획과 동일하게 구현했다.\n\n"
"## 주요 설계 결정\n\n상태 전이를 직렬화했다.\n\n"
"## 검증 결과\n\n결과: PASS (exit 0)\n",
"| 항목 | 완료 여부 |\n|---|---|\n| TEST-1 | [ ] |\n\n"
"## 구현 체크리스트\n\n"
"- [x] TEST-1\n"
"- [v] TEST-2\n"
"- [✅] TEST-3\n",
)
self.assertEqual(dispatch.implementation_review_errors(task), [])