3탄 권2 제0·1장
3탄 권2 — Phase 1.0 BUILD · Day 1~36
제0·1장 — BUILD 시작 가이드 + Day 1~3
매일 펼치는 36일의 첫 페이지 — TheSportsDB 인증 · Rust 워커 · DB schema
📑 이 챕터에서 다룰 내용
서문 권1에서 권2로

권1 (Phase 0)이 21h로 완성됐습니다. 5파일+ 체계 가동 + 5확장 5/5 + 두 도메인 일관 패턴 6 완성. 1탄 v2 메타 원칙 8개 모두 입증 데이터가 박혔습니다.

이제 코드 작업 — 권2 Phase 1.0 BUILD. 권1과 다른 톤이 시작됩니다.

본질 문서 패턴
권1 (Phase 0) "무엇·언제·READY" (메타·계획) SPEC·PLAN·REVIEW 만드는 자리
권2 (Phase 1.0) "매일 코드 + LogOnTable + ★ Day 14 CRITICAL" (실행) BUILD.md 매일 갱신
권3 (Phase 1.1) "광고·SEO·19리그 확장" (성장) BUILD 후속 + 외부 노출 4 확장

권2는 36일 (Phase 1.0 옵션 C). G1·G2·G3·G4·G5 다섯 게이트 + ★ G2 Day 14 CRITICAL 가설 검증.

0-1 Phase 1.0 5주차 구조 — 36일 한 페이지
💻 Phase 1.0 36일 전체 구조
┌──────────────────────────────────────────────────────────────┐
│ 1주차 (Day 1~7) — G1: TheSportsDB + 5리그 시드                │
│   Day 1: TheSportsDB 인증 + Rust 프로젝트 init                │
│   Day 2: Rust 워커 prototype + 5 리그 fetch 테스트            │
│   Day 3: DB schema migration + matches 시드 INSERT            │
│   Day 4: schema fallback (G-4) + cron 정상 7일 시작           │
│   Day 5: persona_prompts 테이블 (G-1, versioning)             │
│   Day 6: G1 통과 조건 점검                                    │
│   Day 7: ★ G1 PASSED 의식 (git tag + 휴식)                    │
├──────────────────────────────────────────────────────────────┤
│ 2주차 (Day 8~14) — ★ G2 Day 14 CRITICAL 가설 검증             │
│   Day 8: Sonnet 페르소나 분석 prototype + cache_control       │
│   Day 9: STAT 페르소나 첫 5건 분석 (사실 + 톤)                │
│   Day 10: OBSERVER 페르소나 첫 5건 + 일관성 테스트            │
│   Day 11: 일 10글 발행 cron 시작                              │
│   Day 12: click_events 측정 시스템 + 어드민 분석 페이지       │
│   Day 13: Day 14 측정 SQL + 가설 검증 데이터 누적             │
│   Day 14: ★★ CRITICAL 가설 검증 결과 (PASS/W-C/FAIL)          │
├──────────────────────────────────────────────────────────────┤
│ 3주차 (Day 15~21) — G3: 페르소나 안정 + Cloudflare WAF        │
│   Day 15: G2 휴식 + 결과 분기 결정                            │
│   Day 16: 페르소나 prompt v2 (G2 결과 반영)                   │
│   Day 17: Cloudflare WAF 룰 5개 적용                          │
│   Day 18: 톤 키워드 70% 자동 측정                             │
│   Day 19: 자동 일관성 테스트 7일 무장애 시작                  │
│   Day 20: G3 점검                                             │
│   Day 21: ★ G3 PASSED + 토요일 X 의식                         │
├──────────────────────────────────────────────────────────────┤
│ 4주차 (Day 22~28) — G4: SEO + 일관성 + Sentry                 │
│   Day 22: SEO 구조 (canonical URL + 페르소나별 키워드)        │
│   Day 23: Sentry 통합 + crash-free rate 측정                  │
│   Day 24: ⚖️ Position C 콘텐츠 면책 (Footer + Disclaimer)     │
│   Day 25: 자동 일관성 테스트 7일 관측 마지막                  │
│   Day 26: G4 점검                                             │
│   Day 27: ★ G4 PASSED 의식                                    │
│   Day 28: 일요일 회고 + 5주차 준비                            │
├──────────────────────────────────────────────────────────────┤
│ 5주차 (Day 29~36) — ★ G5 FINAL + Phase 1.1 결정               │
│   Day 29: SEO 자연 유입 측정 (Google Search Console)          │
│   Day 30: 일 10글 7일 무장애 검증                             │
│   Day 31: Phase 1.0 종합 KPI 측정 (어드민)                    │
│   Day 32: ⚖️ E1 외부 노출 4자리 1차 (About+약관 placeholder)  │
│   Day 33: 운영자 시간 측정 (BUILD.md 자동 파싱)               │
│   Day 34: G5 점검 + Phase 1.1 진입 결정 회의                  │
│   Day 35: 최종 회고 작성 (Phase 1.0 종합)                     │
│   Day 36: ★ G5 PASSED FINAL + git tag v0.1-phase1-0-completed│
└──────────────────────────────────────────────────────────────┘

