# Agent Meeting: 위임 작업 품질 자동화 -- 아누 의존도 제거

- **일시**: 2026-04-24
- **의장**: 아누 (Anu, 개발실장)
- **참석자**: 13명 (헤르메스, 오딘, 다그다, 비슈누, 엔키, 쿠쿨칸, 이잠나, 라, 로키, 마아트, 다빈치, 비너스, 아틀라스)
- **안건**: 위임 작업 품질 자동화 -- 아누 의존도 제거
- **제이회장님 지시**: "3가지 모두 반영. CLAUDE.md, memory.md, 아누가이드 뿐만 아니라 아누가 잊어먹어도 실제로 코드가 자동으로 진행할 수 있게 해야함"

---

## 배경: 3대 루트 원인

| # | 루트 원인 | 사례 | 핵심 파일 |
|---|-----------|------|-----------|
| RC-1 | 영향 범위 전수조사 누락 | task-2142: FeatureGate.tsx 수정, use-feature-access.ts 누락 | dispatch.py (pre-flight) |
| RC-2 | 연쇄 이슈 직렬 발견 | task-2143/2144: Vitest supabaseUrl -> pytest doc_parser 연쇄 | finish-task.sh (post-flight) |
| RC-3 | 실동작 미검증 | task-2140: "UI 직접 확인 불가"인데 완료 처리 | finish-task.sh + qc_verify.py |

---

## Cycle 1: 문제 정의 및 현황 분석 (Independent Mode)

### 아누 분석

현재 시스템의 코드를 분석한 결과, 기존 방어 장치는 다음과 같이 존재하지만 불충분하다:

1. **dispatch.py의 pre-flight**: `_parse_affected_files()`, `_enrich_affected_files_with_ast()`, `_check_affected_files_overlap()` 함수가 이미 존재. 그러나 이것은 task 파일에 affected_files가 **수동 기재**된 경우에만 동작. 아누가 지시서에 affected_files를 빠뜨리면 무력화.
2. **finish-task.sh의 post-flight**: Git 게이트(커밋 존재 확인, uncommitted 변경 없음, 빈 커밋 방어)와 G3 독립 검증이 있으나, "CI 전체 패스 확인"이나 "연쇄 이슈 사전 탐지"는 없음.
3. **qc_verify.py**: 20개 이상의 verifier가 있으나 `browser_verify`는 스크린샷 파일 존재만 확인하고 **실제 서버 기동 + 스크린샷 캡처** 자동화는 없음.
4. **DIRECT-WORKFLOW.md**: L1 스모크테스트 절차가 상세히 기술되어 있으나 "봇이 읽고 따를 것을 기대"하는 문서 규칙에 불과. 코드 강제가 아님.

### 페르소나 의견 (Independent)

**헤르메스 (dev1)**: dispatch.py의 `_enrich_affected_files_with_ast()`는 Python만 지원한다. TypeScript/TSX 파일(task-2142 사례)은 AST 분석 범위 밖이다. `ast_dependency_map.py`에 TypeScript 지원을 추가하거나, `grep -rn` 기반 패턴 매칭 fallback이 필요하다.

제안: `scripts/impact_scanner.py` 신규 생성. 입력: 함수명/변수명 목록. 출력: 해당 심볼을 사용하는 전체 파일 목록. Python은 AST, TypeScript는 `grep -rn` + `tsc --listFiles` 조합.

**오딘 (dev2)**: 근본 원인은 "affected_files를 아누가 수동 기재"하는 구조다. 아누가 빠뜨리면 모든 후속 체크가 무력화된다. 해결: dispatch.py가 task 파일 내용에서 키워드를 추출하고, **자동으로 grep 기반 affected_files를 생성**해야 한다. `_auto_detect_affected_files(task_desc) -> list[str]` 함수를 dispatch.py에 추가.

**다그다 (dev3)**: finish-task.sh의 Git 게이트에서 `git diff --name-only` 결과를 `affected_files`와 대조하는 step이 없다. 봇이 affected_files 외 파일을 수정하거나, affected_files에 있는 파일을 수정하지 않아도 통과한다. "Step 2.6: scope_verification_gate" 추가를 제안.

**비슈누 (dev4)**: task-2143/2144의 연쇄 이슈는 finish-task.sh가 "pytest만 돌리고 tsc/vitest는 돌리지 않는" 구조 때문이다. `finish-task.sh`에 multi-runner gate 추가: 프로젝트 루트에 `package.json`이 있으면 `npm test`, `vitest.config.*`가 있으면 `npx vitest run`, `tsconfig.json`이 있으면 `npx tsc --noEmit`도 실행.

**엔키 (dev5)**: 모든 자동화의 핵심은 "CI 전체 패스 확인"이다. 현재 `finish-task.sh`는 `qc_verify.py`를 호출하지만 qc_verify.py 내부의 `test_runner`는 `--check-files` 기반 부분 테스트만 돌린다. 완전한 CI green 검증이 아니다. 제안: `scripts/ci_preflight.sh` 스크립트 → pytest 전체 + tsc --noEmit + vitest run을 순차 실행, exit code를 finish-task.sh에 반환.

**쿠쿨칸 (dev6)**: "1태스크=1완결" 위반의 원인은 봇이 "범위 외 미해결"을 남발하기 때문이다. finish-task.sh에서 보고서 파싱 → "미해결" 키워드 카운트가 임계치(예: 3건) 초과하면 `.done` 생성 차단을 제안.

**이잠나 (dev7)**: 실동작 미검증은 봇이 "UI 직접 확인 불가"라고 보고서에 쓰고 통과하는 구조가 문제. qc_verify.py의 `browser_verify` verifier를 강화: 보고서에 "확인 불가" 패턴이 있으면 FAIL 반환. 또한 `l1_smoketest_check` verifier가 이미 존재하는데 이것이 실제로 서버 기동 + curl 체크를 하는지 확인 필요.

**라 (dev8)**: GLM/Codex 관점에서, 외부 AI 검증 레이어를 추가할 수 있다. finish-task.sh 완료 직전에 `codex_gate_check.py`가 diff를 분석하여 "놓친 파일"을 경고하는 구조가 이미 있다. 이것을 RC-1(영향 범위 누락) 대응에 활용 가능.

**로키 (Red Team)**: 전원 "자동화 추가"만 말하고 있다. **거짓 양성(false positive)** 문제를 누구도 언급하지 않았다. grep 기반 affected_files 자동 감지는 common 변수명(e.g., `data`, `result`, `config`)에 대해 수백 개 파일을 반환할 것이다. 이러면 봇이 affected_files를 무시하게 되어 오히려 역효과. **정밀도 필터**가 없는 자동 감지는 재앙이다.

**마아트 (QC)**: qc_verify.py의 `scope_check` verifier가 이미 "예상 파일 vs 실제 변경 대조"를 하지만 WARN만 내보낸다(FAIL 아님). 이것을 FAIL로 승격하면 RC-1이 해결되지만, 작업 범위가 확장되는 정당한 케이스(리팩토링 중 발견)도 차단한다. 임계치 기반 접근이 필요.

**다빈치 (Design)**: 프론트엔드 변경의 실동작 검증은 Playwright 스크린샷만으로 불충분. 렌더링 결과의 시각적 회귀(visual regression)를 감지하려면 이전 스크린샷과의 pixel diff가 필요하다. 하지만 현재 baseline 관리 시스템이 없으므로 단기적으로는 "스크린샷 존재 + 콘솔 에러 0건"이 현실적 게이트.

**비너스 (Gemini)**: Gemini PR 리뷰가 이미 Lv.2+ 작업에 적용되지만, PR 리뷰는 코드 품질에 집중하고 "영향 범위 누락"은 감지 못한다. Gemini에 "이 diff에서 수정된 함수/타입을 다른 어디에서 참조하는지 분석해라"는 추가 프롬프트를 넣으면 RC-1 보완 가능.

**아틀라스 (Codex)**: Codex CLI의 `codex_gate_check.py`가 이미 존재하고 diff 분석을 하지만, 현재는 Lv.3+ 작업에만 적용된다. 모든 코딩 작업에 확대 적용하면 RC-1/RC-2 모두 커버 가능. 단, Codex API 호출 비용이 증가.

### Devil's Advocate
**지정**: 로키 (Loki)

1. **실패 시나리오**: grep 기반 자동 affected_files 감지를 도입하면, 일반적인 변수명(`data`, `props`, `config`)에 대해 프로젝트 전체 파일이 반환됨. 봇이 "affected_files 500개" 목록을 받으면 무시하고 작업. 결국 아무것도 개선되지 않고 dispatch.py만 느려짐.
2. **후회 이유**: CI 전체 실행을 finish-task.sh에 추가하면, InsuRo 같은 대형 프로젝트에서 테스트 실행 시간이 10분 이상. 봇 세션 토큰 한도를 빠르게 소진. 3회 실패하면 에스컬레이션되어 오히려 아누 부담 증가.
3. **더 단순한 대안**: affected_files 자동 감지 대신, "봇이 수정한 파일 목록을 finish-task.sh가 수집하고, 각 수정 파일의 모든 import/reference를 grep으로 찾아서, 수정 대상이 아닌 파일에도 동일 심볼이 있으면 WARNING"만 발생. 즉 pre-flight(아누 의존)이 아닌 post-flight(봇 자동) 검증.

**반박**: 
- 헤르메스: "common 변수명 문제는 함수/클래스 단위 grep으로 해결. `grep -rn 'def calculate_premium'`은 정밀도가 높다."
- 엔키: "CI 시간 문제는 `--affected-only` 플래그로 changed files 관련 테스트만 실행하면 해결."

**판정**: 로키의 "post-flight에서 봇이 수정한 파일 기반 역방향 스캔" 아이디어가 핵심적. pre-flight(아누 의존)와 post-flight(봇 자동) 양쪽 모두 필요하되, post-flight 쪽에 무게를 두는 것이 "아누 의존도 제거"라는 목표에 부합.

### 비관습적 대안
**제시자**: 라 (Ra)

**대안**: "Shadow Run" 패턴 -- 봇이 작업을 완료하면, finish-task.sh가 두 번째 세션(Haiku)을 spawn하여 "이 diff에서 놓친 파일이 있는가?"를 독립적으로 검증. 마치 CI의 code review bot처럼 작동. 비용은 Haiku이므로 저렴.

