# task-1285.1 미팅 Cycle 9
날짜: 2026-03-31
안건: 미해결 이슈 6건 전수 해소

## 참석자 발언

### 헤르메스 (백엔드/인프라)

**1. feature_flags.json 스키마 정의 및 로딩 메커니즘**

스키마를 확정합니다.

```json
{
  "$schema": "feature_flags.schema.json",
  "_meta": {
    "version": "1.0.0",
    "updated_at": "2026-04-01T00:00:00Z",
    "updated_by": "hermes"
  },
  "flags": {
    "progressive_disclosure": {
      "enabled": true,
      "description": "build_prompt() disclosure_phase 3단계 활성화",
      "rollout_percent": 100,
      "owner": "hermes"
    },
    "worktree_readonly": {
      "enabled": true,
      "description": "worktree_manager.py --read-only 모드 활성화",
      "rollout_percent": 100,
      "owner": "hermes"
    },
    "hooks_enabled": {
      "enabled": true,
      "description": "PostToolUse pyright+ruff hooks 활성화",
      "rollout_percent": 100,
      "owner": "hermes"
    },
    "ab_test_haiku": {
      "enabled": false,
      "description": "QC haiku A/B 테스트 활성화",
      "rollout_percent": 50,
      "owner": "maat"
    },
    "trust_summary": {
      "enabled": false,
      "description": "qc_verify.py trust_summary JSON 태그 부착",
      "rollout_percent": 100,
      "owner": "maat"
    },
    "model_map": {
      "enabled": false,
      "description": "team_prompts.py MODEL_MAP 모델 가이드 삽입",
      "rollout_percent": 100,
      "owner": "hermes"
    }
  }
}
```

**스키마 규칙:**
- 각 플래그: `enabled`(bool), `description`(string), `rollout_percent`(0-100), `owner`(string)
- `_meta`: 버전, 수정 시각, 수정자 기록
- `rollout_percent`: 향후 점진적 배포 시 사용. 현재는 100(전체) 또는 50(A/B)만 사용

**로딩 메커니즘:**

```python
# utils/feature_flags.py (신규 파일)
import json
import os
from pathlib import Path
from functools import lru_cache

_FLAGS_PATH = Path(__file__).parent.parent / "feature_flags.json"
_cache_mtime: float = 0.0
_cache_data: dict = {}

def load_flags() -> dict:
    """feature_flags.json을 읽고 캐싱. 파일 mtime이 변경되면 리로드."""
    global _cache_mtime, _cache_data
    try:
        current_mtime = os.path.getmtime(_FLAGS_PATH)
        if current_mtime != _cache_mtime:
            with open(_FLAGS_PATH, "r") as f:
                _cache_data = json.load(f)
            _cache_mtime = current_mtime
        return _cache_data
    except (FileNotFoundError, json.JSONDecodeError):
        return {"flags": {}}

def is_enabled(flag_name: str) -> bool:
    """플래그 활성 여부. 파일 없거나 키 없으면 False(안전 기본값)."""
    data = load_flags()
    flag = data.get("flags", {}).get(flag_name, {})
    return flag.get("enabled", False)
```

**핵심 설계 결정:**
- **mtime 기반 캐시**: 파일 변경 시 자동 리로드. 별도 파일 감시(watchdog) 불필요. 프로세스 재시작 불필요
- **안전 기본값**: 파일 없거나 파싱 실패 시 모든 플래그 = False. 즉 기존 동작 유지
- **hot reload 불지원**: mtime 비교가 사실상 hot reload. 매 `is_enabled()` 호출마다 mtime을 확인하므로 수 ms 이내 반영
- **환경 변수 오버라이드**: `FF_OVERRIDE_{FLAG_NAME}=true/false`로 파일 설정을 무시 가능. 긴급 시 `.env` 수정으로 대응

**dispatch.py 통합 예시:**

```python
from utils.feature_flags import is_enabled

disclosure = "full"  # 기본값
if is_enabled("progressive_disclosure"):
    disclosure = determine_disclosure_phase(task_level)
```

---

**2. circuit breaker "동일에러" 판정 기준**

"동일에러 3회 연속 즉시 중단" 규칙의 판정 기준을 확정합니다.

**판정 키: `(error_code, file_path)` 튜플**

