# task-634.1: chain_manager.py 듀얼 태스크 번호 버그 수정

## 작업 완료
2026-03-17 09:35

---

## 문제 현상
chain-scoped-628에서 Phase 3에 대해 2개의 task가 생성됨:
- task-632.1 (09:10:44 시작)
- task-633.1 (09:12:26 시작)

둘 다 동일 내용. 본래 task-628.3이어야 하는데 전혀 다른 번호가 할당됨.

---

## 근본 원인 분석

### 원인 1: FAIL 감지 오탐 (거짓 양성) ✅ 수정 완료
**기존 코드 (라인 337):**
```python
if "FAIL" in content:
    chain_data["status"] = "stalled"
```

**문제:**
- 단순 문자열 매칭이라 "QC FAIL (범위 외 기존 테스트 1건)" 같은 맥락 무시
- "FAIL" 문자가 보고서 어디든 있으면 체인 강제 정지

**수정 후:**
```python
import re

# 패턴 1: 명시적 판정 라인
verdict_pattern = r'(?:종합|overall|최종)\s*(?:판정|verdict|결과)[:\s]*FAIL'
# 패턴 2: QC FAIL (단, "범위 외" 제외)
qc_fail_pattern = r'QC\s+FAIL(?!\s*[\(\[]?\s*범위\s*외)'

if re.search(verdict_pattern, content, re.IGNORECASE) or re.search(qc_fail_pattern, content):
    chain_data["status"] = "stalled"
```

### 원인 2: 멱등성 미보장 ✅ 수정 완료

**기존 코드:**
- 이미 done인 task에 대한 재호출 차단 없음
- 다음 task가 이미 running이어도 재dispatch 가능

**수정 내용:**

1. **이미 done인 task 재호출 감지 (라인 325~333):**
```python
original_status = target_task.get("status")
if original_status == "done" and target_task.get("completed_at"):
    output = {
        "action": "already_done",
        "task_id": task_id,
        "message": "Task already marked as done",
    }
    print(json.dumps(output, ensure_ascii=False, indent=2))
    return
```

2. **다음 task가 이미 running이면 dispatch 안 함 (라인 382~390):**
```python
elif task.get("status") == "running":
    _save_chain_file(chain_file, chain_data)
    output = {
        "action": "already_running",
        "task_id": task.get("task_id"),
        "message": "Next task is already running",
    }
    print(json.dumps(output, ensure_ascii=False, indent=2))
    return
```

3. **dispatch 전에 미리 running으로 마킹 (라인 393~395):**
```python
if next_task is not None:
    next_task["status"] = "running"
    next_task["started_at"] = datetime.now().isoformat()
```

---

## 수정 파일
- `/home/jay/workspace/chain_manager.py`
- `/home/jay/workspace/tests/test_chain_manager.py`

---

## 테스트 결과

**기존 테스트:** 40개 → **43개** (신규 3개 추가)

### 신규 테스트:
1. `test_next_qc_fail_out_of_scope_does_not_stall` — "QC FAIL (범위 외)"는 stall 안 함
2. `test_next_idempotency_already_done` — 이미 done인 task 재호출 시 already_done 반환
3. `test_next_idempotency_next_already_running` — 다음 task가 이미 running이면 dispatch 안 함

```
============================== 43 passed in 0.21s ==============================
```

---

## 검증 시나리오

### 1. FAIL 오탐 방지
- "QC FAIL (범위 외 기존 테스트 1건)" 포함 보고서 → 체인 계속 진행 ✅
- "종합 판정: FAIL" 포함 보고서 → 체인 stalled ✅
- "QC FAIL: 오류 발견" 포함 보고서 → 체인 stalled ✅

### 2. 이중 dispatch 방지
- 같은 task_id로 cmd_next 2회 호출 → 2번째는 `already_done` 반환 ✅
- 다음 task가 이미 running → `already_running` 반환, dispatch 안 함 ✅

---

## 완료 기준

- [x] FAIL 감지 정밀화 (정규식, "범위 외" 제외)
- [x] 이미 done인 task 재호출 감지
- [x] 다음 task가 이미 running이면 dispatch 안 함
- [x] dispatch 전에 미리 running으로 마킹
- [x] 기존 테스트 40개 통과
- [x] 신규 테스트 3개 추가 (전체 43개 통과)
- [x] black 포맷팅 적용

---

## 라(Ra) 팀장 검토 및 추가 수정 (task-634.1 QC)

### 발견 이슈 3건 자체 해결

#### 이슈 1: test_notify_completion.py 회귀 (기존 문제)
- **원인**: 이전 다른 작업(2026-03-17 08:14)이 `notify-completion.py`에서 `create_done_clear` / `build_prompt` / `wake_anu_session` 함수를 삭제했으나 테스트 파일을 업데이트하지 않음
- **해결**: `tests/test_notify_completion.py` 업데이트
  - 삭제된 함수 import 제거
  - `TestCreateDoneClear` / `TestBuildPrompt` / `TestWakeAnuSession` 제거
  - `TestLogProtocol` 신규 추가 (3개 테스트)
  - `TestMain` 테스트 업데이트 (`wake_anu_session` → `send_telegram_notification`)
  - 결과: 19/19 통과

#### 이슈 2: test_dispatch.py 참조 오류
- **원인**: `tests/test_dispatch.py`의 `test_notify_completion_send_failure_no_exit`에서 `create_done_clear` mock 참조
- **해결**: `patch.object(mod, "create_done_clear", return_value=True)` 라인 제거
- **결과**: PASS

### 범위 외 미해결 1건
- **test_group_chat.py 3건 실패**: 조직 구조 변경(21명→20명, 센터명 '디자인 센터'→'Gemini 센터')으로 인한 테스트 불일치
- **사유**: `organization-structure.json` 및 `test_group_chat.py` 담당은 다른 팀 소관
- ⚠️ 기존 테스트 실패 3건 (본 작업 범위 외): `TestLoadPersonas::test_org_loads_expected_count`, `TestLoadPersonasFromOrg::test_parses_centers`, `TestLoadPersonasFromOrg::test_excludes_planned_teams`

### 최종 검증 결과

| 검사 항목 | 결과 |
|---------|------|
| black + isort | PASS |
| pyright | 0 errors, 0 warnings |
| test_chain_manager.py | 43/43 PASS |
| test_notify_completion.py | 19/19 PASS |
| test_dispatch.py 수정 테스트 | PASS |
| 전체 tests/ | 848 passed, 3 failed (범위 외) |
| qc_verify.py | 6 PASS, 1 FAIL (범위 외 test_runner), 3 SKIP |