5점 평가:
- 효과성: 4/5 (독립 시각의 검증은 강력)
- 실현 가능성: 3/5 (추가 세션 관리 복잡도)
- 비용: 3/5 (Haiku 세션 비용, 하지만 낮음)
- 부작용: 2/5 (완료 지연, 세션 실패 시 .done 블로킹)
- 기존 시스템 호환: 3/5 (기존 finish-task.sh 파이프라인에 추가 가능)

### 합의/결론
- RC-1은 pre-flight(dispatch.py) + post-flight(finish-task.sh) 양면 방어 필요
- RC-2는 multi-runner CI gate가 finish-task.sh에 필요
- RC-3은 browser_verify 강화 + "확인 불가" 패턴 차단 필요
- False positive 관리가 모든 자동화의 핵심 과제

### 미해결 항목
- grep vs AST vs LSP: 어느 수준의 심볼 분석이 적정한가?
- CI 전체 실행 vs 관련 테스트만 실행: 시간/비용 트레이드오프
- post-flight 역방향 스캔의 구체적 구현 방법
- "확인 불가" 차단의 예외 케이스 (진짜 확인 불가인 경우)

---

## Cycle 2: 구체적 설계안 (Sequential Mode)

### 아누 분석

Cycle 1에서 pre-flight + post-flight 양면 방어, multi-runner CI gate, browser_verify 강화라는 3축이 도출되었다. 이제 각각의 구체적 함수 시그니처, 파일 위치, 로직 플로우를 설계한다.

### 페르소나 의견 (Sequential)

**헤르메스 (dev1)**: RC-1 해결을 위한 post-flight 역방향 스캔 구현안:

```
파일: scripts/impact_scanner.py
함수: scan_modified_files(modified_files: list[str], project_root: str) -> dict

로직:
1. git diff --name-only로 봇이 수정한 파일 목록 수집 (finish-task.sh에서 전달)
2. 각 수정 파일에서 변경된 함수/클래스/타입 이름 추출
   - Python: ast.parse → FunctionDef/ClassDef 이름
   - TypeScript: regex 기반 (export function X, export class X, export type X, export interface X)
3. 추출된 심볼을 프로젝트 전체에서 grep -rn
4. 결과에서 수정된 파일 자체는 제외
5. 남은 파일 = "영향 받지만 수정되지 않은 파일" → WARNING 목록 반환

반환값:
{
  "warnings": [
    {"symbol": "calculatePremium", "defined_in": "utils/calc.ts", "referenced_in": ["pages/quote.tsx", "hooks/useQuote.ts"], "modified": false}
  ],
  "total_unmodified_references": 3,
  "gate_result": "WARN"  # 0건=PASS, 1-5건=WARN, 6건+=BLOCK
}
```

**오딘 (dev2)**: pre-flight 자동 affected_files 감지는 "심볼 추출 → grep"이 아니라 **task 파일의 키워드에서 추출**해야 한다. task 파일에 "FeatureGate 컴포넌트 수정"이라 쓰여 있으면 `grep -rn "FeatureGate" --include="*.tsx" --include="*.ts"` 실행. 결과를 task 파일의 `## affected_files` 섹션에 자동 삽입.

```
파일: dispatch.py
함수: _auto_inject_affected_files(task_desc: str, workspace_root: str) -> str

로직:
1. task_desc에서 ## affected_files 섹션이 이미 있으면 SKIP
2. task_desc에서 코드 관련 키워드 추출 (정규식: /`[A-Z][a-zA-Z]+`/ for 클래스명, /`[a-z_]+\(\)`/ for 함수호출)
3. 각 키워드를 workspace에서 grep
4. 결과를 ## affected_files 섹션으로 task_desc에 append
5. 수정된 task_desc 반환
```

**다그다 (dev3)**: finish-task.sh에 scope_verification_gate 추가:

```bash
# Step 2.6: Scope Verification Gate
# 수정된 파일 vs affected_files 대조
MODIFIED_FILES=$(git -C "$PROJ_DIR" diff --name-only HEAD~${COMMIT_COUNT}..HEAD 2>/dev/null)
if [ -f "$TASK_FILE" ]; then
    AFFECTED_FILES=$(python3 -c "
import re
with open('$TASK_FILE') as f:
    content = f.read()
# affected_files 파싱 (dispatch.py의 _parse_affected_files와 동일)
# ... 파싱 로직 ...
print('\\n'.join(files))
" 2>/dev/null)
    
    # 수정했지만 affected_files에 없는 파일 = 범위 초과
    EXTRA=$(comm -23 <(echo "$MODIFIED_FILES" | sort) <(echo "$AFFECTED_FILES" | sort))
    if [ -n "$EXTRA" ]; then
        echo "[SCOPE-GATE] WARNING: affected_files에 없는 파일 수정됨:"
        echo "$EXTRA"
        # WARNING만, BLOCK은 하지 않음 (정당한 확장 허용)
    fi
fi
```

**비슈누 (dev4)**: RC-2 해결을 위한 multi-runner CI gate:

```
파일: scripts/ci_preflight.sh
사용: finish-task.sh에서 호출

로직:
1. 프로젝트 루트에서 tech stack 자동 감지
   - pyproject.toml / setup.py / requirements.txt → Python
   - package.json → Node.js
   - tsconfig.json → TypeScript
   - vitest.config.* → Vitest
   - jest.config.* → Jest
2. 감지된 스택에 따라 순차 실행:
   a. Python: pytest --tb=short -q (관련 테스트만: --co -q로 수집 후 실행)
   b. TypeScript: npx tsc --noEmit
   c. Vitest: npx vitest run --reporter=dot
   d. Jest: npx jest --ci --silent
3. 각 runner의 exit code 수집
4. 하나라도 non-zero면 전체 FAIL, .done 생성 차단
5. 결과를 JSON으로 반환: {"runners": [{"name": "pytest", "exit_code": 0}, ...], "overall": "PASS"}
```

**엔키 (dev5)**: ci_preflight.sh의 "관련 테스트만" 모드 구현:

```bash
# --affected-only 모드: git diff에서 변경 파일 추출 → 관련 테스트만 실행
CHANGED_FILES=$(git diff --name-only HEAD~$COMMIT_COUNT..HEAD)
RELATED_TESTS=""
for f in $CHANGED_FILES; do
    base=$(basename "$f" | sed 's/\.\(py\|ts\|tsx\)$//')
    # test_X.py, X.test.ts, X.spec.ts 패턴 매칭
    found=$(find . -name "test_${base}.py" -o -name "${base}.test.ts" -o -name "${base}.test.tsx" -o -name "${base}.spec.ts" 2>/dev/null)
    RELATED_TESTS="$RELATED_TESTS $found"
done
# 중복 제거 후 실행
RELATED_TESTS=$(echo "$RELATED_TESTS" | tr ' ' '\n' | sort -u | tr '\n' ' ')
```

**쿠쿨칸 (dev6)**: "미해결 이슈" 남발 차단을 finish-task.sh에 추가:

```bash
# Step 2.11: Unresolved Issue Gate
if [ -f "$REPORT_FILE" ]; then
    UNRESOLVED_COUNT=$(grep -ciE "미해결|범위 외|확인 불가|UI 직접 확인 불가" "$REPORT_FILE" 2>/dev/null || echo "0")
    if [ "$UNRESOLVED_COUNT" -gt 3 ]; then
        echo "[UNRESOLVED-GATE] BLOCKED: 미해결 이슈 ${UNRESOLVED_COUNT}건 (한도: 3건)"
        echo "[UNRESOLVED-GATE] 미해결 이슈를 해결하거나, 정당한 사유를 보고서에 명시하세요."
        exit 1
    elif [ "$UNRESOLVED_COUNT" -gt 0 ]; then
        echo "[UNRESOLVED-GATE] WARNING: 미해결 이슈 ${UNRESOLVED_COUNT}건"
    fi
fi
```

**이잠나 (dev7)**: RC-3 해결. qc_verify.py `l1_smoketest_check` 강화:

```python
# verifiers/l1_smoketest_check.py 수정
def verify(task_id, report_content, **kwargs):
    # 1. "확인 불가" 패턴 차단
    BLOCK_PATTERNS = [
        r"UI 직접 확인 불가",
        r"서버 기동 불가",
        r"환경 제약으로 확인 불가",
        r"해당없음.*스모크",
        r"N/A.*L1",
    ]
    for pattern in BLOCK_PATTERNS:
        if re.search(pattern, report_content):
            return {"result": "FAIL", "reason": f"L1 스모크테스트 회피 감지: {pattern}"}
    
    # 2. 증거 필수 확인 (스크린샷 경로 또는 curl 결과)
    has_screenshot = bool(re.search(r'(screenshot|스크린샷|\.png|\.jpg)', report_content, re.I))
    has_curl = bool(re.search(r'(curl.*200|HTTP/.*200|응답 코드.*200)', report_content, re.I))
    has_test_output = bool(re.search(r'(pytest.*passed|PASSED|tests passed)', report_content, re.I))
    
    if not (has_screenshot or has_curl or has_test_output):
        return {"result": "FAIL", "reason": "L1 스모크테스트 증거 없음 (스크린샷/curl/테스트 결과)"}
    
    return {"result": "PASS"}
```

**라 (dev8)**: Codex 게이트를 RC-1 보완으로 모든 코딩 작업에 확대:

```python
# scripts/codex_gate_check.py 수정
# 기존: Lv.3+ 작업만
# 변경: 모든 coding task (task_type == "coding")
# 단, Lv.1-2는 lightweight 모드 (함수명 매칭만, 전체 diff 분석 X)
def check_impact(task_id, level="normal"):
    if level in ("critical", "security"):
        return full_analysis(task_id)  # 기존 전체 분석
    else:
        return lightweight_analysis(task_id)  # 수정된 함수명 기반 grep만
```

**로키 (Red Team)**: 오딘의 "task_desc에서 키워드 추출 → grep" 안에 대해. 키워드 추출 정규식 `/[A-Z][a-zA-Z]+/`는 "Phase", "Step", "Section" 같은 문서 키워드도 매칭한다. **false positive 폭발**. 키워드 추출은 backtick으로 감싸진 코드 토큰만 대상으로 해야 한다: `/`([A-Z][a-zA-Z]+)`/`.

또한 쿠쿨칸의 "미해결 이슈 3건 한도"는 정당한 미해결도 차단한다. "범위 외" 미해결과 "범위 내" 미해결을 구분해야. "범위 내 미해결"만 카운트해야 한다.

