# task-1223.1 완료 보고서

## SCQA

**S**: dispatch.py의 `_find_available_bot()`은 composite/dynamic 태스크의 `bot` 필드를 활용하여 가용 봇을 선택하지만, dev팀 고정 봇 위임 경로에서는 봇 점유 상태를 확인하지 않는다.

**C**: composite 작업이 bot-g를 점유 중일 때 dev6-team 위임이 차단 없이 진행되어 같은 봇(bot-g)에 2개 세션이 동시 할당되는 버그가 발생했다 (task-1220.1 + task-1222.1 충돌 사례).

**Q**: dev팀 고정 봇 위임 시 composite/dynamic 작업의 봇 점유를 검사하여 중복 할당을 방지할 수 있는가?

**A**: `_get_busy_bots_info()` 헬퍼 함수 도입 + `dispatch()` dev팀 경로에 봇 충돌 검사 추가로 해결. composite/marketing/design 작업이 봇을 점유 중이면 error 반환, `--force` 플래그로 강제 진행 가능. pytest 126건 전체 통과, pyright 에러 0건.

## 작업 내용

### 1. `_get_busy_bots_info()` 헬퍼 함수 추가
- `task-timers.json`에서 running 상태 태스크를 순회하여 `{bot_id: {"task_id": ..., "team_id": ...}}` 매핑 반환
- dev팀은 `TEAM_TO_BOT_ID` 매핑, composite/marketing/design은 `bot` 필드 직접 활용

### 2. `_find_available_bot()` 리팩터링
- 기존 중복 로직을 `_get_busy_bots_info()` 호출로 대체
- 독스트링 업데이트 (composite 포함 명시)

### 3. `dispatch()` dev팀 경로에 봇 충돌 검사 추가
- `bot_id_meta` 결정 직후 `_get_busy_bots_info()`로 충돌 확인
- `force=False`(기본): error 반환 + `_cleanup_task()` 호출
- `force=True`: 경고 로깅 후 강제 진행
- 에러 메시지 형식: "봇 {bot_id}가 {team_id} 작업({task_id})에 점유 중입니다. 완료 후 재시도하세요."

### 4. 테스트 15건 추가
- `TestGetBusyBotsInfo` 7건: 헬퍼 함수 단위 테스트
- `TestDevTeamBotConflict` 8건: 봇 충돌 검사 통합 테스트

## 산출물

- `/home/jay/workspace/dispatch.py`
- `/home/jay/workspace/tests/test_dispatch.py`

## 테스트 결과

- pytest: 126건 전체 통과 (기존 111건 + 신규 15건)
- pyright: 에러 0건, 경고 0건
- black/isort: 준수

## 발견 이슈 및 해결

### 자체 해결 (3건)
1. **`_find_available_bot()` 코드 중복** — `_get_busy_bots_info()` 헬퍼로 추출하여 DRY 원칙 적용
   - 상세: `dispatch.py:184-235` 리팩터링, timer 파일 읽기 로직 일원화
2. **독스트링 오해 소지** — `_find_available_bot()` 주석이 "marketing/consulting → bot field"로만 표기되어 composite 누락 인상
   - 상세: `dispatch.py:222` 독스트링을 "composite/marketing/design running → bot 필드로 판별"로 수정
3. **동일 팀 충돌과 봇 충돌 구분 부재** — 봇 충돌 검사에서 동일 팀 running 태스크를 이중 차단하지 않도록 `conflict_team_id != team_id` 조건 추가
   - 상세: `dispatch.py:1047` 조건문으로 기존 팀 충돌 검사와 중복 방지

## QC 자동 검증 결과

```json
{
  "test_runner": "PASS (126 passed in 0.77s)",
  "pyright_check": "PASS (0 errors, 0 warnings)",
  "style_check": "PASS (black OK, isort OK)",
  "data_integrity": "PASS"
}
```
