Files
reloop-v2/Plans.md
reloop d892d8298b feat(phase10A): KICE 비수학 제거 + 수학 단원 분리
- schema: MathUnit enum (common/prob_stat/calculus/geometry)
- User: focusUnits(Json), bojHandle(VarChar32)
- ProblemSet: mathUnit optional + unique(year,examType,subjectName,mathUnit)
- migration: phase10_math_units_and_ps
- scripts: wipe-non-math (dry/apply), kice-import refactor (math only,
  30문항을 공통22 + 확통/미적/기하 8씩으로 분할 → 2년×4 = 8 ProblemSet)
- parsing strategy/ocr-fix/seed/me/study-logs: 비수학 참조 전면 제거
- data/kice, backend/uploads/problems: 비수학 폴더 삭제
- Plans.md: Phase 10 계획 확정

Phase 10A DoD 통과 — prisma validate/format OK, nest build OK,
kice-import dry-run 2년 각 46문항 확인, wipe dry-run 8 set/260 problems 확인.
2026-04-14 18:27:00 +09:00

277 lines
38 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ReLoop v2 — Plans.md (Phase 7 active)
작업 히스토리 (Phase 0~6 완료) 는 `.claude/archive/plans-phase0-6.md` 로 이동. 이 파일은 **현재 활성 Phase 만** 다룬다.
## Context
- Repo: `/home/erang/reloop-v2` (backend NestJS+Prisma, frontend Next.js 14 + styled-components)
- Deploy: `reloop.nabomhalang.co.kr` / `reloop-api.nabomhalang.co.kr` (mailu-dev VM PM2, HEAD cc946e4)
- DB: MariaDB on 10.10.10.146:33006, `reloop_v2`
- Persona forgetting curve 운영 중, 2nd pass review APPROVE
## Phase 7 — KICE 실데이터 + 사용자 문제집 추가 + 12 페이지 재디자인 (styled-components)
**목적**: 2025/2026학년도 수능 실제 PDF (mailu-dev `/home/dev/reloop-v2/data/kice/`, 290MB) 를 텍스트 파싱해서 ProblemSet 카탈로그로 import 하고, 사용자가 원하는 문제집을 자기 목록에 추가해 개별 문항을 풀 수 있게 한다. 동시에 사용자 제공 디자인 12종을 styled-components 로 재현해 프론트엔드를 재구축한다. 수식이 많은 수학/과탐은 이미지 crop 필요성 때문에 Phase 8 별도 트랙으로 분리.
### 기술 결정 (사용자 확정분)
| # | 결정 포인트 | 확정 내용 |
|---|---|---|
| D1 | CSS 스택 | **styled-components 유지**. Tailwind 도입 안 함. 디자인 파일의 Tailwind 클래스를 styled-components CSS 로 1:1 변환. 디자인 토큰은 기존 `theme.ts` 에 확장 |
| D2 | PDF 파싱 방식 | **pdf-parse + 정규식 기반 본문 파싱**. 문제 본문·선택지·정답을 DB 에 저장해 우리 화면 디자인으로 직접 렌더. PDF Viewer 임베드 안 함 |
| D3 | 과목 범위 (MVP) | **텍스트 과목만**: 국어 / 영어 / 한국사 / 사회탐구(생활과 윤리 1과목). 2025+2026 × 4 = **8 ProblemSet, 약 240 문항**. 수식이 벡터인 수학/과탐은 Phase 8 (별도 이미지 crop 파이프라인) |
| D4 | 정답률 데이터 | MVP 는 `baseCorrectRate: null` 허용. EBSi 오답률 엑셀 수동 import 는 별도 트랙 |
| D5 | 사용자 문제집 모델 | **`UserProblemSet` join** (userId, problemSetId, addedAt, archivedAt?). 카탈로그는 공유, 추가는 개인. ProblemSet 전체를 큐에 bulk insert 하지 않고, 사용자가 개별 Problem 을 "풀기" 할 때 StudyLog 생성 |
### 평가원 PDF 파싱 — 휴리스틱 설계
평가원 기출 PDF 는 포맷이 매우 일관적이라 LLM 없이 정규식 + 룰베이스로 70~90% 파싱 가능. 실패 케이스만 import 스크립트에서 `needs_review=true` flag 로 표시하고 나중에 수동 보정.
**문항 분할 규칙**:
- 문항 시작: `/\n?\s*(\d{1,2})\.\s+/` — 1~45 번
- 선택지 시작: `/[①②③④⑤]/` — 원문자 5 종
- 공통 지문: `/\[(\d+)~(\d+)\]/` — "[1~3]" 형식. 해당 범위 문항은 같은 `Passage` FK 공유
- 정답표 파싱: `/(\d{1,2})번?\s*[①②③④⑤1-5]/` 또는 표 구조 raw text
**Problem 모델 확장**:
- `bodyText Text` — 문제 본문 (선택지 제외)
- `choices Json``{"1":"...","2":"...","3":"...","4":"...","5":"..."}`
- `answerNumber Int?` — 1~5
- `passageId Int?` — 공통 지문 FK (별도 `Passage` 테이블)
- `needsReview Boolean @default(false)` — 파싱 엣지케이스 표시
### Phase 7A — KICE 데이터 파이프라인 (텍스트 파싱)
| Task | 내용 | DoD | Depends | Status |
|------|------|-----|---------|--------|
| 7A.1 | Prisma 스키마 확장: `ProblemSet``audioUrls Json?`. `Problem``bodyText String? @db.Text`, `choices Json?`, `answerNumber Int?`, `passageId Int?`, `needsReview Boolean @default(false)`. 새 모델 `Passage` (공통 지문). examRound 는 기존 examType 과 중복이라 제외 | `prisma migrate dev --name kice_text_fields` 성공, `@prisma/client` 재생성 | - | cc:완료 [e2e3e4b] |
| 7A.2 | `backend/scripts/kice-import.ts` 스캐너 — `data/kice/{year}/{과목}/*.pdf` 순회, `생활과 윤리` 특수 경로 처리, 영어 mp3 수집, dry-run 출력 | `pnpm cli:kice-import --dry-run` 이 2025/2026 전체 파일을 tree 로 출력 | 7A.1 | cc:완료 [35dcf82] |
| 7A.3 | **정답표 파서**: `pdftotext -layout` + 홀/짝 분리 + `/(\d{1,2})\s*[①②③④⑤]/` + 원문자→1..5 정규화. 2025 는 이미지 PDF 라 0 bytes → answerNumber null + needsReview | 2026 4과목 정답 추출 완료, 2025 는 OCR 필요 flag | 7A.2 | cc:완료 [35dcf82] |
| 7A.4 | **문제지 파서 — 본문 분할**: `pdftotext -raw` (2단 컬럼 physical order), `/^\d{1,2}\.\s+/` monotonic 체크, ①..⑤ 위치로 본문/선택지 분할 | 2026 4과목 각 45문항 전부 본문+5 선택지 분리 | 7A.2 | cc:완료 [35dcf82] |
| 7A.5 | **공통 지문 파서**: `/\[\s*(\d+)\s*[~-]\s*(\d+)\s*\]/` 감지 → Passage 레코드 생성 → 범위 내 Problem 에 passageId FK 부착 | 2026 국어/영어에서 26개 passage 생성, 각 Passage 에 2+문항 연결 | 7A.4 | cc:완료 [35dcf82] |
| 7A.6 | **파싱 실패 가드**: bodyText <10 chars, choices!=5, 빈 choice → needsReview=true | 2026 영어 5 needsReview (듣기 문항), 2025 전체 130 needsReview (OCR 대상) | 7A.4, 7A.5 | cc:완료 [35dcf82] |
| 7A.7 | **ProblemSet + Problem upsert**: (year, examType, subjectName) upsert, Passage delete+recreate, Problem upsert (problemSetId_number), audioUrls 영어만 | 로컬 DB: 9 ProblemSet / 260 Problem / 26 Passage, 재실행 멱등성 확인 | 7A.3, 7A.6 | cc:완료 [35dcf82] |
| 7A.8 | `seed.ts` 의 DEMO_PROBLEMSET(2024 수학) 블록 제거 + 2024 수학 ProblemSet DB DELETE + seed 재실행 검증 | seed.ts DEMO_PROBLEMSET 0건, seed 재실행 에러 없음, ProblemSet.count() 는 kice import 만 | 7A.7 | cc:완료 [35dcf82] |
**제외** (Phase 8 별도 트랙): 수학·과탐·직업탐구·제2외국어 — 수식/도표 비율 높아서 텍스트 파싱 부적합. 이미지 crop 또는 Vision API 파이프라인이 필요해 Phase 7 에 포함하지 않음.
### Phase 7B — 사용자 문제집 추가 플로우
| Task | 내용 | DoD | Depends | Status |
|------|------|-----|---------|--------|
| 7B.1 | Prisma: `UserProblemSet` 모델 (`userId`, `problemSetId`, `addedAt`, `archivedAt DateTime?`, `@@unique([userId, problemSetId])`, `@@index([userId])`) | migration 통과, User/ProblemSet 양방향 relation 정의됨 | 7A.1 | cc:TODO |
| 7B.2 | backend `me/problem-sets` module: `GET /api/me/problem-sets` (내 추가분 list), `POST /api/me/problem-sets` (`{ problemSetId }`), `DELETE /api/me/problem-sets/:id` (soft archive) | 데모 계정으로 3개 API 모두 200/201/204 응답. 중복 추가 시 409 | 7B.1 | cc:TODO |
| 7B.3 | frontend `lib/api.ts``UserProblemSet`, `addProblemSet`, `listMyProblemSets`, `removeProblemSet` 타입/헬퍼 추가 | 타입 에러 없이 컴파일, stats 페이지에서 호출 가능 | 7B.2 | cc:TODO |
| 7B.4 | frontend `/study` 페이지 탭 재설계: [내 문제집 / 카탈로그 / 직접 입력] 3 탭. "카탈로그" 에 전체 ProblemSet 목록 + 각 카드에 "+ 내 목록에 추가" 버튼. "내 문제집" 에 추가한 것만 표시 | 카탈로그에서 카드 클릭 → 내 목록 탭에 즉시 반영. 중복 클릭 방지 | 7B.3 | cc:TODO |
| 7B.5 | "내 문제집" 탭 상세: 선택한 ProblemSet → 문항 리스트 → 각 문항 "풀기" 버튼 → 기존 `/study/form?problemId=N` 으로 이동해 결과 입력 | 데모 계정에서 2026 수학 → 15번 클릭 → 결과 기록 → 복습 큐 진입까지 전 플로우 통과 | 7B.4 | cc:TODO |
### Phase 7C — 디자인 토큰 확장 + styled-components primitives 증축
기존 `theme.ts` + `primitives.tsx` 를 유지하고 디자인 파일의 토큰/컴포넌트만 이식한다. Tailwind 도입 없음.
| Task | 내용 | DoD | Depends | Status |
|------|------|-----|---------|--------|
| 7C.1 | `frontend/src/styles/theme.ts` 확장 — 새 컬러 (bgDeep `#0F0F14`, surfaceDeep `#15151C`, surfaceHover `#1A1A24`, borderSoftAlpha `rgba(255,255,255,0.06)`, brandGradient 함수), shadow (glowIndigo/glowViolet/cardElevated), keyframes(fadeInUp/float/slideToast/shimmer), font.display (Inter) | `theme.color.bgDeep` 등 5+ 신규 토큰 접근 가능, 기존 토큰과 공존 | - | cc:완료 [ba4d6e4] |
| 7C.2 | Phosphor Icons React 도입 — `pnpm add @phosphor-icons/react`. `components/ui/Icon.tsx` 어댑터 (name 매핑 + size/weight props) | `<Icon name="squares-four" weight="regular" size={20} />` 가 디자인 파일의 `<i class="ph ph-squares-four">` 와 시각적으로 동일 | 7C.1 | cc:완료 [0d2303a] |
| 7C.3 | Inter + JetBrains Mono 웹폰트 — `next/font/google` 로 Pretendard(유지) 외에 Inter, JetBrains Mono 로딩. `theme.font.display` / `theme.font.mono` 바인딩, FOUT 없음 | 3종 웹폰트가 production HTML 에 load | 7C.1 | cc:완료 [140a7c3] |
| 7C.4 | `components/ui/primitives.tsx` 증축 — 새 variants: `Button $variant="white"` (흰색 CTA, 디자인 1의 "지금 시작"), `Card $variant="glow"` (radial glow), `KbdHint` (키보드 단축키 pill), `StatCard` (대형 숫자 카드 레이아웃), `AppShellLayout` | 5+ 신규 export, TypeScript 통과 | 7C.1 | cc:완료 [b9874ad] |
| 7C.5 | `components/ui/Toast.tsx` 신규 — slideToast 애니메이션, undo 버튼 + 원형 progress ring (2초 타이머). `showToast({ message, onUndo })` 헬퍼 | toast 호출 시 bottom-center 슬라이드 인, 2초 후 자동 dismiss 또는 undo 클릭 시 콜백 | 7C.1, 7C.2 | cc:완료 [eb10dfe] |
### Phase 7D — 페이지 재디자인 (12종)
| Task | 내용 | DoD | Depends | Status |
|------|------|-----|---------|--------|
참조 디자인 파일: `/home/erang/reloop-v2/.claude/archive/designs/design-NN.html` (Tailwind 기반 mockup, styled-components 로 1:1 이식). **Tailwind 도입하지 않음** — 클래스를 styled-components CSS 로 변환.
| Task | 내용 | DoD | Depends | Status |
|------|------|-----|---------|--------|
| 7D.1 | **AppShell (사이드바)** — design-01.html 의 `aside.w-64` 레이아웃 (256px, 다크 bgDeep, indigo radial glow blur-80px, backdrop-blur-xl). 로고 indigo→violet gradient + Phosphor `ph-infinity`, nav 5 항목(대시보드/복습/학습 기록/통계/과목 및 태그) Phosphor icons, 활성 라우트 bg-white/5 + border-white/5, 하단 user card (아바타 + nickname + plan badge + caret 드롭다운). 모바일에서는 BottomNav 로 대체 | 기존 SideNav.tsx 교체, `/dashboard` 진입 시 새 사이드바 렌더, 활성 라우트 강조, 로그아웃 클릭 동작 | 7C.5 | cc:완료 [d35fc08] |
| 7D.2 | **Landing (`/`)** — design-02.html 1:1 이식. nav + hero "잊기 직전에 딱 한 번 더" + screenshot mockup + features 3카드 + how-it-works 4단계 + stats 카운터 + pricing teaser + FAQ + footer. SSR/하이드레이션 (로그인 상태면 `/dashboard` 리다이렉트는 middleware 쿠키) | 로그아웃 상태에서 `/` 200, 모든 섹션 렌더, 로그인 사용자는 `/dashboard` 리다이렉트 | 7C.5 | cc:완료 [0605ba8] |
| 7D.3 | **Auth (`/login` + `/register`)** — design-04.html 이식. 단일 카드 UI, 로그인/회원가입 탭 토글, 이메일/비번/닉네임 필드, 에러 shake 애니메이션, "로그인 없이 체험하기" = 데모 계정 자동 로그인 | 두 경로 모두 같은 컴포넌트 사용 (URL path 로 초기 탭 결정), 기존 auth flow 유지 + 에러 UI | 7C.5 | cc:완료 [750378c] |
| 7D.4 | **Pricing (`/pricing`)** — design-03.html 이식. 월/연 토글, Free/Pro(가장 인기)/School 3 카드, "모든 플랜 공통 4 기능", 상세 기능 비교 테이블(3 섹션 구조), FAQ 4개, 바닥 CTA | 토글 동작 시 가격 숫자 전환, 테이블 모바일 숨김, FAQ 아코디언 | 7C.5 | cc:완료 [92e054c] |
| 7D.5 | **Dashboard filled (`/dashboard`)** — design-01.html 우측 main 영역 이식. 헤더 (오늘 N개 복습 대기 + streak 뱃지 `ph-fill ph-fire`), 3 통계 카드 (오늘 큐 / 주간 정답률 + sparkline / 마스터리 원형 SVG 34%), "곧 내 것이 되는 영역" 리스트, "최근 학습" 리스트, 적응형 학습 코치 배너. 실제 API (/dashboard/summary) 연동 | 데모 계정 로그인 후 `/dashboard` 에서 실데이터 렌더, 마스터리 원형 SVG + streak 숫자 정확 | 7D.1 | cc:완료 [eeb772a] |
| 7D.6 | **Dashboard empty (`/dashboard`)** — design-08.html 이식. Welcome + 3단계 체크리스트. 조건: `subjects.length === 0 && studyLogs.length === 0` 에 자동 전환 | 신규 계정 (/register 직후) 은 empty 상태 렌더, 과목 1개 이상 생기면 filled 로 | 7D.5 | cc:완료 [e79c4d8] |
| 7D.7 | **Review 세션 (`/review`)** — design-05.html 이식. 집중 모드 헤더 (뒤로 + ReLoop 로고 + 집중 모드 뱃지), Day N 제목 + 진행률 바 + 키보드 힌트, 카드 스택 (3 layer stacked), 과목 뱃지 + 회차 + 예측 P%, 메모 블록, 4 액션 버튼 (틀림/부분/맞음/스킵) 색상별, Toast "결과 기록됨 + undo 2s" | 2026 큐에서 카드 전체 처리 + undo 동작 + 단축키 1/2/3/S | 7D.1 | cc:완료 [fdc3e4f] |
| 7D.8 | **Study History (`/study/history`)** — design-10.html 5 컬럼 테이블 (항목/과목·태그/날짜·시간/결과/자신감) 이식. 검색 + 과목 필터 + 날짜 필터 + 결과 탭. 페이지네이션 (20건/페이지). design-12.html 는 참고용 대안 | `/api/study-logs` 전체 목록이 테이블 렌더, 필터 동작, 빈 상태 UI | 7D.1 | cc:완료 [46c6a45] |
| 7D.9 | **Subjects & Tags (`/subjects`)** — design-07.html 이식. 상단 tabs (전체/과목별), 과목 섹션 카드 (색상 dot + 이름 + 마스터리 %), 각 섹션 안에 태그 리스트 (P-value + bar + 수정/보관 액션) | /subjects 에서 실제 데이터 렌더, 태그 편집 인라인, 보관 soft archive 동작 | 7D.1 | cc:완료 [0850507] |
| 7D.10 | **Stats (`/stats`)** — design-11.html 이식. 학습 히트맵 (주×월 grid) + 과목별 정답률 bar 리스트 + 마스터리 P-value 성장 곡선 (SVG path). 기존 `/api/stats/*` + `/api/stats/mastery-path` 활용. design-06.html 는 참고용 대안 | 3 패널 전부 실데이터 렌더, 곡선 mastery-path 응답 기반 | 7D.1 | cc:완료 [0e61f0e] |
| 7D.11 | **Settings/Profile (`/profile`)** — design-09.html 이식. 계정 + Pro plan 카드, 학습 목표 슬라이더, 알고리즘 민감도 3 카드, 3 toggle 알림, 연속 학습 달력 (7 열 heatmap), 데이터 export / 진행 초기화 / 로그아웃 | 모든 설정이 `/me/profile` PATCH 와 연동, 변경사항 저장 시 즉시 반영 | 7D.1 | cc:완료 [22f3be2] |
| 7D.12 | **Study 폼 (`/study`)** — 디자인 제외 영역. 기존 기능 유지 + Phase 7B 의 "내 문제집" 탭 추가는 7B 에서. 이 태스크는 기존 탭들을 새 theme/primitives 로 시각적 통일만 | 기존 플로우 유지, 새 theme 컬러/폰트 적용 | 7C.5 | cc:완료 [22ee6ab] |
### Phase 7F — 7D 피드백 리비전 (용어 순화 + 모의고사 + 모바일)
사용자 피드백 3건을 정리한다.
| Task | 내용 | DoD | Depends | Status |
|------|------|-----|---------|--------|
| 7F.1 | **수학 용어 순화** — UI 전반에서 `P`, `P-value`, `P=0.85`, `P ≥ 0.9` 같은 수학 표기를 제거하고 "예상 정답률", "숙련도", "마스터 기준(90%)" 같은 자연어로 교체. 변경 대상: dashboard/stats/review/subjects/profile/dashboard-empty 등 모든 UI 텍스트. 내부 변수명/API 필드는 유지, **화면에 표시되는 문자열만** 변경 | 모든 페이지에서 "P=" / "P-value" / "P ≥" 리터럴 0건, 의미 동등한 자연어로 대체 | 7D.12 | cc:완료 [cbc3af2] |
| 7F.2 | **모의고사 학습 플로우**`/study` 의 "새 학습 기록" 화면에 "문제집에서 풀기" 탭 추가. 탭에서 Phase 7A 로 import 된 KICE ProblemSet 을 선택 → 모의고사 인터페이스 (시험지 느낌의 문제 네비게이션, 카운트다운 타이머, 1-5 선택지 버튼, 표시/건너뛰기, 답안지 확인 모드, 제출 후 각 문항 자동 채점 + StudyLog 일괄 생성). 과목별 실제 시험 시간 매핑 (국어 80분/영어 70분/한국사·탐구 30분) | 데모 계정으로 2026 국어 선택 → 타이머 동작 → 문항 번호 클릭 네비 → 답 체크 → 제출 → 각 Problem 당 StudyLog row 생성 + /dashboard 의 "최근 학습" 에 즉시 반영 | 7A.7, 7D.12 | cc:완료 [31d62fb] |
| 7F.4 | **파서 리팩토링 + segmentation 수정**`backend/scripts/kice-import.ts` 의 파싱 로직을 `backend/src/problem-sets/parsing/` 모듈로 추출 (범용 설계, 전략 기반). 페이지 헤더/푸터 strip (`제N교시`, `홀수형/짝수형`, `2026학년도 대학수학능력시험 문제지 N`, 페이지 번호, 저작권 문구), 문제 경계 정확도 (다음 `\d{1,2}\.\s+` OR 다음 `[N~M]` OR EOF 중 최소값), passage 헤더 포함 (`[1~3] 다음 글을 읽고…` 를 본문 안에 보존), ⓐⓑⓒⓓⓔ 각주 마커 유지. 재임포트 후 샘플 문항 수동 검증. 향후 업로드 엔드포인트에서 재사용 가능한 순수 함수 시그니처 (buffer 입력) | 2026 국어/영어/한국사/생활과윤리 재임포트 후 needsReview ≤ 5% (듣기 외), 문항 3번 선택지 안에 다른 지문/헤더 오염 0건, passage 첫 줄 "다음 글을 읽고…" 포함 | 7A.7 | cc:완료 [c9935cf] |
| 7F.5 | **OCR fallback (Vision API)** — 7F.4 이후에도 needsReview 로 남는 엣지케이스 문항만 타겟. `backend/scripts/ocr-fallback.ts` 스크립트가 `pdftoppm` 으로 해당 페이지 crop 렌더 → Claude Vision API 로 구조화 JSON 추출 → Problem 레코드 업데이트. 2025 4과목 답안지 (이미지 PDF) 도 이 경로로 처리 | 2026 4과목 needsReview 0건, 2025 4과목 답안지 OCR 결과로 answerNumber 전부 채움 | 7F.4 | cc:완료 [dcf1eb6] |
| 7F.3 | **모바일 반응형 스윕** — 12 개 신규 페이지 전부 모바일 브레이크포인트 (`theme.breakpoint.tablet` 이하) 대응. 사이드바 숨김 + BottomNav 노출, 그리드 스택, 테이블 카드 레이아웃 전환, 폰트 크기 축소, 패딩 축소. 특히 /dashboard 3카드 1열, /pricing 3카드 1열, /study/history 테이블 → 카드 리스트, /stats 3패널 1열, /review 카드 스택 풀스크린, /subjects 태그 리스트 간격 축소, 모의고사 (7F.2) 인터페이스 모바일 터치 최적화 | iPhone SE (375×667) 뷰포트에서 12 페이지 전부 가로 스크롤 없음, 주요 버튼 터치 영역 44px+, 모바일에서 사이드바 숨김 확인 | 7F.2 | cc:완료 [80b3e98] |
### Phase 7G — 사용자 피드백 리비전
| Task | 내용 | DoD | Depends | Status |
|------|------|-----|---------|--------|
| 7G.1 | **IA 재구성 + 모바일 로그아웃**`/exams` 신규 top-level 라우트로 문제집 카탈로그 분리. SideNav 에 "문제집" 항목 추가, `/study` 폼에서 "문제집에서 풀기" 탭 제거. 시험 페이지 우상단 "집중 모드" 토글 버튼 (ON=풀스크린 / OFF=AppShell 포함). `/login` `/register` 상단 "← 홈으로" 링크. 모바일에서 로그아웃/프로필 접근 (BottomNav 에 프로필 탭 또는 상단 메뉴) | /exams 진입 가능, study 폼 문제집 탭 제거, 집중모드 토글 동작, 로그인에서 홈 이동, 모바일 로그아웃 접근 가능 | 7F.5 | cc:완료 [47a70f2] |
| 7G.2 | **시험 UX 전면 개선** — 네비 4버튼 (이전/건너뛰기/다음/항목이동) 전부 노출, 시험 종료는 `⋮` 메뉴로. 데스크톱/태블릿 문제 패널 드래그 리사이즈 (localStorage). 모바일 탭 시 확대 모달. 자동 채점 제출 시 Subject+Tag 자동 생성/매칭 보장. Problem.imageUrl 렌더 (7H.1 의존). Subject+Tag 자동 생성 강화: topic 필드 → Tag | 4 버튼 동작, 시험 종료 숨김, 데스크톱 리사이즈, 모바일 확대, 제출 후 과목/태그 자동 생성 확인 | 7G.1 | cc:완료 [7c5cdf4] |
| 7G.3 | **StudyLog 상세 + 복습 파이프라인 검증**`/study-logs/[id]` 신규 상세 라우트 (문항 재열람: 본문/선택지/정답/내 답/메모). 대시보드 "최근 학습" + 학습 기록 행 클릭 → 상세 이동. 모의고사 제출 → ReviewSchedule 생성 플로우 검증 + 오늘 복습 큐 진입 확인. 첫 시도 S₀ 시드 조정 (baseCorrectRate 반영) | 문항 상세 재열람, 모의고사 제출 후 24h 내 복습 큐 등장, 초기 복습 간격 난이도 반영 | 7G.2 | cc:완료 [9068fcb] |
| 7G.4 | **랜딩 리파인 + D-day 동기화 + 애니메이션** — Hero typing effect (3-5개 격려 문구 rotation), 수능 D-day 실시간 계산 (고정 상수 = 2026-11-13, 매일 자동 갱신), 전역 소프트 애니메이션 (카드 hover/scroll fade-in via Intersection Observer), dashboard/review/stats 문구 동기부여 톤 재작성 | Typing effect 동작, D-day 매일 자동 갱신, 스크롤 애니메이션 부드러움, 톤 개선 | 7C.5 | cc:완료 [feb1a62] |
| 7G.5 | **통계 히트맵 리사이즈 + 테마드 폼 컴포넌트 + 과목 페이지 수정** — 통계 히트맵 50% 축소 + 요일 라벨 + 클릭 시 해당일 드로어. `components/ui/Select.tsx` + `DatePicker.tsx` (다크 테마). /study/history 필터/profile 셀렉터/subjects 정렬 전부 새 컴포넌트로 교체. 과목 페이지 모바일 탭 버튼 세로 글씨 수정 → horizontal scroll pill | 히트맵 축소 + 기능, 모든 드롭다운 다크 테마, 과목 탭 가로 정렬 | 7D.10 | cc:완료 [e332d27] |
| 7G.6 | **프로필 + 아바타 + 온보딩** — Backend: `User.avatarUrl`, `POST /me/avatar` multipart (`backend/uploads/avatars/`). Frontend `/profile` 아바타 업로드 UI. `/onboarding` 플로우 보강 (현재/목표 등급, 중점 과목, 수능 연도, 페르소나). 신규 가입 후 자동 진입. 프로필 드롭다운 모바일 접근 | 아바타 업로드 + 저장 + URL 반환, 온보딩 완전 동작, 프로필 드롭다운 모바일 접근 가능 | 7G.1 | cc:완료 [a5dab35] |
### Phase 7H — 수학 + 이미지 기반 문제 지원
| Task | 내용 | DoD | Depends | Status |
|------|------|-----|---------|--------|
| 7H.1 | **수학 데이터 + 스키마 + 이미지 파이프라인** — mailu-dev 에서 2025/2026 수학 PDF rsync. Prisma 확장 `Problem.imageUrl String?`, `Problem.pageImageUrl String?` + migration. `parsing/strategies/math.ts` — pdftoppm 으로 per-problem PNG 렌더. 이미지는 `backend/uploads/problems/{year}/{subject}/{number}.png`. `kice-import --subject=수학` 동작, 답안지 OCR fallback 재사용 | 2025/2026 수학 각 30문제 import 완료, imageUrl 채움, 답안 자동 채점 | 7F.4, 7F.5 | cc:완료 [acc35e9] |
| 7H.2 | **프론트 이미지 렌더 + 시험 UI 통합** — Problem 타입에 imageUrl 추가. 시험/복습 UI: imageUrl 있으면 `<img>` 메인. 사진/표 포함 문제도 동일 경로. /uploads static 서빙 NestJS | 수학 문항 시험 화면에서 정상 렌더, 이미지 포함 문제 시각 표시 | 7H.1, 7G.2 | cc:완료 [8a91ab4] |
### Phase 8 — 판매 준비 (production ready)
| Task | 내용 | DoD | Depends | Status |
|------|------|-----|---------|--------|
| 8.1 | **PWA + SEO 메타** — manifest.webmanifest, 앱 아이콘 세트 (192/512 + apple-touch), theme-color, per-route `generateMetadata` 로 OG 태그, robots.txt, sitemap.xml | `/` 설치 배너, OG 미리보기, robots/sitemap 서빙 | 7G.6 | cc:완료 [7ba7b4a] |
| 8.2 | **법적 페이지 + 404/500 + 에러 바운더리**`/privacy`, `/terms`, `/contact` 페이지 (footer 링크). `app/not-found.tsx`, `app/error.tsx`, `app/global-error.tsx` 테마드 페이지 | 4 경로 렌더, 404/500 테마드 | 7G.4 | cc:완료 [eb0e07b] |
| 8.3 | **보안 + rate limit + 환경 하드닝** — helmet 강화, CORS 화이트리스트, @nestjs/throttler 전역 rate limit (분당 60), 데모 계정 평문 비밀번호 stdout 제거, `.env` 템플릿 정리 | 로그인 스팸 60/분 초과 429, seed 평문 노출 0 | 7G.6 | cc:완료 [c36978c] |
### Phase 7I — 2차 피드백 리비전 (이미지 우선 + UX)
| Task | 내용 | DoD | Depends | Status |
|------|------|-----|---------|--------|
| 7I.1 | **전 과목 per-problem 이미지 crop**`pdftotext -bbox` 로 문제 번호 Y좌표 추출 → `pdftoppm -x -y -W -H` 으로 문제별 PNG crop → `uploads/problems/{year}/{subject}/{number}.png` 저장. 모든 과목 적용. 텍스트 파싱은 보조, 이미지가 주 표시. 재임포트 후 320 문항 전부 imageUrl 보유 | 2025/2026 × 5과목 재임포트, 320 problems 전부 imageUrl, 수학 포함 정상 | 7H.1 | cc:완료 [e76e14e] |
| 7I.2 | **chosenAnswer + 개별 풀기 + 프로필 통합** — (a) `StudyLog.chosenAnswer Int?` migration. 제출 시 저장. (b) `/solve/[studyLogId]` 개별 문제 풀기 라우트. (c) `/profile` + `/settings` 통합: 단일 페이지 탭 | chosenAnswer 저장 + detail 표시, 개별 풀기, 프로필 단일 페이지 | 7I.1 | cc:완료 [4c61b87] |
| 7I.4 | **3차 피드백 일괄** — (a) 집중모드 기본 OFF (review+exam localStorage 기본값 false). (b) /profile 등급 슬라이더 네이티브 HTML → 테마드 커스텀 슬라이더. (c) 문제 표시 이미지 ONLY (텍스트 파싱 제거, 선택지도 이미지). 수학 등 OMR 스타일 1-5 버블 (식 표기 대신). (d) 이미지 가운데 정렬. (e) 국어 지문+문제 세트: passage 이미지 crop 도 생성 + 시험 UI 에서 지문 이미지 먼저 표시. (f) /pricing 연결 (로그인 후 접근). (g) 영어 듣기 오디오 재생기 (mp3 서빙 + `<audio>` UI). (h) / 메인 typing effect 속도 줄임 + 긴 타이틀 overflow 수정 + 전반 애니메이션 속도 감속 | 모든 항목 동작 확인 | 7I.3 | cc:완료 [d14fde3] |
| 7I.3 | **UI 일괄 수정** — (a) 복습 집중모드 토글. (b) 통계 3패널 재배치. (c) 시험 간격/그림자/긴 지문. (d) SideNav 프로필 고정. (e) 배경 통일 | 복습 토글, 통계 깔끔, 시험 가독성, SideNav 고정, 배경 일관 | 7I.2 | cc:완료 [59f6652] |
### Phase 9 — 학교/학원 관리 (School Plan)
**목적**: 선생님(관리자)이 학급을 만들고, 학생을 초대하고, 문제집을 과제로 배정하고, 학생별 진도/성적을 추적할 수 있는 기관 관리 기능. Pricing 의 "School" 플랜에 대응.
| Task | 내용 | DoD | Depends | Status |
|------|------|-----|---------|--------|
| 9.1 | 스키마 + 백엔드 API (Organization/Class/Assignment) | 엔드포인트 200, seed, migration | 8.3 | cc:완료 [f4b80d3] |
| 9.2 | 선생님 대시보드 + 학급 + 초대 (9.5 통합) | 대시보드/학급/초대/역할 분기 | 9.1 | cc:완료 [bed5b80] |
| 9.3 | 과제 CRUD + 제출 현황 UI | 과제 생성→풀기→현황 전 플로우 | 9.2 | cc:완료 [8ebd419] |
| 9.4 | 학생 진도 + 리포트 (3 차트) | 학생 상세, 학급 리포트 렌더 | 9.3 | cc:완료 [f9c8b9a] |
### Phase 10 — KICE 비수학 제거 + Solved.ac PS 통합 + 온보딩 재설계
**목적**: 저작권 리스크(비수학 KICE 기출)를 제거하고, 수능 수학 4단원(공통/확통/미적/기하) + Solved.ac 기반 PS 두 가지 학습 트랙만 남긴 구조로 재편한다. 사용자는 온보딩에서 `수학 수능 / PS / 자격증 / 어학 / 취미` 중 목표를 선택하고, `수학 수능` 선택 시 세부 단원을, `PS` 선택 시 solved.ac 핸들을 연결한다. Solved.ac 풀이 이력은 자동 sync 되어 `StudyLog` 로 복습 큐에 진입한다.
### 기술 결정 (Phase 10)
| # | 결정 포인트 | 확정 내용 |
|---|---|---|
| P10-D1 | 수학 단원 모델 | `enum MathUnit { common, prob_stat, calculus, geometry }``common` = 수학Ⅰ+Ⅱ, 선택 3종(확률과 통계/미적분/기하). `ProblemSet.mathUnit MathUnit?` 으로 nullable 추가 |
| P10-D2 | KICE 30문항 단원 분할 | 공통 22문항 (1~22) + 선택 8문항 × 3단원 (23~30 각 세트). 즉 year 당 수학 ProblemSet = 4개(common/prob_stat/calculus/geometry), problems = 22+8+8+8 = 46. 2년치 × 4 = **8 ProblemSet** 총 92 problems |
| P10-D3 | 비수학 데이터 처리 | Dev DB 에서 `ProblemSet.subjectName NOT IN ('수학')` 에 연결된 Problem/StudyLog/ReviewSchedule/AssignmentSubmission cascade delete. 되돌리기 불가 — 마이그레이션 전 mysqldump 백업 필수 |
| P10-D4 | Solved.ac 통합 깊이 | **(c) 풀: 검색+북마크 + 핸들 자동 sync**. `SolvedAcClient``https://solved.ac/api/v3` 공개 API 사용(인증 불필요), `user/show` + `user/problem_stats` + `user/top_100` 조합으로 bulk import |
| P10-D5 | PS → StudyLog 매핑 | `PsProblem` 별도 테이블에 캐시. `StudyLog.problemId` 는 KICE `Problem` FK 라 재사용 불가 → `StudyLog.psProblemId Int?` 추가. `subject` 는 자동 생성되는 시스템 Subject `"PS"` 로 묶음 |
| P10-D6 | 온보딩 GoalType | `'math-suneung' \| 'ps' \| 'certificate' \| 'language' \| 'none'`. `User.focusUnits Json?` 추가(수학 단원 멀티셀렉트 저장), `User.bojHandle String?` 추가 |
| P10-D7 | 기존 focusSubjects | `focusSubjects`는 그대로 유지하되 수학 선택지만 남김. 자격증/어학/취미 트랙은 자유 입력 유지 |
| P10-D8 | 학교/학원 기능 | Phase 9 Assignment 는 수학 ProblemSet 에 대해서만 작동. 스키마 변경 없음 |
### Phase 10A — 스키마 + KICE 비수학 제거
| Task | 내용 | DoD | Depends | Status |
|------|------|-----|---------|--------|
| 10A.1 | Prisma 스키마: `MathUnit` enum, `ProblemSet.mathUnit MathUnit?`, `User.bojHandle String? @db.VarChar(32)`, `User.focusUnits Json?`. migration `phase10_math_units_and_ps` 생성 | `prisma migrate dev --name phase10_math_units_and_ps` 성공, `@prisma/client` 재생성, 기존 행 NULL 기본값 | - | cc:TODO |
| 10A.2 | 비수학 데이터 wipe 스크립트 `backend/scripts/wipe-non-math.ts``ProblemSet where subjectName != '수학'` 연쇄 삭제(Assignment/AssignmentSubmission, StudyLog, ReviewSchedule, Problem, Passage 포함). 실행 전 mysqldump 백업을 `backups/` 에 저장 | dry-run 시 삭제 대상 수 출력, `--apply` 시 실제 삭제 후 `ProblemSet count where subjectName='수학'` 만 남음 | 10A.1 | cc:TODO |
| 10A.3 | `kice-import.ts` 리팩터: `TARGET_SUBJECTS = ['수학']`, 30문항을 1~22 = common, 23~30 = 선택 × 3 단원으로 split 하여 4 ProblemSet 생성. `ProblemSet.title``YYYY학년도 수능 수학 (공통)` 형태 | `pnpm cli:kice-import --dry-run --year=2026` 시 common 22 + 확통 8 + 미적 8 + 기하 8 = 46 문항 리포트 | 10A.2 | cc:TODO |
| 10A.4 | `backend/src/problem-sets/parsing/strategies/kice.ts` 및 관련 OCR 프롬프트에서 비수학 분기 제거. `shared.ts`·`ocr-fix.ts`·`seed.ts` 에서 비수학 과목 참조 삭제 | `grep -rE "국어\|영어\|한국사\|생활과 윤리\|사회탐구" backend/src backend/scripts` 결과 0 | 10A.3 | cc:TODO |
| 10A.5 | `data/kice/{2025,2026}/{국어,영어,한국사,사회탐구}/` 삭제 + `.gitignore` 정리. `backend/uploads/problems/{year}/{비수학 과목}/` 도 삭제 | 디스크에 수학 PDF + 수학 이미지 crop 만 남음 | 10A.4 | cc:TODO |
### Phase 10B — Solved.ac PS 모듈
| Task | 내용 | DoD | Depends | Status |
|------|------|-----|---------|--------|
| 10B.1 | Prisma: `model PsProblem { id Int, bojId Int @unique, title, level Int, tags Json, acceptedUserCount Int?, averageTries Float?, updatedAt }`, `model PsBookmark { userId, psProblemId, createdAt @@unique([userId,psProblemId]) }`, `StudyLog.psProblemId Int?` FK 추가. migration `phase10_ps_tables` | `prisma migrate dev` 성공, 관계 매핑 검증 | 10A.1 | cc:TODO |
| 10B.2 | `backend/src/ps/solved-ac.client.ts` — axios 인스턴스(`https://solved.ac/api/v3`, User-Agent 명시, 타임아웃 10s, 지수 백오프 3회). 메서드: `searchProblems(query, page)`, `getProblem(bojId)`, `getUser(handle)`, `getUserSolvedProblems(handle, page)`. 내부 rate-limit (초당 3req) | 단위 테스트: mocked axios 로 4 메서드 응답 파싱 정상 | 10B.1 | cc:TODO |
| 10B.3 | `PsModule` + `PsService` + `PsController` — endpoints: `GET /api/ps/search?q=&level=&page=`, `GET /api/ps/problems/:bojId`, `POST /api/ps/bookmarks { bojId }`, `DELETE /api/ps/bookmarks/:bojId`, `GET /api/ps/bookmarks`, `POST /api/ps/sync { bojHandle? }` (핸들 없으면 `User.bojHandle` 사용). JWT guard + throttler | Postman/curl 6 endpoint 200, sync 실행 시 PsProblem upsert + 시스템 Subject "PS" 생성 + StudyLog bulk insert, 멱등성 확인(중복 sync 시 같은 BOJ 문제 StudyLog 추가 생성 안 함) | 10B.2 | cc:TODO |
| 10B.4 | Sync 멱등성 저장소: `PsSyncState { userId @unique, lastSyncedAt, lastSolvedCount }` 추가해 마지막 sync 이후 새로 맞힌 문제만 가져옴. 초기 sync 는 `top_100` 기준 상위 100개로 제한(과도한 StudyLog 폭주 방지) | 1차 sync 후 `PsSyncState.lastSolvedCount` 저장, 2차 sync 는 차분만 insert | 10B.3 | cc:TODO |
### Phase 10C — 온보딩/프론트 재구성
| Task | 내용 | DoD | Depends | Status |
|------|------|-----|---------|--------|
| 10C.1 | `frontend/src/app/onboarding/page.tsx` — GoalType 옵션 라벨/설명 교체. `math-suneung` 선택 시 4 단원 체크박스 그룹 노출, `ps` 선택 시 `bojHandle` 인풋(정규식 검증 `^[a-zA-Z0-9_-]{3,20}$`) 노출 | 각 GoalType 분기 UI 정상, 저장 후 `/auth/me` 응답에 `focusUnits` 또는 `bojHandle` 반영 | 10B.1 | cc:TODO |
| 10C.2 | `frontend/src/lib/api.ts` + `MeUser` 타입: `bojHandle`, `focusUnits: MathUnit[]`, `GoalType` 유니언 갱신. `api/me` PATCH payload 확장 | 타입 에러 0, API PATCH 200 | 10C.1 | cc:TODO |
| 10C.3 | 전역 과목 리스트 정리 — `subjects/page.tsx`, `exams/page.tsx`, `review/page.tsx`, `review/history/page.tsx`, `dashboard`, `school/*` 에서 비수학 하드코딩 참조 제거. `exam/shared.ts::getExamDurationSeconds` → 수학 100분 고정. `SideNav` 에서 국어/영어/한국사 링크 제거 | `grep -rE "국어\|영어\|한국사\|생활과 윤리" frontend/src` = 0 | 10C.2 | cc:TODO |
| 10C.4 | `frontend/src/app/ps/` 신규 섹션 — `page.tsx`(검색 + 필터 + 북마크 버튼), `bookmarks/page.tsx`(북마크 목록), `sync/page.tsx`(핸들 동기화 버튼 + 상태). BottomNav/SideNav 에 `PS` 진입점 추가 | 4 경로 200 + search/bookmark/sync e2e 수동 검증 통과 | 10C.2 | cc:TODO |
| 10C.5 | `landing`/`pricing`/`dashboard` 카피 업데이트 — "수능 국어/영어…" 문구를 "수학 수능 + PS" 로 교체, 아카이브 design HTML 과 docs 에서도 혼동 없도록 정리 | `grep -rE "국어\|영어\|한국사\|생활과 윤리" frontend/src docs README.md` = 0 (아카이브 `.claude/archive/` 는 제외) | 10C.3 | cc:TODO |
### Phase 10D — Dev 배포 + 검증
| Task | 내용 | DoD | Depends | Status |
|------|------|-----|---------|--------|
| 10D.1 | 로컬 커밋 + push → Gitea `hanarang/reloop-v2 master`. commit 단위는 10A / 10B / 10C 별로 나눔 | `git push` 성공, `tea pr list` 없이 master 직접 반영 | 10C.5 | cc:TODO |
| 10D.2 | `ssh dev@10.10.10.169``cd ~/reloop-v2 && git pull`. backend 에서 `mysqldump reloop_v2 > backups/phase10-pre.sql` 실행 후 `pnpm --filter backend exec prisma migrate deploy``pnpm --filter backend build` | 마이그레이션 적용됨, dist/main.js 빌드 성공, DB 백업 존재 | 10D.1 | cc:TODO |
| 10D.3 | 비수학 wipe 스크립트 운영 실행 — `node backend/dist/scripts/wipe-non-math.js --apply`. 이어서 `pnpm cli:kice-import --year=2025 && --year=2026` 로 수학 8 ProblemSet 재생성 | DB 에 수학 8 ProblemSet / 92 problems, 비수학 0 | 10D.2 | cc:TODO |
| 10D.4 | Frontend 빌드 + standalone copy + `pm2 reload reloop-web reloop-api --update-env` | PM2 status online, `curl https://reloop-api.nabomhalang.co.kr/health` 200, `curl https://reloop.nabomhalang.co.kr` 200 | 10D.3 | cc:TODO |
| 10D.5 | 스모크 테스트 — 온보딩 `math-suneung` 플로우 + 단원 선택 저장, `ps` 플로우 + 더미 BOJ 핸들 sync, `/ps/search` 검색, `/exams` 에 수학 4단원 표시, `/review` 에 비수학 흔적 없음 | 5 시나리오 수동 통과 스크린샷 또는 로그 캡처 | 10D.4 | cc:TODO |
### Phase 10 범위 / 비 범위
**포함**:
- KICE 비수학 과목(국어/영어/한국사/생활과 윤리) 코드·데이터·DB 전량 제거
- 수학 4단원 분할(common/prob_stat/calculus/geometry) + 재import
- Solved.ac 기반 PS 모듈 (검색/북마크/핸들 자동 sync → StudyLog)
- 온보딩 재설계 (`math-suneung` 세부 단원 / `ps` bojHandle)
- Dev 배포 + DB 백업/마이그레이션/스모크
**제외** (별도 트랙):
- Solved.ac 인증 API(로그인 연동) — 공개 API 로 충분
- PS 문제 로컬 렌더링 — BOJ 링크 아웃바운드 유지
- 수학 30문항 → 단원 분할 비율 조정(정밀 매핑표 필요 시 별도 Phase)
- 기존 사용자 데이터 마이그레이션 보전(비수학 StudyLog 는 cascade delete)
- i18n / 영어 UI / PWA
- 이 Phase 에서는 새 UI 디자인 토큰 변경 없음
### 실행 모드
- 전량 `harness-work --codex` 위임, Lead 세션이 codex helper 를 통해 Task 단위로 구현/검증
- 배포(10D)는 ssh + PM2 수동 단계 포함 → Lead 세션이 Bash 로 직접 실행
- DB 파괴적 작업(10A.2, 10D.3) 실행 전 mysqldump 백업 필수
### 범위 / 비 범위
**포함**:
- KICE 2025/2026 × 5 과목 = 10 ProblemSet import
- UserProblemSet 추가/조회/보관 API + UI
- Tailwind 도입 + 디자인 토큰 이식
- 12 페이지 재디자인 (AppShell 포함)
**제외** (Phase 8 이후):
- 2025/2026 탐구 전 과목 (46+46) 전체 import — 먼저 5과목 패턴 확립 후 Phase 8 자동 확장
- 정답률/등급컷 데이터 수동 입력 — EBSi 엑셀 별도 트랙
- 실제 결제 연동 (PortOne/Toss)
- Pro tier 실제 gating 강제 (P6-F5 TODO 주석 유지)
- 문제지 PDF 이미지/수식 OCR
- PWA manifest / offline 지원
- 학교 Pro 전용 기능 (선생님 대시보드)
### 실행 모드 제안
전체 35+ 태스크 → **Breezing** 자동 선택되지만 Phase 별로 나눠 순차 실행:
1. 먼저 **Phase 7C** (Tailwind 도입) — 5 태스크, 다른 Phase 의 전제
2. **Phase 7A** (KICE 파이프라인) — 7 태스크, backend 중심
3. **Phase 7B** (UserProblemSet) — 5 태스크, backend + 최소 frontend
4. **Phase 7D** (12 페이지 재디자인) — 12 태스크, 가장 무거움. 서브 Phase 로 더 쪼개 가능
5. **Phase 7E** (배포+감사) — 3 태스크
**사용자 컨펌 대기 항목**:
- D1 ~ D5 기술 결정 승인
- Phase 7D 재디자인을 하나의 세션에서 12개 전부 할지, 서브 Phase (7D-a 사이드바+dashboard / 7D-b auth+landing+pricing / 7D-c review+study+history / 7D-d subjects+stats+profile) 로 쪼갤지
## Execution
Mode: **Breezing (명시 플래그 --breezing) — 2 Wave**
Wave 1: 6개 QA 병렬 Worker (정적 분석)
Wave 2: 5개 fix Cluster 병렬 Worker + Lead 리뷰 + Cluster별 commit
Lead: 본 세션 (claude-opus-4-6[1m])
Workers: `claude-code-harness:task-worker` (Wave 2, write 권한)
AI Residuals: clean (TODO/FIXME/Claude/ChatGPT/Copilot 어느 것도 소스 없음)