**마아트 (QC)**: 모든 제안을 통합한 qc_verify.py 변경 범위:
1. `l1_smoketest_check`: "확인 불가" 패턴 FAIL + 증거 필수
2. `scope_check`: WARN → 조건부 FAIL 승격 (unmodified references > 5건)
3. `browser_verify`: 프론트 작업인데 스크린샷 0건이면 FAIL (기존과 동일, 강화 불필요)
4. 신규 verifier `ci_runner_check`: ci_preflight.sh 결과 파일 확인

**다빈치 (Design)**: 프론트 작업의 "확인 불가" 차단에 대한 예외 설계:
- 서버 의존성이 없는 유틸리티 함수 변경 → "해당없음" 허용
- 판별 기준: task 파일에 "프론트" "UI" "컴포넌트" "화면" 키워드가 있는지
- 키워드 없으면 l1_smoketest의 스크린샷 요구를 SKIP

**비너스 (Gemini)**: Gemini PR 리뷰에 "영향 범위 분석" 프롬프트 추가:
```
기존 프롬프트에 추가:
"이 PR에서 수정된 함수/타입/인터페이스를 식별하고, 
이들이 프로젝트의 다른 파일에서 참조되는지 확인하세요.
수정되지 않은 파일에 참조가 있으면 [IMPACT WARNING]으로 표시하세요."
```

**아틀라스 (Codex)**: Codex CLI의 lightweight 분석 모드 구현:
```bash
# codex --task check-impact --files "utils/calc.ts" --symbols "calculatePremium"
# 반환: { "unmodified_references": ["pages/quote.tsx:42", "hooks/useQuote.ts:15"] }
```

### Devil's Advocate
**지정**: 로키 (Loki)

1. **실패 시나리오**: ci_preflight.sh가 InsuRo 프로젝트에서 `npx tsc --noEmit`을 실행. InsuRo는 4000+ TS 파일. tsc 실행 시간 3분. 봇 세션의 200k 토큰 중 이미 150k 소진 상태에서 3분 대기 → 세션 타임아웃 → .done 미생성 → orphan task. 아누가 수동 정리해야 함.
2. **후회 이유**: 모든 게이트를 blocking(exit 1)으로 만들면, legitimate한 작업도 차단당하여 봇의 "재시도 3회 → 에스컬레이션" 루프에 진입. 아누의 에스컬레이션 처리 부담이 현재보다 증가.
3. **더 단순한 대안**: blocking 게이트 대신 **advisory 게이트** (WARNING + Telegram 알림). 아누가 판단하여 수동 .done 생성 가능. 단, "확인 불가" 같은 명백한 위반만 blocking.

**반박**:
- 엔키: "tsc 타임아웃은 `--timeout 120` 플래그로 해결. 120초 초과 시 WARNING만 내보내고 계속 진행."
- 마아트: "blocking vs advisory 구분은 verifier별로 설정 가능하게 해야. `QC-RULES.md`의 verifier 테이블에 FAIL/WARN 이미 정의되어 있으므로 그대로 활용."

**판정**: 로키의 "타임아웃 + advisory 기본" 원칙 채택. 신규 게이트는 WARN 기본, 검증된 후 FAIL로 승격하는 점진적 접근.

### 비관습적 대안
**제시자**: 오딘 (Odin)

**대안**: "Goal Assertion" 패턴 -- task 파일에 `## 완료 기준 (goal_assertions)` 섹션을 필수화. 각 assertion은 실행 가능한 shell command:
```markdown
## 완료 기준 (goal_assertions)
- `curl -s http://localhost:8000/api/status | jq .status` → "ok"
- `pytest tests/test_feature_gate.py -q` → exit 0
- `grep -c "FeatureGate" src/hooks/use-feature-access.ts` → > 0
```
finish-task.sh가 이 assertions를 파싱하여 **실제 실행**. 전부 통과해야 .done 생성.

5점 평가:
- 효과성: 5/5 (Goal-Driven Execution 원칙과 완벽 합치)
- 실현 가능성: 4/5 (shell command 파싱은 간단)
- 비용: 5/5 (추가 비용 없음)
- 부작용: 3/5 (잘못된 assertion이 정당한 작업 차단)
- 기존 시스템 호환: 4/5 (task 파일 포맷 확장만 필요)

### 합의/결론
C2-1: impact_scanner.py는 post-flight(finish-task.sh)에서 실행 (pre-flight 아님)
C2-2: ci_preflight.sh는 타임아웃(120초/runner) + WARN 기본 + 점진적 FAIL 승격
C2-3: l1_smoketest_check에 "확인 불가" 패턴 차단 추가
C2-4: goal_assertions 패턴을 task 파일 표준에 추가
C2-5: 모든 신규 게이트는 WARN 기본, 2주 운영 데이터 수집 후 FAIL 승격 여부 결정

### 미해결 항목
- goal_assertions의 구체적 파서 구현
- impact_scanner.py의 TypeScript 심볼 추출 정규식 검증
- ci_preflight.sh의 프로젝트별 설정 파일 (.ci-config.json) 필요 여부
- finish-task.sh에 새 step들의 정확한 삽입 위치

---

## Cycle 3: 구현 계획 확정 (Sequential Mode)

### 아누 분석

Cycle 2에서 도출된 5개 합의 항목을 구체적 코드 레벨로 확정한다. 각 파일의 정확한 수정 위치, 함수 시그니처, 에러 핸들링을 정의한다.

### 페르소나 의견 (Sequential)

**헤르메스 (dev1)**: `scripts/impact_scanner.py` 전체 설계:

```python
#!/usr/bin/env python3
"""
impact_scanner.py -- 수정 파일 기반 역방향 영향 범위 스캐너

finish-task.sh에서 호출. 봇이 수정한 파일의 심볼이
수정되지 않은 다른 파일에서 참조되는지 검사.

Usage:
    python3 impact_scanner.py --project-root /path/to/project --modified-files "a.py,b.tsx"
    python3 impact_scanner.py --project-root /path --task-id task-2145 (git diff에서 자동 추출)
"""

def extract_symbols_python(file_path: str, diff_lines: list[int]) -> list[str]:
    """Python AST에서 변경된 라인의 함수/클래스 이름 추출"""
    # ast.parse → walk → FunctionDef/ClassDef → lineno가 diff_lines에 포함되면 수집
    
def extract_symbols_typescript(file_path: str, diff_lines: list[int]) -> list[str]:
    """TypeScript에서 변경된 라인의 export 심볼 추출 (regex 기반)"""
    # regex: r'export\s+(function|class|type|interface|const|enum)\s+(\w+)'
    # + r'export\s+default\s+(function|class)\s+(\w+)'

def grep_references(symbol: str, project_root: str, exclude_files: list[str]) -> list[str]:
    """프로젝트 전체에서 심볼 참조를 grep으로 검색"""
    # grep -rn --include="*.py" --include="*.ts" --include="*.tsx" 
    # exclude: node_modules, .git, __pycache__, dist, build, .worktrees

def scan(project_root: str, modified_files: list[str], task_id: str = "") -> dict:
    """메인 스캔 로직"""
    # 1. 각 modified_file에서 diff_lines 추출 (git diff --unified=0)
    # 2. diff_lines 기반 심볼 추출
    # 3. 각 심볼을 프로젝트에서 grep
    # 4. 결과 집계
    return {
        "task_id": task_id,
        "modified_files": modified_files,
        "symbols_checked": [...],
        "unmodified_references": [
            {"symbol": "X", "file": "Y", "line": 42}
        ],
        "gate_result": "WARN",  # PASS(0건) / WARN(1-5건) / BLOCK(6건+)
        "timestamp": "..."
    }
```

**오딘 (dev2)**: dispatch.py의 `_auto_inject_affected_files` 수정 -- 로키의 피드백 반영하여 backtick 코드 토큰만 추출:

```python
def _auto_inject_affected_files(task_desc: str, workspace_root: str) -> str:
    """task_desc에 ## affected_files 미기재 시, 코드 토큰 기반 자동 탐지.
    
    로직:
    1. task_desc에 ## affected_files 이미 있으면 그대로 반환 (SKIP)
    2. backtick 감싸인 코드 토큰 추출: `FeatureGate`, `calculatePremium()` 등
    3. common 키워드 필터 (data, config, result, props, state 등 제외)
    4. 각 토큰을 workspace에서 grep -rl (파일명만)
    5. 결과를 ## affected_files (auto-detected) 섹션으로 append
    6. 자동 감지 결과가 20개 초과면 경고만 로깅 (주입 안 함)
    """
    if "## affected_files" in task_desc:
        return task_desc
    
    # backtick 코드 토큰 추출
    tokens = re.findall(r'`([A-Za-z_]\w*(?:\(\))?)`', task_desc)
    
    # common 변수명 필터
    COMMON_FILTER = {"data", "result", "config", "props", "state", "error",
                     "value", "item", "items", "list", "name", "path",
                     "type", "id", "key", "index", "event", "options"}
    tokens = [t.rstrip("()") for t in tokens if t.rstrip("()").lower() not in COMMON_FILTER]
    tokens = list(dict.fromkeys(tokens))  # 중복 제거, 순서 유지
    
    if not tokens:
        return task_desc
    
    # grep 실행
    affected = set()
    for token in tokens[:10]:  # 최대 10개 토큰만
        try:
            result = subprocess.run(
                ["grep", "-rl", "--include=*.py", "--include=*.ts", "--include=*.tsx",
                 "--include=*.js", "--include=*.jsx",
                 "--exclude-dir=node_modules", "--exclude-dir=.git",
                 "--exclude-dir=__pycache__", "--exclude-dir=.worktrees",
                 token, workspace_root],
                capture_output=True, text=True, timeout=10
            )
            for line in result.stdout.strip().split("\n"):
                if line:
                    affected.add(os.path.relpath(line, workspace_root))
        except (subprocess.TimeoutExpired, OSError):
            continue
    
    if not affected:
        return task_desc
    if len(affected) > 20:
        logger.warning(f"[auto-affected] 자동 감지 파일 {len(affected)}개 (한도 초과, 주입 안 함)")
        return task_desc
    
    section = "\n\n## affected_files (auto-detected)\n"
    for f in sorted(affected):
        section += f"- {f}\n"
    section += "\n> 위 목록은 dispatch.py가 자동 감지한 것입니다. 정확하지 않을 수 있습니다.\n"
    
    return task_desc + section
