diff --git a/.agents/skills/api/timo-api-integration/SKILL.md b/.agents/skills/api/timo-api-integration/SKILL.md new file mode 100644 index 00000000..0fa82bd0 --- /dev/null +++ b/.agents/skills/api/timo-api-integration/SKILL.md @@ -0,0 +1,77 @@ +# timo-api-integration + +## 트리거 + +- "API 연동해줘 / 연동 세팅하자" +- "orval / 제너레이터 나온 거 붙여줘" +- mock 데이터를 실제 API 호출로 교체하는 작업 + +## 참조 + +- `docs/architecture/state.md` → 서버 상태는 React Query가 단일 출처 +- `docs/architecture/structure.md` → `_queries/` 위치 규칙 +- `apps/timo-web/api/generated/` → orval이 생성한 훅·zod 스키마·모델 (직접 수정 금지) +- `apps/timo-web/api/client/custom-instance.ts`, `apps/timo-web/api/client/axios.ts` → axios 인스턴스, 에러 인터셉터 +- `apps/timo-web/api/error/api-error.ts` → `ApiError`, `parseApiError` + +## 배경 + +- `api/generated/endpoints/{domain}/{domain}.ts` — orval이 생성한 react-query 훅(`useGetXxx`)과 순수 fetcher 함수, `queryKey` 헬퍼, `getXxxQueryOptions` 팩토리 +- `logout`/`withdraw`/`deleteTodo`/`deleteTag`/`completeOnboarding`처럼 실제로 반환 데이터가 없는 엔드포인트는 백엔드가 `data`를 Java `Object`(빈 스키마)로 선언해서 여전히 `data: zod.unknown().optional()`로 생성된다 — 이건 버그가 아니라 정상이다(정말로 shape이 없는 값이므로). +- `api/generated/models/*.ts` — 응답 DTO 타입. 실제로 항상 오는 필드는 이제 `?` 없이 생성된다. 다만 gen 스키마는 백엔드가 스펙을 바꿀 때마다 사람 리뷰 없이 재생성되므로 100% 맹신하지는 않는다. +- 그래도 도메인 로컬 zod 스키마(`app/(domain)/_types/*.ts`)로 한 번 더 검증한다 — 로컬 스키마의 `.parse()`가 "백엔드가 계약을 조용히 느슨하게 바꿨는지"를 잡아내는 방어선 역할을 한다. 로컬 스키마 작성 시 gen 응답 스키마를 기반 초안으로 그대로 가져다 쓰고, UI가 추가로 좁혀야 하는 부분(더 좁은 enum, 파생 필드 등)만 수정한다. + +## 워크플로우 + +### Phase 1 — 대상 엔드포인트 확인 + +- `/v3/api-docs`에서 대상 성공 응답의 `application/json` content type과 필드별 `required`를 확인한다. +- 확인 결과를 반영하도록 `pnpm gen:api`를 실행한 뒤 생성된 모델·Zod 스키마를 검토한다. +- `api/generated/endpoints/{domain}/{domain}.ts`에서 필요한 fetcher 함수(예: `getHome`)와 `getGetXxxQueryKey` 헬퍼를 찾는다. +- 응답이 `BaseResponseXxx { status, message, data }` 형태로 감싸져 있는지 `api/generated/models/`에서 확인한다 — 실제 필요한 값은 `.data`에 있다. + +### Phase 2 — 로컬 zod 스키마로 응답 검증 + +- 도메인 `_types/*.ts`에 이미 손으로 작성한 zod 스키마가 있는지 확인한다. 없으면 생성된 응답 zod 스키마(`api/generated/endpoints/{domain}/{domain}.zod.ts`)를 기반 초안으로 가져와 작성한다 — 이제 실제 shape을 담고 있으므로 신뢰할 수 있다. +- gen 스키마와 UI가 실제로 필요로 하는 shape이 다르면(더 좁은 enum, 파생 필드 등) 로컬 스키마에서 추가로 좁힌다. +- `select`에서 로컬 스키마의 `.parse()`/`.safeParse()`로 최종 검증한다 — gen 스키마를 UI까지 직접 노출하지 않고 반드시 로컬 스키마를 한 번 거친다. 백엔드가 스펙을 다시 느슨하게 바꿔도 이 단계가 조용히 깨지지 않고 파싱 실패로 드러나게 하는 방어선이다. + +### Phase 3 — `_queries/` 훅 작성 + +- 위치: `app/(domain)/_queries/use-xxx.ts` +- `useSuspenseQuery`를 쓸지 `useQuery`를 쓸지는 컴포넌트가 `AsyncBoundary`(Suspense) 안에 있는지로 결정한다. `AsyncBoundary`로 감싸져 있으면 `useSuspenseQuery`를 쓴다. +- **주의**: 생성된 `getGetXxxQueryOptions()` 팩토리를 그대로 `useSuspenseQuery`에 스프레드하면 `UseQueryOptions`가 허용하는 `skipToken`과 `UseSuspenseQueryOptions`가 타입 충돌을 일으킨다. `queryKey`/`queryFn`은 생성된 `getGetXxxQueryKey` + 원본 fetcher 함수로 직접 조립한다. +- `select`에서 `BaseResponse.data`를 언랩하고 로컬 zod 스키마로 `.parse()`해서 반환한다. + +```ts +"use client"; + +import { useSuspenseQuery } from "@tanstack/react-query"; + +import { + getGetHomeQueryKey, + getHome, +} from "@/api/generated/endpoints/home/home"; +import { homeViewDataSchema } from "@/app/[locale]/(main)/(with-time-sidebar)/home/_types/home-view-type"; + +export const useHomeView = ({ filter, baseDate }: GetHomeViewParams) => + useSuspenseQuery({ + queryKey: getGetHomeQueryKey({ filter, baseDate }), + queryFn: ({ signal }) => getHome({ filter, baseDate }, undefined, signal), + select: ({ data }) => homeViewDataSchema.parse(data), + }); +``` + +### Phase 4 — 컨테이너 연결 + +- mock 함수 import를 제거하고 새 훅으로 교체한다. +- 다른 도메인이 같은 mock을 참조하고 있는지 확인(`Grep`)하고, 참조 중이면 mock 파일 자체는 지우지 않는다. +- 로그인 연동 전이라 요청이 401/네트워크 에러로 실패할 수 있다 — `useSuspenseQuery`가 던지는 에러는 상위 `error.tsx`(라우트 레벨) 또는 `AsyncBoundary`의 `errorFallback`이 잡는지 확인한다. 둘 다 없으면 최소한 라우트 레벨 `error.tsx` 존재 여부를 확인해 화면이 완전히 깨지지 않게 한다. + +### Phase 5 — 자가 검토 + +- [ ] `api/generated/` 내부 파일을 직접 수정하지 않았는가 (재생성 시 사라짐) +- [ ] 응답을 로컬 zod 스키마로 `.parse()`해서 검증했는가 (gen 응답 스키마를 그대로 UI까지 노출하지 않았는가) +- [ ] `useSuspenseQuery` 사용 시 `queryKey`/`queryFn`을 직접 조립해 타입 충돌을 피했는가 +- [ ] `tsc --noEmit`, `eslint` 통과 +- [ ] 다른 도메인이 참조하는 mock을 실수로 지우지 않았는가 diff --git a/.agents/skills/git/timo-commit/SKILL.md b/.agents/skills/git/timo-commit/SKILL.md index ab413a29..01c9cff9 100644 --- a/.agents/skills/git/timo-commit/SKILL.md +++ b/.agents/skills/git/timo-commit/SKILL.md @@ -1 +1,81 @@ - +# timo-commit + +## 트리거 + +- "커밋해줘", "커밋 만들어줘", "commit" +- "staged 변경 정리해줘", "변경사항 나눠줘" + +## 참조 + +- `docs/conventions/commit.md` → 커밋 타입·스코프·형식·규칙 + +## 핵심 원칙 + +1. **secret guard 먼저**: `.env`, 비밀번호, API 키, 토큰이 스테이징되어 있으면 즉시 멈추고 알린다. +2. **atomic 단위 분리**: 논리적으로 무관한 변경은 별도 커밋으로 나눈다. +3. **2단계 진행**: Phase 1(계획표 출력 → 승인) → Phase 2(실행). 승인 없이 커밋하지 않는다. +4. **push는 명시적 요청 시에만**: 커밋까지만 하고, push는 사용자가 동의하면 수행한다. + +--- + +## Phase 1 — 분석 및 계획 수립 + +1. `git status`와 `git diff --cached`로 스테이징된 변경 확인. +2. **secret guard**: `.env`, `password`, `api_key`, `secret`, `token` 패턴 검색 — 발견 시 즉시 중단. +3. 변경된 파일들을 도메인/기능/계층 단위로 커밋 그룹 분류. +4. 아래 형식의 계획표를 출력하고 **사용자 승인 대기**. + +### 계획표 출력 형식 + +``` +## 커밋 계획 + +### 커밋 1: feat(web): 소셜 로그인 컴포넌트 추가 (#5) +- apps/timo-web/src/components/SocialLogin/index.tsx +- 소셜 로그인 UI 컴포넌트를 추가했습니다 + +### 커밋 2: chore(root): turbo filter 스크립트 추가 +- package.json +- dev:web, build:web 등 필터 스크립트를 추가했습니다 + +계속 진행할까요? +``` + +--- + +## Phase 2 — 커밋 실행 + +승인 후 각 그룹별 `git add <파일>` → `git commit -m` 순으로 실행한다. +완료 후 `git log --oneline -n <커밋 수>`로 결과 요약 출력. + +--- + +## 계층별 분리 원칙 + +같은 기능이라도 계층이 다르면 **별도 커밋**으로 분리한다: + +1. **의존성 변경**: `package.json`, `pnpm-lock.yaml` → `chore` +2. **핵심 구현**: 컴포넌트, 훅, 유틸리티 → `feat` / `fix` / `refactor` +3. **설정/라우팅**: 라우트 등록, 설정 파일 → `chore` / `feat` + +--- + +## 커밋 메시지 형식 + +→ `docs/conventions/commit.md` 참조 (타입 목록·스코프 기준·형식·규칙) + +핵심 요약: + +- 형식: `타입(스코프): 한국어 제목 (#이슈번호)` +- 본문: 반드시 `~했습니다` 체로 종결 +- 대괄호 스코프 금지: `feat: [web] …` ❌ +- 이슈 번호 모르면 사용자에게 확인 + +--- + +## 금지 + +- `--no-verify` 플래그 사용 금지 (사용자가 요청해도 이유를 먼저 묻는다) +- 스테이징되지 않은 파일을 임의로 `git add` 하지 않는다 +- `.env`, 인증서, 토큰 파일을 커밋에 포함하지 않는다 +- 승인 없이 커밋을 실행하지 않는다 diff --git a/.agents/skills/git/timo-issue/SKILL.md b/.agents/skills/git/timo-issue/SKILL.md index c15b47b1..4781cfaf 100644 --- a/.agents/skills/git/timo-issue/SKILL.md +++ b/.agents/skills/git/timo-issue/SKILL.md @@ -1 +1,165 @@ - +# timo-issue + +## 트리거 + +다음 요청이 오면 이 스킬을 사용한다: + +- "이슈 만들어줘", "이슈 올려줘", "이슈 써줘" +- 새 기능 개발 또는 버그 수정 시작 전 + +인자 없이 실행하면 현재 대화 컨텍스트에서 작업 내용을 자동으로 추론한다. + +--- + +## 이슈 타입 + +→ `docs/conventions/issue-pr.md` 참조 (TYPE 목록·형식·규칙) + +PREFIX는 `[FEAT]`, `[FIX]`, `[CI]` 등 작업 성격에 따라 대문자로 자유롭게 지정한다. + +--- + +## 이슈 본문 템플릿 + +### Feature Request — feat / refactor / ui / style / docs / chore / ci / perf / init + +`.github/ISSUE_TEMPLATE/feature_request.md` 기반: + +```markdown +### 🛠️ 만들고자 한 기능 설명 + +{작업 목적과 내용을 2-3문장으로 서술} + +### ✅ TODO LIST + +- [ ] {세부 작업 1} +- [ ] {세부 작업 2} + +### ⏰ 예상 작업 기간 + +{컨텍스트 기반 예상 기간, 모르면 "-"} + +### 📝 참고 링크(선택) + +### 🗣️ ETC(선택) + +### 📸 피그마 스크린샷 +``` + +### Bug Report — fix + +`.github/ISSUE_TEMPLATE/bug_report.md` 기반: + +```markdown +## 어떤 버그인가요? + +{버그를 간결하게 설명} + +

