Phase I 계획 수립: 전수 정합성 복구 + 10가지 런타임 문제 해결
전수 검토 결과 발견: - 실제 블록 38개 (문서 46개 표기 → 8개 미존재) - STEP_B_PROMPT가 미존재 블록 3개를 적극 추천 (프롬프트 자기모순) - catalog.yaml not_for에서 미존재 블록 참조 - 슬롯 의미 미전달 → 편집자가 source/rows/cards 채우지 못함 13개 항목 3패턴 분류: A. 프롬프트 자기모순 해소 (I-1~I-3) B. 슬롯 의미 전달 (I-4~I-5) C. 코드 안전망 확장 (I-6~I-9) D. 문서 동기화 (I-10~I-13) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
308
IMPROVEMENT-PHASE-I.md
Normal file
308
IMPROVEMENT-PHASE-I.md
Normal file
@@ -0,0 +1,308 @@
|
||||
# Phase I: 전수 정합성 복구 + 10가지 런타임 문제 해결 — 실행 상세
|
||||
|
||||
> 전수 검토에서 발견된 프롬프트 자기모순, 문서-코드 불일치, 코드 안전망 부족을 해결.
|
||||
> 원칙: 하드코딩 금지. 범용 해결. 회귀 금지. persona_agent 수정 0건.
|
||||
|
||||
---
|
||||
|
||||
## 문제 진단 총괄
|
||||
|
||||
### 전수 검토에서 발견된 근본 원인
|
||||
|
||||
**실제 블록 수: 38개** (문서는 46개로 표기)
|
||||
삭제된 8개: card-text-grid, quote-left-border, conclusion-accent-bar, details-block, layer-diagram, timeline-vertical, timeline-horizontal, pyramid-hierarchy
|
||||
|
||||
이 8개가 삭제되었지만 프롬프트, catalog, INDEX.md, README.md에 여전히 참조되고 있음.
|
||||
→ AI가 존재하지 않는 블록을 선택 → 부적절한 강제 교체 → 빈 블록, 잘못된 배치
|
||||
|
||||
---
|
||||
|
||||
## 패턴 A: 프롬프트 자기모순 (문제 2, 7)
|
||||
|
||||
### I-1: STEP_B_PROMPT purpose 가이드에서 미존재 블록 제거
|
||||
|
||||
**현재 문제:**
|
||||
```python
|
||||
# design_director.py 264~271행
|
||||
"- 근거사례 → quote-left-border, card-text-grid" ← 둘 다 미존재!
|
||||
"- 용어정의 → card-text-grid" ← 미존재!
|
||||
"- 구조시각화 → venn-diagram, layer-diagram" ← layer-diagram 미존재!
|
||||
```
|
||||
|
||||
허용 목록에는 없는데 purpose 가이드에서 적극 추천 → **프롬프트 자기모순** → Sonnet이 미존재 블록 선택
|
||||
|
||||
**수정:**
|
||||
```
|
||||
- 근거사례 → quote-big-mark (출처 포함), card-icon-desc (항목 나열)
|
||||
- 용어정의 → card-icon-desc (정의+출처), card-numbered (순서 있으면)
|
||||
- 구조시각화 → venn-diagram (단독 배치)
|
||||
```
|
||||
|
||||
**수정 파일:** src/design_director.py (STEP_B_PROMPT)
|
||||
|
||||
---
|
||||
|
||||
### I-2: catalog.yaml의 not_for/when에서 미존재 블록 참조 제거
|
||||
|
||||
**현재 문제:**
|
||||
- circle-gradient not_for: "topic-header" → 실제 이름은 topic-left-right/topic-center/topic-numbered
|
||||
- circle-gradient not_for: "conclusion-accent-bar" → 미존재. 실제는 banner-gradient
|
||||
- process-horizontal not_for: "timeline" → 미존재. 실제 없음 (삭제됨)
|
||||
|
||||
**수정:** catalog.yaml 전체에서 미존재 블록 참조를 실존 블록으로 교체
|
||||
|
||||
**수정 파일:** templates/catalog.yaml
|
||||
|
||||
---
|
||||
|
||||
### I-3: 미등록 블록 교체를 purpose 기반으로 변경
|
||||
|
||||
**현재 문제:**
|
||||
```python
|
||||
# design_director.py 574행
|
||||
block["type"] = "callout-solution" # 모든 미등록 블록을 일괄 교체
|
||||
```
|
||||
|
||||
결론 꼭지의 미등록 블록도 callout-solution으로 교체 → 의미적으로 부적절
|
||||
|
||||
**수정:** purpose 기반 교체 맵
|
||||
```python
|
||||
PURPOSE_FALLBACK = {
|
||||
"문제제기": "callout-warning",
|
||||
"근거사례": "quote-big-mark",
|
||||
"핵심전달": "comparison-2col",
|
||||
"용어정의": "card-icon-desc",
|
||||
"결론강조": "banner-gradient",
|
||||
"구조시각화": "card-icon-desc",
|
||||
}
|
||||
# 미등록 블록 거부 시: block의 purpose를 보고 적절한 대체 블록 선택
|
||||
fallback_type = PURPOSE_FALLBACK.get(block.get("purpose", ""), "callout-solution")
|
||||
```
|
||||
|
||||
**수정 파일:** src/design_director.py
|
||||
|
||||
---
|
||||
|
||||
## 패턴 B: 슬롯 의미 정보 미전달 (문제 3, 5, 9, 10)
|
||||
|
||||
### I-4: BLOCK_SLOTS에 slot_desc 추가
|
||||
|
||||
**현재 문제:**
|
||||
편집자가 슬롯 이름만 받고 의미를 모름:
|
||||
- source에 출처가 아닌 꼭지 제목을 넣음 (문제 3)
|
||||
- text/sub_text 순서를 뒤집음 (문제 5)
|
||||
- rows[][] 배열 구조를 채우지 못함 (문제 9)
|
||||
- cards[] 배열 구조를 채우지 못함 (문제 10)
|
||||
|
||||
**수정:** BLOCK_SLOTS에 slot_desc 필드 추가
|
||||
```python
|
||||
"quote-big-mark": {
|
||||
"required": ["quote_text"],
|
||||
"optional": ["source"],
|
||||
"slot_desc": {
|
||||
"quote_text": "인용할 본문 텍스트",
|
||||
"source": "출처 (예: 국토교통부, 2024). 꼭지 제목이 아님!",
|
||||
},
|
||||
},
|
||||
"banner-gradient": {
|
||||
"required": ["text"],
|
||||
"optional": ["sub_text"],
|
||||
"slot_desc": {
|
||||
"text": "핵심 결론 한 줄 (굵은 대형 텍스트. 가장 중요한 메시지)",
|
||||
"sub_text": "부연 설명 (작은 보조 텍스트. text보다 덜 중요)",
|
||||
},
|
||||
},
|
||||
"compare-2col-split": {
|
||||
"required": ["left_title", "right_title", "rows"],
|
||||
"optional": [],
|
||||
"slot_desc": {
|
||||
"left_title": "왼쪽 열 헤더",
|
||||
"right_title": "오른쪽 열 헤더",
|
||||
"rows": "비교 행 배열. 각 행: {criteria: '비교 기준', left: '왼쪽 내용', right: '오른쪽 내용'}. 최소 3행.",
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
**수정 범위:** 38개 블록 모두에 slot_desc 추가 (가장 큰 작업)
|
||||
|
||||
### I-5: 편집자 프롬프트에 slot_desc 전달
|
||||
|
||||
**현재:** content_editor.py 87~88행
|
||||
```python
|
||||
f" 필수 슬롯: {slots.get('required', [])}\n"
|
||||
f" 선택 슬롯: {slots.get('optional', [])}"
|
||||
```
|
||||
|
||||
**수정:**
|
||||
```python
|
||||
f" 필수 슬롯: {slots.get('required', [])}\n"
|
||||
f" 선택 슬롯: {slots.get('optional', [])}\n"
|
||||
# slot_desc가 있으면 각 슬롯의 의미 전달
|
||||
slot_desc = slots.get("slot_desc", {})
|
||||
if slot_desc:
|
||||
desc_lines = [f" {k}: {v}" for k, v in slot_desc.items()]
|
||||
req_text += "\n 슬롯 설명:\n" + "\n".join(desc_lines)
|
||||
```
|
||||
|
||||
**수정 파일:** src/content_editor.py, src/design_director.py (BLOCK_SLOTS)
|
||||
|
||||
---
|
||||
|
||||
## 패턴 C: 코드 안전망 부족 (문제 1, 4, 6, 8)
|
||||
|
||||
### I-6: 제목 유사도 검증 (문제 1)
|
||||
|
||||
**현재:** 프롬프트에 "달라야 한다"고 지시했지만 코드 검증 없음
|
||||
|
||||
**수정:** pipeline.py 또는 design_director.py에서 `analysis["title"]`과 `analysis["topics"][0]["title"]` 비교
|
||||
```python
|
||||
from difflib import SequenceMatcher
|
||||
title = analysis.get("title", "")
|
||||
first_topic_title = analysis.get("topics", [{}])[0].get("title", "")
|
||||
similarity = SequenceMatcher(None, title, first_topic_title).ratio()
|
||||
if similarity > 0.7:
|
||||
# 첫 꼭지 제목을 purpose 기반으로 변경
|
||||
first_topic = analysis["topics"][0]
|
||||
purpose = first_topic.get("purpose", "문제제기")
|
||||
first_topic["title"] = f"{purpose}: {first_topic.get('summary', '')[:30]}"
|
||||
logger.warning(f"[제목 중복 교정] 유사도 {similarity:.0%} → 첫 꼭지 제목 변경")
|
||||
```
|
||||
|
||||
**수정 파일:** src/pipeline.py
|
||||
|
||||
### I-7: compare-pill-pair 단독 사용 금지 (문제 4)
|
||||
|
||||
**현재:** compare-pill-pair가 비교 내용 없이 라벨만 표시
|
||||
|
||||
**수정:** _validate_height_budget() 안에서 compare-pill-pair가 해당 zone에서 유일한 "비교" 블록이면 경고
|
||||
```python
|
||||
# compare-pill-pair는 비교 테이블의 헤더로만 사용
|
||||
# 같은 zone에 compare-2col-split, compare-3col-badge, comparison-2col이 없으면 부적절
|
||||
COMPARISON_BLOCKS = {"compare-2col-split", "compare-3col-badge", "comparison-2col"}
|
||||
for area, area_blocks in zone_blocks.items():
|
||||
types = {b.get("type") for b in area_blocks}
|
||||
if "compare-pill-pair" in types and not types & COMPARISON_BLOCKS:
|
||||
# pill-pair를 comparison-2col로 교체
|
||||
for block in area_blocks:
|
||||
if block.get("type") == "compare-pill-pair":
|
||||
block["type"] = "comparison-2col"
|
||||
logger.warning(f"[pill-pair 단독 금지] compare-pill-pair → comparison-2col")
|
||||
```
|
||||
|
||||
**수정 파일:** src/design_director.py
|
||||
|
||||
### I-8: 대형 테이블 → detail_target 자동 설정 (문제 6)
|
||||
|
||||
**현재:** 원본의 12행 비교표가 detail_target으로 잡히지 않음
|
||||
|
||||
**수정:** pipeline.py에서 1단계-B 완료 후
|
||||
```python
|
||||
for table in analysis.get("tables", []):
|
||||
if table.get("rows", 0) >= 5 and not table.get("fits_single_page", True):
|
||||
for topic in analysis.get("topics", []):
|
||||
if topic.get("id") == table.get("topic_id"):
|
||||
if not topic.get("detail_target"):
|
||||
topic["detail_target"] = True
|
||||
logger.info(f"대형 테이블 → detail_target 자동: topic {topic['id']}")
|
||||
```
|
||||
|
||||
**수정 파일:** src/pipeline.py
|
||||
|
||||
### I-9: DOWNGRADE_MAP 확장 + 다단계 교체 (문제 8)
|
||||
|
||||
**현재:** body 높이 600px > 490px인데 medium 블록 교체 대상 없음
|
||||
|
||||
**수정:**
|
||||
1. medium → compact 교체 맵 추가
|
||||
2. 교체 임계치 `>= 250` → `>= 150`으로 낮춤
|
||||
3. 1회 교체 후에도 초과이면 다음 블록도 교체 (현재는 break)
|
||||
|
||||
```python
|
||||
DOWNGRADE_MAP = {
|
||||
# 기존 large/xlarge → medium/compact
|
||||
"venn-diagram": "card-icon-desc",
|
||||
"card-step-vertical": "card-numbered",
|
||||
...
|
||||
# 추가: medium → compact
|
||||
"quote-big-mark": "divider-text",
|
||||
"comparison-2col": "compare-pill-pair", # 주의: I-7 규칙과 함께 적용
|
||||
"process-horizontal": "flow-arrow-horizontal",
|
||||
"dark-bullet-list": "highlight-strip",
|
||||
"card-icon-desc": "highlight-strip",
|
||||
}
|
||||
```
|
||||
|
||||
**수정 파일:** src/design_director.py
|
||||
|
||||
---
|
||||
|
||||
## 문서 동기화
|
||||
|
||||
### I-10: INDEX.md 동기화 (38개 실제 현황)
|
||||
|
||||
미존재 8개 블록 항목 제거: card-text-grid, quote-left-border, conclusion-accent-bar, details-block, layer-diagram, timeline-vertical, timeline-horizontal, pyramid-hierarchy
|
||||
|
||||
**수정 파일:** templates/blocks/INDEX.md
|
||||
|
||||
### I-11: README.md 동기화
|
||||
|
||||
- "46개 + _legacy 13개" → "38개"
|
||||
- Sonnet fallback 표기 제거 (Phase G에서 제거됨)
|
||||
- 블록 트리 구조에서 미존재 블록 제거
|
||||
- 각 카테고리 개수 수정: cards 9, visuals 6, emphasis 10
|
||||
|
||||
**수정 파일:** README.md
|
||||
|
||||
### I-12: BLOCK_SLOTS 주석 수정
|
||||
|
||||
- "visuals/ (10개)" → "visuals/ (6개)"
|
||||
- "emphasis/ (12개)" → "emphasis/ (10개)"
|
||||
|
||||
**수정 파일:** src/design_director.py (주석만)
|
||||
|
||||
### I-13: 데드 코드 정리
|
||||
|
||||
- `_call_anthropic_direct()` 함수 제거 (kei_client.py) — G-2에서 호출 제거했지만 함수 자체는 잔존
|
||||
|
||||
**수정 파일:** src/kei_client.py
|
||||
|
||||
---
|
||||
|
||||
## 수정 파일 총괄
|
||||
|
||||
| 파일 | 항목 | 변경 성격 |
|
||||
|------|------|----------|
|
||||
| `src/design_director.py` | I-1, I-3, I-7, I-9, I-12 | purpose 가이드 교체 + 교체 맵 개선 + pill-pair 검증 + DOWNGRADE 확장 + 주석 |
|
||||
| `src/design_director.py` (BLOCK_SLOTS) | I-4 | 38개 블록에 slot_desc 추가 |
|
||||
| `src/content_editor.py` | I-5 | 편집자에게 slot_desc 전달 |
|
||||
| `src/pipeline.py` | I-6, I-8 | 제목 유사도 + detail_target 자동 |
|
||||
| `src/kei_client.py` | I-13 | 데드 코드 제거 |
|
||||
| `templates/catalog.yaml` | I-2 | not_for 미존재 블록 참조 교체 |
|
||||
| `templates/blocks/INDEX.md` | I-10 | 미존재 블록 제거 |
|
||||
| `README.md` | I-11 | 블록 수 + fallback + 트리 구조 동기화 |
|
||||
|
||||
---
|
||||
|
||||
## 검증 체크리스트
|
||||
|
||||
- [ ] I-1: STEP_B_PROMPT의 purpose 가이드에 미존재 블록 0건
|
||||
- [ ] I-2: catalog.yaml의 not_for/when에 미존재 블록 참조 0건
|
||||
- [ ] I-3: 미등록 블록 교체가 purpose 기반으로 동작
|
||||
- [ ] I-4: BLOCK_SLOTS 38개 블록 모두에 slot_desc 존재
|
||||
- [ ] I-5: 편집자 프롬프트에 슬롯 설명 포함
|
||||
- [ ] I-6: 제목 유사도 70% 이상 시 자동 교정
|
||||
- [ ] I-7: compare-pill-pair 단독 사용 시 comparison-2col로 교체
|
||||
- [ ] I-8: 5행 이상 테이블 → detail_target 자동 true
|
||||
- [ ] I-9: body 높이 초과 시 medium 블록도 교체 가능
|
||||
- [ ] I-10: INDEX.md에 미존재 블록 0건
|
||||
- [ ] I-11: README.md 블록 수 38개, Sonnet fallback 표기 없음
|
||||
- [ ] I-12: BLOCK_SLOTS 주석이 실제 개수와 일치
|
||||
- [ ] I-13: _call_anthropic_direct() 함수 없음
|
||||
|
||||
---
|
||||
|
||||
## 수정 이력
|
||||
|
||||
| 날짜 | 내용 |
|
||||
|------|------|
|
||||
| 2026-03-26 | 초안. 전수 정합성 검토 기반 13개 항목. 3패턴(프롬프트 모순 + 슬롯 의미 + 코드 안전망) 분류. |
|
||||
@@ -328,6 +328,34 @@ CLAUDE.md 요구사항 전수검토 결과 발견된 미구현/부분구현/위
|
||||
|
||||
---
|
||||
|
||||
## Phase I: 전수 정합성 복구 + 10가지 런타임 문제 해결 (13개)
|
||||
|
||||
> **실행 상세:** [IMPROVEMENT-PHASE-I.md](IMPROVEMENT-PHASE-I.md)
|
||||
> 전수 검토에서 발견된 프롬프트 자기모순 + 슬롯 의미 미전달 + 코드 안전망 부족 해결.
|
||||
|
||||
### 패턴 A: 프롬프트 자기모순 (I-1~I-3) — 최우선
|
||||
- I-1: STEP_B_PROMPT에서 미존재 블록 3개(quote-left-border, card-text-grid, layer-diagram) → 실존 블록으로 교체
|
||||
- I-2: catalog.yaml not_for에서 미존재 블록 참조 → 실존 블록으로 교체
|
||||
- I-3: 미등록 블록 교체를 callout-solution 일괄 → purpose 기반 교체 맵
|
||||
|
||||
### 패턴 B: 슬롯 의미 미전달 (I-4~I-5)
|
||||
- I-4: BLOCK_SLOTS 38개 블록에 slot_desc 추가 (각 슬롯의 의미/예시/구조)
|
||||
- I-5: 편집자 프롬프트에 slot_desc 전달
|
||||
|
||||
### 패턴 C: 코드 안전망 (I-6~I-9)
|
||||
- I-6: 제목 유사도 검증 (70% 이상 → 자동 교정)
|
||||
- I-7: compare-pill-pair 단독 사용 금지 (비교 테이블 없으면 comparison-2col로 교체)
|
||||
- I-8: 대형 테이블(5행+) → detail_target 자동 설정
|
||||
- I-9: DOWNGRADE_MAP 확장 (medium → compact) + 다단계 교체
|
||||
|
||||
### 문서 동기화 (I-10~I-13)
|
||||
- I-10: INDEX.md 미존재 블록 제거 (46→38)
|
||||
- I-11: README.md 동기화 (블록 수, Sonnet fallback 제거)
|
||||
- I-12: BLOCK_SLOTS 주석 수정
|
||||
- I-13: 데드 코드(_call_anthropic_direct) 제거
|
||||
|
||||
---
|
||||
|
||||
## Phase별 의존 관계
|
||||
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user