# task-2147: dispatch.py 확장 — auto_inject_affected_files + auto_generate_goal_assertions

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

## 3문서 참조 (필독)
- 프로젝트 계획서: `/home/jay/workspace/memory/plans/system/dispatch-quality-gates/plan.md` — 컴포넌트 4번
- 맥락노트: `/home/jay/workspace/memory/plans/tasks/dispatch-quality-gates/context-notes.md` — task-2147 섹션
- 미팅 기록: `/home/jay/workspace/memory/meetings/2026-04-24-dispatch-quality-automation.md` (Cycle 2-3 오딘 의견)

## 문제
1. affected_files를 아누가 수동 기재 → 빠뜨리면 영향 범위 체크 전체 무력화
2. 완료 기준이 문서 규칙에만 존재 → 코드로 자동 검증 불가

## 구현

### 파일: `/home/jay/workspace/dispatch.py`

#### 함수 1: `_auto_inject_affected_files(task_desc, workspace_root) -> str`

**호출 위치**: dispatch() 함수 내, task_file.write_text() 직전 (약 2900행)
**호출 조건**: 항상 (task_type 무관)

```python
def _auto_inject_affected_files(task_desc: str, workspace_root: str) -> str:
    """task_desc에 ## affected_files 미기재 시, 백틱 코드 토큰 기반 자동 탐지"""
    if "## affected_files" in task_desc:
        return task_desc  # 이미 기재됨 → SKIP
    
    # 1. 백틱 코드 토큰 추출
    tokens = re.findall(r'`([A-Za-z_]\w*(?:\(\))?)`', task_desc)
    
    # 2. COMMON_FILTER 제외
    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))[:10]  # 중복 제거, 최대 10개
    
    # 3. grep -rl 실행
    affected = set()
    for token in tokens:
        result = subprocess.run(
            ["grep", "-rl", "--include=*.py", "--include=*.ts", "--include=*.tsx",
             "--exclude-dir=node_modules", "--exclude-dir=.git",
             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))
    
    # 4. 20파일 초과 시 주입 안 함 (정밀도 부족)
    if len(affected) > 20:
        logger.warning(f"[auto-affected] {len(affected)}개 파일 감지 (>20) — 주입 안 함")
        return task_desc
    
    # 5. 섹션 추가
    if affected:
        section = "\n\n## affected_files (auto-detected)\n" + "\n".join(f"- {f}" for f in sorted(affected))
        task_desc += section
    
    return task_desc
```

#### 함수 2: `_auto_generate_goal_assertions(task_desc, workspace_root) -> str`

**호출 위치**: _auto_inject_affected_files 직후
**호출 조건**: task_type == "coding" 일 때만

```python
ALLOWED_COMMANDS = {"grep", "curl", "pytest", "python3", "tsc", "cat", "jq", "npx", "npm"}

def _auto_generate_goal_assertions(task_desc: str, workspace_root: str) -> str:
    """검증 시나리오에서 실행 가능한 goal_assertions 자동 생성"""
    if "## goal_assertions" in task_desc:
        return task_desc
    
    # 검증 시나리오 섹션에서 백틱 command 추출
    # 패턴: `command args` 형태
    commands = re.findall(r'`((?:grep|curl|pytest|python3|tsc|cat|jq|npx|npm)\s[^`]+)`', task_desc)
    
    if not commands:
        return task_desc
    
    # 보안: ALLOWED_COMMANDS 화이트리스트 체크
    safe_commands = []
    for cmd in commands:
        first_word = cmd.split()[0]
        if first_word in ALLOWED_COMMANDS:
            safe_commands.append(cmd)
    
    if safe_commands:
        section = "\n\n## goal_assertions (auto-generated)\n"
        for cmd in safe_commands[:5]:  # 최대 5개
            section += f"- `{cmd}`\n"
        task_desc += section
    
    return task_desc
```

## ★ 먼저 읽을 파일
- `/home/jay/workspace/dispatch.py` L2880-2920 — task_file.write_text() 주변 (삽입 위치)
- `/home/jay/workspace/dispatch.py` L766-932 — 기존 affected_files 함수들 (참조)

## 검증 시나리오

### 시나리오 1: 백틱 토큰 자동 추출
task_desc에 `` `FeatureGate` `` 포함 → grep → 2파일 발견 → affected_files 자동 주입

### 시나리오 2: COMMON_FILTER 동작
task_desc에 `` `data` `` 포함 → COMMON_FILTER에 의해 건너뜀

### 시나리오 3: 기존 affected_files 있으면 SKIP
task_desc에 `## affected_files` 이미 존재 → 함수가 원본 그대로 반환

### 시나리오 4: goal_assertions 자동 생성
검증 시나리오에 `` `grep -c "FeatureGate" src/hooks/use-feature-access.ts` `` 포함 → goal_assertions에 추가

### 시나리오 5: 20파일 초과 시 주입 안 함
common 토큰으로 30파일 매칭 → 경고만 로깅, 주입 안 함

## 완료 시그니처
- 2개 함수 구현 + dispatch() 내 호출 삽입
- 기존 dispatch 동작 회귀 없음
- 단위 테스트 4건+ PASS
- pytest 전체 PASS

## 레벨
- critical

## 프로젝트
- dev-system