+ +## 어떤 상황에서 발생한 버그인가요? + +- **Given**: {사전 조건} +- **When**: {어떤 행동을 했을 때} +- **Then**: {어떤 문제가 발생했는지} + +

+ +## 예상 결과 + +{정상적으로 동작했어야 할 결과} + +

+ +## 참고자료 + +{관련 스크린샷, 에러 로그 등 — 없으면 생략} +``` + +--- + +## 브랜치 네이밍 컨벤션 + +→ `docs/conventions/branch.md` 참조 (구조·타입·형식·규칙) + +``` +{type}/{scope}/{이슈번호}-{kebab-case-영어설명} + +feat/web/5-social-login +fix/web/7-redirect-error +refactor/ui/12-button-cleanup +chore/root/15-turbo-filter-scripts +``` + +--- + +## 워크플로우 + +### Phase 1 — 분석 및 계획 수립 + +1. 인자에서 타입과 설명 파싱. 없으면 현재 대화 컨텍스트에서 추론. +2. 타입에 맞는 템플릿 선택 후 내용 채우기. +3. `git config user.name`으로 작성자 확인. +4. 계획표 출력 후 **사용자 승인 대기**. + +``` +## 이슈 생성 계획 + +타입: feat +제목: [FEAT] 소셜 로그인 기능 구현 +Assignee: @me + +### 이슈 본문 미리보기 +--- +(본문 내용) +--- + +생성될 브랜치: feat/web/{번호}-social-login +Base 브랜치: develop + +계속 진행할까요? +``` + +### Phase 2 — 이슈 생성 + +```bash +gh issue create \ + --title "[FEAT] 소셜 로그인 기능 구현" \ + --body "$(cat <<'EOF' +... +EOF +)" \ + --assignee "@me" +``` + +출력된 이슈 URL에서 번호 파싱. + +### Phase 3 — 브랜치 생성 및 체크아웃 + +```bash +git checkout -b {type}/{scope}/{번호}-{description} origin/develop +``` + +``` +이슈 생성 완료: #{번호} — {제목} + URL: https://github.com/{repo}/issues/{번호} +브랜치 생성 완료: {type}/{scope}/{번호}-{description} + 이제 작업을 시작할 수 있습니다. +``` + +--- + +## 주의사항 + +- assignee는 항상 `@me` +- `gh` CLI 미인증 시 `gh auth login` 먼저 안내 +- 같은 이름의 브랜치가 이미 있으면 사용자에게 알리고 다른 이름 제안 +- `origin/develop`이 없는 경우 사용자에게 base 브랜치를 물어본다 + +--- + +## 금지 + +- 사용자 승인 없이 이슈를 생성하지 않는다 +- 브랜치 설명에 한국어를 그대로 쓰지 않는다 (반드시 영어 kebab-case 변환) +- `gh` 인증 상태를 확인하지 않고 실행하지 않는다 diff --git a/.agents/skills/git/timo-pr/SKILL.md b/.agents/skills/git/timo-pr/SKILL.md index c774be5f..d01fcf2e 100644 --- a/.agents/skills/git/timo-pr/SKILL.md +++ b/.agents/skills/git/timo-pr/SKILL.md @@ -1 +1,303 @@ - +# timo-pr + +## 트리거 + +- "PR 만들어줘", "PR 올려줘", "pull request" +- 브랜치 작업이 완료되고 `develop`으로 머지 준비가 된 시점 + +## 참조 + +- 템플릿: `.github/PULL_REQUEST_TEPLATE.md` +- 제목 컨벤션: `docs/conventions/issue-pr.md` + +## 핵심 원칙 + +1. **전체 커밋 히스토리 기준**: `develop` 브랜치에서 갈라진 전체 변경을 분석한다. 최신 커밋 하나만 보지 않는다. +2. **제목은 `[TYPE] 한국어 요약` 형식** +3. **근거 기반으로 쓴다**: screenshot, route, network, CI처럼 실제로 확인한 자료만 적는다. 확인하지 않은 증거를 만들지 않는다. +4. **구현 의도 설명**: 무엇을 바꿨는지는 diff가 대신한다. 왜 이 구조를 택했는지, 어떤 불일치를 줄였는지를 적는다. +5. **실제 검증과 계획된 검증을 분리**: 실행하지 않은 명령은 체크하거나 성공처럼 쓰지 않는다. +6. **base 브랜치 확인**: 기본값은 `develop`. 다르면 사용자에게 먼저 물어본다. + +--- + +## Phase 1 — 커밋 계획 + +PR 생성 전, 실제 diff를 기능/작업 단위로 분해한다. +**커밋 실행은 반드시 `timo-commit` 스킬을 따른다** (메시지 형식, secret guard, 승인 절차 포함). + +```bash +git log develop...HEAD --oneline --reverse +git diff develop...HEAD --stat +git diff develop...HEAD --name-only +``` + +### 별도 커밋 판단 기준 + +아래 중 하나라도 다르면 별도 커밋을 우선 고려한다: + +- 변경 surface가 다름 (UI / API / config / docs) +- reviewer가 확인할 관점이 다름 +- revert 단위가 다름 +- 검증 방식이 다름 +- 순수 refactor와 기능 변경이 섞임 + +커밋 계획 형식: + +``` +- Commit 1: `[TYPE] scope: 요약` + - files: 해당 파일 목록 + - reason: 이 단위로 나눈 이유 +- Commit 2: `[TYPE] scope: 요약` + - files: + - reason: +``` + +--- + +## Phase 2 — 컨텍스트 수집 + +브랜치 이름에서 `{type}`과 `{scope}` 추출 (`feat/web/5-social-login` → `FEAT`, `web`, `#5`) + +```bash +git branch --show-current +``` + +브랜치 push 여부 확인 → 없으면 `git push -u origin ` 수행. + +### PR 본문 근거 수집 + +PR 본문 작성 전에 아래 항목을 먼저 정리한다: + +- 기존 동작과 문제 원인 +- 구현 선택지와 실제 선택한 접근 +- 실행한 검증 명령 +- 관찰 가능한 증거 (browser route, screenshot, network, CI) +- 남은 리스크와 reviewer가 집중해서 봐야 할 지점 + +--- + +## Phase 3 — PR 생성 + +```bash +gh pr create \ + --title "[FEAT] 소셜 로그인 기능 구현" \ + --base develop \ + --body "$(cat <<'EOF' +## ISSUE 🔗 + +close #5 + +