32일 작업 + 4일 의식·휴식·점검 = 36일.

0-2 매일 양식 — BUILD.md 5분 entry

권1 양식 그대로 + TSV 도메인 추가 (Rust 코드 줄 수·persona 분석 결과).

💻 BUILD.md 매일 entry 양식
## Phase 1.0 Day N (날짜)

[계획]
- [PLAN.md Day N 작업 항목 그대로]

[실행]
- [실제 산출물 — 코드 줄 수·파일·페르소나 결과·DB 변경]

[LogOnTable 트레이스]
> 결정: [핵심 결정 한 줄]
> 근거: [왜 그 결정인가]
> 대안 비교: [다른 옵션 트레이드오프]
> 부작용: [부산물·미래 부담]

[페르소나·일관성 점검 — Day 8+]
- 페르소나별 글 수: STAT N · OBSERVER M
- 일관성 테스트: PASS/FAIL
- 톤 키워드 비율: STAT X% / OBSERVER Y%

[이슈]
- [발생/해결, 없으면 "없음"]

[누적 시간] [Phase 0 21h] + [Phase 1.0 누적] = [총합]

[E2 페이스 점검]
- 누적 시간 60h 트리거 거리: [Xh 여유]
- 토요일 작업 X 준수
- 주간 회고: 일요일 30분
- 매월 마지막 주 1일 추가 휴식 (12개월 강화 의식)
💡 매일 5분 양식의 본질

매일 4h 일하면 그 중 5분만 BUILD.md 갱신합니다. 그러나 그 5분이 12개월 후 동업자가 펼치는 답 + R4 자동 감지 + 권3·4·5 회고 자료입니다.

5분 × 36일 ≈ 3h. 3시간으로 36일 운영 매뉴얼이 박힙니다. 줍줍 (2h × 73일) 패턴과 일관합니다.

0-3 MCP 활용 — TSV 매일 시간 절감

1탄 v2 새 14장 MCP가 권2부터 작동합니다.

TSV 권2의 MCP 조합

💻 MCP 구성
[필수 4개]
- Anthropic MCP: 페르소나 분석 자연어 호출 (cache_control 자동 적용)
- GitHub MCP: commit + PR + push 자동 (매일)
- Sentry MCP: crash-free rate 자연어 조회 (Day 23+)
- Oracle Cloud MCP: DB·VM 조회 (자연어, Day 1+)

[선택 1개]
- Slack MCP: 운영 알림 (cron 정지·일관성 fail 등)

MCP 시간 절감 측정

작업MCP 없이MCP 있을 때절감
DB 조회5분 (Oracle 대시보드)10초-95%
LLM 호출 디버그5분 (코드 확인)1분-80%
Sentry 통계3분30초-83%
commit2분5초-95%
매일 약30~50분MCP 활용-80%

권2 끝 (Day 36)까지 누적 약 24h 절감. R4 1인 번아웃 핵심 완화 효과입니다.