| 대안 | 장점 | 단점 | 채택 여부 |
|---|---|---|---|
| 에러 메시지 전체 해시 | 정확한 매칭 | 라인 번호가 1줄만 바뀌어도 다른 해시. 같은 에러인데 다르다고 판정 | 기각 |
| 에러 타입(에러 코드)만 | 같은 종류의 에러를 포착 | 다른 파일의 같은 에러 코드를 동일하다고 판정. 실제로는 다른 에러 | 기각 |
| **(error_code, file_path)** | 같은 파일에서 같은 종류의 에러가 반복 = 에이전트가 해당 에러를 수정 못함 | 라인 번호가 다르면 놓칠 수 있음 (수용 가능한 트레이드오프) | **채택** |
| (error_code, file_path, line_number) | 가장 정확 | 에이전트가 코드를 수정하면 라인 번호가 이동. 같은 에러인데 다른 에러로 판정 | 기각 |

**구현 방식:**

```python
# hooks/circuit_breaker.py
from collections import defaultdict

class CircuitBreaker:
    def __init__(self, warning_limit=15, critical_limit=30, same_error_limit=3):
        self.total_count = 0
        self.error_history: list[tuple[str, str]] = []  # (error_code, file_path)
        self.warning_limit = warning_limit
        self.critical_limit = critical_limit
        self.same_error_limit = same_error_limit

    def record(self, error_code: str, file_path: str) -> str:
        """에러 기록 후 상태 반환: 'continue' | 'warning' | 'halt'"""
        self.total_count += 1
        key = (error_code, file_path)
        self.error_history.append(key)

        # 동일 에러 연속 3회 체크
        if len(self.error_history) >= self.same_error_limit:
            recent = self.error_history[-self.same_error_limit:]
            if all(e == key for e in recent):
                return "halt"  # 즉시 중단

        # 총 횟수 체크
        if self.total_count >= self.critical_limit:
            return "halt"
        if self.total_count >= self.warning_limit:
            return "warning"
        return "continue"
```

**판정 예시:**
- pyright E1101, `/src/dispatch.py` 3회 연속 → 즉시 중단 (에이전트가 이 에러를 수정 불가)
- pyright E1101, `/src/dispatch.py` 1회 + ruff E501, `/src/dispatch.py` 1회 + pyright E1101, `/src/dispatch.py` 1회 → 중단 안 함 (연속이 아님)
- 서로 다른 파일에서 같은 E1101 → 중단 안 함 (다른 에러)

---

### 오딘 (프론트/워크플로우)

**1. 토큰 버짓 상세 설계**

Cycle 3에서 합의한 Progressive Disclosure의 토큰 절감 목표를 구체화합니다.

**토큰 버짓 상한선:**

| 모드 | 절대 상한 (토큰) | full 대비 비율 | 설명 |
|---|---|---|---|
| summary (brief) | 600 | 15~25% | CRITICAL 최소 셋 + 작업 설명 + 산출물 형식만 |
| standard (normal) | 1,800 | 40~60% | summary + 팀원 코워크 섹션 + WORKFLOW 핵심 규칙 |
| full (verbose) | 상한 없음 (현재 ~3,000) | 100% | 기존 전체 프롬프트 |

**측정 방법:**

```python
# team_prompts.py 내부
import tiktoken

_enc = tiktoken.encoding_for_model("claude-3-5-sonnet-20241022")

def build_prompt(disclosure_phase: str = "full", ...) -> str:
    prompt = _assemble_prompt(disclosure_phase, ...)
    token_count = len(_enc.encode(prompt))

    # 디버그 로깅 (프로덕션에서는 WARNING 레벨)
    logger.debug(f"[build_prompt] phase={disclosure_phase}, tokens={token_count}")

    # summary 모드 상한 초과 시 경고
    if disclosure_phase == "summary" and token_count > 600:
        logger.warning(f"[build_prompt] summary 모드 상한 초과: {token_count} > 600")

    return prompt
```

**CRITICAL 최소 셋 (~80토큰 하드코딩):**

이것은 summary 모드에서도 반드시 포함되는 불변 텍스트입니다.