+ +## What is this PR? 🔍 + +... + +

+ +## To Reviewers + +... + +## Screenshot 📷 + + + +

+ +## Test Checklist ✔ + +- [ ] 기능 동작 확인 +- [ ] 엣지 케이스 확인 +EOF +)" +``` + +--- + +## PR 제목 형식 + +``` +[TYPE] <전체 변경사항을 아우르는 한국어 요약> +``` + +TYPE은 작업 성격에 맞게 대문자로 자유롭게 지정한다. +(`[FEAT]`, `[FIX]`, `[REFACTOR]`, `[CI]`, `[INIT]` 등) + +--- + +## 본문 작성 규칙 + +`.github/PULL_REQUEST_TEPLATE.md`의 모든 섹션을 빠짐없이 채운다. + +### ISSUE 🔗 + +브랜치명에서 추출한 이슈 번호를 적는다. + +```markdown +close #5 +``` + +--- + +### What is this PR? 🔍 + +**변경 파일 목록으로 끝내지 않는다.** 아래 흐름으로 서술한다. + +#### 1. 첫 문단 — 전체 요약 + +이번 PR에서 무엇을 했는지 전체를 한두 문장으로 요약한다. + +#### 2. 배경 + +`### 배경` 헤더를 쓰고, 아래 세 항목을 불릿(`-`)으로 나열한다. 각 항목은 **굵게** 레이블을 붙인다. + +```markdown +### 배경 + +- **기존 구조**: 클라이언트에서 로그인 상태를 확인한 뒤 query가 실행되는 구조였습니다. +- **발생 문제**: 첫 진입 시 서버에서 목록 데이터를 준비하기 어려워 초기 렌더링이 빈 상태로 표시됐습니다. +- **해결 방향**: 인증 흐름을 서버 컴포넌트 단으로 올려 SSR이 가능한 구조로 전환했습니다. +``` + +피해야 할 예 (레이블 없이 완료 항목만 나열): + +``` +- 탐색 SSR 적용 +- query 파일 분리 +- build 성공 +``` + +#### 3. 변경 도메인별 섹션 + +변경된 도메인·컴포넌트·기능 단위로 `### 섹션명` 헤더를 나눠 서술한다. +각 섹션은 아래 네 항목을 불릿(`-`)으로 나열한다. 각 항목은 **굵게** 레이블을 붙인다. + +```markdown +### {도메인명} + +- **변경 요약**: 이 도메인에서 무엇을 바꿨는지 한 줄로 요약합니다. +- **이유**: 왜 이 변경이 필요했는지 — 기존 구조의 한계나 버그 원인을 구체적으로 설명합니다. +- **구현 방식**: 실제로 어떻게 동작하는지 메커니즘을 설명합니다. 핵심 설정값·데이터 흐름·패턴을 구체적으로 적고, 인터페이스가 비자명하면 코드 블록으로 보여줍니다. 대안을 검토했다면 선택하지 않은 이유도 함께 적습니다. +- **경계 · 제약**: server/client 경계, 모듈 분리 기준, 의도적으로 제외한 범위 등을 적습니다. (없으면 생략) +``` + +항목별 작성 기준: + +- **변경 요약**: 동사로 시작하는 단문. "~를 추가했습니다", "~를 분리했습니다" +- **이유**: "기존에는 ~였기 때문에", "~가 발생해서" 형식으로 원인을 명확히 +- **구현 방식**: "무엇을 했다"가 아니라 "어떻게 동작하는가"를 중심으로 쓴다. 핵심 설정값·데이터 흐름·동작 순서를 구체적으로 서술하고, 인터페이스나 설정이 비자명하면 짧은 코드 블록으로 보여준다. 대안을 검토했다면 선택하지 않은 이유도 함께 적는다. 긴 구현 전체는 붙이지 않는다. +- **경계 · 제약**: 런타임 경계(server/client), 모듈 분리 단위(shared/base/options 등), 중복 허용 이유 + +예시: + +```markdown +## What is this PR? 🔍 + +로그인 페이지의 반응형 레이아웃을 수정하고, 토큰 갱신 로직에서 발생하던 경쟁 조건을 해결했습니다. + +### 배경 + +- **기존 구조**: 리프레시 요청이 여러 탭에서 동시에 발생할 수 있는 구조였습니다. +- **발생 문제**: 중복 요청으로 인해 먼저 완료된 토큰이 덮어씌워지는 경쟁 조건이 간헐적으로 발생했습니다. +- **해결 방향**: 요청을 큐에 넣어 순차 처리하고, 서버·클라이언트가 동일한 query key를 공유하는 구조로 재편했습니다. + +### 로그인 페이지 + +- **변경 요약**: 모바일 환경에서 폼 컨테이너 최대 너비 기준을 통일했습니다. +- **이유**: 375px 이하에서 폼 너비가 뷰포트를 초과해 가로 스크롤이 발생했습니다. +- **구현 방식**: `max-w-sm` / `max-w-md`가 혼용된 컨테이너를 `max-w-lg w-full`로 통일했습니다. `w-full`로 뷰포트보다 좁으면 꽉 채우고, `max-w-lg`로 넓을 때는 상한에서 중앙 정렬되도록 합니다. 브레이크포인트마다 다른 max-w를 주는 방법(`sm:max-w-md lg:max-w-lg`)도 검토했으나, 디자인 토큰 기준과 맞지 않아 제외했습니다. + +### 토큰 갱신 + +- **변경 요약**: 리프레시 요청 중복 실행을 큐 방식으로 직렬화했습니다. +- **이유**: 동시 요청이 발생하면 먼저 완료된 토큰이 이후 응답으로 덮어씌워져 인증이 끊기는 문제가 있었습니다. +- **구현 방식**: `isRefreshing` 플래그로 리프레시 진행 여부를 추적합니다. 리프레시 중에 들어오는 요청은 Promise 형태로 `failedQueue`에 쌓아 두고, 리프레시 완료 후 성공이면 queue 전체를 resolve, 실패면 reject해 대기 요청을 일괄 처리합니다. interceptor 반환값을 Promise로 감싸야 axios가 해당 요청의 응답을 리프레시 이후로 미룰 수 있습니다. +- **경계 · 제약**: query key를 options / server / shared 세 단위로 분리해 서버 컴포넌트와 클라이언트가 같은 cache를 이어받을 수 있도록 했습니다. 리프레시 실패 시 fallback은 이번 PR 범위 밖이며 후속 작업으로 남깁니다. +``` + +--- + +### To Reviewers + +reviewer가 중점적으로 봐야 할 지점을 좁혀 준다. 별도 항목 구조 없이 본문 안에 자연스럽게 녹여 쓴다. + +적을 내용: + +- 판단이 필요한 구조 선택 +- 서버/클라이언트 import boundary가 올바른지 +- 현재 구현이 기대는 정책 또는 전제 +- 남은 리스크, 의도적으로 제외한 범위 +- 임시 fixture, mock, TODO + +예시: + +```markdown +## To Reviewers + +interceptor 내부 로직이 조금 복잡해졌는데 한번 봐주세요. +AccessToken을 browser에서 읽는 전제가 현재 인증 정책과 맞는지 확인 부탁드립니다. +리프레시 실패 시 fallback 처리는 이번 PR 범위에서 제외했으며 후속 작업으로 남깁니다. +``` + +--- + +### Screenshot 📷 + +UI 변경이 있으면 `| 컴포넌트 | 화면 |` 표로 정리한다. +UI 변경이 없으면 주석(``)만 유지한다. + +```markdown +| 컴포넌트 | Before | After | +| --------------- | ---------- | ---------- | +| `LoginForm.tsx` | (스크린샷) | (스크린샷) | +``` + +--- + +### Test Checklist ✔ + +**실제 실행한 명령만 체크**한다. 실행하지 못한 항목은 unchecked로 두거나 "미실행: 이유"를 적는다. + +```markdown +## Test Checklist ✔ + +- [x] `pnpm lint` 통과 +- [x] `pnpm check-types` 통과 +- [x] 로그인 페이지 모바일(375px) 레이아웃 확인 +- [ ] `pnpm build` — 미실행: CI에서 확인 예정 +- [ ] 토큰 갱신 경쟁 조건 재현 테스트 — 후속 작업 +``` + +--- + +## 작성 스타일 + +- **서술형**: "~했습니다", "~되었습니다" 체 사용. 명사형·축약형 금지. + - 나쁜 예: "토큰 갱신 로직 수정" → 좋은 예: "토큰 갱신 로직을 수정했습니다" +- **불필요한 수식어 제거**: "~를 진행했습니다", "~에 대해서" 금지. +- **없는 증거를 만들지 않는다**: screenshot, trace, network log가 없으면 있다고 쓰지 않는다. +- **local production 측정과 dev server 확인을 구분**한다. + +--- + +## 사전 검증 + +PR 본문 완성 전, 아래가 통과됐는지 확인한다. 미통과 시 먼저 수정을 권고한다: + +```bash +pnpm check-types +pnpm lint +``` + +--- + +## 금지 + +- PR을 merge하지 않는다. +- force-push로 base 브랜치를 덮어쓰지 않는다. +- `[TYPE]` 형식을 반드시 대괄호로 감싸 사용한다. +- reviewer, milestone은 사용자가 명시한 경우에만 추가한다. +- 실행하지 않은 명령을 체크하거나 성공처럼 기록하지 않는다. +- 없는 증거(screenshot, CI 결과, metric)를 있는 것처럼 쓰지 않는다. diff --git a/.agents/skills/meta/timo-manage/SKILL.md b/.agents/skills/meta/timo-manage/SKILL.md index 6ea926ee..fee4f388 100644 --- a/.agents/skills/meta/timo-manage/SKILL.md +++ b/.agents/skills/meta/timo-manage/SKILL.md @@ -1 +1,111 @@ - +# timo-manage + +## 트리거 + +- "스킬 추가해줘 / 수정해줘 / 없애줘" +- 같은 작업 흐름이 반복되어 스킬화가 필요해진 경우 +- 기존 스킬의 Phase나 참조가 더 이상 맞지 않을 때 + +--- + +## 참조 + +- `AGENTS.md` → 트리거 매핑 테이블 (스킬 추가·수정·삭제 시 갱신 대상) + +--- + +## 언제 스킬로 만들 것인가 + +아래 중 2개 이상이면 스킬화 가치가 있다: + +- 같은 흐름으로 3번 이상 반복 요청이 왔다 +- 특정 순서를 지켜야 실수가 줄어드는 작업이다 +- 참조해야 할 문서·파일 경로가 명확히 존재한다 +- Phase가 자연스럽게 2개 이상으로 나뉜다 + +하나짜리 명령("린트 실행해줘")은 스킬보다 AGENTS.md 메모로 충분하다. + +--- + +## 좋은 트리거 설계 + +- 사용자가 실제로 쓰는 자연어를 넣는다 ("PR 만들어줘", "리뷰해줘") +- 영어·한국어 변형을 둘 다 넣는다 +- 과도하게 세분화하지 않는다 — 트리거가 겹치면 `timo-orchestrate`로 분기 + +--- + +## SKILL.md 기본 형식 + +```markdown +# timo-{name} + +## 트리거 + +- "{자연어 요청 예시}" +- {언제 이 스킬을 쓰는지 한 줄} + +## 참조 + +- `{경로}` → {무엇을 참조하는지} + +## Phase 1 — {단계 이름} + +{이 단계에서 무엇을 확인하거나 실행하는지} + +## Phase N — {단계 이름} + +{완료 조건 또는 출력 형식} +``` + +--- + +## Phase 1 — 작업 유형 파악 + +요청에서 작업 유형(추가·수정·폐기)과 대상 스킬명을 확인한다. + +**추가**: 반복 패턴이 맞는지, 기존 스킬과 겹치지 않는지 먼저 확인한다. +**수정**: 해당 SKILL.md 읽고 어떤 Phase·참조가 달라져야 하는지 파악한다. +**폐기**: 대체 스킬이 있는지, 없으면 AGENTS.md에서 트리거만 제거할지 판단한다. + +--- + +## Phase 2 — SKILL.md 작업 + +**추가** + +1. 카테고리 결정: `git/`, `ui/`, `quality/`, `meta/` +2. `.agents/skills/{category}/timo-{name}/SKILL.md` 생성 +3. 위 기본 형식으로 작성, 트리거·참조·Phase 순서 준수 + +**수정** + +1. 해당 SKILL.md 를 읽고 변경 내용 반영 +2. 참조 경로가 존재하는지 확인 (`docs/` 경로 변경 여부) + +**폐기** + +1. SKILL.md 상단에 `> ⚠️ DEPRECATED — {대체 스킬명} 사용` 표시 +2. AGENTS.md 트리거 행 제거 + +--- + +## Phase 3 — AGENTS.md 갱신 + +| 작업 | AGENTS.md 변경 | +| ---- | ------------------------------------------- | +| 추가 | 트리거 표에 새 행 추가 (트리거 → 스킬 경로) | +| 수정 | 라우팅 변경 있으면 해당 행 갱신 | +| 폐기 | 트리거 행 제거 | + +--- + +## Phase 4 — 스킬 품질 체크 + +완성 후 아래를 확인한다: + +- [ ] 트리거가 자연어로 쓰여 있고 실제 요청과 일치하는가 +- [ ] Phase가 2개 이상이고 각 단계가 구체적인가 +- [ ] 참조 파일 경로가 실제로 존재하는가 (`docs/conventions/`, `docs/architecture/` 등) +- [ ] AGENTS.md에 트리거가 등록됐는가 +- [ ] 완료 후 `timo-commit`으로 커밋할지 사용자에게 확인 diff --git a/.agents/skills/meta/timo-orchestrate/SKILL.md b/.agents/skills/meta/timo-orchestrate/SKILL.md index 6aa3ada6..c9e9fe02 100644 --- a/.agents/skills/meta/timo-orchestrate/SKILL.md +++ b/.agents/skills/meta/timo-orchestrate/SKILL.md @@ -1 +1,75 @@ - +# timo-orchestrate + +## 트리거 + +- 요청 하나가 2개 이상의 스킬을 순서대로 필요로 할 때 +- "처음부터 끝까지 만들어줘", "기능 전체 작업해줘" +- 어떤 스킬로 시작해야 할지 판단이 필요할 때 + +단일 스킬로 해결되는 요청("커밋해줘", "리뷰해줘")은 바로 해당 스킬을 실행한다. + +--- + +## 실행 전 확인 + +아래가 없으면 시작 전에 먼저 물어본다: + +- 무엇을 만들거나 고칠 것인지 (목표) +- 어느 위치에 들어갈지 (`timo-web` / `timo-design-system` / 루트) +- 이슈 번호 (커밋·PR 이 필요한 경우) + +피그마 링크, API 문서, 스크린샷이 있으면 바로 활용한다. + +--- + +## 자주 쓰는 스킬 체인 + +| 상황 | 실행 순서 | +| --------------------------------- | ------------------------------------------------------------------------------------ | +| 새 기능 처음부터 끝까지 | `timo-issue` → 구현 스킬 → `timo-review` → `timo-verify` → `timo-commit` → `timo-pr` | +| 피그마 디자인 → 컴포넌트 + 스토리 | `timo-figma` → `timo-component` → `timo-storybook` → `timo-commit` | +| 코드 구조 개선 후 PR | `timo-refactor` → `timo-review` → `timo-verify` → `timo-commit` → `timo-pr` | +| 이슈 없이 빠른 버그 픽스 | `timo-component` 또는 `timo-page` → `timo-verify` → `timo-commit` | +| 페이지 + 데이터 연동 | `timo-issue` → `timo-page` → `timo-review` → `timo-commit` | + +구현 스킬은 작업 유형에 따라 `timo-component` / `timo-page` / `timo-figma` 중 하나를 선택한다. + +--- + +## Phase 1 — 스킬 체인 도출 + +요청에서 작업 단위를 분해하고 실행 순서를 결정한다. + +```text +예시: "로그인 페이지 만들고 커밋까지" + +1. timo-issue → 이슈 생성 + 브랜치 생성 +2. timo-page → 라우트·컴포넌트 구현 +3. timo-review → 코드 리뷰 +4. timo-verify → 타입·린트·빌드 확인 +5. timo-commit → 커밋 +6. timo-pr → PR 작성 +``` + +## Phase 2 — 계획 확인 + +아래 형식으로 계획을 출력하고 사용자 승인을 받는다. + +```text +## 작업 계획 + +목표: {요청 내용 한 줄 요약} +위치: {대상 패키지/경로} + +실행 순서: +1. timo-xxx — 무엇을 하는 단계인지 +2. timo-xxx — 무엇을 하는 단계인지 + +시작할까요? +``` + +## Phase 3 — 순차 실행 + +각 스킬의 Phase 절차를 그대로 따른다. +블로커(빌드 실패, 미결 리뷰 이슈 등)가 생기면 즉시 멈추고 사용자에게 알린다. +모든 스킬 완료 후 변경 파일 목록과 결과를 한 줄로 요약한다. diff --git a/.agents/skills/quality/timo-refactor/SKILL.md b/.agents/skills/quality/timo-refactor/SKILL.md index a6935bbc..f34a8e48 100644 --- a/.agents/skills/quality/timo-refactor/SKILL.md +++ b/.agents/skills/quality/timo-refactor/SKILL.md @@ -1 +1,119 @@ - +# timo-refactor + +## 트리거 + +- "리팩터링 필요해? / 이 코드 개선해줘" +- `timo-review` 결과에서 BLOCK 이슈가 구조 문제일 때 +- 기능 추가 없이 코드 구조 개선이 목적인 경우 + +## 참조 + +- `docs/conventions/code-style.md` → 함수·파일 크기 기준 +- `docs/architecture/components.md` → 컴포넌트 계층 +- `docs/architecture/structure.md` → 모듈 경계 + +--- + +## Phase 1 — 필요성 판단 + +아래 중 하나라도 해당하면 리팩터링을 권고한다: + +| 기준 | 판단 방법 | +| ------------------------------------ | ------------------------------------------------ | +| 함수 50줄 초과 | 파일 직접 확인 | +| 파일 800줄 초과 | `wc -l` 또는 에디터 라인 수 확인 | +| 동일 로직 3곳 이상 중복 | `git grep` 으로 패턴 검색 | +| `_components`에 클라이언트 로직 혼재 | `'use client'`, useQuery, zustand 임포트 여부 | +| 컴포넌트 계층 위반 | `packages/timo-design-system` → `apps` 참조 여부 | + +--- + +## Phase 2 — 범위 정의 + +리팩터링 전, 아래를 명확히 한다: + +- 기능 동작은 변경하지 않는다 +- 테스트가 있다면 리팩터링 후에도 통과해야 한다 +- 커밋 단위: 리팩터링 커밋에 기능 변경을 섞지 않는다 + +--- + +## Phase 3 — 추출 패턴 선택 + +### 컴포넌트 분리 + +`_components` → `_containers` 분리가 필요한 경우: + +```text +Before: _components/LoginForm.tsx (useQuery, zustand 혼재) +After: _components/LoginForm.tsx (props만 받는 순수 UI) + _containers/LoginFormContainer.tsx ('use client', useQuery 담당) +``` + +### 쿼리 코로케이션 + +여러 도메인에서 쓰이지 않는 쿼리는 도메인 내부로: + +```text +Before: queries/useLoginMutation.ts +After: app/auth/_queries/useLoginMutation.ts +``` + +### 훅 추출 + +컴포넌트 내 로직이 복잡할 때: + +```text +Before: 컴포넌트 안에 useEffect, 상태, 파생 계산이 뒤섞임 +After: _hooks/useLoginForm.ts 로 분리, 컴포넌트는 JSX만 반환 +``` + +### 공통 유틸 추출 + +같은 변환·포맷 로직이 3곳 이상이면 아래 기준으로 위치를 정한다: + +- **공통** (여러 도메인에서 재사용 가능) → `apps/timo-web/utils`(함수) 또는 `apps/timo-web/constants`(상수)로 추출 +- **도메인 종속** (특정 기능에서만 사용) → 해당 도메인의 `_utils` 폴더 안에 유지 +- 새로 만들거나 옮길 때는 JSDoc을 반드시 작성한다 + - **함수(utils)**: 설명, `@param`, `@returns`, 필요 시 `@example` + - **상수(constants)**: 설명, 단위(있는 경우), 사용 범위(어디서/왜 쓰이는지) + +```text +Before: apps/timo-web/app/[locale]/(main)/focus/_utils/duration.ts + (SECONDS_PER_MINUTE, convertDurationToTimeText 로컬 정의 — home/timer 등 다른 도메인에도 유사 로직 중복) +After: apps/timo-web/constants/time.ts (SECONDS_PER_MINUTE 등 공통 상수) + apps/timo-web/utils/convert-duration-to-time-text.ts (convertDurationToTimeText, JSDoc 포함 공통 함수) +``` + +--- + +## Phase 4 — 실행 계획 제안 + +변경할 파일, 추출할 단위, 커밋 분리 계획을 제안하고 승인 후 실행한다. + +```text +## 리팩터링 계획 + +대상: apps/timo-web/app/auth/_components/LoginForm.tsx +이유: 'use client' + useQuery + 순수 UI가 혼재 (75줄) + +변경: +1. LoginForm.tsx → props 전용 순수 컴포넌트로 축소 +2. LoginFormContainer.tsx 신규 생성 (클라이언트 로직 이전) + +커밋 단위: +- refactor(web): LoginForm 컴포넌트·컨테이너 분리 (#이슈번호) + +진행할까요? +``` + +--- + +## Phase 5 — 안전 체크 + +실행 후: + +- [ ] 화면 동작이 리팩터링 전과 동일한가 +- [ ] `pnpm check-types` 통과 +- [ ] `pnpm lint` 통과 +- [ ] 의도치 않게 변경된 파일이 없는가 (`git diff`) diff --git a/.agents/skills/quality/timo-review/SKILL.md b/.agents/skills/quality/timo-review/SKILL.md index 65488d2c..4c8d080b 100644 --- a/.agents/skills/quality/timo-review/SKILL.md +++ b/.agents/skills/quality/timo-review/SKILL.md @@ -1 +1,103 @@ - +# timo-review + +## 트리거 + +- "리뷰해줘 / 코드 리뷰" +- PR 올리기 전 또는 구현 완료 후 검토 요청 + +## 참조 + +- `docs/conventions/code-style.md` → 코드 스타일 규칙 +- `docs/architecture/components.md` → 컴포넌트 계층 +- `docs/architecture/structure.md` → 모듈 경계 + +--- + +## Phase 1 — 변경 범위 파악 + +```bash +git diff develop...HEAD --name-only +git diff develop...HEAD --stat +``` + +파일 분류 후 각 카테고리별로 집중 체크한다: + +- `app/` 하위 → 라우트·컴포넌트 계층 +- `stores/`, `queries/` → 상태 관리 패턴 +- `packages/timo-design-system/` → 모듈 경계 + +--- + +## Phase 2 — 타입 안전성 + +- `any` 사용 여부 +- Props 타입 누락 여부 (`interface XxxProps` 확인) +- 외부 API 응답을 `unknown` 없이 직접 사용하는 경우 +- exported 함수에 반환 타입 누락 + +--- + +## Phase 3 — 스택별 패턴 체크 + +### Next.js / 컴포넌트 계층 + +- `page.tsx`에 비즈니스 로직이 들어가 있는가 +- `_components/`에 `'use client'`, useQuery, zustand가 섞인 컴포넌트가 있는가 → `_containers/`로 분리 대상 +- Server Component에서 클라이언트 전용 API 호출 여부 + +### React Query + +- 서버 데이터를 Zustand store에 복사하는 패턴 (`set(state => { state.users = data })` 등) +- 동일한 queryKey를 서버/클라이언트에서 다르게 정의하는 경우 +- `useQuery` 결과를 useState에 재저장하는 패턴 + +### Zustand + +- 파생 값을 store에 저장 (selector로 계산해야 할 값) +- store 내부에서 직접 fetch 호출 + +### 모듈 경계 + +- `packages/timo-design-system` → `apps/timo-web` import 여부 (역방향 참조 금지) +- 도메인 간 `_components` 직접 import 여부 + +--- + +## Phase 4 — 코드 스타일 + +- Arrow function + Named export 준수 여부 +- 이벤트 핸들러에 `handle` 접두사 외 다른 네이밍 +- 함수 50줄 초과 / 파일 800줄 초과 +- 절대 경로 import 미사용 + +--- + +## Phase 5 — 접근성 + +- 시맨틱 태그 대신 `div`만 쓰는 구조 +- 텍스트 없는 버튼에 `aria-label` 누락 +- `` ↔ `