주신 정보로 초안을 쓸 수 있어서 질문 없이 바로 만들었습니다. 정보에 없는 것은 지어내지 않고 「확인 필요」로 남겼습니다.
1. 파일 목록
| 파일 이름 | 적용 방식 | 한 줄 설명 |
|---|
.cursor/rules/project.mdc | 항상 | 프로젝트 설명, 스택과 버전, 폴더, 명령, 꼭 지킬 규칙 |
.cursor/rules/ui.mdc | 특정 파일 (app/**/*.tsx, components/**/*.tsx) | 서버·클라이언트 컴포넌트 나누기, export 예외, 화면의 날짜 표시 |
.cursor/rules/api.mdc | 특정 파일 (app/api/**) | 권한 확인, 취소 마감을 서버 시각으로 막기 |
.cursor/rules/date-time.mdc | 필요할 때 | 날짜·시간을 계산하거나 보여 줄 때의 세부 규칙 |
.cursor/rules/tests.mdc | 특정 파일 (tests/**) | 시각 고정, 3시간 경계와 시간대 경계 테스트 |
이렇게 나눴습니다.
- 꼭 지킬 규칙과, 새 파일을 만들 때도 지켜야 하는 줄(export, 날짜, DB, 테스트 위치)은 항상 규칙에 넣었습니다. globs 규칙은 맞는 파일이 대화에 들어와 있어야 붙기 때문입니다.
lib/ 와 prisma/ 는 globs 규칙을 따로 두지 않았습니다. 지킬 것(date.ts·db.ts)이 이미 항상 규칙에 있고, 그 폴더만의 규칙은 아직 없습니다.
@경로 는 조건부 규칙에만 걸었습니다. @ 로 건 파일은 규칙이 붙을 때 같이 들어가서, 항상 규칙에 걸면 모든 채팅이 그 파일을 싣게 됩니다.
2. 파일 내용
.cursor/rules/project.mdc
---
description: 필라테스 예약 웹앱의 개요, 스택, 폴더, 명령, 꼭 지킬 규칙
globs:
alwaysApply: true
---
# 프로젝트
- 동네 필라테스 스튜디오 예약 웹앱(예시용 가상 서비스). 회원은 수업을 예약하고 취소하며, 강사는 출석을 체크한다
# 스택
- Next.js 15(App Router), TypeScript 5, Tailwind CSS 4, Prisma 6, PostgreSQL, Vitest
- 새 파일은 `.ts`·`.tsx` 로 만든다. `.js`·`.jsx` 파일을 새로 만들지 않는다
- `params`·`searchParams`·`cookies()`·`headers()` 는 Promise 다. `await` 한 뒤 쓴다
- 이유: Next.js 15 에서 비동기로 바뀌었는데 예전 방식 코드가 자주 나온다
- `package.json` 에 의존성을 더하기 전에 먼저 묻는다
# 폴더
- `app/`: 화면과 라우트
- `app/api/`: 서버 API
- `components/`: 공용 UI
- `lib/`: DB(`db.ts`), 인증(파일 이름 「확인 필요」), 날짜(`date.ts`) 함수
- `prisma/schema.prisma`: DB 스키마
- `tests/`: Vitest 테스트
- 이 목록에 없는 최상위 폴더는 만들기 전에 묻는다
# 명령
- 개발 서버: `npm run dev`
- 테스트: `npm run test`
- 린트: `npm run lint`
- 일을 마쳤다고 하기 전에 `npm run test` 와 `npm run lint` 를 돌리고 결과를 함께 알린다
- 빌드, 타입 검사, DB 마이그레이션 명령: 「확인 필요」
# 꼭 지킬 규칙
- 서버 컴포넌트가 기본이다. `'use client'` 는 `useState`·`useEffect` 같은 훅, `onClick` 같은 이벤트 핸들러, 브라우저 API 를 쓰는 파일에만 붙인다
- 날짜·시간 계산은 `lib/date.ts` 의 함수로만 한다. 필요한 함수가 없으면 `lib/date.ts` 에 더한다
- 이유: 시간대 버그가 두 번 났다
- DB 는 `lib/db.ts` 를 거쳐서만 쓴다. `new PrismaClient()` 는 `lib/db.ts` 에만 둔다
- 예약 취소는 수업 시작 3시간 전까지만 받는다(정확히 3시간 전을 받을지는 「확인 필요」). 판단은 서버(`app/api/`)에서 한다
- named export 만 쓴다
- `export default` 는 Next.js 가 요구하는 파일에만 쓴다: `app/` 의 `page.tsx`·`layout.tsx`·`loading.tsx`·`error.tsx`·`not-found.tsx` 처럼 이름이 정해진 파일, `next.config` 같은 설정 파일
- 새 기능에는 같은 변경 안에 `tests/` 에 테스트를 더한다. 테스트 파일을 소스 옆에 두지 않는다
.cursor/rules/ui.mdc
---
description:
globs: app/**/*.tsx,components/**/*.tsx
alwaysApply: false
---
# 화면: app/ 의 .tsx, components/
- `page.tsx`·`layout.tsx` 에는 `'use client'` 를 붙이지 않는다. 버튼·폼처럼 상호작용이 필요한 부분만 클라이언트 컴포넌트로 떼어 낸다
- `error.tsx` 는 예외로 `'use client'` 를 붙인다. Next.js 가 요구한다
- `'use client'` 파일은 `lib/db.ts` 를 import 하지 않는다. 데이터는 서버 컴포넌트에서 읽어 props 로 넘긴다
- 컴포넌트는 함수로 만들고 named export 로 내보낸다. class 컴포넌트를 쓰지 않는다
- 화면에 보여 줄 날짜·시각 문자열도 `lib/date.ts` 의 함수로 만든다. `toLocaleString()`·`toLocaleDateString()` 을 직접 부르지 않는다
- 이유: 서버와 브라우저의 시간대가 다르면 같은 수업이 다른 시각으로 보인다
- `tailwind.config` 파일(.js·.ts)을 새로 만들지 않는다
- 이유: Tailwind 4 는 설정을 CSS 파일에 둔다
# 본보기
- 서버 컴포넌트 화면: 「확인 필요」
- 클라이언트 컴포넌트: 「확인 필요」
.cursor/rules/api.mdc
---
description:
globs: app/api/**
alwaysApply: false
---
# 서버 API: app/api/
- 핸들러는 `route.ts` 에서 `GET`·`POST` 같은 HTTP 메서드 이름으로 내보낸다
- 로그인과 역할 확인은 `lib/` 의 인증 함수로 한다(파일 이름 「확인 필요」). 핸들러 안에서 따로 만들지 않는다
- 출석 체크 API 는 요청한 사람이 강사인지 먼저 확인한다
- 예약 취소 API 는 그 예약이 요청한 회원의 것인지 먼저 확인한다
- 취소 마감(수업 시작 3시간 전)은 이 API 에서 막는다. 화면에서 버튼을 숨겼더라도 다시 확인한다
- 마감 판단에 쓰는 현재 시각은 서버에서 얻는다. 요청 본문이나 쿼리로 받은 시각을 쓰지 않는다
- 이유: 브라우저의 시계와 시간대는 믿을 수 없다
# 본보기
- DB 쓰는 법: @lib/db.ts
- 잘 만든 API: 「확인 필요」
.cursor/rules/date-time.mdc
---
description: 날짜와 시간을 다루는 코드(수업 시각, 예약 취소 마감, 출석 날짜, 화면의 날짜 표시)를 쓰거나 고칠 때 읽는다
globs:
alwaysApply: false
---
# 날짜와 시간
- 날짜 계산(더하기, 빼기, 비교, 시간대 바꾸기, 표시 형식)은 @lib/date.ts 의 함수로만 한다
- `lib/date.ts` 와 `tests/` 밖에서 `new Date(문자열)` 로 날짜를 읽지 않는다
- 이유: `'2026-11-02'` 는 UTC 자정으로, `'2026-11-02T10:00'` 은 실행하는 곳의 시간대로 읽혀 기대와 다른 시각이 된다
- `lib/date.ts` 와 `tests/` 밖에서 `getHours()`·`setDate()` 같은 Date 메서드나 밀리초 빼기로 시각을 계산하지 않는다
- 필요한 함수가 없으면 `lib/date.ts` 에 더하고, 같은 변경에 `tests/` 테스트를 넣는다. 다른 파일에 날짜 함수를 만들지 않는다
- 스튜디오 기준 시간대: 「확인 필요」
- DB 에 시각을 저장하는 기준(UTC 인지): 「확인 필요」
.cursor/rules/tests.mdc
---
description:
globs: tests/**
alwaysApply: false
---
# 테스트: tests/ (Vitest)
- 현재 시각에 기대는 테스트는 `vi.setSystemTime()` 으로 시각을 고정한다
- 테스트에 넣는 시각은 끝에 `Z` 나 `+09:00` 같은 오프셋을 붙인 문자열로 만든다
- 취소 마감 테스트에는 수업 시작 2시간 59분 전, 정확히 3시간 전, 3시간 1분 전을 모두 넣는다
- 날짜를 다루는 테스트에는 기준 시간대의 날짜와 UTC 날짜가 다른 시각의 수업을 하나 넣는다
- 이유: 서울 기준이라면 오전 9시 전 수업은 UTC 로 전날이다
- 테스트는 운영 서버와 같은 시간대로 돌린다(서버 시간대와 맞추는 방법 「확인 필요」)
- 이유: 개발자 컴퓨터의 시간대에서만 맞는 코드를 잡는다
- 테스트 파일 이름 규칙: 「확인 필요」
- 테스트에서 DB 를 다루는 방법(mock 인지 테스트용 DB 인지): 「확인 필요」
# 본보기
- 잘 만든 테스트: 「확인 필요」
3. AGENTS.md 초안
위 다섯 파일을 머리말 없이 한 파일로 합쳤습니다. 프로젝트 루트에 둡니다.
# 필라테스 스튜디오 예약 웹앱
## 프로젝트
- 동네 필라테스 스튜디오 예약 웹앱(예시용 가상 서비스). 회원은 수업을 예약하고 취소하며, 강사는 출석을 체크한다
## 스택
- Next.js 15(App Router), TypeScript 5, Tailwind CSS 4, Prisma 6, PostgreSQL, Vitest
- 새 파일은 `.ts`·`.tsx` 로 만든다. `.js`·`.jsx` 파일을 새로 만들지 않는다
- `params`·`searchParams`·`cookies()`·`headers()` 는 Promise 다. `await` 한 뒤 쓴다
- 이유: Next.js 15 에서 비동기로 바뀌었는데 예전 방식 코드가 자주 나온다
- `package.json` 에 의존성을 더하기 전에 먼저 묻는다
## 폴더
- `app/`: 화면과 라우트
- `app/api/`: 서버 API
- `components/`: 공용 UI
- `lib/`: DB(`db.ts`), 인증(파일 이름 「확인 필요」), 날짜(`date.ts`) 함수
- `prisma/schema.prisma`: DB 스키마
- `tests/`: Vitest 테스트
- 이 목록에 없는 최상위 폴더는 만들기 전에 묻는다
## 명령
- 개발 서버: `npm run dev`
- 테스트: `npm run test`
- 린트: `npm run lint`
- 일을 마쳤다고 하기 전에 `npm run test` 와 `npm run lint` 를 돌리고 결과를 함께 알린다
- 빌드, 타입 검사, DB 마이그레이션 명령: 「확인 필요」
## 꼭 지킬 규칙
- 서버 컴포넌트가 기본이다. `'use client'` 는 `useState`·`useEffect` 같은 훅, `onClick` 같은 이벤트 핸들러, 브라우저 API 를 쓰는 파일에만 붙인다
- 날짜·시간 계산은 `lib/date.ts` 의 함수로만 한다. 필요한 함수가 없으면 `lib/date.ts` 에 더한다
- 이유: 시간대 버그가 두 번 났다
- DB 는 `lib/db.ts` 를 거쳐서만 쓴다. `new PrismaClient()` 는 `lib/db.ts` 에만 둔다
- 예약 취소는 수업 시작 3시간 전까지만 받는다(정확히 3시간 전을 받을지는 「확인 필요」). 판단은 서버(`app/api/`)에서 한다
- named export 만 쓴다
- `export default` 는 Next.js 가 요구하는 파일에만 쓴다: `app/` 의 `page.tsx`·`layout.tsx`·`loading.tsx`·`error.tsx`·`not-found.tsx` 처럼 이름이 정해진 파일, `next.config` 같은 설정 파일
- 새 기능에는 같은 변경 안에 `tests/` 에 테스트를 더한다. 테스트 파일을 소스 옆에 두지 않는다
## 화면: `app/` 의 `.tsx`, `components/`
- `page.tsx`·`layout.tsx` 에는 `'use client'` 를 붙이지 않는다. 버튼·폼처럼 상호작용이 필요한 부분만 클라이언트 컴포넌트로 떼어 낸다
- `error.tsx` 는 예외로 `'use client'` 를 붙인다. Next.js 가 요구한다
- `'use client'` 파일은 `lib/db.ts` 를 import 하지 않는다. 데이터는 서버 컴포넌트에서 읽어 props 로 넘긴다
- 컴포넌트는 함수로 만들고 named export 로 내보낸다. class 컴포넌트를 쓰지 않는다
- 화면에 보여 줄 날짜·시각 문자열도 `lib/date.ts` 의 함수로 만든다. `toLocaleString()`·`toLocaleDateString()` 을 직접 부르지 않는다
- 이유: 서버와 브라우저의 시간대가 다르면 같은 수업이 다른 시각으로 보인다
- `tailwind.config` 파일(.js·.ts)을 새로 만들지 않는다
- 이유: Tailwind 4 는 설정을 CSS 파일에 둔다
- 본보기: 서버 컴포넌트 화면 「확인 필요」, 클라이언트 컴포넌트 「확인 필요」
## 서버 API: `app/api/`
- 핸들러는 `route.ts` 에서 `GET`·`POST` 같은 HTTP 메서드 이름으로 내보낸다
- 로그인과 역할 확인은 `lib/` 의 인증 함수로 한다(파일 이름 「확인 필요」). 핸들러 안에서 따로 만들지 않는다
- 출석 체크 API 는 요청한 사람이 강사인지 먼저 확인한다
- 예약 취소 API 는 그 예약이 요청한 회원의 것인지 먼저 확인한다
- 취소 마감(수업 시작 3시간 전)은 이 API 에서 막는다. 화면에서 버튼을 숨겼더라도 다시 확인한다
- 마감 판단에 쓰는 현재 시각은 서버에서 얻는다. 요청 본문이나 쿼리로 받은 시각을 쓰지 않는다
- 이유: 브라우저의 시계와 시간대는 믿을 수 없다
- 본보기: DB 쓰는 법 `lib/db.ts`, 잘 만든 API 「확인 필요」
## 날짜와 시간: 날짜·시간을 계산하거나 보여 줄 때
- 날짜 계산(더하기, 빼기, 비교, 시간대 바꾸기, 표시 형식)은 `lib/date.ts` 의 함수로만 한다
- `lib/date.ts` 와 `tests/` 밖에서 `new Date(문자열)` 로 날짜를 읽지 않는다
- 이유: `'2026-11-02'` 는 UTC 자정으로, `'2026-11-02T10:00'` 은 실행하는 곳의 시간대로 읽혀 기대와 다른 시각이 된다
- `lib/date.ts` 와 `tests/` 밖에서 `getHours()`·`setDate()` 같은 Date 메서드나 밀리초 빼기로 시각을 계산하지 않는다
- 필요한 함수가 없으면 `lib/date.ts` 에 더하고, 같은 변경에 `tests/` 테스트를 넣는다. 다른 파일에 날짜 함수를 만들지 않는다
- 스튜디오 기준 시간대: 「확인 필요」
- DB 에 시각을 저장하는 기준(UTC 인지): 「확인 필요」
## 테스트: `tests/` (Vitest)
- 현재 시각에 기대는 테스트는 `vi.setSystemTime()` 으로 시각을 고정한다
- 테스트에 넣는 시각은 끝에 `Z` 나 `+09:00` 같은 오프셋을 붙인 문자열로 만든다
- 취소 마감 테스트에는 수업 시작 2시간 59분 전, 정확히 3시간 전, 3시간 1분 전을 모두 넣는다
- 날짜를 다루는 테스트에는 기준 시간대의 날짜와 UTC 날짜가 다른 시각의 수업을 하나 넣는다
- 이유: 서울 기준이라면 오전 9시 전 수업은 UTC 로 전날이다
- 테스트는 운영 서버와 같은 시간대로 돌린다(서버 시간대와 맞추는 방법 「확인 필요」)
- 이유: 개발자 컴퓨터의 시간대에서만 맞는 코드를 잡는다
- 테스트 파일 이름 규칙: 「확인 필요」
- 테스트에서 DB 를 다루는 방법(mock 인지 테스트용 DB 인지): 「확인 필요」
- 본보기: 잘 만든 테스트 「확인 필요」
- 폴더별 절은
app/api/AGENTS.md·tests/AGENTS.md 처럼 그 폴더로 옮겨 나눠도 됩니다. 더 가까운 폴더의 지시가 앞섭니다.
- 한 저장소에 둘 다 두면 Cursor 에는 같은 규칙이 두 번 붙을 수 있습니다. Cursor 만 쓴다면
.mdc 쪽만 두는 편이 관리하기 쉽고, 둘 다 둔다면 고칠 때 두 곳을 같이 고치세요.
4. .cursorrules 옮긴 표
.cursorrules 의 줄 | 옮긴 곳 | 바꾼 모습 |
|---|
Use TypeScript. | project.mdc 의 스택 | 새 파일은 .ts·.tsx 로 만든다. .js·.jsx 파일을 새로 만들지 않는다 |
Prefer functional components. | ui.mdc | 컴포넌트는 함수로 만들고 named export 로 내보낸다. class 컴포넌트를 쓰지 않는다 |
Write tests for new features. | project.mdc 의 꼭 지킬 규칙·명령, tests.mdc | 새 기능에는 같은 변경 안에 tests/ 에 테스트를 더한다. 끝냈다고 하기 전에 npm run test 를 돌린다. 무엇을 넣을지는 tests.mdc 에 적었다 |
「Prefer」처럼 지켰는지 가릴 수 없는 말은 확인할 수 있는 모양으로 바꿨습니다. AGENTS.md 에서도 같은 이름의 절에 들어 있습니다. 다 옮겼으면 루트의 .cursorrules 는 지우세요. 남겨 두면 옛 규칙이 새 규칙과 같이 붙을 수 있습니다.
5. 직접 확인할 것
- 작은 일 하나 시켜 보기. 새 채팅에서 「내 예약 목록에 취소 버튼을 달아 줘」라고 시킨 뒤 봅니다.
page.tsx 는 서버 컴포넌트 그대로이고 'use client' 는 버튼 컴포넌트에만 붙었는지
- 새로 생긴 파일에
export default 가 없는지
- 3시간 계산이
lib/date.ts 의 함수를 거치고, 취소 API 가 서버 시각으로 막는지
tests/ 에 2시간 59분·3시간·3시간 1분 테스트가 생겼는지
- 명령과 「확인 필요」 채우기.
npm run dev·npm run test·npm run lint 가 실제로 도는지, npm run test 가 tests/ 의 테스트를 실제로 찾아 돌리는지(테스트 수가 0이 아닌지) 봅니다. 그다음 「확인 필요」 자리(시간대, 3시간 경계, 인증 파일, 빌드·마이그레이션 명령, 테스트 방식, 본보기 파일)를 채웁니다. 본보기 자리에는 잘 만든 파일의 경로를 적고, .mdc 에서는 앞에 @ 를 붙입니다.
- 규칙끼리 부딪히는 줄.
- named export 규칙을 따르다
page.tsx·layout.tsx 의 export default 까지 바꾸지 않는지 봅니다. 바꾸면 그 화면이 깨집니다.
- 서버 컴포넌트 규칙을 따르다
error.tsx 의 'use client' 를 지우지 않는지 봅니다. Next.js 는 error.tsx 를 클라이언트 컴포넌트로 요구합니다.
.mdc 와 AGENTS.md 를 둘 다 둔다면 한쪽만 고쳐 둘이 어긋나지 않았는지 봅니다.
처음엔 이 정도로 짧게 시작하고, 에이전트가 같은 실수를 되풀이할 때마다 그 실수를 막는 줄을 하나씩 더하세요.