0-4 Agent Teams 적용 시점 — Day 5+

처음 4일 (Day 1~4)은 순차 작업을 권장합니다 — 첫 흐름 이해 우선. Day 5+부터 Agent Teams를 사용하세요.

CLAUDE.md §6 File Structure 분배 기준

💻 폴더 분배 기준
src/
  workers/
    fetch.rs         ← 에이전트 A (외부 API)
    publish.rs       ← 에이전트 B (LLM 페르소나 발행)
    monitor.rs       ← 에이전트 C (Sentry·alert)
  api/               ← 직접 수정 (Phase 1.2 시점)
  lib/
    match_facts.ts   ← 직접 수정 (SSOT, E5 [1])
    persona_prompts/ ← 직접 수정 (4 페르소나)
  app/               ← 에이전트 D (Next.js 사용자 페이지)
  app/(legal)/       ← 직접 수정 (⚖️ E1 [5])
tests/               ← 통합 단계
💡 권2 Day 5+ Agent Teams 사례 — STAT + OBSERVER 동시

에이전트 A가 lib/persona_prompts/stat.ts를, 에이전트 B가 lib/persona_prompts/observer.ts를 동시에 작성합니다. CLAUDE.md §6 폴더 분리가 분배 기준이 됩니다.

0-5 ⭐ E4 LogOnTable 매일

권2부터 매일 트레이스 1~3개. 권1 (Phase 0) 8 트레이스 → 권2 끝 누적 약 50개.

일자 유형트레이스 분량
일반 작업일트레이스 1개, 5~10줄
게이트 통과일·Day 14트레이스 2~3개, 15~30줄
페이스 점검일트레이스 1개 + 회고 1KB+
📘 12개월 후 동업자 시나리오

12개월 후 비개발자 동업자가 BUILD.md를 펼치면 약 200~250 트레이스 (권1+2+3+4+5+6 합계). 동업자가 "왜 STAT 페르소나 키워드 70% 임계값?" 질문하면 Day 18 트레이스에 답이 있습니다.

매일 5분이 "인수인계 매뉴얼"의 본질입니다.

0-6 ⭐ E2 매일 페이스 점검 — TSV 12개월 강화

권1 제6장 R4 트리거 4가지 + ★ 12개월 강화 1 의식 = 매일 점검.

💻 BUILD.md E2 매일 점검 양식
[E2 페이스 점검]
- 누적 시간: [Phase 0 21h] + [Phase 1.0 누적 Xh] = [Total]
- 60h/4주 트리거 거리: [Yh 여유]
- 토요일 작업 X: [Day 5·12·19·26·33 점검]
- 주간 회고 (일요일 30분): [Day 7·14·21·28·35 작성 의무]
- ★ 12개월 강화: 매월 마지막 주 1일 추가 휴식 (Day 28·56·84·112... 의식)

게이트 통과 의식

G1
Day 7
git tag v0.1-G1-passed + 1일 휴식
G2
★ Day 14 CRITICAL
git tag v0.1-G2-passed (가설 결과 명시) + 1.5일 휴식
G3
Day 21
git tag v0.1-G3-passed + 1일 휴식
G4
Day 28
git tag v0.1-G4-passed + 1일 휴식 + ★ 4주차 끝 의식
G5
★ Day 36 FINAL
git tag v0.1-phase1-0-completed + 2일 휴식

5번 게이트 휴식. 권2 36일 안에 5 의식적 휴식 + 5 주간 회고.

📌 제0장 정리

핵심 한 줄: 권2 = 매일 코드 + LogOnTable + MCP + Agent Teams + ★ Day 14 CRITICAL. 36일 (G1~G5).

  • 5주차 구조: G1(D7) → ★G2(D14 CRITICAL) → G3(D21) → G4(D28) → ★G5(D36 FINAL)
  • 매일 5분 양식: 계획 + 실행 + LogOnTable + 페르소나 점검 + 페이스
  • MCP 조합: Anthropic + GitHub + Sentry + Oracle (필수 4개) + Slack (선택)
  • 시간 절감: MCP 매일 30~50분 → 권2 누적 24h 절감
  • Agent Teams: Day 5+ 시작. CLAUDE.md §6 폴더 분리가 분배 기준
  • E4 매일: 권2 누적 ~50개 트레이스
  • E2 매일 점검: 4 트리거 + ★ 12개월 강화 의식 + 5 게이트 휴식

