# Phase M: 비중 시스템 + 역할-블록 매핑 + 블록 안전성 + 원본 보존

> 상태: ✅ 완료 — Kei 비중 시스템 구축. Phase O에서 컨테이너 시스템으로 발전.
>
> Phase I~L에서 코드 정합성, 블록 선택 권한, 프롬프트 원칙, 렌더링 측정을 다뤘지만
> **근본 문제가 해결되지 않음: "이 페이지의 본심이 뭔지" 판단이 없음.**
> Kei가 콘텐츠마다 본심/배경/첨부/결론을 판단하고, 비중(weight)을 결정해야 함.
> 코드 상수(하드코딩)가 아닌 **Kei의 매번 판단**.
>
> **후속 변경 (Phase O):**
> - pipeline.py의 Phase M 공간 할당 코드 → Phase O `calculate_container_specs()`로 교체
> - `PURPOSE_WEIGHT` 상수 → 삭제 (Kei weight 직접 사용)
> - `allocate_height_budget()` → `calculate_container_specs()` + `finalize_block_specs()`로 교체

---

## 문제점 전체 리스트 (9건)

### P-1: 비중(weight) 개념 부재

**현상:** Kei가 꼭지 5개를 분류하면, 팀장이 5개를 동등하게 1:1 배치. "본심이 60%, 배경이 15%"라는 공간 비중 개념이 파이프라인 어디에도 없음.

**예시:** DX vs BIM 비교(본심)와 용어 정의(첨부)가 동일한 크기의 블록을 받음.

**영향:** 핵심 메시지가 묻히고, 보조 정보가 과도한 공간 차지.

**위치:** 1단계(Kei) 출력 → 2단계(팀장) 입력 사이.

**Phase I~L에서 한 것:** Phase K에서 PURPOSE_WEIGHT 상수 추가, Phase L에서 allocate_height_budget() 함수 추가.
**문제:** 하드코딩된 고정 비율. 콘텐츠마다 다른데 코드가 일괄 적용.

---

### P-2: 편집자적 구조 판단 부재

**현상:** Kei가 꼭지를 "나열"만 함. 아래와 같은 편집 구조를 잡지 못함:

```
(배경/목적) 왜 이 페이지가 필요한가
(본심)     이 페이지가 말하려는 핵심
(첨부)     본심을 이해하기 위한 보조 정보
(잊지마)   절대 잊으면 안 되는 결론
```

**현재:** purpose와 layer가 있지만 "이 페이지의 본심은 꼭지2이고 나머지는 보조다"라는 판단이 없음.

**영향:** 모든 꼭지가 동등하게 취급됨. 스토리라인은 있으나 강약이 없음.

**위치:** 1단계 KEI_PROMPT.

**Phase I~L에서 한 것:** Phase K에서 인지 흐름 원칙 추가.
**문제:** 원칙만 줬지 Kei 출력 스키마에 "본심/배경/첨부/결론" 구분이 없음.

---

### P-3: 블록 선택이 "콘텐츠 역할"이 아닌 "데이터 타입"으로 결정됨

**현상:** 팀장(Sonnet)이 블록을 고를 때 "텍스트 → 텍스트 블록, 표 → 표 블록"으로 데이터 형식만 보고 선택. "이것이 본심이니까 정보 밀도 높은 블록" 판단 안 함.

**올바른 선택 기준:**
```
본심(핵심전달) → 정보형 블록 (compare-2col-split 등) → 공간 최대
배경(문제제기) → 컴팩트 블록 (topic-left-right 등) → 공간 최소
첨부(용어정의) → 참조형 블록 (card-numbered 등) → sidebar
결론(강조)    → 선언형 블록 (banner-gradient) → footer
```

**위치:** 2단계 STEP_B_PROMPT + FAISS 검색.

**Phase I~L에서 한 것:** Phase K에서 purpose별 허용/금지 블록 규칙 추가.
**문제:** purpose 기반이지 "본심/배경" 기반이 아님. Kei가 비중을 출력해야 팀장이 비중대로 블록 크기 결정.