```
[CRITICAL]
1. 금지 행위: git push, 파일 삭제(rm -rf), 프로덕션 직접 수정 절대 금지
2. QC 의무: 모든 코드 수정은 QC 검증 대상. QC 미통과 시 .done 보고 금지
3. 보고 형식: .done 파일은 반드시 지정 형식(task-{id}.done.md) 준수
```

이 텍스트는 `team_prompts.py`에 상수로 하드코딩됩니다. disclosure_phase에 관계없이 항상 포함.

**비율 검증 (Cycle 8 로키 제안 반영):**

테스트에서는 절대 토큰 수가 아닌 비율로 검증합니다.

```python
def test_summary_token_ratio():
    full_prompt = build_prompt(disclosure_phase="full", team="alpha", level=1)
    summary_prompt = build_prompt(disclosure_phase="summary", team="alpha", level=1)
    full_tokens = len(enc.encode(full_prompt))
    summary_tokens = len(enc.encode(summary_prompt))
    ratio = summary_tokens / full_tokens
    assert 0.15 <= ratio <= 0.25, f"summary/full 비율 {ratio:.2%} 범위 초과"
```

---

**2. contingency plan: P1 실패 시 P1-2는 계속하는가?**

**원칙: P1 3항목은 독립 실패, 독립 롤백**

| 실패 항목 | 다른 P1 항목에 미치는 영향 | 조치 |
|---|---|---|
| P1-1 (Progressive Disclosure) 실패 | P1-2, P1-7에 영향 없음. disclosure_phase="full" 기본값이므로 롤백해도 다른 코드 경로 영향 0 | P1-2, P1-7 계속 진행. P1-1만 롤백 후 재설계 |
| P1-2 (읽기/쓰기 격리) 실패 | P1-1, P1-7에 영향 없음. --agent-type 파라미터는 worktree 단에서만 작동 | P1-1, P1-7 계속 진행. P1-2만 롤백 |
| P1-7 (hooks) 실패 | P1-1, P1-2에 영향 없음. hooks는 settings.json에서만 정의, 코드 로직과 분리 | P1-1, P1-2 계속 진행. P1-7만 settings.json 킬 스위치로 비활성화 |
| P1-1 + P1-2 동시 실패 | 둘 다 dispatch.py를 수정하므로 충돌 가능성 있음 | 그룹A 전체 롤백. P1-7(hooks)는 독립이므로 계속 진행 |
| P1 3항목 전부 실패 | 프로젝트 전체 리셋 | 3-Layer 롤백 실행. P2 착수 보류. 2주 내 원인 분석 후 범위 축소안 재설계 |

**P1 실패가 P2에 미치는 영향:**

| 시나리오 | P2 진행 여부 |
|---|---|
| P1 1~2개 실패, 1개 이상 성공 | P2 예정대로 진행. 실패 항목만 재설계 |
| P1 전부 실패 | P2 착수 2주 보류. 실패 원인 분석 + feature_flags 인프라 점검 후 재개 |
| P1 성공했지만 QC 패스율 20%+ 하락 | P2 착수 1주 보류. 원인 분석 후 조건부 재개 |

**데드라인 초과 contingency (구체화):**

| 항목 | 데드라인 | 초과 시 1차 조치 | 초과 시 2차 조치 (1주 추가 초과) |
|---|---|---|---|
| P1-1 | Week 2 | summary 모드 제거, standard/full 2단계로 축소 | full 단일 모드 유지 (사실상 기각) |
| P1-2 | Week 2 | --read-only를 소프트 제한(경고만)으로 변경 | 읽기/쓰기 격리 기각, WORKFLOW 문서 가이드만 유지 |
| P1-7 | Week 3 | ruff 제거, pyright만 유지 | hooks 전체 기각, QC 강화로 대체 |

---

### 마아트 (품질/검증)

**1. A/B 통계 검정 방법**

**채택: Fisher's exact test (양측)**

| 대안 | 적합성 | 채택 |
|---|---|---|
| t-test | 연속 변수용. FNR은 비율(이항)이므로 부적합 | 기각 |
| chi-squared test | 표본 크기 큰 경우 적합. n=100 수준에서는 기대 빈도가 5 미만인 셀이 발생할 수 있음 | 차선 |
| **Fisher's exact test** | 소표본에서도 정확. FNR 비교에 최적. 2x2 분할표(haiku/sonnet x FN/TN) 직접 확률 계산 | **채택** |

