인간 검토 5%만 거치면 장문 X 아티클을 대량 생산하는 방법
요약
Claude Code를 활용하여 고품질의 X(Twitter) 아티클을 대량 생산하는 자동화 인프라 구축 방법을 소개합니다. 완전 자동화 대신 인간이 5%의 검토 과정을 거치는 '인간 검토 게이트' 설계를 통해 콘텐츠의 품질을 유지하는 전략을 다룹니다.
핵심 포인트
- Claude Code를 이용한 아티클 생성 및 셀프 채점 파이프라인 구축
- 완전 자동화의 한계를 극복하기 위한 '인간 검토 5%' 전략
- 셸 스크립트와 환경 변수를 활용한 유연한 자동화 워크플로우
- 품질 임계값(Threshold) 설정을 통한 콘텐츠 선별 메커니즘
AI가 포스팅하게 두면, 읽고 나면 아무것도 남지 않는 것처럼 보이는 기사들이 끝없이 쏟아집니다. 이것은 Claude Code를 이용해 개인 프로젝트를 대규모로 배포하기 위한 자동화 인프라 구축에 관한 저의 지속적인 시리즈 중 일부입니다. 이번에는 Claude Code를 사용해 장문 X(이전 Twitter) 아티클을 대량 생산하면서도 품질을 높게 유지하는 데 제가 설계한 '인간 검토 5%' 게이트에 대해 이야기하고자 합니다.
완전히 자동화하지 않은 이유
AI가 작성하게 두면, 읽고 나면 아무것도 남지 않는 것처럼 보이는 기사들을 대량 생산하게 됩니다. 제 솔직한 생각은 포스트 리드 인상(post-read impression)을 결정하는 것은 정보의 정확성이 아니라 그 안에 직접 경험한 정보가 있는지 여부라는 것입니다.
처음에는 간단한 3단계 파이프라인을 목표로 했습니다: 생성 → 품질 검토 → X에 자동 포스팅. 이를 모두 셸 스크립트로 연결하고 크론(cron)에서 실행했습니다. 기술적으로는 어렵지 않았습니다. 하지만 실제로 실행해 보니, 그저 평범한 7/10 수준의 기사들을 조용히 포스팅할 뿐이었습니다. 거기서 무언가를 배울 수는 있지만, 영혼 없는 글들이었습니다. '도대체 누가 이걸 쓴 거지?'라는 느낌을 지울 수 없었습니다. Lily의 이름으로 출판되는 것이 되기에는 적합하지 않았습니다.
원인은 명확했습니다. Claude는 '평균 점수를 목표로 하는' 데 능합니다. 아무도 불쾌해하지 않지만 자극적이지 않은, 정확하지만 영감이 부족한 기사들을 만들어내는 경향이 있습니다. 제가 내린 결론은 이렇습니다: 9/10 수준의 아티클을 배포하려면, 인간이 최종 게이트를 지켜야 한다는 것입니다.
그래서 저는 Claude가 대량 생산하고, 인간이 단지 5%만 터치하여 9/10 수준의 글을 선별하는 디자인으로 정착했습니다. 완전한 자동화는 포기하고, 인간의 판단력을 최소한의 작업량으로 압축하는 데 집중했습니다.
공장 개요
이것이 ~/dev/x-article-factory/ 디렉터리 구조입니다.
x-article-factory/
├── generate.sh # Claude가 초안을 작성하고, 스스로 점수를 매김
├── review-gate.sh # TUI에서 인간이 승인/거부함
...
처리 흐름은 세 단계로 이루어져 있습니다.
[launchd] daily.sh
↓ N개의 아티클 반복 (기본값 2)
generate.sh
...
분업 구조는 간단합니다: AI가 생성하고 스스로 점수를 매기며, 인간은 최종 결정만 내립니다.
generate.sh: Claude가 스스로 쓰고 채점하기
생성 과정의 핵심은 generate.sh입니다. 설계 과정을 순서대로 살펴보겠습니다.
환경 변수를 통해 조정 가능한 설정값들
MODEL="${X_ARTICLE_MODEL:-sonnet}"
GEN_TIMEOUT="${X_ARTICLE_GEN_TIMEOUT:-420}"
MIN_AVG="${X_ARTICLE_MIN_AVG:-7.0}"
모델은 기본적으로 sonnet을 사용하며, 타임아웃은 420초(7분), 품질 임계값(threshold)은 7.0으로 설정됩니다. 장문의 아티클을 초안 작성하는 데는 생성 시간이 걸리므로, 타임아웃을 충분한 여유를 두고 설정했습니다. 모든 설정은 환경 변수를 통해 재정의할 수 있으므로, "품질을 좁히기 위해 임계값을 높이기" 또는 "다른 모델 시도하기"와 같은 실험을 .env 파일의 한 줄 수정만으로 전환할 수 있습니다.
또한 PAUSED 파일이 존재하면 스크립트가 즉시 종료되는 메커니즘도 있습니다.
[ -f "$DIR/PAUSED" ] && { echo "[gen] PAUSED。停止中。"; exit 0; }
상황이 통제에서 벗어나거나 예상치 못한 아티클이 나오기 시작하면, touch ~/dev/x-article-factory/PAUSED 명령 하나만으로 모든 것을 중단할 수 있습니다.
지식을 5개의 파일로 나누어 프롬프트(prompt)에 쌓기
KNOW=""
for f in persona hooks style post-types quality-rubric; do
KNOW+=$'\n\n===== '
GROUND=""
for src in "$HOME/.remember/recent.md" "$HOME/.remember/now.md"
"$HOME/Documents/claude-obsidian/wiki/hot.md"; do
...
나는 Claude에게 전달할 "직접적인 정보 (firsthand information)"로서 세 가지 소스를 불러옵니다.
- `~/.remember/recent.md` — 최근 작업 노트
- `~/.remember/now.md` — 현재 작업 중인 내용
- `~/Documents/claude-obsidian/wiki/hot.md` — Obsidian에서 추출한 핵심 요약 (약 500단어)
각 파일은 `head -c 4000` 명령어를 통해 최대 4,000바이트까지 읽혀 프롬프트(prompt)에 전달됩니다. 프롬프트의 지침은 다음과 같습니다: "**항상 고유명사, 숫자, 그리고 실패 사례를 포착하여 본문에 포함하세요. 빌려온 일반론에 의존하지 마세요.**" 이 두 부분의 조합이 핵심적인 역할을 합니다.
예를 들어, 근거 자료(grounding)에 "iOS 앱이 리뷰를 통과했다", "스크립트의 특정 단계에서 오류가 발생했다", 또는 "이번 주에 출시한 기능의 노출 수(impression count)"와 같은 정보가 포함되어 있다면, Claude는 이를 기사의 주인공으로 만들려고 시도할 것입니다. 바로 이 지점에서 AI가 대량 생산한 쓰레기(junk)와의 격차가 만들어집니다.
모든 파일이 비어 있으면 "작업 로그 없음"으로 간주하고 일반적인 지식에 기반하여 글을 씁니다. 이 경우 품질이 떨어지므로, 기사를 생성하기 전에 작업 로그를 업데이트하는 습관이 중요합니다.
### 중복을 피하기 위해 과거의 형식과 주제를 가져오기
LAST_TYPES=$(tail -3 "$TYPES_LOG" | paste -sd '、' -); [ -z "$LAST_TYPES" ] && LAST_TYPES="(まだ無し)"
LAST_TOPICS=$(tail -8 "$TOPICS_LOG" | paste -sd '、' -); [ -z "$LAST_TOPICS" ] && LAST_TOPICS="(まだ無し)"
나는 Claude에게 마지막 3개의 형식(format)과 마지막 8개의 주제(topic)를 전달합니다. 이를 "이것들을 피하고 다른 형식을 선택하세요" 또는 "재탕하지 말고 다른 관점을 취하세요"와 같은 지침과 결합함으로써, 동일한 패턴의 기사가 연속해서 나오는 것을 방지합니다.
형식보다 주제의 윈도우(window)를 더 넓게(8개) 설정한 이유는 주제의 고갈이 더 먼저 일어나기 때문입니다. 형식은 10개 이상 존재하지만, 주제의 "좁은 길(Lily lane)"(Claude Code / 개인 개발 / AI 자동화)은 매우 좁습니다.
### 출력 형식: 메타 정보 + 본문의 엄격한 구조
저는 Claude의 출력 형식 사양을 엄격하게 만듭니다.
1행: TYPE: <사용된 유형의 이름 (post-types.md의 번호와 이름)>
2행: TOPIC: <이 기사의 주제를 15자 이내로>
3행: SCORE: {"hook":N,"value":N,"specificity":N,"firsthand":N,"tempo":N,"reproducibility":N,"structure":N,"surprise":N,"style":N,"cta":N,"avg":N.N}
...
SCORE JSON에는 10개의 항목이 포함됩니다. Claude는 자신이 작성한 기사를 10가지 축을 따라 스스로 점수를 매깁니다.
| 항목 | 채점 관점 |
| --- | --- |
| hook | 도입부(hook)의 흡입력. 독자가 계속 읽고 싶어 하는가? |
| ... | |
### 품질 게이트(Quality gate): avg < 7.0은 절대 초안에 도달하지 못함
AVG=$(printf '%s' "$SCORE" | python3 "$DIR/lib/parse_avg.py" 2>/dev/null)
[ -z "$AVG" ] && AVG=0
if python3 -c "import sys; sys.exit(0 if float('$AVG') >= float('$MIN_AVG') else 1)" 2>/dev/null; then
...
이 코드는 SCORE JSON에서 `avg`를 추출하여 7.0 미만인 모든 것을 거부합니다. 거부될 경우, `drafts/` 디렉토리에 출력되지 않으며 로그에 `REJECTED_LOWSCORE`로 기록됩니다.
설계 의도는 다음과 같습니다: "만약 Claude가 스스로 매긴 평균 점수가 7.0 미만이라면, 애초에 인간의 눈에 닿지도 않게 한다." 이를 통해 7점대 기사들에 들어가는 인간의 검토 노력을 제로(0)로 만듭니다. 로그를 살펴보면, 초안 작성된 기사의 20~30%가 이 게이트에서 거부됩니다.
> **참고:** "7.0을 통과했다 = 좋은 기사이다"라는 말은 사실이 아닙니다. Claude의 자기 채점은 관대한 경향이 있으며, 실제로 읽어보면 "7.2점이지만 임팩트가 없는" 기사들이 많습니다. 7.0 게이트는 명백한 실패작을 걸러내기 위한 필터입니다. 9/10점짜리 기사를 골라내는 것은 인간의 몫입니다.
품질 게이트를 통과한 모든 것은 `drafts/`에 기록됩니다.
TS=$(date +%Y-%m-%d_%H%M%S)
FNAME="$DIR/drafts/${TS}.md"
{
...
파일명은 타임스탬프(`2026-06-26_142030.md` 형식)입니다. 첫 번째 줄에는 메타 정보가 HTML 주석으로 삽입됩니다. `review-gate.sh`에서 이 메타 라인을 표시함으로써, 검토 중에 "형식 / 주제 / 점수"를 한눈에 확인할 수 있습니다.
### 검증 및 3회 재시도
```bash
resp_is_valid() {
local r="$1"
[ -z "$r" ] && return 1
...
이것은 생성 결과에 대한 검증 함수(validation function)입니다. 이 함수는 TYPE 라인의 존재 여부, SCORE 라인의 존재 여부, 타임아웃(timeout) 또는 속도 제한(rate-limit) 오류의 부재, 그리고 최소 본문 크기가 600바이트 이상인지를 확인합니다. 이 중 하나라도 누락되면 유효하지 않은 것으로 간주하며 최대 3회까지 재시도합니다.
for attempt in 1 2 3; do
RESP=$(timeout "$GEN_TIMEOUT" "$CLAUDE" -p "$PROMPT" --allowedTools WebSearch --model "$MODEL" </dev/null 2>/dev/null)
resp_is_valid "$RESP" && break
...
Claude가 "지금 무엇이 최신인지"를 확인할 수 있도록 --allowedTools WebSearch를 추가했습니다. 프롬프트에는 "필요한 경우 WebSearch를 사용하여 시의적절한 내용을 확인하라"고 지시되어 있으며, 이는 시의성 있는 자료를 다룰 때 작동합니다.
review-gate.sh: 인간이 단 5%만 관여하는 TUI
인간은 품질 게이트(quality gate)를 통과한 drafts/ 폴더의 아티클들을 TUI(Text User Interface)에서 하나씩 검토합니다.
echo "未チェックの下書き: ${#drafts[@]} 本"
echo "操作: [a]承認 [s]保留 [d]却下 [e]編集 [q]終了"
다섯 가지 선택지가 있습니다.
| 키(Key) | 동작(Action) |
|---|---|
| a | approved/로 이동 (포스팅 대기열에 진입) |
| ... |
미리보기(preview)에는 메타 라인(형식 / 주제 / 점수)과 본문의 처음 60줄이 표시됩니다.
head -1 "$f" | sed 's/<!-- *//; s/ *-->//' # 메타 라인(형식/주제/점수)
tail -n +2 "$f" | sed '/./,$!d' | head -60
이 TUI의 핵심은 전체를 다 읽을 필요가 없도록 설계되었다는 점입니다. 의도는 처음 60줄의 미리보기를 통해 "승인할지 말지"를 직관적으로 판단하게 하는 것입니다. 점수가 7.5점 이상이라 하더라도, 읽었을 때 "Lily답지 않다"거나 "직접적인 정보가 기본적으로 일반론에 불과하다"고 느껴지면 즉시 d를 눌러 거절합니다.
반대로, 점수가 7.2점이라 하더라도 "이 에피소드는 괜찮다"고 느껴지면, 때때로 e를 눌러 에디터를 열고 약간의 수정을 거친 뒤 승인합니다.
검토가 끝난 후의 출력은 간단합니다.
echo "승인됨: $(ls "$DIR"/approved/*.md 2>/dev/null | wc -l | tr -d ' ') 권 (approved/)"
echo "다음: 승인된 내용을 X의 예약 게시물에 붙여넣기 → 게시 후 approved/ 에서 삭제"
나는 승인된 아티클을 X(구 트위터)의 예약 게시물에 수동으로 붙여넣고, 게시한 후에는 approved/ 디렉토리에서 삭제합니다. 승인 → 게시 → 삭제 사이클을 실행함으로써, ls approved/ 명령만으로 대기열 관리가 완전히 처리됩니다.
합격률에 대한 현실적인 감각을 말씀드리자면, 생성된 10개의 아티클 중 12개를 승인하는 것(1020%)이 현실적입니다. 나는 "10번 중 7번 성공하는 게시물을 10개 올리는 것보다, 10번 중 9번 성공하는 게시물을 1~2개 올리는 것이 전체적인 인게이지먼트 (Engagement) 측면에서 더 낫다"는 가설을 바탕으로 이 작업을 수행합니다.
daily.sh: launchd를 통한 하루 2개의 아티클 초안 작성
N="${X_ARTICLE_DAILY_N:-2}"
# nvm default 의 PATH를 설정 (launchd 최소 환경 대비)
...
이것은 launchd 래퍼 (Wrapper) 스크립트입니다. 기본적으로 하루에 2개의 아티클 초안을 작성합니다 (X_ARTICLE_DAILY_N을 통해 변경 가능).
중요한 점은 nvm PATH 해결입니다. launchd의 최소 환경에는 nvm이 없으며, 아무런 설정이 없는 상태에서는 claude: command not found 오류가 발생합니다. nvm.sh를 명시적으로 소싱 (Sourcing)하면 이 문제가 해결됩니다. 이를 잊어버리면, 대화형 셸 (Interactive shell)에서는 잘 작동하는데 launchd를 통해서만 실패하는 혼란스러운 상황에 처하게 됩니다.
중간에 삽입된 sleep 2는 안정성을 위한 조치입니다. N개의 아티클을 병렬로 한꺼번에 실행하는 대신, 다음 아티클을 실행하기 전 2초를 기다림으로써 API 호출을 더 안정적으로 만듭니다.
루프 내부에서는 generate.sh의 종료 코드 (Exit code)를 감시하여 성공 횟수를 계산합니다 (품질 게이트에서 거절될 경우 종료 코드 2를 반환하므로, ok에 추가되지 않기 때문입니다). 마지막에는 "1/2개 초안 작성됨"과 같은 형식으로 결과가 출력됩니다.
운영을 통해 배운 설계 포인트 (Design Points)
실제로 한동안 운영해 본 결과, 몇 가지 사실이 드러났습니다.
품질 게이트 (Quality-gate) 임계값 7.0은 현실적인 하한선입니다.
처음에는 6.5를 시도해 보았지만, 6점대의 높은 점수를 받은 기사들도 대부분 "읽을 수는 있지만 내용이 빈약한" 상태였고, review-gate.sh에서 모두 d 등급을 받았습니다. 7.0으로 전환한 후에는 통과된 기사 중 상당수가 "읽기에 흥미로운" 수준에 더 가까워졌습니다. 반면, 이를 8.0으로 높이면 통과율이 너무 낮아져 초안(drafts)이 쌓이지 않게 됩니다. 7.0~7.5가 현실적인 운영 범위입니다.
그라운딩 파일 (Grounding files)이 풍부할수록 기사의 밀도가 높아집니다
Obsidian의 hot.md가 500단어 이상 작성된 날과 거의 비어 있는 날을 비교해 보면, 기사의 구체성이 확연히 다릅니다. "오늘 무엇을 했는지"와 "이번 주에 나온 수치들"을 자주 메모하는 습관은 기사의 품질로 직결됩니다. 업무 로그(work log)를 작성하는 것 또한 기사 작성을 위한 준비 과정입니다.
포맷 이력 관리 (최근 3개)는 중복 억제에 효과적입니다
동일한 포맷(예: "실패 사례")이 연속해서 생성되는 경향이 있었기 때문에, 최근 3개의 포맷은 피하도록 지시했습니다. 이것만으로도 포맷의 다양성이 증가했습니다. 주제(topics)를 선정할 때 최근 8개를 살펴보는 이유는 동일한 경로 내에서 소재가 더 빨리 고갈되기 때문입니다. 주제는 포맷보다 더 빨리 고갈됩니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기