---

### P-4: 공간 배분 로직 부재

**현상:** 팀장이 zone별 height_cost만 검증하고, "이 꼭지에 몇 px를 줘야 하는가"는 계산하지 않음.

**현재 로직:** 블록 선택 → height_cost 합산 확인 → 초과하면 교체
**필요한 로직:** 비중(weight) 확인 → weight에 따라 zone 예산 배분 → 배분된 px에 맞는 블록 선택

**위치:** 2단계 create_layout_concept().

**Phase I~L에서 한 것:** Phase L에서 allocate_height_budget() + max-height CSS 적용.
**문제:** PURPOSE_WEIGHT가 하드코딩. Kei가 판단한 weight를 사용해야 함.

---

### P-5: Figma 비추출 블록 사용

**현상:** 38개 블록 중 9개가 Figma 디자인 없이 코드로 만든 블록. 디자인 품질 미검증.

**비-Figma 블록 (9개):**
- topic-numbered, card-numbered, table-simple-striped
- venn-diagram, process-horizontal
- comparison-2col, callout-warning
- divider-text, image-before-after

**영향:** 시각적 통일성 저하.

**위치:** 2단계 블록 선택 시 필터링 없음.

**Phase I~L에서 한 것:** 안 다룸.

---

### P-6: 블록-zone 적합성 검증 부재

**현상:** sidebar(35%)에 full-width 전용 블록을 배치하면 찌그러짐. 블록이 어떤 zone에서 작동하는지 검증 없음.

**full-width 전용 블록 (15개):**
- card-icon-desc, card-compare-3col, comparison-2col
- topic-left-right, compare-pill-pair, process-horizontal 등

**영향:** sidebar에서 블록 깨짐, 텍스트 한 글자씩 줄바꿈.

**위치:** 2단계 블록 선택 후 검증.

**Phase I~L에서 한 것:** Phase J에서 sidebar 1열 강제(column_override). 불완전.

---

### P-7: 블록별 글자 수용량 미정의

**현상:** 블록에 텍스트를 넣을 때 "얼마나 들어가는지" 기준 없음. char_guide 참고하지만 실제 렌더링과 괴리.

**결과:** 텍스트 과다 → overflow / 텍스트 과소 → 빈 페이지.

**위치:** catalog.yaml에 schema 미정의. 3단계 편집자 프롬프트.

**Phase I~L에서 한 것:** Phase I에서 slot_desc 추가, Phase K에서 분량 가이드라인 추가. 실제 수용량은 미정의.

---

### P-8: 내부 스크롤 미감지

**현상:** 5단계 검수에서 area 레벨 overflow만 체크. 블록 내부의 overflow: auto/hidden으로 인한 내부 스크롤/잘림은 감지 못함.

**예시:** compare-3col-badge는 overflow: auto여서 area는 OK인데 블록 안에서 스크롤 발생.

**영향:** "검증 통과"했는데 실제로는 내용 잘림.

**위치:** 5단계 검수.

**Phase I~L에서 한 것:** Phase L에서 Selenium 측정 추가. 하지만 블록 내부 overflow까지 체크하는지 미확인.

---

### P-9: 원본 텍스트 임의 재작성

**현상:** 3단계 편집자가 원본을 "편집"이 아닌 "재작성". 원본 문구, 출처, 수치 변경/누락.

**영향:** 정보 정확도 저하, 출처 누락.

**위치:** 3단계 편집자 프롬프트 + Kei API 응답 품질.

**Phase I~L에서 한 것:** Phase J에서 source 슬롯 규칙 추가, EDITOR_PROMPT에 보존 원칙. 강제력 부족.

---

## 개선 방향 (4가지)

### 방향 1: 비중(weight) 시스템 — P-1, P-2, P-4 해결 [긴급]

**핵심:** Kei가 콘텐츠마다 본심/배경/첨부/결론을 판단하고 weight를 출력.