다음 장: 제1장 — Day 1~3 (TheSportsDB 인증 + Rust 워커 prototype + 5리그 fetch).

준비됐습니다. Day 1을 시작합시다.

3탄 권2 제1장
Day 1~3: TheSportsDB 인증 + Rust 워커 + 5리그 fetch
TSV의 첫 실제 코드 — 가장 위험한 외부 의존성부터 시작합니다
📚 사전 지식 체크🎯 이 장의 목적✅ 완료 후 결과물
권1 + 권2 제0장 / Claude Code + 5파일 / Rust 환경 (cargo·rustc) / 2탄 v2 권2 제1장 인지 Day 1 (TheSportsDB 인증 + Rust init) + Day 2 (Rust 워커 + 5리그 fetch) + Day 3 (DB migration + matches 시드) TheSportsDB API 키 + Rust 프로젝트 + workers/fetch.rs prototype + matches 시드 100건+
1-1 Day 1 — TheSportsDB API 인증 + Rust 프로젝트 init

작업 시작

💻 Phase 0 → Phase 1.0 전환
cd tsv
git status                  # 깨끗한 상태
cat REVIEW.md | tail -3     # READY: YES 확인

claude
/model claude-sonnet-4-6    # BUILD는 Sonnet (1탄 v2 새 28장)
/effort medium
/context                    # 5파일 + MCP 활성 확인

TheSportsDB API 인증 (15분)

📘 TheSportsDB 무료 tier 인증 가이드
1
회원가입 (5분)
thesportsdb.com → Sign Up (이메일만). 즉시 무료 API 키 발급. 무료 tier 한도: 100 req/min · 월 100K req (TSV 5리그 충분)
2
5 리그 ID 확인
EPL: 4328 / 라리가: 4335 / 세리에: 4332 / 분데스: 4331 / 리그앙: 4334
3
.env.local 저장
THESPORTSDB_API_KEY=... — .env.local이 .gitignore 차단 확인 필수
⚠️ ANTHROPIC_API_KEY (Sonnet) + SENTRY_DSN (Day 23+)도 같은 .env.local에
.env.local이 .gitignore에 차단됐는지 반드시 확인하세요.

Rust 프로젝트 init (1.5h)

💻 Cargo.toml — workers crate (발췌)
[workspace]
members = ["workers", "api", "shared"]

[workspace.dependencies]
tokio = { version = "1.40", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
sqlx = { version = "0.8", features = ["postgres", "runtime-tokio-rustls", "macros", "uuid", "chrono"] }
reqwest = { version = "0.12", features = ["json"] }
dotenvy = "0.15"
anyhow = "1"
chrono = { version = "0.4", features = ["serde"] }
uuid = { version = "1", features = ["v4", "serde"] }

[package]
name = "tsv-workers"
version = "0.1.0"
edition = "2021"
📘 Day 1 BUILD.md 트레이스 ① — workspace 결정

결정: cargo workspace + 3 crate (workers·api·shared)

근거: workers (cron job·LLM 호출)와 api (Phase 1.2 사용자 인증)가 다른 빌드·배포 cycle. 분리 시 컴파일 시간 ↓ + Vultr 배포 시 binary 분리 가능.

대안: 단일 crate — 빌드 빠르지만 의존성 충돌 가능성 (Phase 1.2)

부작용: workspace 의존성 관리 약간 복잡 (workspace.dependencies 사용)

💻 Day 1 commit
git add Cargo.toml workers/Cargo.toml api/Cargo.toml shared/Cargo.toml
git add .gitignore
git commit -m "Phase 1.0 Day 1: TheSportsDB 인증 + Rust workspace (workers·api·shared)"

누적: Phase 0 21h + Day 1 (1.5h) = 22.5h

1-2 Day 2 — Rust 워커 prototype + 5리그 fetch 테스트

workers/src/fetch.rs (약 100줄, 발췌)

💻 fetch.rs — 5 리그 동시 호출 (Tokio join!)
// workers/src/fetch.rs

use anyhow::{Result, anyhow};
use serde::{Deserialize, Serialize};
use std::env;

const LEAGUES: [(&str, i32); 5] = [
    ("EPL", 4328),
    ("La Liga", 4335),
    ("Serie A", 4332),
    ("Bundesliga", 4331),
    ("Ligue 1", 4334),
];

#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct Match {
    #[serde(rename = "idEvent")]
    pub event_id: String,
    #[serde(rename = "strHomeTeam")]
    pub home_team: String,
    #[serde(rename = "strAwayTeam")]
    pub away_team: String,
    #[serde(rename = "dateEvent")]
    pub date: String,
    #[serde(rename = "intHomeScore", default)]
    pub home_score: Option<String>,
    #[serde(rename = "intAwayScore", default)]
    pub away_score: Option<String>,
    // graceful: 새 필드는 무시
    #[serde(flatten)]
    pub _extra: serde_json::Value,
}

