Files
reloop-v2/.claude/state/review-result.1st.json
reloop cc946e4722 chore(review): Phase 6 2nd-pass APPROVE — fix cluster 전부 해결 확인
- harness-review 2nd pass (base 6d8deef → head 71cf469): APPROVE
- 1st pass 의 major 1 + minor 4 전부 해결 검증
- 회귀 없음, AI residuals clean
- 새 recommendation 2건 (iterationsToTarget null 가드, Problem IDOR)
  은 Phase 7 확장 시 주의사항으로 tracking

.claude/state/review-result.json: 2nd pass 결과
.claude/state/review-result.1st.json: 1st pass 결과 (히스토리)
2026-04-12 01:52:06 +09:00

112 lines
5.5 KiB
JSON

{
"schema_version": "review-result.v1",
"verdict": "REQUEST_CHANGES",
"reviewer_profile": "static",
"base_ref": "e0ac7a4",
"head_ref": "871ca89",
"scope": "phase-6",
"commits": [
"9939f29 feat(phase6): mastery-path API + stats UI 재설계 + Pretendard + 드래그 방지",
"f22f837 feat(stats): mastery-path ceilingP + effectiveTarget",
"871ca89 feat(phase6): BM tiers + Landing + Pricing + 평가원 기출 ProblemSet"
],
"stats": {
"files_changed": 27,
"insertions": 2398,
"deletions": 115
},
"ai_residuals": {
"tool": "review-ai-residuals",
"scan_mode": "diff",
"summary": { "verdict": "APPROVE", "major": 0, "minor": 0, "recommendation": 0, "total": 0 }
},
"critical_issues": [],
"major_issues": [
{
"id": "P6.major.1",
"category": "Security",
"location": "backend/src/study-logs/study-logs.service.ts:64-79",
"issue": "problemId 가 서버에서 존재 검증 없이 INSERT 된다. 존재하지 않는 problemId 를 넘기면 Prisma FK 위반으로 500 이 튀어 raw stack trace 가 노출될 수 있고, 500 vs 400 응답 차이로 problemId 존재 여부가 leak 된다.",
"suggestion": "tagId 와 동일한 패턴으로 사전 조회 후 없으면 NotFoundException. subjectId/tagId 체크 블록 바로 뒤에 삽입."
}
],
"observations": [
{
"severity": "minor",
"category": "Security",
"location": "backend/src/problem-sets/problem-sets.controller.ts, backend/src/stats/stats.controller.ts:58",
"issue": "subscriptionTier 가 저장만 되고 어디서도 enforce 안 됨. Landing/Pricing 이 mastery-path/problem-sets 를 Pro 전용으로 광고하지만 실제 gating 은 없음.",
"suggestion": "Phase 7 결제 연동 시 TierGuard 또는 inline 체크. 현재는 TODO 주석 필수."
},
{
"severity": "minor",
"category": "Quality",
"location": "frontend/src/app/stats/page.tsx:256-262",
"issue": "masteryPath 가 이미 목표 도달 상태일 때 iterationsToTarget=0, daysToTarget=0 으로 반환하고 프론트 카피가 '앞으로 0회차 더 복습하면 약 0일 뒤 마스터' 같은 어색한 문구를 낸다.",
"suggestion": "iterationsToTarget === 0 특수 분기 추가: '이미 목표 P 에 도달한 상태야 ✨'."
},
{
"severity": "minor",
"category": "Accessibility",
"location": "frontend/src/app/stats/page.tsx:142-156, frontend/src/app/pricing/page.tsx:37-43, frontend/src/app/study/page.tsx",
"issue": "마스터리/망각 토글, 월/연 토글, study 탭에 aria-pressed 또는 role=tab/aria-selected 가 없음. 스크린리더가 상태 전달 못함. WCAG 2.1 SC 4.1.2.",
"suggestion": "각 토글 버튼에 aria-pressed={$active} 추가."
},
{
"severity": "minor",
"category": "AI Residuals",
"location": "frontend/src/app/page.tsx:58, backend/prisma/seed.ts:105",
"issue": "Landing 에 demo@reloop.local / demo1234 가 공개. seed log 에도 평문 비밀번호 stdout 출력. 데모 단계엔 괜찮지만 런칭 시 제거 대상.",
"suggestion": "TODO 주석 추가. Phase 7 런칭 전 제거 또는 /demo 라우트로 분리."
},
{
"severity": "recommendation",
"category": "Performance",
"location": "backend/src/problem-sets/problem-sets.service.ts:19-28",
"issue": "getOne() 의 problems include 에 take 없음. 현재 스케일(10문제) 문제 없음. 전과목 번들이 되면 주의.",
"suggestion": "방어적으로 take: 100 또는 나중에 커서 페이지네이션."
},
{
"severity": "recommendation",
"category": "Performance",
"location": "frontend/src/app/stats/page.tsx:9-10",
"issue": "MasteryPathChart, ForgetCurveChart 둘 다 정적 import. recharts 는 무거운 라이브러리(~140KB gz). 토글당 한쪽만 렌더되므로 dynamic import 가치.",
"suggestion": "next/dynamic 으로 각 차트 lazy 로드 (ssr:false)."
},
{
"severity": "recommendation",
"category": "Quality",
"location": "frontend/src/app/pricing/page.tsx:86-89",
"issue": "'7일 무료 체험 시작' 버튼이 /register 로만 보내고 실제 체험 로직 없음. 사용자 신뢰 리스크.",
"suggestion": "Phase 7 전까지 '무료로 시작하기 (Pro 체험 Phase 7 예정)' 로 문구 변경 또는 disabled."
},
{
"severity": "recommendation",
"category": "Security",
"location": "frontend/src/app/pricing/page.tsx:108",
"issue": "<a href='mailto:op@nabomhalang.co.kr'> 평문 노출로 스팸 harvesting 리스크.",
"suggestion": "문의 폼 또는 JS 난독화."
},
{
"severity": "recommendation",
"category": "Quality",
"location": "frontend/src/app/page.tsx:13-23",
"issue": "Landing 이 useEffect 안에서 hasToken() 체크 후 setReady. SSR 초기는 null 렌더. 초기 flash/CLS 가능.",
"suggestion": "Next.js middleware 쿠키 기반 redirect 또는 full landing 을 initial state 로."
}
],
"summary": {
"critical": 0,
"major": 1,
"minor": 4,
"recommendation": 5,
"verdict_rule_applied": "1 major → REQUEST_CHANGES"
},
"next_actions": [
"Fix P6.major.1 in study-logs.service.ts with NotFoundException guard",
"Minor fixes 3건 (마스터리 카피, aria-pressed, demo credential 주석) 묶어서 한 commit",
"Subscription tier enforcement TODO 주석 추가 — Phase 7 연동",
"Recommendation 5건은 별도 tracking 태스크로 Phase 7 이후 일정"
]
}