# task-679.1 완료 보고서

## SCQA

**S**: ThreadAuto 5단계 파이프라인(`FiveStagePipeline`)은 cardnews/text_* 두 유형의 콘텐츠를 생성하며, cardnews는 정상 동작 중이다. pytest 78건 전체 통과, pyright 에러 0건 상태.

**C**: text_insight 타입 실행 시 `{"text": "", ...}` 빈 문자열이 반환되며, 1팀장도 동일 증상을 경험. 원인은 3가지로 분석됨: (1) `_parse_json_response` regex의 중첩 JSON 취약성, (2) `_build_result`에서 빈 문자열 dict가 `or` 체인을 통과, (3) writing 단계 출력 키 불일치(`text` 대신 `content` 등) 미처리.

**Q**: text_insight 빈 문자열 반환을 방지하고, 향후 유사 버그를 예방할 수 있는가?

**A**: 3가지 근본 원인을 모두 수정하고 테스트 7건을 추가하여 85건 전체 통과. `_parse_json_response`는 first{~last} 방식으로 outermost JSON 추출, `_build_result`는 `_extract_text_content()` 헬퍼로 다중 소스/다중 키 폴백, writing 단계 후 키 정규화 추가. 무한루프는 MAX_FULL_RETRIES=2로 제한됨을 확인(최대 3회 시도 후 RuntimeError).

## 수정 파일

- `content/five_stage_pipeline.py` — 4개 수정 (regex, 헬퍼 추가, _build_result 리팩터, 키 정규화)
- `tests/test_five_stage_pipeline.py` — 테스트 7건 추가 (TestTextInsightEmptyText 6건, TestInfiniteLoopPrevention 1건)

## 수정 상세

### 1. `_parse_json_response` regex 강화 (line 312~348)
- **이전**: `\{.*?\}` 비탐욕 매칭 → 중첩 JSON에서 내부 객체만 캡처 위험
- **이후**: 코드블록 내용 전체 추출 → `first { ~ last }` (rfind)로 outermost JSON 추출
- **검증**: `test_parse_json_response_nested_json_in_codeblock`, `test_parse_json_response_with_braces_in_text` PASS

### 2. `_extract_text_content()` 헬퍼 추가 (line 131~142)
- 다중 소스(review→hooking→writing)에서 순서대로 비어있지 않은 텍스트 탐색
- `"text"`, `"content"`, `"body"`, `"thread"` 키를 모두 확인
- `isinstance(val, str) and val.strip()` 으로 빈 문자열/None 필터링

### 3. `_build_result` text_* 분기 수정 (line 180~214)
- **이전**: `{"text": "", "hashtags": [...]}` dict가 truthy → `or` 체인 통과 → `.get("text", default)` 에서 키 존재로 `""` 반환
- **이후**: `_extract_text_content()` 로 빈 문자열 폴백 처리
- **검증**: `test_text_insight_empty_text_fallback_to_writing`, `test_build_result_or_chain_skips_empty_text_dict` PASS

### 4. writing 출력 키 정규화 + 디버그 로깅 (line 78~96)
- writing 단계 후 `"text"` 키 없으면 `content`/`body`/`thread` → `text` 키로 통일
- 각 단계 후 `logger.debug`로 출력 키 목록 기록

## 발견 이슈 및 해결

### 자체 해결 (3건)
1. **regex 중첩 JSON 캡처 실패** — `_parse_json_response`에서 코드블록 내용 전체 추출 후 first{~last} 방식으로 변경
2. **빈 문자열 dict의 or 체인 통과** — `_extract_text_content()` 헬퍼로 `.strip()` 기반 유효성 검사
3. **writing 출력 키 불일치** — `generate()` 내 키 정규화 로직 추가

### 범위 외 미해결 (1건)
1. **무한루프/장시간 행** — MAX_FULL_RETRIES=2로 3회 시도 제한 확인됨. 실제 "무한루프"는 Claude CLI timeout(600초) × stage retry(2) × full retry(3) = 최대 60분 소요 가능한 것이 원인. timeout 단축은 콘텐츠 품질에 영향 → 별도 튜닝 필요.

## 테스트 결과
- pytest: **85 passed** in 0.23s (기존 78 + 신규 7)
- pyright: **0 errors**, 0 warnings, 0 informations
- black + isort: 포매팅 적용 완료

## QC 자동 검증 결과

- **overall**: WARN (PASS gate — .done 생성됨)
- file_check: PASS
- data_integrity: PASS
- tdd_check: PASS
- style_check: PASS (black + isort OK)
- pyright_check: WARN — worktree 경로에서 `content` 패키지 import 미해석 (`reportMissingImports` 8건). 프로젝트 루트 실행 시 0 errors 확인 완료.
- test_runner: SKIP — `test_cta_linebreak.py::TestFactDbContainsBusinessPage` 기존 실패 1건 (fact_db.md에 '사업단 페이지' 표기 부재). **본 작업 범위 외**.
- critical_gap: PASS

## 머지 판단
- **머지 필요**: Yes
- **브랜치**: task/task-679.1-dev2
- **워크트리 경로**: /home/jay/projects/ThreadAuto/.worktrees/task-679.1-dev2
- **머지 의견**: 테스트 85건 전체 통과, pyright 에러 0건. 기존 코드 동작 호환성 유지하면서 빈 문자열 버그만 수정. cardnews 기존 테스트도 변경 없이 통과하므로 회귀 위험 없음.