// 5 리그 동시 호출 (Rust 강점)
pub async fn fetch_all_leagues() -> Result<Vec<(String, Vec<Match>)>> {
    let futures: Vec<_> = LEAGUES.iter()
        .map(|(name, id)| async move {
            let result = fetch_league(*id, name).await;
            (name.to_string(), result)
        })
        .collect();

    let results = futures::future::join_all(futures).await;
    // ... graceful degradation (G-4)
    Ok(output)
}

실행 결과

🎉 5 리그 동시 호출 — 0.8초

✓ EPL: 5 matches, parse_success
✓ La Liga: 5 matches, parse_success
✓ Serie A: 5 matches, parse_success
✓ Bundesliga: 5 matches, parse_success
✓ Ligue 1: 5 matches, parse_success

총 25 경기, 0.8초 (Tokio 동시 호출, 단일 호출 대비 -75%)

📘 Day 2 트레이스 — Tokio join! 결정

결정: 5 리그 동시 호출 (futures::future::join_all)

근거: TheSportsDB 무료 tier 100 req/min 한도. 5 동시 호출 = 한도 내 + 단일 호출 대비 75% 시간 절감 = Rust 강점 활용.

대안: 순차 호출 — 5 × 0.4s = 2s (느림)

부작용: 한 리그 fail 시 다른 리그 영향 X (각자 Result)

누적: 22.5h + Day 2 (3h) = 25.5h

1-3 Day 3 — DB schema migration + matches 시드 INSERT

0001_init.sql — 5 테이블 (발췌)