**KEI_PROMPT 출력 스키마 변경:**
```json
{
  "title": "건설산업 DX의 올바른 이해",
  "core_message": "BIM은 DX의 기초적 일부분이다",
  "page_structure": {
    "본심": {"topic_ids": [2, 3], "weight": 0.60},
    "배경": {"topic_ids": [1], "weight": 0.15},
    "첨부": {"topic_ids": [4], "weight": 0.15},
    "결론": {"topic_ids": [5], "weight": 0.10}
  },
  "topics": [...]
}
```

**파이프라인 반영:**
- 1단계: Kei가 page_structure + weight 출력 (콘텐츠마다 다름, 하드코딩 아님)
- 2단계: weight → px 변환 (body 490px × 0.6 = 294px → 본심)
- 2단계: 배분된 px에 맞는 블록 선택
- 배치: 본심 비중이 결정하면 가로/세로/구조화 방식도 자연스럽게 따라옴
- Phase L의 PURPOSE_WEIGHT 하드코딩 제거 → Kei 출력 weight 사용

---

### 방향 2: 역할-블록 매핑 체계 — P-3 해결 [중요]

**콘텐츠 역할 × 콘텐츠 성격 → 블록 결정:**

```
본심 + 비교    → compare-2col-split, compare-3col-badge
본심 + 구조    → keyword-circle-row, card-step-vertical
본심 + 정의    → card-numbered (large), dark-bullet-list (large)
배경 + 문제    → topic-left-right (compact), quote-question (compact)
배경 + 사례    → callout-warning (compact), quote-big-mark (compact)
첨부 + 정의    → card-numbered (sidebar), dark-bullet-list (sidebar)
결론          → banner-gradient (footer)
```

**반영 위치:** STEP_B_PROMPT — 현재 purpose별 허용/금지를 "역할 × 성격" 매트릭스로 확장.

---

### 방향 3: 블록 안전성 인프라 — P-5, P-6, P-7, P-8 해결 [중요]

| 항목 | 내용 | 해결 방법 |
|------|------|----------|
| P-5 Figma 블록 필터 | 비-Figma 9개 블록 식별 | 블록 선택 시 Figma 블록 우선 또는 비-Figma 경고 |
| P-6 블록-zone 적합성 | full-width 15개 블록 식별 | zone별 허용 블록 맵 (코드 검증) |
| P-7 글자 수용량 | 블록별 max chars | catalog.yaml에 zone별 max_chars 추가 |
| P-8 내부 스크롤 | 블록 내부 overflow 감지 | Selenium 측정 시 블록 내부까지 scrollHeight 체크 |

---

### 방향 4: 원본 보존 강화 — P-9 해결 [보통]

**3단계 편집자에게 source_text 직접 전달:**
- 현재: 원본 콘텐츠 전체를 주고 "여기서 가져와라"
- 변경: 각 꼭지별로 Kei가 source_hint에 명시한 원본 텍스트를 **직접 추출하여** 편집자에게 전달
- "이 텍스트에서 추출하라. 새로 쓰지 마라. 축약만 허용."

---

## 우선순위

```
[긴급] P-1 + P-2 + P-4 → 방향 1: 비중 시스템
      ← 이것이 없으면 나머지를 해도 의미 없음
      ← Kei가 판단. 하드코딩 아님.
      ← 비중이 결정되면 배치, 블록 크기, 가로/세로 흐름이 자동으로 따라옴

[중요] P-3             → 방향 2: 역할-블록 매핑
      ← 비중 시스템 위에서 역할별 블록 정확 매칭

[중요] P-7 + P-8       → 방향 3-a: 스키마 + 검증
      ← 글자 수용량 정의 + 내부 overflow 감지

[보통] P-5 + P-6       → 방향 3-b: 필터링
      ← Figma 블록 우선 + zone 적합성 검증

[보통] P-9             → 방향 4: 편집자 원본 보존
```

---

## Phase I~L과의 관계