```

**다그다 (dev3)**: finish-task.sh에 impact_scanner 호출 삽입 위치:

```bash
# Step 2.6 위치: Git 게이트(2.5) 이후, member-status 복원(2.7) 이전
# 이유: git diff가 필요하므로 git 게이트 이후. .done 생성 전이므로 차단 가능.

# 2.6. Impact Scanner Gate (RC-1 방어)
if [ "$GIT_GATE_SKIP" -eq 0 ]; then
    IMPACT_SCRIPT="$WORKSPACE/scripts/impact_scanner.py"
    if [ -f "$IMPACT_SCRIPT" ]; then
        echo "[IMPACT-GATE] 영향 범위 역방향 스캔 실행."
        IMPACT_RESULT=$(python3 "$IMPACT_SCRIPT" --project-root "$PROJ_DIR" --task-id "$TASK_ID" 2>&1)
        IMPACT_EXIT=$?
        echo "$IMPACT_RESULT"
        
        IMPACT_GATE=$(echo "$IMPACT_RESULT" | python3 -c "
import json, sys
try:
    for line in sys.stdin:
        if line.strip().startswith('{'):
            data = json.loads(line.strip())
            print(data.get('gate_result', 'PASS'))
            break
    else:
        print('PASS')
except Exception:
    print('PASS')
" 2>/dev/null || echo "PASS")
        
        case "$IMPACT_GATE" in
            PASS)
                echo "[IMPACT-GATE] PASS -- 미수정 참조 0건."
                ;;
            WARN)
                echo "[IMPACT-GATE] WARNING -- 미수정 참조 발견. 보고서에 사유 명시 필요."
                ;;
            BLOCK)
                echo "[IMPACT-GATE] BLOCKED -- 미수정 참조 6건 이상. 영향 파일도 수정하세요."
                # Phase 1: WARNING만. BLOCK은 2주 후 활성화.
                # exit 1
                echo "[IMPACT-GATE] (Phase 1: WARNING 모드. 2주 후 BLOCK 활성화 예정)"
                ;;
        esac
    else
        echo "[IMPACT-GATE] impact_scanner.py 없음 -- 스킵."
    fi
fi
```

**비슈누 (dev4)**: `scripts/ci_preflight.sh` 전체 구현:

```bash
#!/bin/bash
# ci_preflight.sh -- 멀티 러너 CI 게이트
# 사용: ci_preflight.sh <project_root> [--affected-only <commit_count>]
set -euo pipefail

PROJECT_ROOT="${1:-.}"
AFFECTED_ONLY="${2:-}"
COMMIT_COUNT="${3:-1}"
TIMEOUT_PER_RUNNER=120
RESULTS=()
OVERALL="PASS"

# Tech stack 감지
detect_stack() {
    STACKS=()
    [ -f "$PROJECT_ROOT/pyproject.toml" ] || [ -f "$PROJECT_ROOT/setup.py" ] && STACKS+=("python")
    [ -f "$PROJECT_ROOT/tsconfig.json" ] && STACKS+=("typescript")
    [ -f "$PROJECT_ROOT/vitest.config.ts" ] || [ -f "$PROJECT_ROOT/vitest.config.js" ] && STACKS+=("vitest")
    [ -f "$PROJECT_ROOT/jest.config.ts" ] || [ -f "$PROJECT_ROOT/jest.config.js" ] && STACKS+=("jest")
    echo "${STACKS[@]}"
}

run_with_timeout() {
    local name="$1"
    shift
    local exit_code=0
    timeout "$TIMEOUT_PER_RUNNER" "$@" 2>&1 || exit_code=$?
    if [ "$exit_code" -eq 124 ]; then
        echo "[CI-PREFLIGHT] $name: TIMEOUT (${TIMEOUT_PER_RUNNER}s)"
        RESULTS+=("{\"name\":\"$name\",\"result\":\"TIMEOUT\",\"exit_code\":124}")
    elif [ "$exit_code" -ne 0 ]; then
        echo "[CI-PREFLIGHT] $name: FAIL (exit $exit_code)"
        RESULTS+=("{\"name\":\"$name\",\"result\":\"FAIL\",\"exit_code\":$exit_code}")
        OVERALL="FAIL"
    else
        echo "[CI-PREFLIGHT] $name: PASS"
        RESULTS+=("{\"name\":\"$name\",\"result\":\"PASS\",\"exit_code\":0}")
    fi
}

STACKS=$(detect_stack)
echo "[CI-PREFLIGHT] Detected stacks: $STACKS"

for stack in $STACKS; do
    case "$stack" in
        python)
            if [ "$AFFECTED_ONLY" = "--affected-only" ]; then
                # 관련 테스트만 실행
                RELATED=$(python3 -c "
import subprocess, os, re
changed = subprocess.run(['git', '-C', '$PROJECT_ROOT', 'diff', '--name-only', 'HEAD~$COMMIT_COUNT..HEAD'], capture_output=True, text=True).stdout.strip().split('\n')
tests = set()
for f in changed:
    base = os.path.splitext(os.path.basename(f))[0]
    for pattern in [f'test_{base}.py', f'tests/test_{base}.py']:
        if os.path.exists(os.path.join('$PROJECT_ROOT', pattern)):
            tests.add(pattern)
print(' '.join(tests))
" 2>/dev/null)
                if [ -n "$RELATED" ]; then
                    run_with_timeout "pytest" python3 -m pytest $RELATED --tb=short -q
                else
                    echo "[CI-PREFLIGHT] pytest: SKIP (관련 테스트 없음)"
                fi
            else
                run_with_timeout "pytest" python3 -m pytest --tb=short -q
            fi
            ;;
        typescript)
            run_with_timeout "tsc" npx tsc --noEmit --project "$PROJECT_ROOT/tsconfig.json"
            ;;
        vitest)
            run_with_timeout "vitest" npx vitest run --reporter=dot --project "$PROJECT_ROOT"
            ;;
        jest)
            run_with_timeout "jest" npx jest --ci --silent --project "$PROJECT_ROOT"
            ;;
    esac
done

# 결과 JSON 출력
echo "[CI-PREFLIGHT] overall=$OVERALL"
```

**엔키 (dev5)**: finish-task.sh에 ci_preflight.sh 호출 삽입:

```bash
# Step 2.6.5: CI Preflight Gate (RC-2 방어)
# 위치: impact_scanner(2.6) 이후, member-status 복원(2.7) 이전
CI_SCRIPT="$WORKSPACE/scripts/ci_preflight.sh"
if [ -f "$CI_SCRIPT" ] && [ "$GIT_GATE_SKIP" -eq 0 ]; then
    echo "[CI-PREFLIGHT] 멀티 러너 CI 게이트 실행."
    CI_OUTPUT=$(bash "$CI_SCRIPT" "$PROJ_DIR" --affected-only "$COMMIT_COUNT" 2>&1)
    echo "$CI_OUTPUT"
    
    CI_OVERALL=$(echo "$CI_OUTPUT" | grep "overall=" | sed 's/.*overall=//')
    if [ "$CI_OVERALL" = "FAIL" ]; then
        echo "[CI-PREFLIGHT] FAIL -- CI 미통과. 수정 후 재실행."
        # Phase 1: WARNING만
        echo "[CI-PREFLIGHT] (Phase 1: WARNING 모드)"
    fi
fi
```

**쿠쿨칸 (dev6)**: "미해결 이슈" 게이트 -- 로키 피드백 반영하여 "범위 내"만 카운트:

```bash
# Step 2.11: Unresolved Issue Gate
if [ -f "$REPORT_FILE" ]; then
    # "범위 외"가 명시되지 않은 "미해결/확인 불가" = 범위 내 미해결
    TOTAL_UNRESOLVED=$(grep -ciE "미해결|확인 불가" "$REPORT_FILE" 2>/dev/null || echo "0")
    OUT_OF_SCOPE=$(grep -ciE "범위 외|외부 의존|다른 팀 소관" "$REPORT_FILE" 2>/dev/null || echo "0")
    IN_SCOPE_UNRESOLVED=$((TOTAL_UNRESOLVED - OUT_OF_SCOPE))
    if [ "$IN_SCOPE_UNRESOLVED" -lt 0 ]; then IN_SCOPE_UNRESOLVED=0; fi
    
    if [ "$IN_SCOPE_UNRESOLVED" -gt 3 ]; then
        echo "[UNRESOLVED-GATE] BLOCKED: 범위 내 미해결 이슈 ${IN_SCOPE_UNRESOLVED}건 (한도: 3건)"
        # Phase 1: WARNING
        echo "[UNRESOLVED-GATE] (Phase 1: WARNING 모드)"
    elif [ "$IN_SCOPE_UNRESOLVED" -gt 0 ]; then
        echo "[UNRESOLVED-GATE] WARNING: 범위 내 미해결 이슈 ${IN_SCOPE_UNRESOLVED}건"
    fi
fi
```

**이잠나 (dev7)**: `goal_assertions` 파서 -- finish-task.sh에 통합:

```bash
# Step 2.12: Goal Assertions Gate (오딘 제안)
if [ -f "$TASK_FILE" ]; then
    GOAL_ASSERTIONS=$(python3 -c "
import re, sys

with open('$TASK_FILE', encoding='utf-8') as f:
    content = f.read()

# ## 완료 기준 (goal_assertions) 섹션 추출
section_match = re.search(r'## 완료 기준.*?\n(.*?)(?=\n## |\Z)', content, re.DOTALL)
if not section_match:
    sys.exit(0)  # 섹션 없으면 SKIP

section = section_match.group(1)
assertions = []
for line in section.split('\n'):
    line = line.strip()
    # - \`command\` -> expected 형식
    m = re.match(r'-\s*\x60(.+?)\x60\s*(?:->|→)\s*(.+)', line)
    if m:
        assertions.append({'cmd': m.group(1).strip(), 'expected': m.group(2).strip().strip('\"')})

import json
print(json.dumps(assertions))
" 2>/dev/null)

    if [ -n "$GOAL_ASSERTIONS" ] && [ "$GOAL_ASSERTIONS" != "[]" ]; then
        echo "[GOAL-GATE] 완료 기준 검증 시작."
        GOAL_PASS=true
        echo "$GOAL_ASSERTIONS" | python3 -c "
import json, subprocess, sys, shlex

assertions = json.load(sys.stdin)
for a in assertions:
    cmd = a['cmd']
    expected = a['expected']
    try:
        result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=30, cwd='$PROJ_DIR')
        actual = result.stdout.strip()
        
        # 비교: 문자열 일치, exit code, 부등호
        if expected.startswith('exit '):
            expected_code = int(expected.split()[1])
            passed = result.returncode == expected_code
        elif expected.startswith('> '):
            passed = int(actual) > int(expected[2:])
        elif expected.startswith('>= '):
            passed = int(actual) >= int(expected[3:])
        else:
            passed = (actual == expected) or (expected in actual)
        
        status = 'PASS' if passed else 'FAIL'
        print(f'  [{status}] {cmd} => {actual} (expected: {expected})')
        if not passed:
            sys.exit(1)
    except Exception as e:
        print(f'  [ERROR] {cmd} => {e}')
        sys.exit(1)
