# task-2152: finish-task.sh 게이트 통합 — 4개 신규 step + .done gate_results

## ★ 프로젝트: `/home/jay/workspace/`

## 3문서 참조 (필독)
- 프로젝트 계획서: `/home/jay/workspace/memory/plans/system/dispatch-quality-gates/plan.md` — 컴포넌트 5번
- 맥락노트: `/home/jay/workspace/memory/plans/tasks/dispatch-quality-gates/context-notes.md` — task-2148 섹션 + 팀간 인터페이스 약속
- 미팅 기록: `/home/jay/workspace/memory/meetings/2026-04-24-dispatch-quality-automation.md` (Cycle 2-3 다그다/비슈누 의견)

## 배경
P1에서 5개 컴포넌트가 모두 완료됨:
- `scripts/impact_scanner.py` — 역방향 영향 범위 스캔 (1팀 완료)
- `scripts/ci_preflight.sh` — 멀티 러너 CI 게이트 (4팀 완료)
- `teams/shared/verifiers/l1_smoketest_check.py` — 회피 문구 차단 (7팀 완료)
- `dispatch.py` — auto_inject + auto_generate (2팀 완료)
- `config/gate-config.json` + `utils/gate_config_loader.py` — 설정 중앙 관리 (5팀 완료)

이제 finish-task.sh에 4개 신규 step을 삽입하여 이 컴포넌트들을 통합해야 함.

## 수정

### 파일: `/home/jay/workspace/scripts/finish-task.sh`

#### Step 2.6: Impact Scanner Gate (기존 Step 2.5 Git 게이트 이후)
```bash
# 2.6. Impact Scanner Gate
GATE_CONFIG="/home/jay/workspace/config/gate-config.json"
IMPACT_ENABLED=$(python3 -c "from utils.gate_config_loader import is_gate_enabled; print(is_gate_enabled('impact_scanner'))" 2>/dev/null || echo "false")
if [ "$IMPACT_ENABLED" = "True" ]; then
    IMPACT_MODE=$(python3 -c "from utils.gate_config_loader import get_gate_mode; print(get_gate_mode('impact_scanner'))" 2>/dev/null || echo "warn")
    IMPACT_RESULT=$(python3 scripts/impact_scanner.py --project-root "$PROJ_DIR" --task-id "$TASK_ID" 2>/dev/null | tail -1)
    IMPACT_GATE=$(echo "$IMPACT_RESULT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('gate_result','PASS'))" 2>/dev/null || echo "PASS")
    echo "[IMPACT-GATE] result=$IMPACT_GATE mode=$IMPACT_MODE"
    if [ "$IMPACT_GATE" = "BLOCK" ] && [ "$IMPACT_MODE" = "fail" ]; then
        echo "[IMPACT-GATE] BLOCKED: 수정되지 않은 참조 파일 6건 이상"
        exit 1
    fi
fi
```

#### Step 2.6.5: CI Preflight Gate
```bash
# 2.6.5. CI Preflight Gate
CI_ENABLED=$(python3 -c "from utils.gate_config_loader import is_gate_enabled; print(is_gate_enabled('ci_preflight'))" 2>/dev/null || echo "false")
if [ "$CI_ENABLED" = "True" ]; then
    CI_MODE=$(python3 -c "from utils.gate_config_loader import get_gate_mode; print(get_gate_mode('ci_preflight'))" 2>/dev/null || echo "warn")
    bash scripts/ci_preflight.sh "$PROJ_DIR" --affected-only "$COMMIT_COUNT"
    CI_EXIT=$?
    echo "[CI-PREFLIGHT] exit=$CI_EXIT mode=$CI_MODE"
    if [ $CI_EXIT -ne 0 ] && [ "$CI_MODE" = "fail" ]; then
        echo "[CI-PREFLIGHT] BLOCKED: CI 테스트 실패"
        exit 1
    fi
fi
```

#### Step 2.11: Unresolved Issue Gate (기존 Step 2.10 Codex 이후)
```bash
# 2.11. Unresolved Issue Gate
UNRESOLVED_ENABLED=$(python3 -c "from utils.gate_config_loader import is_gate_enabled; print(is_gate_enabled('unresolved_gate'))" 2>/dev/null || echo "false")
if [ "$UNRESOLVED_ENABLED" = "True" ]; then
    UNRESOLVED_MODE=$(python3 -c "from utils.gate_config_loader import get_gate_mode; print(get_gate_mode('unresolved_gate'))" 2>/dev/null || echo "warn")
    if [ -f "$REPORT_FILE" ]; then
        UNRESOLVED_COUNT=$(grep -ciE "범위 내 미해결|in.scope.*unresolved" "$REPORT_FILE" 2>/dev/null || echo "0")
        MAX_UNRESOLVED=$(python3 -c "from utils.gate_config_loader import load_gate_config; print(load_gate_config('unresolved_gate').get('max_in_scope_unresolved', 3))" 2>/dev/null || echo "3")
        echo "[UNRESOLVED-GATE] count=$UNRESOLVED_COUNT max=$MAX_UNRESOLVED mode=$UNRESOLVED_MODE"
        if [ "$UNRESOLVED_COUNT" -gt "$MAX_UNRESOLVED" ] && [ "$UNRESOLVED_MODE" = "fail" ]; then
            echo "[UNRESOLVED-GATE] BLOCKED"
            exit 1
        fi
    fi
fi
```