| 기존 Phase | Phase M에서 변경 |
|-----------|----------------|
| Phase K PURPOSE_WEIGHT 하드코딩 | **제거** → Kei 출력 weight 사용 |
| Phase K purpose 가이드 | **유지** + 역할×성격 매트릭스로 확장 |
| Phase L allocate_height_budget() | **유지** + 입력을 PURPOSE_WEIGHT 대신 Kei weight로 변경 |
| Phase L measure_rendered_heights() | **유지** + 블록 내부 overflow 체크 추가 (P-8) |
| Phase L calculate_trim_chars() | **유지** |
| Phase J Opus 존중 규칙 | **유지** |
| Phase J Kei 최종 검수 | **유지** + 비중 기반 검수 항목 추가 |
| Phase I slot_desc | **유지** |
| Phase I SSE 공통 유틸 | **유지** |

**회귀 없음.** 기존 인프라(측정, 계산, 검수) 위에 비중 시스템을 추가.
**제거 대상:** PURPOSE_WEIGHT 하드코딩 상수만.

---

## 실행 순서

### M-Step 1: Kei 비중 시스템 (P-1 + P-2 + P-4) [긴급]

1. KEI_PROMPT 출력 스키마에 page_structure 추가
2. Kei가 본심/배경/첨부/결론 + weight를 출력하도록 프롬프트 수정
3. pipeline.py에서 Kei 출력의 weight를 읽어서 allocate_height_budget()에 전달
4. PURPOSE_WEIGHT 하드코딩 제거
5. STEP_B_PROMPT에 weight 기반 블록 크기 지시 추가

### M-Step 2: 역할-블록 매핑 (P-3)

6. STEP_B_PROMPT purpose 가이드를 역할×성격 매트릭스로 재구성
7. Kei 출력의 relation_type + 역할(본심/배경/첨부)로 블록 결정

### M-Step 3: 블록 안전성 (P-5 + P-6 + P-7 + P-8)

8. P-5: catalog.yaml에 figma_source 필드 추가 (Figma 블록 식별)
9. P-6: 블록-zone 적합성 맵 정의 + 코드 검증 추가
10. P-7: catalog.yaml에 zone별 max_chars 추가
11. P-8: slide_measurer.py에서 블록 내부 overflow까지 체크

### M-Step 4: 원본 보존 (P-9)

12. 편집자에게 꼭지별 source_text 직접 전달
13. "추출만. 재작성 금지." 강화

---

## 기술 조사 결과

### M-Step 1에 필요한 것

| 항목 | 현재 | 변경 | 도구 |
|------|------|------|------|
| KEI_PROMPT 출력 | topics만 | + page_structure (본심/배경/첨부/결론 + weight) | 프롬프트 수정 |
| page_structure 파싱 | 없음 | `analysis.get("page_structure")` | 코드 추가 |
| PURPOSE_WEIGHT 상수 | 하드코딩 (space_allocator.py) | **제거** → Kei weight 사용 | 코드 수정 |
| allocate_height_budget() | PURPOSE_WEIGHT 참조 | weight_override 파라미터 추가 | 함수 시그니처 변경 |
| STEP_B_PROMPT | purpose별 규칙만 | + weight 기반 블록 크기 지시 | 프롬프트 수정 |

**충돌:** 없음. page_structure는 새 필드. PURPOSE_WEIGHT 제거는 개선.
**Kei vs Sonnet:** Kei가 weight 판단. Sonnet은 weight를 **받아서** 블록 크기 결정.

---

### M-Step 2에 필요한 것

| 항목 | 현재 | 변경 | 도구 |
|------|------|------|------|
| FAISS 쿼리 | title+summary+role+layer | + purpose + relation_type + expression_hint | block_search.py `_build_query()` 수정 |
| STEP_B_PROMPT 가이드 | purpose 6종 허용/금지 | 역할(본심/배경/첨부) × 성격(비교/정의/구조) 매트릭스 | 프롬프트 확장 |

**충돌:** Phase K purpose 가이드 **위에** 매트릭스 확장. 기존 규칙 유지.

---

### M-Step 3에 필요한 것