print('  [GOAL-GATE] 전체 PASS')
" 2>&1
        GOAL_EXIT=$?
        if [ "$GOAL_EXIT" -ne 0 ]; then
            echo "[GOAL-GATE] FAIL -- 완료 기준 미충족."
            exit 1
        fi
    fi
fi
```

**라 (dev8)**: goal_assertions의 보안 문제: `shell=True`는 command injection 위험. 화이트리스트 기반 명령어만 허용:

```python
ALLOWED_COMMANDS = {"curl", "grep", "python3", "pytest", "npx", "node", "cat", "wc", "jq"}
cmd_base = shlex.split(cmd)[0] if cmd else ""
if cmd_base not in ALLOWED_COMMANDS:
    print(f"  [BLOCKED] 허용되지 않은 명령: {cmd_base}")
    continue
```

**로키 (Red Team)**: goal_assertions 패턴에 대한 공격:
1. 봇이 assertion을 일부러 쉽게 만들 수 있다. `grep -c "anything" file.txt` → `> 0`은 거의 항상 통과.
2. 아누가 assertion을 작성하면 "아누 의존도 제거"와 모순.
3. **방어**: assertion은 아누(또는 dispatch 시스템)가 자동 생성하는 것이어야 함. task_desc에서 "X 수정" → `grep -c "X" file` assertion 자동 생성.

**마아트 (QC)**: 로키 지적 동의. goal_assertions는 2단계로 운영:
- Level 1: 아누/dispatch.py가 task_desc에서 자동 생성 (backtick 코드 토큰 → grep assertion)
- Level 2: 봇이 작업 중 추가 assertion 기재 가능 (단, 아누 자동 생성분 삭제 불가)

**다빈치 (Design)**: 프론트 작업의 goal_assertions 자동 생성 예시:
- task_desc에 "컴포넌트 수정" → `curl -s http://localhost:3000 | grep -c "200"` → `> 0`
- task_desc에 "UI 변경" → `ls screenshots/ | wc -l` → `> 0`

**비너스 (Gemini)**: assertion 자동 생성은 Gemini/LLM이 task_desc를 분석하여 생성하면 더 정확. 하지만 외부 API 의존성이 생겨 dispatch.py가 느려짐. 대안: 규칙 기반 자동 생성(regex) + 선택적 LLM 보강.

**아틀라스 (Codex)**: Codex로 assertion 자동 생성은 가능하지만 비용 대비 효과가 낮음. 규칙 기반 충분.

### Devil's Advocate
**지정**: 로키 (Loki)

1. **실패 시나리오**: impact_scanner.py가 대형 프로젝트(InsuRo, 4000+ 파일)에서 `grep -rn` 10개 심볼 × 4000 파일 = 40,000 grep 연산. 실행 시간 30초+. 봇 세션 토큰 소진 가속.
2. **후회 이유**: 심볼 추출이 부정확하면(regex 기반 TypeScript 파싱) false negative 발생. "영향 있는 파일을 놓침" → 원래 문제와 동일. 사용자는 "스캐너가 있으니 안전하겠지"라는 안심 효과만 얻고 실제 품질은 미개선.
3. **더 단순한 대안**: impact_scanner 대신, 봇의 최종 커밋 메시지에 `[IMPACT: file1.ts, file2.tsx 확인 완료]`를 **필수 기재**하도록 DIRECT-WORKFLOW.md에 추가. finish-task.sh에서 이 태그 존재 확인. 심볼 분석 없이 "봇이 영향 범위를 의식적으로 확인했는지" 체크.

**반박**:
- 헤르메스: "grep 연산 40,000은 과장. grep은 매우 빠르다. 10 심볼 × `grep -rl`(파일명만) = 10초 이내. `--include` 필터로 .py/.ts/.tsx만 대상."
- 마아트: "커밋 메시지 기반 체크는 봇이 `[IMPACT: 확인 완료]`를 형식적으로 적을 위험. 코드 자동화가 목표인데 문서 규칙 추가는 역행."

**판정**: impact_scanner.py 유지하되, 타임아웃 15초 + 심볼 최대 5개로 제한. 대형 프로젝트에서도 5초 이내 완료 가능.

### 비관습적 대안
**제시자**: 엔키 (Enki)

**대안**: "Pre-commit Hook as Gate" -- 봇의 git commit 시점에 pre-commit hook으로 impact_scanner를 실행. finish-task.sh가 아닌 **커밋 시점**에서 차단. 이점: 봇이 이미 커밋하고 나서 ".done에서 차단"이 아니라, 애초에 불완전한 커밋 자체를 방지.

5점 평가:
- 효과성: 4/5 (커밋 시점 차단은 강력)
- 실현 가능성: 2/5 (worktree별 .git/hooks 관리 복잡, 봇이 --no-verify 사용 위험)
- 비용: 5/5 (추가 비용 없음)
- 부작용: 3/5 (커밋 실패 → 봇 작업 흐름 중단 → 세션 낭비)
- 기존 시스템 호환: 2/5 (worktree_manager.py에 hook 주입 로직 추가 필요)

### 합의/결론
C3-1: impact_scanner.py는 finish-task.sh Step 2.6에서 호출 (pre-commit hook이 아님)
C3-2: 심볼 추출 최대 5개, grep 타임아웃 15초, 전체 실행 타임아웃 30초
C3-3: goal_assertions는 dispatch.py가 backtick 토큰에서 자동 생성 (Level 1)
C3-4: goal_assertions 실행은 finish-task.sh Step 2.12, ALLOWED_COMMANDS 화이트리스트 적용
C3-5: ci_preflight.sh는 finish-task.sh Step 2.6.5에서 호출, 타임아웃 120초/runner
C3-6: 모든 신규 게이트의 Phase 1은 WARNING 모드 (2주 후 데이터 기반 FAIL 승격)

### 미해결 항목
- dispatch.py에서 goal_assertions 자동 생성 로직의 정확한 위치
- l1_smoketest_check의 "확인 불가" 패턴 목록 확정
- finish-task.sh의 step 번호 최종 정리 (2.6 ~ 2.12)

---

## Cycle 4: 통합 테스트 시나리오 및 엣지 케이스 (Sequential Mode)

### 아누 분석

Cycle 3에서 구현 계획이 확정되었다. 이제 "이 코드가 실제로 문제를 해결하는지" 검증 시나리오를 정의한다. 3대 루트 원인 각각에 대해 "이게 되면 성공" 시나리오를 구체화한다.

### 페르소나 의견 (Sequential)

**헤르메스 (dev1)**: RC-1 검증 시나리오:

```
시나리오: task-2142 재현 (FeatureGate.tsx 수정, use-feature-access.ts 누락)

사전 조건:
  - FeatureGate.tsx에 `isPremiumUser()` 함수 존재
  - use-feature-access.ts에서 `isPremiumUser` import하여 사용
  - 봇이 FeatureGate.tsx만 수정하고 커밋

기대 결과:
  1. impact_scanner.py 실행 → "isPremiumUser"가 use-feature-access.ts에서 참조됨 감지
  2. gate_result: "WARN" (1건)
  3. finish-task.sh 콘솔 출력:
     [IMPACT-GATE] WARNING -- 미수정 참조 발견:
       isPremiumUser → use-feature-access.ts:15 (미수정)

검증 방법:
  1. 테스트 프로젝트 준비 (projects/test-impact-scanner/)
  2. python3 scripts/impact_scanner.py --project-root ./test --modified-files "FeatureGate.tsx"
  3. JSON 출력에 unmodified_references에 use-feature-access.ts 포함 확인
```

**오딘 (dev2)**: RC-2 검증 시나리오:

```
시나리오: task-2143/2144 재현 (Vitest + pytest 연쇄)

사전 조건:
  - 프로젝트에 vitest.config.ts 존재
  - vitest 테스트 1건 실패, pytest 테스트 1건 실패
  - 봇이 vitest 실패만 수정하고 완료 시도

기대 결과:
  1. ci_preflight.sh 실행 → vitest PASS, pytest FAIL 감지
  2. [CI-PREFLIGHT] overall=FAIL
  3. finish-task.sh가 .done 생성 차단 (Phase 2에서)

검증 방법:
  1. 테스트 프로젝트에 의도적 pytest 실패 삽입
  2. bash scripts/ci_preflight.sh ./test
  3. overall=FAIL 확인
```

**다그다 (dev3)**: RC-3 검증 시나리오:

```
시나리오: task-2140 재현 (UI 직접 확인 불가)

사전 조건:
  - 프론트엔드 작업 (task_desc에 "컴포넌트" 키워드)
  - 봇이 보고서에 "UI 직접 확인 불가" 기재

기대 결과:
  1. l1_smoketest_check 실행 → "확인 불가" 패턴 감지
  2. qc_verify.py 결과: l1_smoketest_check = FAIL
  3. finish-task.sh의 QC FAIL → .done 생성 차단

검증 방법:
  1. 테스트 보고서에 "UI 직접 확인 불가" 기재
  2. python3 qc_verify.py --task-id test-001
  3. l1_smoketest_check: FAIL 확인
```

**비슈누 (dev4)**: 엣지 케이스 목록:

