# task: chain_manager.py 듀얼 태스크 번호 버그 조사 + 개선

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

둘 다 동일 내용 (ThreadAuto 5단계 파이프라인 출력 구조 통합 + E2E 검증).
본래 task-628.3이어야 하는데 전혀 다른 번호가 할당됨.

## 조사 대상 파일
1. `/home/jay/workspace/chain_manager.py` — cmd_next 로직
2. `/home/jay/workspace/scripts/notify-completion.py` — dispatch_next_phase + dispatch.py 호출
3. `/home/jay/workspace/dispatch.py` — generate_task_id, --task-id 파라미터 처리
4. `/home/jay/workspace/scripts/finish-task.sh` — 완료 시 notify-completion 호출
5. `/home/jay/workspace/memory/chains/chain-scoped-628.json` — 실제 상태 (증거)
6. `/home/jay/workspace/memory/chains/chain-scoped-628.json.bak` — 이전 상태 (증거)

## 이미 파악된 근본 원인 후보 (3건, 검증 필요)

### 원인 1: FAIL 감지 오탐 (거짓 양성)
chain_manager.py cmd_next의 gate 검사:
```python
if "FAIL" in content:
    chain_data["status"] = "stalled"
```
- 단순 문자열 매칭이라 "QC FAIL (범위 외 기존 테스트 1건)" 같은 맥락 무시
- "FAIL" 문자가 보고서 어디든 있으면 체인 강제 정지
- **예시**: task-628.2 보고서에 "QC FAIL (사유: 범위 외 기존 테스트 1건 — 에스컬레이션)" 포함 → 체인 stalled

### 원인 2: 이중 dispatch 트리거
같은 Phase 완료에 대해 notify-completion.py가 2회 호출될 수 있는 경로 존재 여부 확인:
- done-watcher.sh (시스템 와처) + finish-task.sh (팀 자체 완료 핸들러) 동시 트리거?
- 또는: stall 후 수동 복구 시 dispatch가 2회 실행?

### 원인 3: 멱등성 미보장
chain_manager.py cmd_next가 동일 task_id에 대한 중복 호출 차단 없음:
- 이미 "done"인 task에 대해 재호출하면 또 dispatch 가능
- "running" 상태인 다음 task에 대해서도 재dispatch 차단 없음

## 추가 조사 사항
- `.bak` 파일 분석: stalled에서 active로 어떻게 바뀌었는지
- task-632.1과 task-633.1 중 어느 것이 실제 실행되었는지 (또는 둘 다?)
- 2팀 Phase 3 현재 상태: 실제 완료? 실행 중? 실패?

## 수정 작업

### 수정 1: FAIL 감지 정밀화
```python
# 현재 (너무 넓음):
if "FAIL" in content:

# 개선안 1: 전체 판정 라인만 검사
# "종합 판정: FAIL" 또는 "overall: FAIL" 같은 패턴
# "범위 외" 컨텍스트는 제외

# 개선안 2: 정규식으로 판정 라인 추출
import re
verdict_pattern = r'(?:종합|overall|최종)\s*(?:판정|verdict)[:\s]*FAIL'
if re.search(verdict_pattern, content, re.IGNORECASE):
```
- 팀장 판단: 두 안 중 더 안전한 방식 선택

### 수정 2: 이중 dispatch 방지
cmd_next에 멱등성 보호 추가:
```python
# 1. 이미 done인 task에 대한 재호출 감지
if target_task.get("status") == "done":
    # 이미 처리됨 — 현재 상태만 반환
    return

# 2. 다음 task가 이미 running이면 dispatch 건너뜀
if next_task.get("status") == "running":
    return already_dispatched
```

### 수정 3: chain_manager update 커맨드에 task_id 동기화
- dispatch.py가 다른 task_id로 dispatch했을 때, chain에 반영하는 메커니즘 확인
- chain의 task_id와 실제 dispatch된 task_id가 불일치하지 않도록 보장

## 검증
1. 수정 후 기존 테스트 통과 확인: `pytest tests/test_chain_manager.py tests/test_notify_completion.py -v`
2. 시뮬레이션: 같은 task_id로 cmd_next 2회 호출 → 2번째는 무시되는지 확인
3. FAIL 오탐 시뮬레이션: "QC FAIL (범위 외)" 포함 보고서에서 체인이 stalled되지 않는지 확인
4. "종합 판정: FAIL" 포함 보고서에서는 정상 stalled되는지 확인

## 산출물
- 수정된 코드: chain_manager.py, (필요시) notify-completion.py
- 테스트: 기존 + 신규 (모두 통과)
- 보고서: `memory/reports/<task_id>.md`

## 작업 레벨: Lv.2 (코드 분석 + 설계 + 구현)