| 항목 | 현재 | 변경 | 도구 |
|------|------|------|------|
| P-5 Figma 식별 | 구분 없음 | catalog.yaml에 `figma_source` 필드 | YAML 수정 |
| P-6 zone 적합성 | sidebar 1열만 (J-6) | **블록-zone 적합성 맵** 코드 검증 | 신규 상수 + 검증 로직 |
| P-7 글자 수용량 | slot_desc 의미만 | catalog.yaml에 **zone별 max_chars** | YAML + 편집자 연동 |
| P-8 내부 overflow | zone 레벨만 측정 | **블록 내부** scrollHeight 체크 | slide_measurer.py JS 확인 |

**P-6 블록-zone 적합성 맵:**
```python
# 신규 상수 (design_director.py 또는 별도 모듈)
SIDEBAR_SAFE_BLOCKS = {
    "card-numbered", "card-step-vertical",
    "banner-gradient", "callout-solution", "callout-warning",
    "dark-bullet-list", "divider-text", "highlight-strip",
    "quote-question", "tab-label-row",
    "topic-left-right", "topic-numbered",
    "table-simple-striped", "process-horizontal",
    "image-before-after", "image-grid-2x2", "image-row-2col",
}

FULL_WIDTH_ONLY_BLOCKS = {
    "card-compare-3col", "card-dark-overlay", "card-icon-desc",
    "card-image-3col", "card-image-round", "card-stat-number", "card-tag-image",
    "section-title-with-bg", "section-header-bar", "topic-center",
    "quote-big-mark", "image-full-caption",
    "compare-2col-split", "compare-pill-pair", "comparison-2col",
}
```

**충돌:** Phase J의 sidebar 1열 강제와 **보완 관계.** J-6은 열 수 제한, M-Step 3은 블록 자체 제한.

---

### M-Step 4에 필요한 것

| 항목 | 현재 | 변경 | 도구 |
|------|------|------|------|
| 원본 전달 | 전체 content 한 번에 | **토픽별 source_text 추출하여 전달** | fill_content() 수정 |
| source_hint | 정의됨, 사용 안 됨 | **편집자에게 전달** | 프롬프트 수정 |
| source_data | 텍스트 설명만 | **실제 원본 텍스트 추출 참조** | 코드 추가 |
| 재작성 방지 | "보존" 원칙만 | **"추출만. 재작성 금지."** 절대 규칙 | 프롬프트 강화 |

**충돌:** Phase J source 규칙 **유지 + 보강.**

---

## 실행 방안 상세

### M-Step 1: Kei 비중 시스템

#### M-1a: KEI_PROMPT 출력 스키마 변경

**위치:** `src/kei_client.py` KEI_PROMPT (20~79행)

**추가할 출력 필드:**
```json
{
  "title": "...",
  "core_message": "...",
  "page_structure": {
    "본심": {"topic_ids": [2, 3], "weight": 0.60},
    "배경": {"topic_ids": [1], "weight": 0.15},
    "첨부": {"topic_ids": [4], "weight": 0.15},
    "결론": {"topic_ids": [5], "weight": 0.10}
  },
  "topics": [...]
}
```

**프롬프트에 추가할 지시:**
```
## 4단계: 페이지 구조 판단
콘텐츠를 분석하여 이 페이지의 구조를 판단하라:
- **본심**: 이 페이지가 말하려는 핵심. 가장 큰 공간을 차지해야 함.
- **배경**: 본심을 이해하기 위한 도입/배경. 간결하게.
- **첨부**: 본심을 보조하는 참조 정보 (용어 정의 등). sidebar 배치.
- **결론**: 절대 잊으면 안 되는 핵심 한 줄. footer.

각 역할에 해당하는 topic_ids와 공간 비중(weight, 합계 1.0)을 결정하라.
콘텐츠에 따라 비중은 매번 달라진다. 고정값이 아니다.
```

**충돌:** 없음. 기존 출력 필드에 page_structure 추가만. `.get()` 방식이라 무시 가능.

#### M-1b: pipeline.py에서 Kei weight 읽기

**위치:** `src/pipeline.py` Phase L 공간 할당 부분 (현재 132~165행)