**검정 설계:**

```
영가설(H0): haiku FNR = sonnet FNR (두 모델의 FN 비율에 차이 없음)
대립가설(H1): haiku FNR ≠ sonnet FNR (양측 검정)
유의수준: α = 0.05
검정력(power): 1-β ≥ 0.80 at effect size = 15%p 차이 (sonnet FNR ~5% 가정, haiku FNR ~20%이면 탐지)
```

**최소 표본 크기 계산:**

sonnet 기준 FNR = 5% 가정, haiku FNR = 20% (15%p 차이)를 탐지하기 위해:

```
- Fisher's exact test, α=0.05, power=0.80
- n_per_group ≈ 75 (보수적 계산)
- 총 표본: 150건 (그룹당 75건)
- 현재 계획: n > 100 = 그룹당 50건 이상
```

**수정: 최소 표본을 n > 150 (그룹당 75건)으로 상향.**

4주간 주당 약 40건의 QC가 실행된다고 가정하면, 총 160건. 80:80 분배 시 그룹당 80건. 150건 목표 달성 가능.

만약 4주 내 150건 미달 시: **1주 연장** (Cycle 8 합의의 "최대 1회 연장" 적용).

**주 1회 sonnet 재검증:**

haiku 그룹의 산출물 중 무작위 20%를 sonnet으로 재검증하여 FN 여부를 확인합니다. 이 재검증 결과가 Fisher's exact test의 입력 데이터가 됩니다.

```
2x2 분할표:
               결함 있음(FN)  결함 없음(TN)
haiku 그룹       a              b
sonnet 그룹      c              d

Fisher's exact test p-value < 0.05 이면 H0 기각 → haiku FNR이 유의미하게 다름
```

---

**2. 기준선 데이터 저장소**

**저장 위치: `.metrics/` 디렉토리**

```
.metrics/
  baseline/
    baseline_2026-04-14.json    # Week 0 기준선 측정 결과
  weekly/
    metrics_2026-04-21.json     # Week 1 측정
    metrics_2026-04-28.json     # Week 2 측정
    ...
  ab_test/
    ab_results_week4.json
    ab_results_week5.json
    ...
  README.md                     # 디렉토리 설명 + 포맷 명세
```

**기준선 JSON 포맷:**

```json
{
  "measurement_date": "2026-04-14",
  "measurement_type": "baseline",
  "measured_by": "maat",
  "data": {
    "build_prompt_tokens": {
      "alpha_lv1": 2850,
      "alpha_lv2": 2920,
      "alpha_lv3": 3010,
      "beta_lv1": 2780,
      "comment": "팀별, 레벨별 토큰 수"
    },
    "qc_pass_rate": {
      "last_30d": 0.72,
      "by_issue_type": {
        "code_defect": 0.15,
        "report_format": 0.08,
        "task_interpretation": 0.03,
        "other": 0.02
      }
    },
    "qc_execution_time_sec": {
      "mean": 45.2,
      "p95": 92.1
    },
    "session_write_edit_count": {
      "mean": 12.3,
      "max": 47
    },
    "session_token_consumption": {
      "mean": 85000,
      "p95": 142000
    },
    "worktree_success_rate": 0.98
  }
}
```

**자동 비교 방법:**

```python
# utils/metrics_compare.py
def compare_to_baseline(current_metrics_path: str, baseline_path: str) -> dict:
    """현재 측정값과 기준선을 비교하여 변화율 계산."""
    # delta_percent = (current - baseline) / baseline * 100
    # 각 지표별 변화율 + 임계값 초과 여부 반환
```

- 자동 비교 결과를 `.metrics/weekly/` 파일에 `delta_from_baseline` 필드로 기록
- QC 패스율이 기준선 대비 20%p 이상 하락하면 자동 경고 (알림 트리거)

---

### 로키 (DA) -- [반드시 공격적 비평]

**이 사이클에서 6개 미해결 이슈를 "해소"한다고 합니다. 각각을 공격합니다.**

**공격 1: feature_flags.json의 mtime 기반 캐시는 레이스 컨디션이 있습니다.**

