📑 이 챕터에서 다룰 내용
- 서문 — 권1에서 권2로
- 0-1. Phase 1.0 5주차 구조 — 36일 한 페이지
- 0-2. 매일 양식 — BUILD.md 5분 entry
- 0-3. MCP 활용 — TSV 매일 시간 절감
- 0-4. Agent Teams 적용 시점 — Day 5+
- 0-5. E4 LogOnTable 매일
- 0-6. E2 매일 페이스 점검
- 제0장 정리
- 제1장 들어가며 — 첫 코드의 자리
- 1-1. Day 1 — TheSportsDB 인증 + Rust 프로젝트 init
- 1-2. Day 2 — Rust 워커 prototype + 5리그 fetch 테스트
- 1-3. Day 3 — DB schema migration + matches 시드 INSERT
- 1-4. Day 1~3 누적 — 첫 LogOnTable 6 트레이스
- 제1장 정리
권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 가설 검증.
┌──────────────────────────────────────────────────────────────┐ │ 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일.
권1 양식 그대로 + TSV 도메인 추가 (Rust 코드 줄 수·persona 분석 결과).
## 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개월 강화 의식)
매일 4h 일하면 그 중 5분만 BUILD.md 갱신합니다. 그러나 그 5분이 12개월 후 동업자가 펼치는 답 + R4 자동 감지 + 권3·4·5 회고 자료입니다.
5분 × 36일 ≈ 3h. 3시간으로 36일 운영 매뉴얼이 박힙니다. 줍줍 (2h × 73일) 패턴과 일관합니다.
1탄 v2 새 14장 MCP가 권2부터 작동합니다.
TSV 권2의 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% |
| commit | 2분 | 5초 | -95% |
| 매일 약 | 30~50분 | MCP 활용 | -80% |
권2 끝 (Day 36)까지 누적 약 24h 절감. R4 1인 번아웃 핵심 완화 효과입니다.
처음 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/ ← 통합 단계에이전트 A가 lib/persona_prompts/stat.ts를, 에이전트 B가 lib/persona_prompts/observer.ts를 동시에 작성합니다. CLAUDE.md §6 폴더 분리가 분배 기준이 됩니다.
권2부터 매일 트레이스 1~3개. 권1 (Phase 0) 8 트레이스 → 권2 끝 누적 약 50개.
| 일자 유형 | 트레이스 분량 |
|---|---|
| 일반 작업일 | 트레이스 1개, 5~10줄 |
| 게이트 통과일·Day 14 | 트레이스 2~3개, 15~30줄 |
| 페이스 점검일 | 트레이스 1개 + 회고 1KB+ |
12개월 후 비개발자 동업자가 BUILD.md를 펼치면 약 200~250 트레이스 (권1+2+3+4+5+6 합계). 동업자가 "왜 STAT 페르소나 키워드 70% 임계값?" 질문하면 Day 18 트레이스에 답이 있습니다.
매일 5분이 "인수인계 매뉴얼"의 본질입니다.
권1 제6장 R4 트리거 4가지 + ★ 12개월 강화 1 의식 = 매일 점검.
[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... 의식)
게이트 통과 의식
5번 게이트 휴식. 권2 36일 안에 5 의식적 휴식 + 5 주간 회고.
핵심 한 줄: 권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을 시작합시다.
| 📚 사전 지식 체크 | 🎯 이 장의 목적 | ✅ 완료 후 결과물 |
|---|---|---|
| 권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건+ |
작업 시작
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_API_KEY=... — .env.local이 .gitignore 차단 확인 필수Rust 프로젝트 init (1.5h)
[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"결정: 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 사용)
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
workers/src/fetch.rs (약 100줄, 발췌)
// 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)
}실행 결과
✓ 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%)
결정: 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
0001_init.sql — 5 테이블 (발췌)
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 시드 결과
5 리그 × 5 경기 = 25 시드. G1 통과 조건 (시드 100건+) 미달 → Day 4·5 추가 fetch 필요 (이전 7일치 경기 누적).
결정: 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)
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 (Phase 0) 8 트레이스 + 권2 Day 1~3 6 트레이스 = 14 트레이스. 매 트레이스가 4 요소 (결정·근거·대안·부작용).
| 자리 | 줍줍 (Day 1~3) | TSV (Day 1~3) |
|---|---|---|
| Day 1 | API 4개 인증 (data.go.kr) | TheSportsDB 1개 + Rust workspace |
| Day 2 | lib/api/test-fetch.ts (TS 80줄) | workers/fetch.rs (Rust 100줄) |
| Day 3 | 시딩 분류 LLM (E5 [4]) | matches 시드 INSERT (DB) |
| 첫 LogOnTable | 6 트레이스 | 6 트레이스 (동일) |
6 트레이스 동일 = 두 도메인 일관 패턴 권2 제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시간 안에.