| # | 엣지 케이스 | 기대 동작 | 처리 방법 |
|---|-------------|-----------|-----------|
| E-1 | impact_scanner 타임아웃 (30초 초과) | SKIP + WARNING | `timeout 30` + fallback PASS |
| E-2 | 프로젝트에 package.json 없는데 .tsx 파일 수정 | tsc 스킵 | ci_preflight.sh의 tech stack 감지 |
| E-3 | goal_assertion 명령어가 ALLOWED_COMMANDS에 없음 | 해당 assertion SKIP | 로깅 후 나머지 assertion 계속 |
| E-4 | 봇이 파일 0건 수정 (문서만 업데이트) | 모든 게이트 SKIP | GIT_GATE_SKIP 이미 존재 |
| E-5 | goal_assertions 섹션에 잘못된 포맷 | 파싱 실패 → SKIP | try/except로 graceful |
| E-6 | 보고서 파일이 없는 상태에서 l1_smoketest_check | FAIL | file_check verifier가 선행 |
| E-7 | impact_scanner에서 common 함수명(init, setup) | false positive 다수 | COMMON_FILTER 확장 |
| E-8 | worktree 외부에서 finish-task.sh 실행 | git diff 실패 | PROJ_DIR fallback → WORKSPACE |

**엔키 (dev5)**: Phase 1 → Phase 2 전환 기준:

```
Phase 1 (2주): 모든 신규 게이트 WARNING 모드
- 데이터 수집: 각 게이트의 WARN 발생 빈도, false positive 비율
- 로그 위치: memory/logs/gate-metrics/
- 수집 항목:
  - impact_scanner: WARN 건수, 심볼 수, 실행 시간
  - ci_preflight: runner별 PASS/FAIL/TIMEOUT
  - l1_smoketest: "확인 불가" 패턴 감지 건수
  - goal_assertions: PASS/FAIL/SKIP 비율
  - unresolved_gate: 범위 내 미해결 건수

Phase 2 전환 기준:
- false positive 비율 < 10%이면 FAIL 승격
- false positive 비율 10-30%이면 임계치 조정 후 유지
- false positive 비율 > 30%이면 로직 재설계
```

**쿠쿨칸 (dev6)**: finish-task.sh의 최종 step 순서 정리:

```
0.   노하우 업데이트 검증 (디자인/마케팅만)
0b.  QC 프로세스 검증 (디자인/이미지만)
1.   QC 실행 (qc_verify.py --gate)
2.   머지 (worktree_manager.py finish)
2.5. Git 게이트 (커밋 존재, uncommitted 없음, 빈 커밋 방어)
2.6. Impact Scanner Gate [신규] (RC-1)
2.6.5. CI Preflight Gate [신규] (RC-2)
2.7. 팀원 idle 복원
2.8. G3 독립 검증 (Lv.3+)
2.9. Lv.4 보안 감사
2.10. Codex 사전검증
2.11. Unresolved Issue Gate [신규]
2.12. Goal Assertions Gate [신규]
3.   .done 원자적 생성
4.   task-timer end
5.   notify-completion.py
```

**이잠나 (dev7)**: l1_smoketest_check 패턴 목록 확정:

```python
BLOCK_PATTERNS = [
    r"UI 직접 확인 불가",
    r"서버 기동 불가",
    r"환경 제약으로 확인 불가",
    r"브라우저 접근 불가",
    r"실동작 확인.*불가",
    r"스크린샷 캡처.*실패",
    r"localhost.*접속.*불가",
]

EXEMPT_PATTERNS = [
    r"코드 수정 없음",
    r"문서.*업데이트만",
    r"유틸리티 함수.*테스트로 검증",
    r"백엔드.*API.*curl.*확인",
]
# EXEMPT 패턴이 있으면 BLOCK 패턴 무시 (정당한 사유)
```

**라 (dev8)**: dispatch.py에서 goal_assertions 자동 생성 위치:

```python
# dispatch.py dispatch() 함수 내, task_desc를 파일에 저장하기 직전
# 약 2900행 부근, task_file.write_text(task_desc) 직전

# ── goal_assertions 자동 생성 (task-2145) ──────────────────
if task_type == "coding":
    task_desc = _auto_generate_goal_assertions(task_desc, str(WORKSPACE))

# task_desc를 파일에 저장
task_file = WORKSPACE / "memory" / "tasks" / f"{task_id}.md"
task_file.parent.mkdir(parents=True, exist_ok=True)
task_file.write_text(task_desc, encoding="utf-8")
```

```python
def _auto_generate_goal_assertions(task_desc: str, workspace_root: str) -> str:
    """task_desc의 backtick 코드 토큰에서 goal_assertions 자동 생성.
    
    이미 ## 완료 기준 섹션이 있으면 SKIP.
    coding task에서만 호출.
    """
    if "## 완료 기준" in task_desc or "## goal_assertions" in task_desc:
        return task_desc
    
    tokens = re.findall(r'`([A-Za-z_]\w*(?:\(\))?)`', task_desc)
    COMMON_FILTER = {"data", "result", "config", "props", "state", "error",
                     "value", "item", "items", "list", "name", "path",
                     "type", "id", "key", "index", "event", "options",
                     "true", "false", "null", "None", "self"}
    tokens = [t.rstrip("()") for t in tokens if t.rstrip("()").lower() not in COMMON_FILTER]
    tokens = list(dict.fromkeys(tokens))[:5]  # 최대 5개, 순서 유지
    
    if not tokens:
        return task_desc
    
    # affected_files에서 파일 경로 추출 (있는 경우)
    af = _parse_affected_files(task_desc)
    
    assertions = []
    for token in tokens:
        if af:
            # affected_files의 첫 번째 파일에서 토큰 존재 확인
            target_file = af[0]
            assertions.append(f"- `grep -c \"{token}\" {target_file}` -> > 0")
        else:
            assertions.append(f"- `grep -rl \"{token}\" .` -> exit 0")
    
    section = "\n\n## 완료 기준 (goal_assertions) [auto-generated]\n"
    section += "\n".join(assertions)
    section += "\n\n> 위 기준은 dispatch.py가 자동 생성. 봇은 추가 가능하나 삭제 불가.\n"
    
    return task_desc + section