#### Step 2.12: Goal Assertions Gate
```bash
# 2.12. Goal Assertions Gate
GOAL_ENABLED=$(python3 -c "from utils.gate_config_loader import is_gate_enabled; print(is_gate_enabled('goal_assertions'))" 2>/dev/null || echo "false")
if [ "$GOAL_ENABLED" = "True" ]; then
    GOAL_MODE=$(python3 -c "from utils.gate_config_loader import get_gate_mode; print(get_gate_mode('goal_assertions'))" 2>/dev/null || echo "fail")
    if [ -f "$TASK_FILE" ]; then
        GOALS=$(python3 -c "
import re
with open('$TASK_FILE') as f:
    content = f.read()
m = re.search(r'## goal_assertions.*?\n(.*?)(?=\n##|\Z)', content, re.S)
if m:
    for line in m.group(1).strip().split('\n'):
        cmd = re.search(r'\x60([^\x60]+)\x60', line)
        if cmd:
            print(cmd.group(1))
" 2>/dev/null)
        GOAL_FAIL=0
        while IFS= read -r cmd; do
            [ -z "$cmd" ] && continue
            eval "$cmd" > /dev/null 2>&1
            if [ $? -ne 0 ]; then
                echo "[GOAL-GATE] FAIL: $cmd"
                GOAL_FAIL=1
            fi
        done <<< "$GOALS"
        if [ $GOAL_FAIL -eq 1 ] && [ "$GOAL_MODE" = "fail" ]; then
            echo "[GOAL-GATE] BLOCKED: goal_assertions 미충족"
            exit 1
        fi
    fi
fi
```

#### Step 3 수정: .done 파일에 gate_results 추가
기존 .done JSON에 `gate_results` 딕셔너리 필드 추가:
```json
{
  "task_id": "...",
  "gate_results": {
    "impact_scanner": "PASS|WARN|BLOCK",
    "ci_preflight": "PASS|FAIL",
    "l1_smoketest": "PASS|FAIL",
    "goal_assertions": "PASS|FAIL",
    "unresolved_gate": "PASS|WARN|BLOCK"
  }
}
```

## ★ 먼저 읽을 파일
- `/home/jay/workspace/scripts/finish-task.sh` — 전체 구조 (특히 Step 2.5~2.10, Step 3 위치)
- `/home/jay/workspace/utils/gate_config_loader.py` — 로더 API
- `/home/jay/workspace/scripts/impact_scanner.py` — CLI 인터페이스, 출력 포맷
- `/home/jay/workspace/scripts/ci_preflight.sh` — CLI, exit code 규칙

## 팀간 인터페이스 (필독)
- impact_scanner: stdout 마지막 줄 JSON, exit 0=PASS / 1=WARN / 2=BLOCK
- ci_preflight: exit 0=PASS / 1=FAIL, 타임아웃 시 exit 0 + overall=WARN
- gate_config_loader: `is_gate_enabled(name) -> bool`, `get_gate_mode(name) -> str`, `load_gate_config(name) -> dict`

## 검증 시나리오

### 시나리오 1: 모든 게이트 PASS
gate-config.json 전부 enabled + mode=warn → 4개 step 실행 → WARNING만 → .done 생성

### 시나리오 2: goal_assertions FAIL (mode=fail)
goal_assertions에 `grep -c "nonexistent" some_file.py` → exit 1 → .done 차단

### 시나리오 3: 게이트 disabled
gate-config.json에서 impact_scanner enabled=false → Step 2.6 건너뜀

### 시나리오 4: .done gate_results
.done 파일에 gate_results JSON 포함 확인

### 시나리오 5: 기존 Step 0~2.10 회귀 없음
기존 finish-task.sh 동작 정상 유지

### 시나리오 6: subshell 격리
한 게이트 에러가 다른 게이트 실행 방해 안 함

## 완료 시그니처
- Step 2.6, 2.6.5, 2.11, 2.12 삽입
- .done gate_results 필드 추가
- 모든 게이트 subshell 격리
- 기존 동작 회귀 없음
- 단위/통합 테스트 PASS

## 레벨
- Lv.3

## 프로젝트
- dev-system