헤르메스가 "매 `is_enabled()` 호출마다 mtime을 확인"한다고 했는데, 동시에 여러 에이전트(프로세스)가 feature_flags.json을 읽는 상황에서:

1. 프로세스 A가 mtime 확인 → 변경 없음 → 캐시 반환
2. 이 사이에 누군가 파일 수정
3. 프로세스 B가 mtime 확인 → 변경 있음 → 리로드

이것은 레이스 컨디션이 아닙니다. 각 프로세스가 독립적으로 캐시하니까요. **하지만 진짜 문제는 다릅니다**: 파일 쓰기 중간에 읽기가 발생하면 불완전한 JSON을 파싱하게 됩니다. `json.JSONDecodeError` 예외 처리가 있지만, 그때 빈 딕셔너리를 반환하면 **모든 플래그가 False**가 됩니다. 즉, 누군가 feature_flags.json을 편집하는 순간 모든 신규 기능이 일시적으로 꺼집니다.

**대안**: 쓰기 시 atomic write 패턴 사용. `feature_flags.json.tmp`에 먼저 쓰고, `os.rename()`으로 원자적 교체. 그리고 `JSONDecodeError` 발생 시 빈 딕셔너리가 아니라 **이전 캐시를 유지**해야 합니다.

**공격 2: circuit breaker의 `(error_code, file_path)` 튜플은 pyright와 ruff의 에러 코드 체계가 다릅니다.**

pyright는 `reportMissingImports`, `reportGeneralClassIssues` 같은 문자열 코드를 사용하고, ruff는 `E501`, `F401` 같은 숫자 코드를 사용합니다. 헤르메스의 예시에서 "pyright E1101"이라고 했는데, **pyright에는 E1101이라는 코드가 없습니다.** pyright는 `reportXxx` 형식입니다.

이것은 에러 코드 파싱 구현 시 pyright와 ruff를 구분하지 않으면 잘못된 튜플이 생성된다는 뜻입니다.

**대안**: 키를 `(tool, error_code, file_path)` 3-튜플로 확장. `("pyright", "reportMissingImports", "/src/dispatch.py")`.

**공격 3: Fisher's exact test는 맞지만, "sonnet FNR = 5%" 가정이 검증되지 않았습니다.**

마아트가 검정력 계산에 "sonnet FNR = 5%"를 가정했습니다. 그런데 **현재 sonnet의 실제 FNR을 측정한 적이 있습니까?** 기준선 측정 항목(Cycle 8)에 "QC 패스율"은 있지만 "QC FNR"은 없습니다. 패스율 72%는 "QC가 결함을 발견한 비율"이지, "QC가 결함을 놓친 비율(FNR)"이 아닙니다.

FNR을 측정하려면 **별도의 gold standard**(인간 리뷰어가 결함을 확인한 데이터)가 필요합니다. 이것이 없으면 Fisher's exact test의 입력 데이터 자체가 부정확합니다.

**대안**: Week 0 기준선 측정에 "sonnet FNR 추정" 항목을 추가. 최근 30건의 QC PASS 산출물을 인간(또는 opus)이 재검증하여 실제 결함 수를 파악. 이것이 FNR 기준선.

**공격 4: contingency plan에서 "P1 전부 실패 시 2주 보류"는 낙관적입니다.**

P1 3항목이 전부 실패한다는 것은 **설계 자체에 근본적 결함**이 있다는 신호입니다. 2주 "원인 분석"으로 해결될 문제가 아닙니다. dispatch.py, team_prompts.py, worktree_manager.py 3개 핵심 파일 모두에서 실패했다면, 아키텍처 수준의 재설계가 필요합니다.

**대안**: P1 전부 실패 시 2주 보류가 아니라, **프로젝트 스코프 자체를 50% 축소하는 의사결정 회의**를 소집. 10개 항목을 5개로 줄이고, 가장 안전한 항목만 재시도.

**공격 5: 기준선 JSON 포맷에 "comment" 필드가 있는데, 이게 코드에서 무시되는지 보장이 없습니다.**

마아트의 기준선 JSON에 `"comment": "팀별, 레벨별 토큰 수"` 필드가 있습니다. `metrics_compare.py`가 이 필드를 무시하지 않으면 비교 로직이 깨집니다. JSON 스키마에 허용 필드를 명시하고, 비교 로직에서 알려진 데이터 필드만 처리해야 합니다.