💻 supabase/migrations/0001_init.sql
CREATE TABLE matches (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  source_id TEXT NOT NULL UNIQUE,  -- TheSportsDB event_id
  league TEXT NOT NULL,
  home_team TEXT NOT NULL,
  away_team TEXT NOT NULL,
  kickoff_at TIMESTAMPTZ NOT NULL,
  score_home INT,
  score_away INT,
  status TEXT NOT NULL DEFAULT 'UPCOMING',
  created_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE TABLE articles (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  match_id UUID NOT NULL REFERENCES matches(id),
  persona TEXT NOT NULL,  -- STAT|OBSERVER|COACH|INSIDER
  title TEXT NOT NULL,
  body TEXT NOT NULL,

  -- ★ P1 패치: denormalized 캐시 컬럼 (N+1 방지)
  match_kickoff_at TIMESTAMPTZ,
  match_home_team TEXT,
  match_away_team TEXT,
  match_league TEXT,

  prompt_version INT,
  published_at TIMESTAMPTZ,
  UNIQUE (match_id, persona)  -- 한 경기 + 한 페르소나 = 1글
);

CREATE TABLE click_events (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  article_id UUID NOT NULL REFERENCES articles(id),
  user_session TEXT NOT NULL,
  clicked_at TIMESTAMPTZ DEFAULT NOW(),
  dwell_time_sec INT
);

CREATE TABLE persona_prompts (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  persona_type TEXT NOT NULL,
  prompt_version INT NOT NULL,
  prompt_text TEXT NOT NULL,
  is_active BOOLEAN DEFAULT FALSE,
  deprecated_at TIMESTAMPTZ
);

CREATE TABLE cron_pause (
  id INT PRIMARY KEY DEFAULT 1,
  paused BOOLEAN NOT NULL DEFAULT FALSE,
  reason TEXT
);
INSERT INTO cron_pause (id, paused) VALUES (1, FALSE);

matches 시드 결과

⚠️ 시드 25건 — G1 통과 조건 (100건+) 미달

5 리그 × 5 경기 = 25 시드. G1 통과 조건 (시드 100건+) 미달 → Day 4·5 추가 fetch 필요 (이전 7일치 경기 누적).

📘 Day 3 트레이스 — denormalized 캐시 컬럼

결정: articles 테이블에 match 정보 4 컬럼 캐시 (denormalized)

근거: 권1 제7장 P1 패치. articles 조회 시 matches JOIN N+1 방지. 12개월 운영 누적 약 10K articles 수준에서 의미 있습니다.

대안: 정규화만 — 매 조회마다 JOIN 비용

부작용: matches.kickoff_at 변경 시 articles 캐시 stale → 변경 시 articles UPDATE cron 자동 (Day 11 cron)

💻 Day 3 commit
git add supabase/migrations/0001_init.sql workers/src/main.rs
git commit -m "Phase 1.0 Day 3: DB schema 5 테이블 + matches 시드 25건 (Day 4·5 추가 예정)"

누적: 25.5h + Day 3 (3h) = 28.5h / E2 60h 트리거 31.5h 여유

1-4 Day 1~3 누적 — 권2 첫 LogOnTable 6 트레이스

권1 (Phase 0) 8 트레이스 + 권2 Day 1~3 6 트레이스 = 14 트레이스. 매 트레이스가 4 요소 (결정·근거·대안·부작용).

💡 줍줍(2탄 v2 권2 제1장)과 비교 — 두 도메인 일관 패턴
자리줍줍 (Day 1~3)TSV (Day 1~3)
Day 1API 4개 인증 (data.go.kr)TheSportsDB 1개 + Rust workspace
Day 2lib/api/test-fetch.ts (TS 80줄)workers/fetch.rs (Rust 100줄)
Day 3시딩 분류 LLM (E5 [4])matches 시드 INSERT (DB)
첫 LogOnTable6 트레이스6 트레이스 (동일)

6 트레이스 동일 = 두 도메인 일관 패턴 권2 제1장에서 시작.

📌 제1장 정리

핵심 한 줄: Day 1~3 = TheSportsDB 인증 + Rust 워커 prototype + DB schema + matches 시드. 첫 6 LogOnTable.

  • Day 1: TheSportsDB 가입 + API 키 / cargo workspace + 3 crate / Cargo.toml + .env.local 보안
  • Day 2: workers/src/fetch.rs (100줄) / Tokio join! 5 리그 동시 (0.8초, -75% 시간) / graceful deserialize (G-4)
  • Day 3: 0001_init.sql (5 테이블, P1 캐시 컬럼) / 25 경기 시드 → Day 4·5 추가 fetch (100건+ 목표)
  • 누적 시간: 28.5h (Phase 0 21h + Day 1~3 7.5h)
  • R4 트리거 거리: 60h - 28.5h = 31.5h 여유 (1주차 안전)

다음 장: 권2 제2장 — Day 4~7 (시드 100건+ + persona_prompts + ★ G1 통과 의식).

TSV의 첫 코드가 만들어졌습니다. TheSportsDB 인증 + Rust workspace + workers/fetch.rs (100줄, Tokio join!) + DB schema 5 테이블 + matches 시드 25건. 7.5시간 안에.