**변경:** PURPOSE_WEIGHT 대신 Kei 출력 weight 사용
```python
# 현재 (Phase L 하드코딩):
allocation = allocate_height_budget(zone_blocks, zone_info.get("budget_px", 490))

# 변경 (Phase M Kei 판단):
page_struct = analysis.get("page_structure", {})
weight_map = {}
for role_name, role_info in page_struct.items():
    for tid in role_info.get("topic_ids", []):
        weight_map[tid] = role_info.get("weight", 0.25)
allocation = allocate_height_budget(
    zone_blocks, zone_info.get("budget_px", 490),
    weight_override=weight_map
)
```

#### M-1c: allocate_height_budget() 시그니처 변경

**위치:** `src/space_allocator.py` (42~75행)

**변경:** `weight_override` 파라미터 추가
```python
def allocate_height_budget(
    blocks, zone_budget_px, gap_px=20,
    weight_override=None,  # {topic_id: weight} — Kei 판단 기반
):
    # weight_override 있으면 사용, 없으면 PURPOSE_WEIGHT fallback
    for block in blocks:
        tid = block.get("topic_id")
        if weight_override and tid in weight_override:
            weight = weight_override[tid]
        else:
            purpose = block.get("purpose", "")
            weight = PURPOSE_WEIGHT.get(purpose, 0.25)
        weights.append(weight)
```

**PURPOSE_WEIGHT:** fallback으로 유지 (Kei가 page_structure 안 줬을 때). 하드코딩 → fallback 강등.

#### M-1d: STEP_B_PROMPT에 weight 전달

**위치:** `src/design_director.py` STEP_B_PROMPT user_prompt 구성부

**추가:** Kei가 판단한 비중을 팀장에게 전달
```
## 페이지 구조 (Kei 실장 판단)
- 본심 (꼭지 2, 3): 공간 비중 60% — body에서 가장 크게
- 배경 (꼭지 1): 공간 비중 15% — compact 도입부
- 첨부 (꼭지 4): 공간 비중 15% — sidebar 참조
- 결론 (꼭지 5): 공간 비중 10% — footer 한 줄

본심에 가장 큰 블록을, 배경에 가장 작은 블록을 배정하라.
비중을 무시하고 동등하게 배치하지 마라.
```

---

### M-Step 2: 역할-블록 매핑

#### M-2a: FAISS 쿼리 강화

**위치:** `src/block_search.py` `_build_query()` (178~188행)

**변경:** purpose + relation_type + expression_hint 추가
```python
def _build_query(topic):
    parts = [
        topic.get("title", ""),
        topic.get("summary", ""),
        f"역할: {topic.get('role', 'flow')}",
        f"레이어: {topic.get('layer', 'core')}",
        f"목적: {topic.get('purpose', '')}",              # 추가
        f"관계: {topic.get('relation_type', '')}",         # 추가
        f"표현: {topic.get('expression_hint', '')}",       # 추가
    ]
    if topic.get("content_type"):
        parts.append(f"콘텐츠: {topic['content_type']}")
    return ". ".join(p for p in parts if p)
```

#### M-2b: STEP_B_PROMPT 역할×성격 매트릭스

**위치:** `src/design_director.py` purpose 가이드 섹션

**기존 Phase K 규칙 유지 + 아래 매트릭스 추가:**
```
## 역할 × 콘텐츠 성격 블록 매트릭스

| 역할 | 비교(comparison) | 구조(hierarchy/inclusion) | 정의(definition) | 흐름(sequence) |
|------|-----------------|------------------------|-----------------|---------------|
| 본심 | compare-2col-split, compare-3col-badge | keyword-circle-row, venn-diagram | card-numbered(large) | process-horizontal, flow-arrow-horizontal |
| 배경 | topic-left-right(compact) | topic-left-right(compact) | quote-question(compact) | topic-left-right(compact) |
| 첨부 | card-numbered(sidebar) | card-numbered(sidebar) | card-numbered(sidebar), dark-bullet-list(sidebar) | card-numbered(sidebar) |
| 결론 | banner-gradient | banner-gradient | banner-gradient | banner-gradient |
```