**대안**: `comment` 대신 `_comment`(언더스코어 접두사)를 사용하고, 비교 로직에서 `_`로 시작하는 키는 무시하는 규칙을 적용.

---

### 프로메테우스 (전략)

**1. 로키 비평에 대한 대응 및 최종 합의안 정리:**

**feature_flags.json atomic write**: 로키 지적 수용. 쓰기 시 `.tmp` + `os.rename()` 패턴 적용. `JSONDecodeError` 시 이전 캐시 유지. 헤르메스가 구현.

**circuit breaker 3-튜플**: 로키 지적 수용. `(tool, error_code, file_path)`로 확장. pyright/ruff 에러 코드 체계 차이를 명시적으로 구분. 헤르메스가 구현.

**sonnet FNR 기준선**: 로키 지적 수용. Week 0 기준선 측정에 "sonnet FNR 추정" 추가. 최근 30건 QC PASS 산출물을 인간/opus 재검증하여 FNR 기준선 확보. 마아트가 실행.

**P1 전부 실패 contingency 강화**: 로키 지적 부분 수용. "2주 보류 + 원인 분석"을 "2주 보류 + 스코프 축소 의사결정 회의 소집"으로 변경. 단, 10→5 축소가 아닌, **실패 원인에 기반한 선택적 축소**로. 어떤 항목을 살리고 어떤 항목을 죽일지는 실패 원인 분석 후 결정.

**기준선 JSON `_comment` 규칙**: 로키 지적 수용. 메타데이터 키는 `_` 접두사, 비교 로직에서 `_` 접두사 키 무시. 마아트가 반영.

**2. 6개 미해결 이슈 해소 요약:**

| # | 이슈 | 해소 결과 | DRI |
|---|---|---|---|
| 1 | feature_flags.json 스키마/로딩 | 6플래그 스키마 확정. mtime 캐시 + atomic write + JSONDecodeError 시 이전 캐시 유지 | 헤르메스 |
| 2 | circuit breaker 판정 기준 | `(tool, error_code, file_path)` 3-튜플. 연속 3회 동일 키 = 즉시 중단 | 헤르메스 |
| 3 | A/B 통계 검정 | Fisher's exact test, α=0.05, power≥0.80, 최소 표본 n>150 (그룹당 75) | 마아트 |
| 4 | 기준선 데이터 저장소 | `.metrics/baseline/`, `.metrics/weekly/`, `.metrics/ab_test/`. JSON 포맷 확정. `_`접두사 메타키 | 마아트 |
| 5 | 토큰 버짓 | summary≤600, standard≤1800, full=현재치. 비율 검증 15-25%/40-60%/100%. CRITICAL 셋 ~80토큰 하드코딩 | 오딘 |
| 6 | contingency plan | P1 각 항목 독립 실패/롤백. 전부 실패 시 2주 보류 + 스코프 축소 의사결정 회의. P2 영향 조건부 | 오딘 |

**추가 합의: 기준선 측정에 "sonnet FNR 추정" 항목 추가 (로키 지적).**

---

## 3 Whys 검증

### feature_flags.json 설계
**Why 1**: 왜 전용 파일(JSON)인가? -> 코드 변경 없이 런타임에 기능을 토글해야 하므로. 환경 변수만으로는 6개 플래그 관리가 복잡
**Why 2**: 왜 mtime 기반 캐시인가? -> watchdog 의존성 추가 불필요. 매 호출마다 stat 1회로 충분한 성능. 프로세스 재시작 없이 반영
**Why 3**: atomic write가 왜 필요한가? -> 파일 쓰기 중 읽기 시 불완전 JSON 파싱 위험. tmp + rename 패턴으로 원자적 교체 보장

### circuit breaker 판정
**Why 1**: 왜 (tool, error_code, file_path) 3-튜플인가? -> pyright/ruff 에러 코드 체계가 다르므로 tool 구분 필수. file_path로 에러 위치를 특정하여 다른 파일의 동일 코드와 구분
**Why 2**: 왜 연속 3회인가? -> 1회는 일시적 실수, 2회는 재시도 중, 3회는 에이전트가 해결 불가능하다는 신호. 3회를 넘기면 토큰만 낭비
**Why 3**: line_number를 제외한 이유는? -> 에이전트가 코드 수정 시 라인 번호가 이동. 같은 에러가 라인 이동만으로 다른 에러로 판정되면 circuit breaker 우회

