{ "version": "1.0.0", "legacy_version": "1.0.0", "artifactType": "precompact-snapshot", "timestamp": "2026-04-17T00:55:24.687Z", "sessionId": "", "previous_state": { "summary": "Before compaction: 20 recent edit(s)", "session_state": null, "plan_counts": { "total": 24, "wip": 0, "blocked": 0, "recent_edits": 20 } }, "next_action": { "taskId": "7B.1", "task": "Prisma: `UserProblemSet` 모델 (`userId`, `problemSetId`, `addedAt`, `archivedAt DateTime?`, `@@unique([userId, problemSetId])`, `@@index([userId])`)", "dod": "migration 통과, User/ProblemSet 양방향 relation 정의됨", "depends": "7A.1", "status": "cc:TODO", "source": "Plans.md", "priority": "normal", "summary": "Continue 7B.1 Prisma: `UserProblemSet` 모델 (`userId`, `problemSetId`, `addedAt`, `archivedAt DateTime?`, `@@unique([userId, problemSetId])`, `@@index([userId])`)" }, "open_risks": [ { "severity": "medium", "kind": "verification", "summary": "20 recent edit(s) should be re-validated after resume", "detail": ".claude/sessions/.last_inbox_check, .claude/state/plans-state.json, .claude/state/tool-failure-counter.txt, .claude/logs/subagent-history.jsonl, .claude/state/breezing-timeline.jsonl" } ], "failed_checks": [], "decision_log": [ { "timestamp": "2026-04-17T00:55:24.687Z", "actor": "pre-compact-save", "decision": "canonical_handoff_artifact_written", "rationale": "Persist a stable JSON artifact in .claude/state for long-running session handoff." }, { "timestamp": "2026-04-17T00:55:24.687Z", "actor": "pre-compact-save", "decision": "legacy_snapshot_mirrored", "rationale": "Keep precompact-snapshot.json for backward compatibility with older hooks." }, { "timestamp": "2026-04-17T00:55:24.687Z", "actor": "pre-compact-save", "decision": "next_action_selected", "rationale": "Continue 7B.1 Prisma: `UserProblemSet` 모델 (`userId`, `problemSetId`, `addedAt`, `archivedAt DateTime?`, `@@unique([userId, problemSetId])`, `@@index([userId])`) (source: Plans.md)" } ], "context_reset": { "policy": { "mode": "auto", "dryRun": false, "thresholds": { "wipTasks": 4, "blockedTasks": 1, "recentEdits": 8, "failedChecks": 1, "sessionAgeMinutes": 120 } }, "recommended": true, "summary": "Context reset recommended (auto): 20 recent edit count exceed threshold 8", "reasons": [ "20 recent edit count exceed threshold 8" ], "candidates": [ { "key": "wip_tasks", "label": "WIP task count", "actual": 0, "threshold": 4, "triggered": false }, { "key": "blocked_tasks", "label": "blocked task count", "actual": 0, "threshold": 1, "triggered": false }, { "key": "recent_edits", "label": "recent edit count", "actual": 20, "threshold": 8, "triggered": true }, { "key": "failed_checks", "label": "failed check count", "actual": 0, "threshold": 1, "triggered": false }, { "key": "session_age_minutes", "label": "session age (minutes)", "actual": 0, "threshold": 120, "triggered": false } ], "counters": { "wipTasks": 0, "blockedTasks": 0, "recentEdits": 20, "failedChecks": 0, "sessionAgeMinutes": null } }, "continuity": { "plugin_first_workflow": true, "resume_aware_effort_continuity": true, "effort_hint": "medium", "active_skill": null, "summary": "plugin-first workflow: enabled; resume-aware effort continuity: medium; next_task=7B.1" }, "planItems": [ { "taskId": "7B.1", "title": "Prisma: `UserProblemSet` 모델 (`userId`, `problemSetId`, `addedAt`, `archivedAt DateTime?`, `@@unique([userId, problemSetId])`, `@@index([userId])`)", "dod": "migration 통과, User/ProblemSet 양방향 relation 정의됨", "depends": "7A.1", "status": "cc:TODO", "tags": { "todo": true, "wip": false, "blocked": false } }, { "taskId": "7B.2", "title": "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)", "dod": "데모 계정으로 3개 API 모두 200/201/204 응답. 중복 추가 시 409", "depends": "7B.1", "status": "cc:TODO", "tags": { "todo": true, "wip": false, "blocked": false } }, { "taskId": "7B.3", "title": "frontend `lib/api.ts` 에 `UserProblemSet`, `addProblemSet`, `listMyProblemSets`, `removeProblemSet` 타입/헬퍼 추가", "dod": "타입 에러 없이 컴파일, stats 페이지에서 호출 가능", "depends": "7B.2", "status": "cc:TODO", "tags": { "todo": true, "wip": false, "blocked": false } }, { "taskId": "7B.4", "title": "frontend `/study` 페이지 탭 재설계: [내 문제집 / 카탈로그 / 직접 입력] 3 탭. \"카탈로그\" 에 전체 ProblemSet 목록 + 각 카드에 \"+ 내 목록에 추가\" 버튼. \"내 문제집\" 에 추가한 것만 표시", "dod": "카탈로그에서 카드 클릭 → 내 목록 탭에 즉시 반영. 중복 클릭 방지", "depends": "7B.3", "status": "cc:TODO", "tags": { "todo": true, "wip": false, "blocked": false } }, { "taskId": "7B.5", "title": "\"내 문제집\" 탭 상세: 선택한 ProblemSet → 문항 리스트 → 각 문항 \"풀기\" 버튼 → 기존 `/study/form?problemId=N` 으로 이동해 결과 입력", "dod": "데모 계정에서 2026 수학 → 15번 클릭 → 결과 기록 → 복습 큐 진입까지 전 플로우 통과", "depends": "7B.4", "status": "cc:TODO", "tags": { "todo": true, "wip": false, "blocked": false } }, { "taskId": "10A.1", "title": "Prisma 스키마: `MathUnit` enum, `ProblemSet.mathUnit MathUnit?`, `User.bojHandle String? @db.VarChar(32)`, `User.focusUnits Json?`. migration `phase10_math_units_and_ps` 생성", "dod": "`prisma migrate dev --name phase10_math_units_and_ps` 성공, `@prisma/client` 재생성, 기존 행 NULL 기본값", "depends": "-", "status": "cc:TODO", "tags": { "todo": true, "wip": false, "blocked": false } }, { "taskId": "10A.2", "title": "비수학 데이터 wipe 스크립트 `backend/scripts/wipe-non-math.ts` — `ProblemSet where subjectName != '수학'` 연쇄 삭제(Assignment/AssignmentSubmission, StudyLog, ReviewSchedule, Problem, Passage 포함). 실행 전 mysqldump 백업을 `backups/` 에 저장", "dod": "dry-run 시 삭제 대상 수 출력, `--apply` 시 실제 삭제 후 `ProblemSet count where subjectName='수학'` 만 남음", "depends": "10A.1", "status": "cc:TODO", "tags": { "todo": true, "wip": false, "blocked": false } }, { "taskId": "10A.3", "title": "`kice-import.ts` 리팩터: `TARGET_SUBJECTS = ['수학']`, 30문항을 1~22 = common, 23~30 = 선택 × 3 단원으로 split 하여 4 ProblemSet 생성. `ProblemSet.title` 은 `YYYY학년도 수능 수학 (공통)` 형태", "dod": "`pnpm cli:kice-import --dry-run --year=2026` 시 common 22 + 확통 8 + 미적 8 + 기하 8 = 46 문항 리포트", "depends": "10A.2", "status": "cc:TODO", "tags": { "todo": true, "wip": false, "blocked": false } }, { "taskId": "10A.4", "title": "`backend/src/problem-sets/parsing/strategies/kice.ts` 및 관련 OCR 프롬프트에서 비수학 분기 제거. `shared.ts`·`ocr-fix.ts`·`seed.ts` 에서 비수학 과목 참조 삭제", "dod": "`grep -rE \"국어|영어|한국사|생활과 윤리|사회탐구\" backend/src backend/scripts` 결과 0", "depends": "10A.3", "status": "cc:TODO", "tags": { "todo": true, "wip": false, "blocked": false } }, { "taskId": "10A.5", "title": "`data/kice/{2025,2026}/{국어,영어,한국사,사회탐구}/` 삭제 + `.gitignore` 정리. `backend/uploads/problems/{year}/{비수학 과목}/` 도 삭제", "dod": "디스크에 수학 PDF + 수학 이미지 crop 만 남음", "depends": "10A.4", "status": "cc:TODO", "tags": { "todo": true, "wip": false, "blocked": false } }, { "taskId": "10B.1", "title": "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`", "dod": "`prisma migrate dev` 성공, 관계 매핑 검증", "depends": "10A.1", "status": "cc:TODO", "tags": { "todo": true, "wip": false, "blocked": false } }, { "taskId": "10B.2", "title": "`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)", "dod": "단위 테스트: mocked axios 로 4 메서드 응답 파싱 정상", "depends": "10B.1", "status": "cc:TODO", "tags": { "todo": true, "wip": false, "blocked": false } }, { "taskId": "10B.3", "title": "`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", "dod": "Postman/curl 6 endpoint 200, sync 실행 시 PsProblem upsert + 시스템 Subject \"PS\" 생성 + StudyLog bulk insert, 멱등성 확인(중복 sync 시 같은 BOJ 문제 StudyLog 추가 생성 안 함)", "depends": "10B.2", "status": "cc:TODO", "tags": { "todo": true, "wip": false, "blocked": false } }, { "taskId": "10B.4", "title": "Sync 멱등성 저장소: `PsSyncState { userId @unique, lastSyncedAt, lastSolvedCount }` 추가해 마지막 sync 이후 새로 맞힌 문제만 가져옴. 초기 sync 는 `top_100` 기준 상위 100개로 제한(과도한 StudyLog 폭주 방지)", "dod": "1차 sync 후 `PsSyncState.lastSolvedCount` 저장, 2차 sync 는 차분만 insert", "depends": "10B.3", "status": "cc:TODO", "tags": { "todo": true, "wip": false, "blocked": false } }, { "taskId": "10C.1", "title": "`frontend/src/app/onboarding/page.tsx` — GoalType 옵션 라벨/설명 교체. `math-suneung` 선택 시 4 단원 체크박스 그룹 노출, `ps` 선택 시 `bojHandle` 인풋(정규식 검증 `^[a-zA-Z0-9_-]{3,20}$`) 노출", "dod": "각 GoalType 분기 UI 정상, 저장 후 `/auth/me` 응답에 `focusUnits` 또는 `bojHandle` 반영", "depends": "10B.1", "status": "cc:TODO", "tags": { "todo": true, "wip": false, "blocked": false } }, { "taskId": "10C.2", "title": "`frontend/src/lib/api.ts` + `MeUser` 타입: `bojHandle`, `focusUnits: MathUnit[]`, `GoalType` 유니언 갱신. `api/me` PATCH payload 확장", "dod": "타입 에러 0, API PATCH 200", "depends": "10C.1", "status": "cc:TODO", "tags": { "todo": true, "wip": false, "blocked": false } }, { "taskId": "10C.3", "title": "전역 과목 리스트 정리 — `subjects/page.tsx`, `exams/page.tsx`, `review/page.tsx`, `review/history/page.tsx`, `dashboard`, `school/*` 에서 비수학 하드코딩 참조 제거. `exam/shared.ts::getExamDurationSeconds` → 수학 100분 고정. `SideNav` 에서 국어/영어/한국사 링크 제거", "dod": "`grep -rE \"국어|영어|한국사|생활과 윤리\" frontend/src` = 0", "depends": "10C.2", "status": "cc:TODO", "tags": { "todo": true, "wip": false, "blocked": false } }, { "taskId": "10C.4", "title": "`frontend/src/app/ps/` 신규 섹션 — `page.tsx`(검색 + 필터 + 북마크 버튼), `bookmarks/page.tsx`(북마크 목록), `sync/page.tsx`(핸들 동기화 버튼 + 상태). BottomNav/SideNav 에 `PS` 진입점 추가", "dod": "4 경로 200 + search/bookmark/sync e2e 수동 검증 통과", "depends": "10C.2", "status": "cc:TODO", "tags": { "todo": true, "wip": false, "blocked": false } }, { "taskId": "10C.5", "title": "`landing`/`pricing`/`dashboard` 카피 업데이트 — \"수능 국어/영어…\" 문구를 \"수학 수능 + PS\" 로 교체, 아카이브 design HTML 과 docs 에서도 혼동 없도록 정리", "dod": "`grep -rE \"국어|영어|한국사|생활과 윤리\" frontend/src docs README.md` = 0 (아카이브 `.claude/archive/` 는 제외)", "depends": "10C.3", "status": "cc:TODO", "tags": { "todo": true, "wip": false, "blocked": false } }, { "taskId": "10D.1", "title": "로컬 커밋 + push → Gitea `hanarang/reloop-v2 master`. commit 단위는 10A / 10B / 10C 별로 나눔", "dod": "`git push` 성공, `tea pr list` 없이 master 직접 반영", "depends": "10C.5", "status": "cc:TODO", "tags": { "todo": true, "wip": false, "blocked": false } }, { "taskId": "10D.2", "title": "`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`", "dod": "마이그레이션 적용됨, dist/main.js 빌드 성공, DB 백업 존재", "depends": "10D.1", "status": "cc:TODO", "tags": { "todo": true, "wip": false, "blocked": false } }, { "taskId": "10D.3", "title": "비수학 wipe 스크립트 운영 실행 — `node backend/dist/scripts/wipe-non-math.js --apply`. 이어서 `pnpm cli:kice-import --year=2025 && --year=2026` 로 수학 8 ProblemSet 재생성", "dod": "DB 에 수학 8 ProblemSet / 92 problems, 비수학 0", "depends": "10D.2", "status": "cc:TODO", "tags": { "todo": true, "wip": false, "blocked": false } }, { "taskId": "10D.4", "title": "Frontend 빌드 + standalone copy + `pm2 reload reloop-web reloop-api --update-env`", "dod": "PM2 status online, `curl https://reloop-api.nabomhalang.co.kr/health` 200, `curl https://reloop.nabomhalang.co.kr` 200", "depends": "10D.3", "status": "cc:TODO", "tags": { "todo": true, "wip": false, "blocked": false } }, { "taskId": "10D.5", "title": "스모크 테스트 — 온보딩 `math-suneung` 플로우 + 단원 선택 저장, `ps` 플로우 + 더미 BOJ 핸들 sync, `/ps/search` 검색, `/exams` 에 수학 4단원 표시, `/review` 에 비수학 흔적 없음", "dod": "5 시나리오 수동 통과 스크린샷 또는 로그 캡처", "depends": "10D.4", "status": "cc:TODO", "tags": { "todo": true, "wip": false, "blocked": false } } ], "wipTasks": [ "Prisma: `UserProblemSet` 모델 (`userId`, `problemSetId`, `addedAt`, `archivedAt DateTime?`, `@@unique([userId, problemSetId])`, `@@index([userId])`)", "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)", "frontend `lib/api.ts` 에 `UserProblemSet`, `addProblemSet`, `listMyProblemSets`, `removeProblemSet` 타입/헬퍼 추가", "frontend `/study` 페이지 탭 재설계: [내 문제집 / 카탈로그 / 직접 입력] 3 탭. \"카탈로그\" 에 전체 ProblemSet 목록 + 각 카드에 \"+ 내 목록에 추가\" 버튼. \"내 문제집\" 에 추가한 것만 표시", "\"내 문제집\" 탭 상세: 선택한 ProblemSet → 문항 리스트 → 각 문항 \"풀기\" 버튼 → 기존 `/study/form?problemId=N` 으로 이동해 결과 입력", "Prisma 스키마: `MathUnit` enum, `ProblemSet.mathUnit MathUnit?`, `User.bojHandle String? @db.VarChar(32)`, `User.focusUnits Json?`. migration `phase10_math_units_and_ps` 생성", "비수학 데이터 wipe 스크립트 `backend/scripts/wipe-non-math.ts` — `ProblemSet where subjectName != '수학'` 연쇄 삭제(Assignment/AssignmentSubmission, StudyLog, ReviewSchedule, Problem, Passage 포함). 실행 전 mysqldump 백업을 `backups/` 에 저장", "`kice-import.ts` 리팩터: `TARGET_SUBJECTS = ['수학']`, 30문항을 1~22 = common, 23~30 = 선택 × 3 단원으로 split 하여 4 ProblemSet 생성. `ProblemSet.title` 은 `YYYY학년도 수능 수학 (공통)` 형태", "`backend/src/problem-sets/parsing/strategies/kice.ts` 및 관련 OCR 프롬프트에서 비수학 분기 제거. `shared.ts`·`ocr-fix.ts`·`seed.ts` 에서 비수학 과목 참조 삭제", "`data/kice/{2025,2026}/{국어,영어,한국사,사회탐구}/` 삭제 + `.gitignore` 정리. `backend/uploads/problems/{year}/{비수학 과목}/` 도 삭제", "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`", "`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)", "`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", "Sync 멱등성 저장소: `PsSyncState { userId @unique, lastSyncedAt, lastSolvedCount }` 추가해 마지막 sync 이후 새로 맞힌 문제만 가져옴. 초기 sync 는 `top_100` 기준 상위 100개로 제한(과도한 StudyLog 폭주 방지)", "`frontend/src/app/onboarding/page.tsx` — GoalType 옵션 라벨/설명 교체. `math-suneung` 선택 시 4 단원 체크박스 그룹 노출, `ps` 선택 시 `bojHandle` 인풋(정규식 검증 `^[a-zA-Z0-9_-]{3,20}$`) 노출", "`frontend/src/lib/api.ts` + `MeUser` 타입: `bojHandle`, `focusUnits: MathUnit[]`, `GoalType` 유니언 갱신. `api/me` PATCH payload 확장", "전역 과목 리스트 정리 — `subjects/page.tsx`, `exams/page.tsx`, `review/page.tsx`, `review/history/page.tsx`, `dashboard`, `school/*` 에서 비수학 하드코딩 참조 제거. `exam/shared.ts::getExamDurationSeconds` → 수학 100분 고정. `SideNav` 에서 국어/영어/한국사 링크 제거", "`frontend/src/app/ps/` 신규 섹션 — `page.tsx`(검색 + 필터 + 북마크 버튼), `bookmarks/page.tsx`(북마크 목록), `sync/page.tsx`(핸들 동기화 버튼 + 상태). BottomNav/SideNav 에 `PS` 진입점 추가", "`landing`/`pricing`/`dashboard` 카피 업데이트 — \"수능 국어/영어…\" 문구를 \"수학 수능 + PS\" 로 교체, 아카이브 design HTML 과 docs 에서도 혼동 없도록 정리", "로컬 커밋 + push → Gitea `hanarang/reloop-v2 master`. commit 단위는 10A / 10B / 10C 별로 나눔", "`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`", "비수학 wipe 스크립트 운영 실행 — `node backend/dist/scripts/wipe-non-math.js --apply`. 이어서 `pnpm cli:kice-import --year=2025 && --year=2026` 로 수학 8 ProblemSet 재생성", "Frontend 빌드 + standalone copy + `pm2 reload reloop-web reloop-api --update-env`", "스모크 테스트 — 온보딩 `math-suneung` 플로우 + 단원 선택 저장, `ps` 플로우 + 더미 BOJ 핸들 sync, `/ps/search` 검색, `/exams` 에 수학 4단원 표시, `/review` 에 비수학 흔적 없음" ], "recentEdits": [ ".claude/sessions/.last_inbox_check", ".claude/state/plans-state.json", ".claude/state/tool-failure-counter.txt", ".claude/logs/subagent-history.jsonl", ".claude/state/breezing-timeline.jsonl", ".claude/state/compaction-events.jsonl", ".claude/state/contracts/7A.1.sprint-contract.json", ".claude/state/contracts/7A.2.sprint-contract.json", ".claude/state/contracts/7A.3.sprint-contract.json", ".claude/state/contracts/7A.4.sprint-contract.json", ".claude/state/contracts/7A.5.sprint-contract.json", ".claude/state/contracts/7A.6.sprint-contract.json", ".claude/state/contracts/7A.7.sprint-contract.json", ".claude/state/contracts/7A.8.sprint-contract.json", ".claude/state/contracts/7D.1.sprint-contract.json", ".claude/state/handoff-artifact.json", ".claude/state/phase7d-base-ref", ".claude/state/precompact-snapshot.json", ".claude/state/task-quality-gate.json", ".claude/state/worktree-info.json" ], "metrics": null }