```

**로키 (Red Team)**: 통합 테스트 누락 지적. 제안된 5개 게이트가 **서로 충돌하지 않는지** 확인 필요:
- impact_scanner WARN + ci_preflight PASS + goal_assertions PASS → .done 생성 가능해야 함 (WARN은 blocking 아님)
- impact_scanner PASS + ci_preflight FAIL → .done 생성 차단되어야 함 (Phase 2에서)
- goal_assertions FAIL → .done 생성 차단 (Phase 1부터)
- 통합 테스트: 5개 게이트 모든 조합 (2^5 = 32개 케이스) 중 핵심 8개 검증

**마아트 (QC)**: 통합 게이트 결과 판정 매트릭스:

| impact | ci_preflight | l1_smoke | goal_assert | unresolved | .done |
|--------|-------------|----------|-------------|------------|-------|
| PASS | PASS | PASS | PASS | PASS | YES |
| WARN | PASS | PASS | PASS | PASS | YES (Phase1) |
| PASS | FAIL | PASS | PASS | PASS | YES (Phase1, WARN only) |
| PASS | PASS | FAIL | PASS | PASS | NO (QC FAIL) |
| PASS | PASS | PASS | FAIL | PASS | NO |
| BLOCK | PASS | PASS | PASS | PASS | YES (Phase1), NO (Phase2) |
| PASS | PASS | PASS | PASS | BLOCK | YES (Phase1), NO (Phase2) |
| any | any | FAIL | any | any | NO (QC FAIL은 항상 blocking) |

**다빈치 (Design)**: 프론트 작업 워크플로우 통합 확인:
- Playwright 스크린샷 → `browser_verify` verifier (기존)
- 서버 재시작 → `l1_smoketest_check` (강화됨)
- 콘솔 에러 → Playwright `browser_console_messages` (기존 문서 규칙)
- 이 3가지가 순차적으로 실행되는지 확인 필요

**비너스 (Gemini)**: Gemini PR 리뷰에 impact 경고 프롬프트를 추가하는 구체적 위치: `scripts/worktree_manager.py`의 PR 생성 후 Gemini 대기 루프. 현재는 Gemini가 자동으로 리뷰하지만, **추가 코멘트**로 impact 분석을 요청.

**아틀라스 (Codex)**: Codex lightweight 분석을 ci_preflight.sh에 통합하는 것은 복잡도가 높으므로 별도 이슈로 분리.

### Devil's Advocate
**지정**: 로키 (Loki)

1. **실패 시나리오**: goal_assertions의 `grep -c "FeatureGate"` assertion이 코드에서 주석으로만 존재하는 경우에도 PASS. 실제 로직 변경이 아닌 주석 참조도 count되므로 의미 없는 검증.
2. **후회 이유**: 5개 신규 게이트를 한꺼번에 도입하면 봇의 finish-task.sh 실행 시간이 30초 → 3분으로 증가. 세션 토큰 소진 가속. 작업 완료율 하락.
3. **더 단순한 대안**: 5개가 아닌 **2개**만 우선 도입. RC-3(l1_smoketest 강화)와 RC-1(impact_scanner)만. RC-2(ci_preflight)는 기존 qc_verify.py의 test_runner로 커버 가능. goal_assertions와 unresolved_gate는 데이터 수집 후 결정.

**반박**:
- 마아트: "우선순위 2개 도입은 동의하지만, 나머지 3개도 WARNING 모드로 함께 도입해야 데이터 수집이 가능. 2주 후 Phase 2 판단 시 데이터가 없으면 결정 불가."
- 엔키: "실행 시간 증가 우려는 타당. impact_scanner(5초) + ci_preflight(120초 최대) = 125초 추가. 하지만 ci_preflight는 `--affected-only`로 관련 테스트만 실행하면 30초 이내."

**판정**: 로키의 "2개 우선 + 나머지 WARNING" 접근 채택. l1_smoketest 강화(FAIL 즉시)와 impact_scanner(WARN)를 Phase 1A로, ci_preflight + goal_assertions + unresolved_gate를 Phase 1B(WARNING 전용 데이터 수집)로 분리.

### 비관습적 대안
**제시자**: 마아트 (Maat)

**대안**: "QC Report Card" -- 각 task의 .done 파일에 gate 결과를 JSON으로 기록. 대시보드에서 팀별/작업레벨별 게이트 통과율을 시각화. 아누가 "어느 팀의 어느 게이트가 자주 FAIL하는지" 한눈에 파악.

5점 평가:
- 효과성: 4/5 (데이터 기반 의사결정 지원)
- 실현 가능성: 5/5 (.done 파일 JSON에 필드 추가만)
- 비용: 5/5 (추가 비용 없음)
- 부작용: 0/5 (부작용 없음)
- 기존 시스템 호환: 5/5 (.done 파일 포맷 확장)

### 합의/결론
C4-1: 도입 우선순위: Phase 1A (즉시) = l1_smoketest 강화 + impact_scanner / Phase 1B (WARN 전용) = ci_preflight + goal_assertions + unresolved_gate
C4-2: Phase 2 전환 기준: 2주 운영 데이터에서 false positive < 10%
C4-3: .done 파일에 gate_results JSON 필드 추가 (QC Report Card)
C4-4: finish-task.sh 전체 추가 실행 시간 한도: 60초 (초과 시 게이트 SKIP + WARNING)
C4-5: 통합 테스트 8개 핵심 케이스 작성 필수

### 미해결 항목
- 대시보드에 QC Report Card 시각화 (별도 task)
- ci_preflight.sh의 프로젝트별 .ci-config.json 도입 시점

---

## Cycle 5: 최종 합의 및 구현 일정 (Sequential Mode)

### 아누 분석

4개 Cycle을 거쳐 3대 루트 원인에 대한 코드 수준 해결책이 확정되었다. 이제 최종 합의 항목을 정리하고, 구현 일정을 Temporal Interrogation으로 확정한다.

### 페르소나 의견 (Sequential -- 최종 확인)

**헤르메스 (dev1)**: 전체 합의에 동의. impact_scanner.py 구현을 담당하겠다. TypeScript 심볼 추출은 regex 기반으로 시작하고, 정밀도 이슈 발생 시 `ts-morph` 라이브러리로 업그레이드.

**오딘 (dev2)**: dispatch.py 변경 (auto_inject_affected_files + auto_generate_goal_assertions) 구현 담당. 기존 `_enrich_affected_files_with_ast` 함수와의 실행 순서 정리 필요: auto_inject → enrich_with_ast → overlap_check.

**다그다 (dev3)**: finish-task.sh 변경 (Step 2.6, 2.6.5, 2.11, 2.12 삽입) 구현 담당. 기존 step 번호와 충돌하지 않도록 주의.

**비슈누 (dev4)**: ci_preflight.sh 구현 담당. 테스트 프로젝트로 InsuRo(TypeScript) + workspace(Python)에서 검증.

**엔키 (dev5)**: Phase 1 → Phase 2 전환 메트릭 수집 로직 구현. `memory/logs/gate-metrics/` 디렉토리에 일별 JSON 로그.

**쿠쿨칸 (dev6)**: unresolved_gate 로직 구현. 기존 보고서 파싱 패턴과의 호환성 확인.

**이잠나 (dev7)**: l1_smoketest_check verifier 강화 구현. BLOCK_PATTERNS + EXEMPT_PATTERNS 적용.

**라 (dev8)**: goal_assertions 자동 생성 + 실행 로직 구현. ALLOWED_COMMANDS 화이트리스트.

**로키 (Red Team)**: 최종 확인 -- 모든 신규 게이트가 `|| true` 또는 `set +e` 패턴으로 비활성화될 수 있는 취약점이 없는지 확인 필요. finish-task.sh에서 `set -euo pipefail`이 설정되어 있으므로, 각 게이트의 exit code가 정확하게 전파되는지 검증.

현재 finish-task.sh의 `set -euo pipefail`은 파일 최상단에 있고, QC 실행(Step 1)에서 `|| true`를 사용한다. 이것은 QC FAIL 시에도 스크립트가 계속 실행되는 것을 의미. QC FAIL은 별도의 exit 1로 처리. 신규 게이트도 동일 패턴을 따라야 한다.

**마아트 (QC)**: .done 파일 포맷 변경안:

```json
{
    "task_id": "task-2145",
    "team": "dev1",
    "qc_result": "PASS",
    "completed_at": "2026-04-24T15:00:00+09:00",
    "status": "done",
    "gate_results": {
        "impact_scanner": {"result": "WARN", "unmodified_refs": 2},
        "ci_preflight": {"result": "PASS", "runners": ["pytest", "tsc"]},
        "l1_smoketest": {"result": "PASS"},
        "goal_assertions": {"result": "PASS", "total": 3, "passed": 3},
        "unresolved_gate": {"result": "PASS", "in_scope_count": 0}
    }
}
```

**다빈치 (Design)**: 프론트 작업의 l1_smoketest에서 EXEMPT_PATTERNS가 정확하게 동작하는지 우려. "유틸리티 함수 테스트로 검증"이 EXEMPT인데, 실제로는 UI에 영향 미치는 유틸리티일 수 있음. **프론트 키워드가 task_desc에 있으면 EXEMPT 적용 불가**로 제한 필요.

수정:
```python
# EXEMPT 적용 조건: task_desc에 프론트 키워드 없을 때만
FRONTEND_KEYWORDS = ["컴포넌트", "UI", "화면", "페이지", "프론트", "frontend", "React", "Next"]
is_frontend = any(kw.lower() in task_desc_lower for kw in FRONTEND_KEYWORDS)
if is_frontend:
    exempt_patterns = []  # 프론트 작업은 EXEMPT 없음
```

**비너스 (Gemini)**: Gemini PR 리뷰 프롬프트 수정은 `scripts/worktree_manager.py`의 PR 생성 로직 내에서 처리. 별도 구현 작업으로 분리.

**아틀라스 (Codex)**: Codex lightweight 분석은 Phase 2 이후로 연기. 현재는 기존 Codex 게이트(Lv.3+)를 유지.

### Devil's Advocate
**지정**: 로키 (Loki)

최종 DA:

1. **가장 큰 위험**: 5개 게이트 동시 도입으로 봇의 finish-task.sh 실행 복잡도 증가 → 예측 불가 실패 모드 발생. 예: impact_scanner가 stderr에 경고를 출력 → finish-task.sh의 Python 파싱이 깨짐 → QC 결과가 무시됨 → .done이 잘못 생성.
2. **방어책**: 각 게이트는 **독립적**으로 실행되어야 함. 한 게이트의 실패가 다른 게이트에 영향을 미치면 안 됨. 각 게이트를 `(subshell)` 또는 별도 함수로 격리.
3. **최종 승인 조건**: 5개 게이트 각각에 대해 `tests/test_gates.sh` 단위 테스트가 존재해야 코드 머지 승인.

**반박**: 전원 동의. 게이트 격리와 단위 테스트 작성을 구현 요구사항에 추가.

**판정**: 로키의 3가지 조건 모두 채택.

### 비관습적 대안
**제시자**: 쿠쿨칸 (Kukulkan)

**대안**: "Gate Config File" -- 각 게이트의 활성화/비활성화, 임계치, 모드(WARN/FAIL)를 JSON 설정 파일로 관리. `config/gate-config.json`:

```json
{
    "impact_scanner": {
        "enabled": true,
        "mode": "warn",
        "timeout_seconds": 30,
        "max_symbols": 5,
        "block_threshold": 6
    },
    "ci_preflight": {
        "enabled": true,
        "mode": "warn",
        "timeout_per_runner": 120,
        "affected_only": true
    },
    "l1_smoketest": {
        "enabled": true,
        "mode": "fail",
        "block_patterns": ["UI 직접 확인 불가", "..."]
    },
    "goal_assertions": {
        "enabled": true,
        "mode": "fail",
        "allowed_commands": ["curl", "grep", "pytest", "npx", "node", "cat", "wc", "jq"]
    },
    "unresolved_gate": {
        "enabled": true,
        "mode": "warn",
        "max_in_scope": 3
    }
}
```

5점 평가:
- 효과성: 5/5 (Phase 1→2 전환이 설정 파일 1줄 변경으로 가능)
- 실현 가능성: 5/5 (JSON 파싱은 간단)
- 비용: 5/5 (추가 비용 없음)
- 부작용: 1/5 (설정 파일이 잘못되면 모든 게이트 비활성화 가능)
- 기존 시스템 호환: 5/5 (기존 config/ 패턴과 일치)

### 합의/결론 (최종)

전원 합의 (만장일치):

1. **C-FINAL-1**: `scripts/impact_scanner.py` 신규 생성 -- 수정 파일 기반 역방향 영향 범위 스캔
2. **C-FINAL-2**: `scripts/ci_preflight.sh` 신규 생성 -- 멀티 러너 CI 게이트
3. **C-FINAL-3**: `teams/shared/verifiers/l1_smoketest_check.py` 수정 -- "확인 불가" 패턴 FAIL + EXEMPT 조건
4. **C-FINAL-4**: `dispatch.py`에 `_auto_inject_affected_files()` 및 `_auto_generate_goal_assertions()` 추가
5. **C-FINAL-5**: `scripts/finish-task.sh`에 Step 2.6 (Impact), 2.6.5 (CI), 2.11 (Unresolved), 2.12 (Goal) 추가
6. **C-FINAL-6**: `config/gate-config.json` 신규 생성 -- 게이트 설정 중앙 관리
7. **C-FINAL-7**: `.done` 파일에 `gate_results` JSON 필드 추가 (QC Report Card)
8. **C-FINAL-8**: Phase 1A(즉시 FAIL) = l1_smoketest + goal_assertions / Phase 1B(WARN 전용) = impact_scanner + ci_preflight + unresolved_gate
9. **C-FINAL-9**: 모든 게이트는 독립 실행 (subshell 격리), 단위 테스트 필수
10. **C-FINAL-10**: 2주 운영 데이터 수집 → false positive < 10%이면 Phase 1B → FAIL 승격

---

## Temporal Interrogation (구현 일정)

### HOUR 1: 긴급 (즉일 착수)
- l1_smoketest_check.py 수정 (BLOCK_PATTERNS + EXEMPT_PATTERNS)
- config/gate-config.json 생성 (초기 설정값)

### HOUR 2-3: 핵심 인프라
- scripts/impact_scanner.py 구현 (Python AST + TS regex + grep)
- dispatch.py에 `_auto_inject_affected_files()` 추가
- dispatch.py에 `_auto_generate_goal_assertions()` 추가

### HOUR 4-5: CI 게이트
- scripts/ci_preflight.sh 구현
- finish-task.sh Step 2.6, 2.6.5, 2.11, 2.12 삽입
- .done 파일 gate_results 필드 추가 (finish-task.sh Step 3 수정)

### HOUR 6+: 테스트 및 검증
- tests/test_impact_scanner.py 작성
- tests/test_ci_preflight.sh 작성
- tests/test_goal_assertions.py 작성
- 통합 테스트 8개 핵심 케이스
- Phase 1B 메트릭 수집 로직 구현

---

## 3문서 매핑 가이드

### 계획서 (plan.md) 매핑

| 합의 항목 | 담당 파일 | 예상 변경량 |
|-----------|-----------|------------|
| C-FINAL-1 | scripts/impact_scanner.py (신규) | ~200줄 |
| C-FINAL-2 | scripts/ci_preflight.sh (신규) | ~80줄 |
| C-FINAL-3 | teams/shared/verifiers/l1_smoketest_check.py (수정) | ~30줄 |
| C-FINAL-4 | dispatch.py (수정) | ~80줄 (2개 함수 추가) |
| C-FINAL-5 | scripts/finish-task.sh (수정) | ~100줄 (4개 step 추가) |
| C-FINAL-6 | config/gate-config.json (신규) | ~40줄 |
| C-FINAL-7 | scripts/finish-task.sh Step 3 (수정) | ~10줄 |

### 맥락 노트 (context-notes.md) 매핑

- RC-1(영향 범위 누락) → impact_scanner.py + auto_inject_affected_files
  - 결정 근거: post-flight 역방향 스캔이 pre-flight 아누 의존보다 "아누 의존도 제거"에 부합
  - 참조: task-2142 사례 분석
- RC-2(연쇄 이슈) → ci_preflight.sh
  - 결정 근거: 기존 test_runner는 --check-files 기반 부분 테스트. 멀티 러너 전체 스택 검증 필요
  - 참조: task-2143/2144 사례 분석
- RC-3(실동작 미검증) → l1_smoketest_check 강화
  - 결정 근거: "확인 불가" 패턴이 보고서에 있으면 QC FAIL. 문서 규칙이 아닌 코드 강제
  - 참조: task-2140 사례 분석
- Phase 1A/1B 분리
  - 결정 근거: 로키의 "false positive 위험" + "2개 우선 도입" 제안
  - 합의: l1_smoketest + goal_assertions = 즉시 FAIL / 나머지 = WARN 모드 데이터 수집

### 체크리스트 (checklist.md) 매핑

- [ ] scripts/impact_scanner.py 구현 + 단위 테스트
- [ ] scripts/ci_preflight.sh 구현 + 단위 테스트
- [ ] teams/shared/verifiers/l1_smoketest_check.py BLOCK_PATTERNS 추가
- [ ] dispatch.py `_auto_inject_affected_files()` 추가
- [ ] dispatch.py `_auto_generate_goal_assertions()` 추가
- [ ] scripts/finish-task.sh Step 2.6, 2.6.5, 2.11, 2.12 삽입
- [ ] config/gate-config.json 생성
- [ ] .done 파일 gate_results 필드 추가
- [ ] 통합 테스트 8개 핵심 케이스 작성
- [ ] DIRECT-WORKFLOW.md에 goal_assertions 포맷 문서화
- [ ] QC-RULES.md에 신규 verifier 설명 추가
- [ ] 2주 후 Phase 2 전환 판단 (메트릭 리뷰)

---

## 구체적 구현 계획

### 1. scripts/impact_scanner.py

```
파일 경로: /home/jay/workspace/scripts/impact_scanner.py
목적: 수정 파일 기반 역방향 영향 범위 스캐너
호출자: finish-task.sh Step 2.6