### A/B 통계 검정
**Why 1**: 왜 Fisher's exact test인가? -> 소표본(n~150)에서 정확한 p-value 계산. chi-squared는 기대 빈도 < 5인 셀에서 부정확
**Why 2**: 왜 α=0.05인가? -> 통계적 관례. 더 엄격(α=0.01)하면 표본 수가 2배 이상 필요하여 테스트 기간이 8주 이상 소요
**Why 3**: 왜 최소 표본을 100→150으로 상향했나? -> sonnet FNR=5%, haiku FNR=20%(15%p 차이) 탐지에 그룹당 75건 필요. 100건(그룹당 50건)으로는 검정력 0.60 수준으로 부족

---

## 합의 사항

### 1. feature_flags.json 스키마 및 로딩 -- 확정
- 6개 플래그 스키마: `progressive_disclosure`, `worktree_readonly`, `hooks_enabled`, `ab_test_haiku`, `trust_summary`, `model_map`
- 각 플래그: `enabled`(bool), `description`(string), `rollout_percent`(0-100), `owner`(string)
- 로딩: `utils/feature_flags.py` 신규 파일. mtime 기반 캐시. 안전 기본값 = False
- atomic write: `.tmp` + `os.rename()` 패턴. `JSONDecodeError` 시 이전 캐시 유지
- 환경 변수 오버라이드: `FF_OVERRIDE_{FLAG_NAME}=true/false`

### 2. circuit breaker 판정 기준 -- 확정
- 판정 키: `(tool, error_code, file_path)` 3-튜플
- 동일 키 연속 3회 = 즉시 중단 (halt)
- 총 횟수: warning=15, critical=30
- pyright: `reportXxx` 형식 코드, ruff: `EXXX`/`FXXX` 형식 코드

### 3. A/B 통계 검정 -- 확정
- 검정법: Fisher's exact test (양측)
- 유의수준: α = 0.05
- 검정력: 1-β ≥ 0.80
- **최소 표본: n > 150 (그룹당 75건)** (기존 n>100에서 상향)
- 4주 내 미달 시 1주 연장 (최대 1회)

### 4. 기준선 데이터 저장소 -- 확정
- 디렉토리: `.metrics/baseline/`, `.metrics/weekly/`, `.metrics/ab_test/`
- 포맷: JSON. 메타 키는 `_` 접두사. 비교 로직에서 `_` 접두사 키 무시
- 자동 비교: `utils/metrics_compare.py`. delta_from_baseline 필드 기록
- **추가: sonnet FNR 추정을 Week 0 기준선에 포함** (QC PASS 30건 재검증)

### 5. 토큰 버짓 -- 확정
- summary: 상한 600토큰, full 대비 15-25%
- standard: 상한 1,800토큰, full 대비 40-60%
- full: 상한 없음 (~3,000), 기존 대비 delta < 5%
- CRITICAL 최소 셋: ~80토큰, team_prompts.py에 상수 하드코딩
- 측정: tiktoken 사용, build_prompt() 내부 로깅

### 6. contingency plan -- 확정
- P1 각 항목: 독립 실패, 독립 롤백. 다른 항목에 영향 없음
- P1 전부 실패: 2주 보류 + **스코프 축소 의사결정 회의** 소집
- P1→P2 영향: 1-2개 실패 시 P2 계속, 전부 실패 시 P2 2주 보류
- 데드라인 초과: 항목별 1차/2차 축소안 사전 정의

---

## 미해결 이슈

**Cycle 9에서 이전 미해결 6건 전부 해소.** 신규 미해결 없음.

잔여 운영 사항 (구현 단계에서 확인):
1. tests/ 디렉토리 구조 확정 -- 헤르메스 (Cycle 8에서 이관)
2. CI 파이프라인 hooks 테스트 통합 -- 헤르메스 (Cycle 8에서 이관)
3. 데드라인 초과 시 "범위 축소"의 세부 기준 -- 프로메테우스 (Cycle 8에서 이관, 본 사이클에서 1차/2차 축소안으로 부분 해소)
