# spec-p1-1-progressive-disclosure.md
# P1-1 Progressive Disclosure — 상세 구현 스펙

**작성자:** 아테나 (UX/UI 디자이너, MoAI-ADK)
**작성일:** 2026-03-31
**상태:** DRAFT
**관련 Task:** MoAI-ADK Phase 1

---

## 1. 목적

`build_prompt` 함수가 호출 맥락에 따라 세 가지 수준(summary / standard / full)으로 프롬프트 토큰을 동적으로 축소한다. 에이전트가 컨텍스트 윈도우를 불필요하게 낭비하지 않도록 하며, 동시에 모든 단계에서 CRITICAL 규칙 셋(금지행위, QC 의무, 보고 형식)은 반드시 포함함을 보장한다.

**핵심 목표:**
- 토큰 낭비 감소로 비용 절감 및 처리 속도 향상
- CRITICAL 규칙 누락 없는 안전한 디스클로저
- Feature Flag로 무중단 점진 배포 가능

---

## 2. 상세 스펙

### 2.1 disclosure_phase 파라미터

| 파라미터명 | 타입 | 기본값 | 허용값 |
|---|---|---|---|
| `disclosure_phase` | `str` | `"full"` | `"summary"`, `"standard"`, `"full"` |

`build_prompt(... , disclosure_phase: str = "full")` 형태로 시그니처에 추가한다.

### 2.2 CRITICAL 셋 (모든 phase 포함 필수)

CRITICAL 셋은 어떠한 phase에서도 생략될 수 없다. 예상 토큰: **~80 tokens**.

| 항목 | 내용 요약 |
|---|---|
| 금지행위 | 무단 외부 API 호출, 파일시스템 임의 삭제, 개인정보 로깅 금지 |
| QC 의무 | pyright + ruff 통과 없이 완료 보고 금지 |
| 보고 형식 | `.done` 파일 생성, JSON 구조체 준수, 타임스탬프 포함 |

CRITICAL 셋은 별도 상수(`CRITICAL_BLOCK: str`)로 분리하여 `build_prompt` 내부에서 항상 prepend한다.

### 2.3 토큰 한도 및 포함 콘텐츠

| Phase | 토큰 한도 | 포함 콘텐츠 |
|---|---|---|
| `summary` | ≤ 600 tokens | CRITICAL 셋 + 태스크 한 줄 요약 + 핵심 제약 3개 |
| `standard` | ≤ 1,800 tokens | CRITICAL 셋 + 역할 정의 + 주요 규칙 + 예시 1개 |
| `full` | 무제한 | 전체 프롬프트 (기존 동작과 동일) |

### 2.4 토큰 카운트 방식

```python
def estimate_tokens(text: str) -> int:
    """빠른 추정: 단어 수 × 1.3 (tiktoken 미설치 환경 대비)"""
    return int(len(text.split()) * 1.3)

def count_tokens(text: str) -> int:
    """tiktoken 사용 가능 시 정확한 카운트, 아니면 추정치 사용"""
    try:
        import tiktoken
        enc = tiktoken.get_encoding("cl100k_base")
        return len(enc.encode(text))
    except ImportError:
        return estimate_tokens(text)
```

- 토큰 한도 초과 시 `PromptTruncationWarning` 로그 출력 후 해당 phase 한도 내에서 뒤쪽 섹션부터 잘라냄
- CRITICAL 셋은 절대 잘리지 않음

### 2.5 Feature Flag

| 플래그명 | 기본값 |
|---|---|
| `progressive_disclosure_enabled` | `false` |

**플래그 활성화(`true`):** `disclosure_phase` 파라미터가 실제 동작에 반영됨.

**플래그 비활성화(`false`):** `disclosure_phase` 파라미터 값을 무시하고 항상 `"full"` 동작을 수행한다. 기존 코드와 완전히 동일한 출력 보장.

```python
def build_prompt(..., disclosure_phase: str = "full") -> str:
    if not feature_flags.get("progressive_disclosure_enabled"):
        disclosure_phase = "full"   # 플래그 비활성화 시 강제 full
    ...
```