함수 시그니처:
  - extract_symbols_python(file_path: str, diff_lines: list[int]) -> list[str]
  - extract_symbols_typescript(file_path: str, diff_lines: list[int]) -> list[str]
  - grep_references(symbol: str, project_root: str, exclude: list[str]) -> list[dict]
  - scan(project_root: str, modified_files: list[str], task_id: str) -> dict

CLI 인터페이스:
  python3 impact_scanner.py --project-root <path> --task-id <id> [--max-symbols 5] [--timeout 30]

출력 포맷 (JSON, stdout 마지막 줄):
  {"task_id": "...", "gate_result": "PASS|WARN|BLOCK", "unmodified_references": [...]}

임계치:
  - 0건: PASS
  - 1-5건: WARN
  - 6건+: BLOCK

제약:
  - grep 타임아웃: 심볼당 3초, 전체 15초
  - 심볼 최대 5개
  - exclude: node_modules, .git, __pycache__, dist, build, .worktrees, .next
  - COMMON_FILTER: data, result, config, props, state, error, value 등 20+ 단어
```

### 2. scripts/ci_preflight.sh

```
파일 경로: /home/jay/workspace/scripts/ci_preflight.sh
목적: 멀티 러너 CI 게이트
호출자: finish-task.sh Step 2.6.5

CLI: bash ci_preflight.sh <project_root> [--affected-only <commit_count>]

tech stack 감지:
  - pyproject.toml/setup.py → pytest
  - tsconfig.json → tsc --noEmit
  - vitest.config.* → vitest run
  - jest.config.* → jest --ci

각 runner 타임아웃: 120초 (gate-config.json에서 설정 가능)
전체 타임아웃: 300초 (5분)

출력: [CI-PREFLIGHT] overall=PASS|FAIL (stdout)
```

### 3. teams/shared/verifiers/l1_smoketest_check.py 수정

```
파일 경로: /home/jay/workspace/teams/shared/verifiers/l1_smoketest_check.py
목적: "확인 불가" 패턴 차단 + 증거 필수

추가 로직:
  1. BLOCK_PATTERNS 목록 대조 → 매칭 시 FAIL
  2. EXEMPT_PATTERNS 목록 대조 → 매칭 시 BLOCK 무시
  3. is_frontend 판별 → True이면 EXEMPT 무효화
  4. 증거 필수: 스크린샷 경로 OR curl 200 OR pytest passed
```

### 4. dispatch.py 수정

```
파일 경로: /home/jay/workspace/dispatch.py
목적: 자동 affected_files 주입 + goal_assertions 생성

추가 함수:
  - _auto_inject_affected_files(task_desc, workspace_root) -> str
    호출 위치: dispatch() 함수 내, task_file.write_text() 직전 (약 2900행)
    호출 조건: 항상 (task_type 무관)
    
  - _auto_generate_goal_assertions(task_desc, workspace_root) -> str
    호출 위치: _auto_inject_affected_files 직후
    호출 조건: task_type == "coding" 일 때만
```

### 5. scripts/finish-task.sh 수정

```
파일 경로: /home/jay/workspace/scripts/finish-task.sh
목적: 4개 신규 게이트 삽입 + .done gate_results 필드 추가

삽입 위치 (기존 step 사이):
  Step 2.6: Impact Scanner Gate (git 게이트 이후, member-status 이전)
  Step 2.6.5: CI Preflight Gate (Impact 이후, member-status 이전)
  Step 2.11: Unresolved Issue Gate (Codex 이후, .done 이전)
  Step 2.12: Goal Assertions Gate (Unresolved 이후, .done 이전)

.done 파일 수정:
  Step 3의 JSON에 gate_results 딕셔너리 추가
```

### 6. config/gate-config.json

```
파일 경로: /home/jay/workspace/config/gate-config.json
목적: 게이트 설정 중앙 관리

초기값:
  impact_scanner: enabled=true, mode=warn, timeout=30, max_symbols=5, block_threshold=6
  ci_preflight: enabled=true, mode=warn, timeout_per_runner=120, affected_only=true
  l1_smoketest: enabled=true, mode=fail
  goal_assertions: enabled=true, mode=fail
  unresolved_gate: enabled=true, mode=warn, max_in_scope=3
```

---

## 팀 배정안

| 합의 항목 | 담당 팀 | 예상 시간 | 우선순위 |
|-----------|---------|-----------|----------|
| C-FINAL-3 (l1_smoketest) | dev7-team (이잠나) | 2시간 | P1 (Phase 1A) |
| C-FINAL-1 (impact_scanner) | dev1-team (헤르메스) | 4시간 | P1 (Phase 1A) |
| C-FINAL-4 (dispatch.py) | dev2-team (오딘) | 3시간 | P1 |
| C-FINAL-5 (finish-task.sh) | dev3-team (다그다) | 3시간 | P1 |
| C-FINAL-2 (ci_preflight) | dev4-team (비슈누) | 3시간 | P2 (Phase 1B) |
| C-FINAL-6 (gate-config) | dev5-team (엔키) | 1시간 | P2 |
| C-FINAL-7 (.done 포맷) | dev3-team (다그다) | 1시간 | P2 |
| 통합 테스트 | composite (로키+마아트) | 4시간 | P1 |

---

## 최종 의결

**만장일치** 합의 달성.

참석자 13명 전원이 위 10개 합의 항목(C-FINAL-1 ~ C-FINAL-10)에 동의합니다.

핵심 원칙: "아누가 잊어먹어도 코드가 자동으로 품질을 강제한다."

---

## 부록: 기존 코드 참조 정보

### dispatch.py 기존 관련 함수 (수정 대상 아닌, 참조용)
- `_parse_affected_files()` (766행): task_desc에서 affected_files 파싱
- `_enrich_affected_files_with_ast()` (812행): AST blast radius 보강
- `_check_affected_files_overlap()` (875행): running task와 겹침 확인
- `_warn_missing_affected_files()` (932행): Lv.2+ affected_files 미기재 경고

### finish-task.sh 기존 게이트 (참조용)
- Step 0: 노하우 업데이트 검증 (디자인/마케팅)
- Step 0b: QC 프로세스 검증 (로키 소환 확인)
- Step 1: QC 실행 (qc_verify.py --gate)
- Step 2: 머지 (worktree_manager.py finish)
- Step 2.5: Git 게이트 (커밋/uncommitted/빈 커밋)
- Step 2.7: 팀원 idle 복원
- Step 2.8: G3 독립 검증 (Lv.3+)
- Step 2.9: Lv.4 보안 감사
- Step 2.10: Codex 사전검증

### qc_verify.py 기존 verifier 목록 (참조용)
api_health, browser_verify, critical_gap, data_integrity, duplicate_check,
file_check, file_touch_ratio_check, git_evidence, l1_smoketest_check,
planned_check, pyright_check, schema_contract, scope_check, signature_check,
spec_compliance, style_check, symbol_existence_check, tdd_check, test_runner,
three_docs_check, png_check, claude_md_check