---

### M-Step 3: 블록 안전성

#### M-3a: catalog.yaml figma_source 필드 (P-5)

**추가할 필드:** 각 블록에 `figma_source: true/false`

#### M-3b: zone 적합성 검증 (P-6)

**위치:** `src/design_director.py` `_validate_height_budget()` 내

**추가:** sidebar에 FULL_WIDTH_ONLY_BLOCKS 배치 시 교체/경고

#### M-3c: 글자 수용량 (P-7)

**위치:** `templates/catalog.yaml`

**추가:** 각 블록에 zone별 max_chars
```yaml
- id: compare-2col-split
  max_chars:
    body: {left: 200, right: 200, criteria: 30}  # 65% 너비 기준
    sidebar: null  # sidebar 사용 불가
```

#### M-3d: 내부 overflow 감지 (P-8)

**위치:** `src/slide_measurer.py` _MEASURE_SCRIPT

**확인:** 현재 JS가 블록 내부 `scrollHeight > clientHeight + 2` 이미 체크 중.
`overflow: auto` 블록(compare-3col-badge)의 수평 스크롤도 `scrollWidth > clientWidth` 체크 추가.

---

### M-Step 4: 원본 보존

#### M-4a: 토픽별 source_text 추출

**위치:** `src/pipeline.py` Stage 3 호출 전

**추가:** Kei가 출력한 source_hint + source_data를 기반으로 원본에서 텍스트 추출
```python
# 토픽별 원본 텍스트 매핑 구성
topic_sources = {}
for topic in analysis.get("topics", []):
    source_hint = topic.get("source_hint", "")
    source_data = topic.get("source_data", "")
    topic_sources[topic["id"]] = {
        "hint": source_hint,
        "data": source_data,
    }
```

#### M-4b: fill_content() 프롬프트에 토픽별 source 전달

**위치:** `src/content_editor.py` fill_content() user_prompt 구성부

**추가:**
```
## 토픽별 원본 데이터 (이 텍스트에서 추출하라. 재작성 금지.)
- 토픽 1: [source_hint 내용]
- 토픽 2: [source_hint 내용]
```

---

## 충돌/회귀/하드코딩 최종 검증

| Step | 충돌 | 회귀 | 하드코딩 | Kei/Sonnet |
|------|:---:|:---:|:------:|:----------:|
| M-1a KEI_PROMPT | 없음 | 없음 | **Kei 판단** | Kei |
| M-1b pipeline weight | 없음 | Phase L 개선 | **Kei weight** | — |
| M-1c allocate 시그니처 | 없음 | 없음 | fallback만 | — |
| M-1d STEP_B weight | 없음 | 없음 | **Kei → 팀장** | Sonnet(기존) |
| M-2a FAISS 쿼리 | 없음 | 없음 | 없음 | — |
| M-2b 매트릭스 | Phase K 위에 확장 | 없음 | 없음 | Sonnet(기존) |
| M-3a Figma | 없음 (신규) | 없음 | 없음 | — |
| M-3b zone맵 | Phase J 보강 | 없음 | 상수(범용) | — |
| M-3c max_chars | Phase I 보강 | 없음 | 없음 | — |
| M-3d 내부overflow | Phase L 확장 | 없음 | 없음 | — |
| M-4a source 추출 | 없음 (신규) | 없음 | 없음 | — |
| M-4b 편집자 강화 | Phase J 보강 | 없음 | 없음 | Kei(편집자) |

---

## 이력

| 날짜 | 내용 |
|------|------|
| 2026-03-26 | Phase I~L 전체 실행 후 결과물 분석. 외부 진단(P-1~P-9) 수용. 비중 시스템(Kei 판단, 하드코딩 아님) 기반 전면 재설계. Phase M 계획 수립. |
| 2026-03-26 | 기술 조사 + 충돌/회귀 정밀 검토 완료. M-Step 1~4 실행 방안 상세 확정. |