---

## 3. 인터페이스

### 3.1 함수 시그니처

```python
# prompts/build_prompt.py

def build_prompt(
    task: str,
    context: dict,
    role: str = "default",
    disclosure_phase: str = "full",   # NEW
) -> str:
    """
    Args:
        task: 에이전트가 수행할 태스크 설명
        context: 추가 컨텍스트 딕셔너리
        role: 에이전트 역할 식별자
        disclosure_phase: 'summary' | 'standard' | 'full'
    Returns:
        완성된 프롬프트 문자열
    Raises:
        ValueError: disclosure_phase가 허용값이 아닌 경우
    """
```

### 3.2 내부 처리 흐름

```
build_prompt() 호출
    │
    ├─ feature_flag 비활성화? → disclosure_phase = "full"
    │
    ├─ CRITICAL 셋 prepend (항상)
    │
    ├─ phase == "summary" → 태스크 요약 + 핵심 제약 3개
    ├─ phase == "standard" → 역할 + 주요 규칙 + 예시 1개
    └─ phase == "full"    → 전체 콘텐츠
    │
    ├─ count_tokens() 검증
    │   └─ 한도 초과 시 PromptTruncationWarning + 후미 섹션 잘라냄
    │
    └─ 완성된 프롬프트 반환
```

### 3.3 예외 및 경고

| 상황 | 처리 |
|---|---|
| 잘못된 `disclosure_phase` 값 | `ValueError: Invalid disclosure_phase` raise |
| 토큰 한도 초과 | `PromptTruncationWarning` 로그, 후미 자동 트리밍 |
| tiktoken 미설치 | `ImportWarning` 로그, 추정치 사용 |

---

## 4. 테스트 기준

### 4.1 단위 테스트

```python
# tests/test_build_prompt.py

def test_summary_phase_token_limit():
    prompt = build_prompt(task="...", context={}, disclosure_phase="summary")
    assert count_tokens(prompt) <= 600

def test_standard_phase_token_limit():
    prompt = build_prompt(task="...", context={}, disclosure_phase="standard")
    assert count_tokens(prompt) <= 1800

def test_critical_block_always_present():
    for phase in ["summary", "standard", "full"]:
        prompt = build_prompt(task="...", context={}, disclosure_phase=phase)
        assert "금지행위" in prompt
        assert "QC 의무" in prompt
        assert "보고 형식" in prompt

def test_flag_disabled_behaves_as_full(monkeypatch):
    monkeypatch.setattr(feature_flags, "get", lambda k: False)
    prompt_summary = build_prompt(task="...", context={}, disclosure_phase="summary")
    prompt_full = build_prompt(task="...", context={}, disclosure_phase="full")
    assert prompt_summary == prompt_full

def test_invalid_phase_raises():
    with pytest.raises(ValueError):
        build_prompt(task="...", context={}, disclosure_phase="invalid")
```

### 4.2 회귀 테스트

- `disclosure_phase="full"` + 플래그 비활성화 시 기존 골든 파일과 바이트 단위 일치
- CRITICAL 셋 토큰 수가 80 tokens 이하임을 검증

---

## 5. DoD (Definition of Done)

- [ ] `build_prompt` 함수에 `disclosure_phase` 파라미터 추가 완료
- [ ] `CRITICAL_BLOCK` 상수 분리 및 모든 phase prepend 검증
- [ ] `count_tokens()` 함수 구현 (tiktoken + fallback)
- [ ] 토큰 한도 초과 시 자동 트리밍 + `PromptTruncationWarning` 동작 확인
- [ ] `progressive_disclosure_enabled` 플래그 비활성화 시 기존 동작 100% 동일
- [ ] 단위 테스트 5건 이상 통과 (pytest)
- [ ] pyright 타입 오류 0건
- [ ] ruff 스타일 경고 0건
- [ ] PR 리뷰 승인 후 merge
