diff --git a/.agents/skills/feature-issue/SKILL.md b/.agents/skills/feature-issue/SKILL.md
new file mode 100644
index 0000000..4a8ac93
--- /dev/null
+++ b/.agents/skills/feature-issue/SKILL.md
@@ -0,0 +1,65 @@
+---
+name: feature-issue
+description: 구현 설계서의 Task를 바탕으로 GitHub Feature Issue를 작성합니다.
+---
+
+# GitHub Feature Issue
+
+구현 설계서의 Task를 GitHub Feature Issue로 변환합니다.
+
+구현 설계서를 먼저 읽고 작업의 목적과 배경을 이해한 뒤 Issue를 작성합니다.
+
+## 사용 시점
+
+다음 상황에서 사용합니다.
+
+- 구현 설계서에서 GitHub Issue를 생성할 때
+- 새로운 기능 구현을 시작할 때
+- 리팩토링 작업을 시작할 때
+
+다음 상황에서는 사용하지 않습니다.
+
+- Bug Issue 작성
+- Pull Request 작성
+- Commit Message 작성
+
+---
+
+## 작업 원칙
+
+- 구현 설계서의 Task 하나를 하나의 GitHub Issue로 작성합니다.
+- 하나의 GitHub Issue는 하나의 Pull Request로 구현 가능한 크기를 유지합니다.
+- 구현 설계서의 목적과 배경을 충분히 이해한 후 Issue를 작성합니다.
+- 구현 설계서에 없는 내용을 추측하여 작성하지 않습니다.
+- 필요한 정보가 부족한 경우 `확인 필요`로 표시합니다.
+
+---
+
+## GitHub 설정
+
+Issue를 생성할 때 아래 설정을 함께 적용합니다.
+
+- Assignee는 `hm1n`으로 고정합니다.
+- Label은 Issue 제목의 라벨과 동일한 GitHub Label을 추가합니다.
+ - 예: Issue 제목이 `[setting-66/ai-agent] Agent 주도 개발 환경 도입`이면 `Setting` 라벨을 추가합니다.
+ - Issue 제목의 라벨이 GitHub Label과 정확히 일치하지 않으면, 기존 Label 목록에서 의미가 같은 라벨을 확인해 사용합니다.
+- Project는 `KANBAN`으로 지정합니다.
+- Project Status는 `TO-DO`로 지정합니다.
+
+---
+
+## 출력 형식
+
+이슈를 생성할 때는 `.github/ISSUE_TEMPLATE`에 있는 `feature_request.md`를 우선 사용합니다.
+별도의 템플릿을 새로 만들거나 Skill의 assets에 중복 저장하지 않습니다.
+
+---
+
+## 품질 기준
+
+Issue를 작성하기 전에 아래 내용을 확인합니다.
+
+- 구현 설계서의 목적을 충분히 이해했는가?
+- 현재 Task가 하나의 Issue로 적절한 크기인가?
+- 하나의 PR로 구현 가능한가?
+- 구현 설계서에 없는 내용을 추측하지 않았는가?
diff --git a/.agents/skills/feature-issue/references/issue-title-convention.md b/.agents/skills/feature-issue/references/issue-title-convention.md
new file mode 100644
index 0000000..5dec5dc
--- /dev/null
+++ b/.agents/skills/feature-issue/references/issue-title-convention.md
@@ -0,0 +1,28 @@
+## Issue 제목 규칙
+
+Issue 제목은 아래 컨벤션을 따릅니다.
+
+```text
+[{라벨}-{작업번호}/{도메인}] 이슈명
+```
+
+예시
+
+```text
+[feature-66/react-query] React Query Provider 추가
+[refactor-67/chat] ChatRoom 책임 분리
+[performance-68/chat] 채팅 렌더링 성능 개선
+[test-69/invite] 링크 초대 E2E 테스트 추가
+```
+
+제목 생성 규칙은 다음과 같습니다.
+
+- `{라벨}`은 현재 레포지토리에 등록된 GitHub Label의 영문 이름을 그대로 사용합니다.
+- `{작업번호}`는 **GitHub Issue 번호가 아닌 프로젝트 작업번호**입니다.
+- 기존 Issue 제목의 컨벤션을 참고하여 가장 최근 작업번호를 찾고, 다음 번호를 사용합니다.
+- `{도메인}`은 작업 대상이 되는 기능 또는 영역을 대표하는 영문 키워드를 사용합니다. (예: `chat`, `invite`, `auth`, `react-query`)
+- `이슈명`은 작업 내용을 이해하기 쉬운 한국어로 작성합니다.
+
+작업번호는 GitHub Issue 번호와 별도로 관리되는 프로젝트 번호이며, 반드시 기존 Issue 제목을 참고하여 연속성을 유지합니다.
+
+---
diff --git a/.agents/skills/production-release/SKILL.md b/.agents/skills/production-release/SKILL.md
new file mode 100644
index 0000000..688788d
--- /dev/null
+++ b/.agents/skills/production-release/SKILL.md
@@ -0,0 +1,196 @@
+---
+name: production-release
+description: 엔빵의 프로덕션 릴리즈를 준비하고 develop에서 main으로 릴리즈 PR을 생성합니다.
+---
+
+# Production Release
+
+프로덕션 릴리즈를 준비할 때 사용하는 스킬입니다.
+
+## Source of Truth
+
+릴리즈 정보는 다음 우선순위를 기준으로 판단합니다.
+
+1. Git 태그
+2. Pull Request
+3. Git History
+
+`package.json`의 `version`은 릴리즈 버전 판단에 사용하지 않습니다.
+
+## Workflow
+
+1. 원격 저장소의 `origin/develop`, `origin/main`, Git 태그를 최신 상태로 동기화합니다.
+2. 릴리즈 기준이 되는 최신 `vX.Y.Z` 태그를 확인합니다.
+3. 최신 태그 이후 현재 `origin/develop`에 포함된 변경사항을 확인합니다.
+4. 해당 변경 범위에 포함된 병합 PR을 수집합니다.
+5. 각 PR의 번호, 제목, 라벨, `Summary`를 읽습니다.
+6. `Summary`만으로 사용자 영향이 불명확한 경우에만 `Impact`를 확인합니다.
+7. 다음 경우에만 제한적으로 PR diff를 확인합니다.
+ - `Summary`가 없는 경우
+ - 제목과 `Summary`가 충돌하는 경우
+ - 보안, 데이터 마이그레이션, 호환성 파괴 변경인 경우
+ - 버전 판단에 필요한 정보가 부족한 경우
+8. 관련 PR을 기능 또는 목적 단위로 묶어 릴리즈 노트를 작성합니다.
+9. 최근 태그와 변경 유형을 기준으로 다음 버전을 제안합니다.
+10. 버전 중복과 변경사항 누락 여부를 검사합니다.
+11. `references/release-pr-template.md`를 사용해 릴리즈 PR 본문을 작성합니다.
+12. `[release] vX.Y.Z` 형식으로 `develop → main` 릴리즈 PR을 생성합니다.
+
+## Latest Release Tag
+
+릴리즈 기준 태그는 `origin/main` 릴리즈 계보에서 접근 가능한 태그 중 다음 조건을 모두 만족하고 버전이 가장 높은 태그를 사용합니다.
+
+- 정확히 `vX.Y.Z` 형식인가
+- Semantic Versioning 형식인가
+- 버전 순으로 가장 높은 태그인가
+- 태그가 가리키는 커밋이 `origin/main` 이력에 포함되어 있는가
+
+다음 태그는 릴리즈 기준으로 사용하지 않습니다.
+
+- `release-*`
+- 테스트 또는 임시 태그
+- prerelease 태그
+- `vX.Y.Z` 형식이 아닌 태그
+
+릴리즈 기준 태그가 없으면 버전을 자동으로 결정하지 않고 사용자에게 확인을 요청합니다.
+
+전역에서 가장 높은 버전 태그가 있더라도 `origin/main` 릴리즈 계보에서 접근할 수 없다면 릴리즈 기준으로 사용하지 않습니다.
+
+## Release Range
+
+릴리즈 대상 변경사항은 다음 범위를 기준으로 판단합니다.
+
+- 최신 릴리즈 태그가 가리키는 커밋 이후
+- 현재 `origin/develop`에 포함된 변경사항
+
+기본 비교 범위는 다음과 같습니다.
+
+latest_tag..origin/develop
+
+최신 태그가 없으면 다음 범위를 참고하되, 자동으로 릴리즈 PR을 생성하지 않습니다.
+
+origin/main..origin/develop
+
+릴리즈 대상 PR은 다음 조건을 만족해야 합니다.
+
+- `develop`에 병합된 PR인가
+- 비교 범위의 커밋이 `origin/develop`에 포함되어 있는가
+- 릴리즈 PR이나 merge-only PR이 아닌가
+- 동일한 PR이 중복 수집되지 않았는가
+
+Git 커밋 범위와 GitHub PR 목록을 함께 확인합니다.
+
+1. `latest_tag..origin/develop`의 커밋을 확인합니다.
+2. 해당 커밋과 연결된 병합 PR을 수집합니다.
+3. PR의 merge commit 또는 포함 커밋이 비교 범위에 존재하는지 확인합니다.
+4. 비교 범위에 포함되지 않은 PR은 제외합니다.
+5. PR과 연결되지 않은 커밋이 있으면 누락 가능성으로 보고합니다.
+
+릴리즈 대상 변경사항이나 병합 PR이 없다면 릴리즈 PR을 생성하지 않습니다.
+
+## Pull Request Information
+
+각 PR에서 다음 정보를 우선적으로 확인합니다.
+
+1. PR 번호
+2. PR 제목
+3. PR 라벨
+4. `Summary`
+5. 필요한 경우 `Impact`
+6. 예외적으로 PR diff
+
+Merge Commit 메시지는 릴리즈 노트의 직접적인 입력으로 사용하지 않습니다.
+
+## Version Rules
+
+버전은 `vX.Y.Z` 형식의 Semantic Versioning을 사용합니다.
+
+### Major
+
+- 기존 기능과 호환되지 않는 변경
+- 주요 사용자 흐름 또는 공개 인터페이스의 파괴적 변경
+
+### Minor
+
+- 새로운 사용자 기능 추가
+- 기존 기능의 의미 있는 확장
+- 새로운 공개 페이지 또는 사용자 흐름 추가
+
+### Patch
+
+- 버그 수정
+- 기존 기능 개선
+- 성능, 접근성, SEO, 내부 안정성 개선
+
+여러 변경 유형이 포함되면 가장 높은 버전 수준을 선택합니다.
+
+major > minor > patch
+
+버전 수준을 신뢰성 있게 판단하기 어렵다면 자동으로 결정하지 않고 사용자에게 확인을 요청합니다.
+
+다음 조건을 확인합니다.
+
+- 제안한 버전 태그가 이미 존재하지 않는가
+- 최신 릴리즈 태그보다 높은 버전인가
+- PR 제목과 PR 본문의 버전이 일치하는가
+- 동일 버전의 열린 릴리즈 PR이 존재하지 않는가
+- 동일한 `develop → main` 릴리즈 PR이 이미 열려 있지 않은가
+
+## Missing Change Check
+
+수집한 모든 PR은 다음 중 하나로 처리합니다.
+
+1. 주요 변경사항에 직접 반영
+2. 다른 PR과 기능 또는 목적 단위로 통합하여 반영
+3. 내부 변경, 중복 또는 릴리즈 제외 대상으로 분류
+
+반영 또는 제외 사유가 없는 PR이 있다면 릴리즈 PR을 생성하지 않습니다.
+
+`포함된 Pull Request` 섹션에는 릴리즈 대상 PR을 모두 작성합니다.
+
+다음 항목도 확인합니다.
+
+- 비교 범위에는 있으나 PR 목록에 없는 커밋이 있는가
+- 수집한 PR 중 릴리즈 노트와 포함 PR 목록에서 누락된 항목이 있는가
+- 동일 PR이 두 번 이상 포함되어 있는가
+- 최신 태그 이전 변경사항이 잘못 포함되어 있는가
+
+## Pull Request
+
+- Base branch: `main`
+- Head branch: `develop`
+- Title: `[release] vX.Y.Z`
+- Body: `references/release-pr-template.md` 사용
+
+다음 조건 중 하나라도 만족하면 새 릴리즈 PR을 생성하지 않습니다.
+
+- 릴리즈 대상 변경사항이 없음
+- 동일 버전의 릴리즈 PR이 존재함
+- 동일한 `develop → main` 열린 PR이 존재함
+- 제안 버전 태그가 이미 존재함
+- 변경사항 누락 검사가 실패함
+
+PR 생성 전 다음을 확인합니다.
+
+- `origin/develop`과 로컬 `develop`이 일치하는가
+- `origin/main`과 로컬 `main`이 일치하는가
+- 릴리즈할 변경사항이 존재하는가
+- 동일한 버전의 릴리즈 PR이 존재하지 않는가
+- 동일한 base와 head를 사용하는 열린 PR이 존재하지 않는가
+
+## Constraints
+
+- `package.json` 버전은 변경하지 않습니다.
+- 릴리즈 브랜치를 생성하지 않습니다.
+- 릴리즈 이슈를 생성하지 않습니다.
+- Git 태그를 생성하지 않습니다.
+- GitHub Release를 생성하지 않습니다.
+- 프로덕션 배포를 직접 수행하지 않습니다.
+- GitHub 자동 릴리즈 노트와 Release Drafter를 사용하지 않습니다.
+- 코드 diff 전체 분석은 기본 동작에서 제외합니다.
+- 구현 세부사항보다 기능과 사용자 영향을 우선 설명합니다.
+
+## References
+
+- `references/release-note.md`
+- `references/release-pr-template.md`
diff --git a/.agents/skills/production-release/agents/openai.yaml b/.agents/skills/production-release/agents/openai.yaml
new file mode 100644
index 0000000..008cca7
--- /dev/null
+++ b/.agents/skills/production-release/agents/openai.yaml
@@ -0,0 +1,4 @@
+interface:
+ display_name: "Production Release"
+ short_description: "엔빵의 프로덕션 릴리즈 PR을 준비하고 생성합니다."
+ default_prompt: "이전 릴리즈 이후 변경사항을 확인하고 릴리즈 노트를 작성한 뒤 develop에서 main으로 프로덕션 릴리즈 PR을 생성해 주세요."
diff --git a/.agents/skills/production-release/references/release-note.md b/.agents/skills/production-release/references/release-note.md
new file mode 100644
index 0000000..72a8de9
--- /dev/null
+++ b/.agents/skills/production-release/references/release-note.md
@@ -0,0 +1,167 @@
+# Release Note Guide
+
+이 문서는 릴리즈 PR과 GitHub Release에 사용할 릴리즈 노트 작성 규칙을 정의합니다.
+
+릴리즈 PR의 제목과 전체 본문 구조는 `references/release-pr-template.md`를 따릅니다.
+
+## 목적
+
+릴리즈 노트는 단순한 개발 기록이 아니라 사용자와 개발자가 이번 릴리즈에서 무엇이 변경되었는지 빠르게 이해할 수 있는 문서입니다.
+
+구현 방법보다 변경 결과와 사용자 영향을 우선 설명합니다.
+
+## 정보 조회 우선순위
+
+각 PR의 다음 정보를 순서대로 사용합니다.
+
+1. PR `Summary`
+2. PR 제목
+3. PR `Impact`
+4. PR diff
+
+`Summary`만으로 변경사항을 이해할 수 있다면 `Impact`와 diff는 확인하지 않습니다.
+
+## Summary 활용 규칙
+
+PR의 `Summary`를 그대로 나열하거나 복사하지 않습니다.
+
+수집한 Summary를 비교해 다음 작업을 수행합니다.
+
+- 중복 내용 제거
+- 관련 PR 통합
+- 기능 또는 목적 단위로 재구성
+- 사용자 또는 운영 관점으로 재작성
+
+여러 PR이 하나의 기능을 구성한다면 각각의 Summary를 한 개의 변경사항으로 통합합니다.
+
+Summary에서 확인할 수 없는 효과나 결과는 추측하지 않습니다.
+
+## 작성 원칙
+
+### 사용자 관점에서 작성합니다
+
+좋은 예
+
+- 비회원도 서비스 기능을 확인할 수 있도록 공개 랜딩페이지를 추가했습니다.
+- 검색엔진이 페이지를 더 정확하게 이해할 수 있도록 SEO 설정을 개선했습니다.
+
+좋지 않은 예
+
+- `app/page.tsx`를 수정했습니다.
+- `generateMetadata`를 공통 함수로 분리했습니다.
+
+### 관련 PR을 하나의 변경사항으로 묶습니다
+
+여러 PR이 하나의 기능이나 목적을 구성한다면 하나의 항목으로 통합합니다.
+
+예시 PR
+
+- 랜딩페이지 구현
+- metadata 적용
+- sitemap 추가
+
+예시 릴리즈 노트
+
+### 공개 페이지와 검색 노출 개선
+
+- 비회원용 공개 랜딩페이지를 추가했습니다.
+- 검색엔진이 페이지를 올바르게 인식하도록 메타데이터와 검색 노출 설정을 개선했습니다.
+
+### 구현 세부사항은 최소화합니다
+
+다음 내용은 특별한 이유가 없다면 작성하지 않습니다.
+
+- 함수명
+- 컴포넌트명
+- 파일 경로
+- 리팩터링 과정
+- 내부 구현 방식
+
+필요한 경우 변경 목적과 결과만 설명합니다.
+
+### 내부 작업은 필요한 경우만 포함합니다
+
+사용자에게 영향이 없는 작업은 릴리즈 노트에서 제외할 수 있습니다.
+
+예시
+
+- 코드 스타일 수정
+- lint 설정 변경
+- 단순 내부 리팩터링
+- 영향이 없는 의존성 업데이트
+- merge-only 작업
+
+운영 안정성이나 개발 생산성에 의미 있는 변화가 있다면 `내부 개선`으로 작성합니다.
+
+## 버전 표기 규칙
+
+- PR 제목과 본문의 버전은 모두 `vX.Y.Z` 형식을 사용합니다.
+- PR 제목과 본문의 버전은 반드시 일치해야 합니다.
+- 최신 릴리즈 태그보다 낮거나 같은 버전을 작성하지 않습니다.
+- 이미 존재하는 태그 또는 열린 릴리즈 PR의 버전을 다시 사용하지 않습니다.
+- `package.json`의 버전은 릴리즈 버전 판단에 사용하지 않습니다.
+
+## 포함된 Pull Request 작성 규칙
+
+- 릴리즈 대상 PR을 모두 나열합니다.
+- 주요 변경사항에서 여러 PR을 통합했더라도 각 PR은 목록에 별도로 작성합니다.
+- 내부 변경으로 제외한 PR도 릴리즈 범위에 포함된다면 목록에 작성할 수 있습니다.
+- 릴리즈 PR 자체는 포함하지 않습니다.
+- 중복 PR은 한 번만 작성합니다.
+- 최신 태그 이후 비교 범위에 포함된 PR만 작성합니다.
+
+## 작성 규칙
+
+- 사용자 또는 운영 관점의 변화를 설명합니다.
+- 구현 방법보다 변경 목적과 결과를 작성합니다.
+- 같은 의미의 변경사항은 하나로 묶습니다.
+- 중복 설명을 작성하지 않습니다.
+- 릴리즈 요약은 2~3문장으로 작성합니다.
+- 각 변경사항은 가능한 한 한두 문장으로 작성합니다.
+- 실제 PR 내용에서 확인할 수 없는 효과를 추측하지 않습니다.
+- Merge Commit 메시지를 그대로 릴리즈 노트에 사용하지 않습니다.
+
+## 제외 대상
+
+다음 내용은 기본적으로 주요 변경사항에서 제외합니다.
+
+- Merge Commit
+- Release PR
+- Version Update
+- 단순 GitHub Actions 수정
+- 단순 CI 설정 변경
+- Chore 성격의 작업
+- 사용자가 인지할 수 없는 내부 수정
+
+단, 사용자 경험, 운영 안정성 또는 개발 생산성에 의미 있는 영향이 있다면 포함할 수 있습니다.
+
+## Quality Rules
+
+릴리즈 노트는 단순한 변경 로그가 아니라 프로젝트의 변경 이력을 설명하는 문서입니다.
+
+GitHub Release뿐 아니라 포트폴리오, 프로젝트 문서, 변경 이력으로도 활용할 수 있는 품질을 목표로 합니다.
+
+릴리즈 노트를 작성할 때 아래 질문에 모두 답할 수 있어야 합니다.
+
+- 이번 릴리즈에서 무엇이 달라졌는가
+- 왜 이 변경을 했는가
+- 사용자는 어떤 변화를 경험하는가
+
+위 질문에 답할 수 없는 내용은 릴리즈 노트에 포함하지 않습니다.
+
+### 체크리스트
+
+릴리즈 노트를 작성한 후 아래 항목을 확인합니다.
+
+- 변경사항이 사용자 관점에서 작성되었는가
+- 구현 방법보다 변경 결과를 설명하고 있는가
+- 여러 PR을 하나의 기능 단위로 적절히 묶었는가
+- Summary를 그대로 복사하지 않고 재구성했는가
+- 수집한 모든 PR이 반영되거나 제외 사유를 가지고 있는가
+- 비교 범위 밖의 PR이 포함되지 않았는가
+- 중복되거나 불필요한 설명이 없는가
+- PR 제목과 본문의 버전이 일치하는가
+- 릴리즈 전체를 2~3분 안에 이해할 수 있는가
+- GitHub Release로 그대로 게시해도 될 정도의 품질인가
+
+모든 항목을 만족하지 못하면 릴리즈 노트를 다시 수정합니다.
diff --git a/.agents/skills/production-release/references/release-pr-template.md b/.agents/skills/production-release/references/release-pr-template.md
new file mode 100644
index 0000000..ffe67ac
--- /dev/null
+++ b/.agents/skills/production-release/references/release-pr-template.md
@@ -0,0 +1,41 @@
+# Release PR Template
+
+## Title
+
+```text
+[release] vX.Y.Z
+```
+
+## Body
+
+```markdown
+## 릴리즈 버전
+
+`vX.Y.Z`
+
+
+
+## 릴리즈 요약
+
+이번 릴리즈의 핵심 변화와 목적을 사용자 또는 운영 관점에서 2~3문장으로 작성합니다.
+
+## 주요 변경사항
+
+### 기능 또는 목표 단위 제목
+
+- 사용자 또는 운영 관점에서 달라진 내용을 작성합니다.
+- 여러 PR이 하나의 기능을 구성한다면 하나의 변경사항으로 통합합니다.
+
+## 포함된 Pull Request
+
+- #PR번호 PR 제목
+
+
+
+```
+
+## Rules
+
+- PR 제목은 반드시 `[release] vX.Y.Z` 형식을 사용합니다.
+- PR 본문의 버전과 제목의 버전은 반드시 일치해야 합니다.
+- `release-note:start`와 `release-note:end` 사이의 내용은 이후 GitHub Release 본문으로 사용합니다.
diff --git a/.agents/skills/pull-request/SKILL.md b/.agents/skills/pull-request/SKILL.md
new file mode 100644
index 0000000..a5c5ffd
--- /dev/null
+++ b/.agents/skills/pull-request/SKILL.md
@@ -0,0 +1,74 @@
+---
+name: pull-request
+description: GitHub Issue와 Git diff를 바탕으로 사람이 빠르게 이해할 수 있는 Pull Request를 작성합니다.
+---
+
+# Pull Request
+
+프로젝트의 GitHub Pull Request Template에 맞춰 Pull Request를 작성합니다.
+
+Pull Request는 변경 내역이 아니라 **의사결정 문서**입니다.
+
+## 사용 시점
+
+다음 상황에서 사용합니다.
+
+- GitHub Issue 구현이 완료되었을 때
+- Pull Request를 생성할 때
+- 변경 사항, 영향 범위, 리뷰 포인트를 정리할 때
+
+다음 상황에서는 사용하지 않습니다.
+
+- GitHub Issue 작성
+- Commit Message 작성
+- Bug Report 작성
+
+---
+
+## 작업 원칙
+
+- 하나의 GitHub Issue는 하나의 Pull Request로 구현합니다.
+- 연결된 GitHub Issue의 Why, Goal, Constraints를 먼저 이해한 후 Pull Request를 작성합니다.
+- Git diff를 확인하여 실제 변경 사항만 작성합니다.
+- PR 본문은 한국어로 작성합니다.
+- PR은 `Why → Decision → Changes → Impact → Review Points → Validation` 흐름으로 작성합니다.
+- 변경 사항을 나열하기보다 왜 그렇게 구현했는지 설명합니다.
+- 확인하지 않은 테스트는 실행했다고 작성하지 않습니다.
+- 불확실한 내용은 `확인 필요`로 표시합니다.
+- 리뷰어가 집중해서 봐야 할 위험 영역을 명확히 표시합니다.
+- Review Points는 가능하면 3개 이내로 제한합니다.
+- 연결된 GitHub Issue가 없다면 먼저 Issue 생성을 제안합니다.
+
+---
+
+## GitHub 설정
+
+PR을 생성할 때 아래 설정을 함께 적용합니다.
+
+- Assignee는 `hm1n`으로 고정합니다.
+- Label은 PR 제목의 라벨과 동일한 GitHub Label을 추가합니다.
+ - 예: PR 제목이 `[setting-66/ai-agent] Agent 주도 개발 환경 도입`이면 `Setting` 라벨을 추가합니다.
+ - PR 제목의 라벨이 GitHub Label과 정확히 일치하지 않으면, 기존 Label 목록에서 의미가 같은 라벨을 확인해 사용합니다.
+
+---
+
+## 출력 형식
+
+프로젝트의 `.github/pull_request_template.md`를 사용합니다.
+
+Pull Request 제목은 연결된 GitHub Issue 제목과 동일하게 작성합니다.
+
+별도의 템플릿을 생성하거나 Skill의 `assets`에 중복 저장하지 않습니다.
+
+---
+
+## 품질 기준
+
+PR을 작성하기 전에 아래 내용을 확인합니다.
+
+- 관련 Issue의 Why와 Goal이 PR에 반영되었는가?
+- 구현 방식의 선택 이유가 설명되어 있는가?
+- 실제 Git diff에 없는 내용을 작성하지 않았는가?
+- 기존 기능에 미치는 영향이 정리되었는가?
+- 한계와 트레이드오프가 있다면 명시했는가?
+- Review Points와 Validation이 실제 구현 내용과 일치하는가?
diff --git a/.agents/skills/workflow.md b/.agents/skills/workflow.md
new file mode 100644
index 0000000..6566bcd
--- /dev/null
+++ b/.agents/skills/workflow.md
@@ -0,0 +1,31 @@
+# AI Workflow
+
+## 전체 흐름
+
+구현 설계서 → GitHub Issue → Codex 구현 → Commit → Pull Request → Codex Review → Merge
+
+## 역할 분리
+
+- ChatGPT: 구현 설계서 작성, Task 분해, Issue 초안 작성
+- Codex: Issue 구현, Commit, Pull Request 작성, Code Review 대응
+- GitHub: Issue / PR / Review 관리
+- Notion: 구현 설계서 관리
+
+## 원칙
+
+- 구현 설계서의 Task 1개는 GitHub Issue 1개로 변환한다.
+- GitHub Issue 1개는 Pull Request 1개로 구현한다.
+- Pull Request 제목은 연결된 Issue 제목과 동일하게 작성한다.
+- PR은 변경사항 나열이 아니라 의사결정 문서로 작성한다.
+- Codex Review는 P0/P1 수준의 위험 요소를 확인하는 용도로 사용한다.
+
+## 작업 순서
+
+1. Notion에서 구현 설계서를 작성한다.
+2. 구현 설계서의 Task를 기준으로 GitHub Issue를 생성한다.
+3. Codex가 Issue 단위로 구현한다.
+4. 변경 사항을 논리적인 작업 단위로 Commit한다.
+5. Issue 제목과 동일한 제목으로 PR을 생성한다.
+6. `@codex review`로 리뷰를 요청한다.
+7. 필요 시 `@codex fix`로 수정 작업을 요청한다.
+8. 최종 확인 후 Merge한다.
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index 66e31bf..c8fe369 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -6,16 +6,73 @@ labels: ''
assignees: ''
---
-## 어떤 버그인가요?
+## Summary
-> 어떤 버그인지 간결하게 설명해주세요
+> 3줄 이내로 버그를 요약합니다.
-## 어떤 상황에서 발생한 버그인가요?
+---
+
+## Problem
+
+### 어떤 문제가 발생했나요?
+
+- 현재 발생하는 현상
+- 사용자에게 미치는 영향
+- 발생 빈도(항상/간헐적)
+
+---
+
+## Expected
+
+### 원래 어떻게 동작해야 하나요?
-> (가능하면) Given-When-Then 형식으로 서술해주세요
+- 기대하는 동작
+
+---
+
+## Reproduction
+
+### 어떻게 재현할 수 있나요?
+
+1.
+2.
+3.
+
+---
-## 예상 결과
+## Scope
+
+### 영향을 받는 범위
+
+- 기능
+- 페이지
+- 컴포넌트
+- API
+- DB
+
+---
+
+## Suspected Cause (Optional)
+
+### 예상 원인
+
+- 의심되는 코드
+- 의심되는 로직
+- 확실하지 않으면 비워둠
+
+---
+
+## Constraints
+
+### 수정 시 고려할 사항
+
+- 기존 동작 유지
+- 변경하면 안 되는 부분
+
+---
-> 예상했던 정상적인 결과가 어떤 것이었는지 설명해주세요
+## Definition of Done
-## 참고할만한 자료(선택)
+- [ ] 버그 재현되지 않음
+- [ ] 기존 기능 정상 동작
+- [ ] 회귀 테스트 완료
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
index 07f6d8d..c679e8d 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -6,14 +6,60 @@ labels: ''
assignees: ''
---
-## 어떤 기능인가요?
+## Summary
-> 추가하려는 기능에 대해 간결하게 설명해주세요
+> 3줄 이내로 이번 작업을 요약합니다.
-## 작업 상세 내용
+---
+
+## Why
+
+### 해결하려는 문제가 무엇인가요?
+
+- 현재 어떤 문제가 있는가?
+- 왜 지금 해결해야 하는가?
+- 해결하지 않으면 어떤 문제가 발생하는가?
+
+---
+
+## Goal
+
+### 완료되면 무엇이 달라지나요?
+
+- 이번 작업의 목표
+- 이번 작업에서 해결하지 않는 범위(Non-goal)
+
+---
+
+## Approach
+
+### 어떻게 해결할 계획인가요?
+
+- 전체 구현 방향
+- 주요 변경 사항
+- 검토한 대안과 선택 이유
+
+---
+
+## Tasks
+
+- [ ] Task 1
+- [ ] Task 2
+- [ ] Task 3
+
+---
+
+## Constraints
+
+- 반드시 유지해야 하는 동작
+- 변경하면 안 되는 사항
+- 기술적 제약사항
+
+---
-- [ ] TODO
-- [ ] TODO
-- [ ] TODO
+## Definition of Done
-## 참고할만한 자료(선택)
+- [ ] 기능 동작 확인
+- [ ] 테스트 완료
+- [ ] lint / typecheck 통과
+- [ ] 문서 업데이트
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index 435f85d..36a56fc 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -1,15 +1,102 @@
-## #️⃣연관된 이슈
+## Summary
-> ex) #이슈번호, #이슈번호
+> 3줄 이내로 이번 PR을 요약합니다.
-## 📝작업 내용
+---
-> 이번 PR에서 작업한 내용을 간략히 설명해주세요(이미지 첨부 가능)
+## Why
-### 스크린샷 (선택)
+### 왜 이 작업을 진행했나요?
-## 💬리뷰 요구사항(선택)
+- 어떤 문제를 해결하기 위한 PR인가요?
+- 왜 지금 필요한 작업인가요?
-> 리뷰어가 특별히 봐주었으면 하는 부분이 있다면 작성해주세요
->
-> ex) 메서드 XXX의 이름을 더 잘 짓고 싶은데 혹시 좋은 명칭이 있을까요?
+---
+
+## Decision
+
+### 어떤 구현 방식을 선택했나요?
+
+- 선택한 구현 방식
+- 검토한 대안
+- 선택 이유
+
+---
+
+## Changes
+
+### 무엇이 변경되었나요?
+
+#### Feature
+
+-
+
+#### Fix
+
+-
+
+#### Refactor
+
+-
+
+#### Test
+
+-
+
+#### Docs
+
+- ***
+
+## Trade-off
+
+### 한계와 트레이드오프
+
+- 현재 구현의 한계
+- 이후 개선 가능한 부분
+
+---
+
+## Impact
+
+### 기존 기능에 미치는 영향
+
+- 영향받는 기능
+- 회귀 가능성이 있는 영역
+- 배포 시 확인할 사항
+
+---
+
+## Edge Cases
+
+### Edge Case 및 실패 시나리오
+
+- 고려한 예외 상황
+- 실패 가능성이 있는 시나리오
+
+---
+
+## Review Points
+
+### 리뷰어가 집중해서 봐야 할 부분
+
+#### 🔴 High
+
+-
+
+#### 🟡 Medium
+
+-
+
+#### 🟢 Low
+
+- ***
+
+## Validation
+
+### 어떻게 검증했나요?
+
+- [ ] 단위 테스트
+- [ ] E2E 테스트
+- [ ] 수동 테스트
+- [ ] lint
+- [ ] typecheck
diff --git a/.github/workflows/create-release-note.yml b/.github/workflows/create-release-note.yml
new file mode 100644
index 0000000..1b15ce0
--- /dev/null
+++ b/.github/workflows/create-release-note.yml
@@ -0,0 +1,218 @@
+name: Create production release
+
+on:
+ status:
+
+permissions:
+ contents: write
+ pull-requests: read
+
+concurrency:
+ group: production-release-${{ github.event.sha }}-${{ github.event.context }}-${{ github.event.state }}
+ cancel-in-progress: false
+
+jobs:
+ create-release:
+ if: >-
+ github.event.context == 'Vercel' &&
+ github.event.state == 'success' &&
+ github.event.sender.login == 'vercel[bot]' &&
+ startsWith(github.event.target_url, 'https://vercel.com/hm1n/nbread/')
+ runs-on: ubuntu-latest
+ env:
+ EVENT_SHA: ${{ github.event.sha }}
+ GH_TOKEN: ${{ github.token }}
+
+ steps:
+ - name: Checkout main
+ uses: actions/checkout@v6
+ with:
+ fetch-depth: 0
+ ref: main
+
+ - name: Verify latest main deployment
+ id: main
+ shell: bash
+ run: |
+ set -euo pipefail
+
+ git fetch origin main --tags
+
+ MAIN_SHA="$(git rev-parse origin/main)"
+ if [[ "$MAIN_SHA" != "$EVENT_SHA" ]]; then
+ echo "현재 main 최신 커밋의 배포가 아니므로 릴리즈를 생성하지 않습니다."
+ echo "event_sha=$EVENT_SHA"
+ echo "main_sha=$MAIN_SHA"
+ echo "eligible=false" >> "$GITHUB_OUTPUT"
+ exit 0
+ fi
+
+ echo "Vercel 성공 상태가 현재 main 최신 커밋과 일치합니다: $EVENT_SHA"
+ echo "eligible=true" >> "$GITHUB_OUTPUT"
+
+ - name: Resolve release pull request
+ if: steps.main.outputs.eligible == 'true'
+ id: release
+ shell: bash
+ run: |
+ set -euo pipefail
+
+ PULLS_JSON="$(
+ gh api \
+ -H "Accept: application/vnd.github+json" \
+ "repos/${GITHUB_REPOSITORY}/commits/${EVENT_SHA}/pulls?per_page=100"
+ )"
+
+ MATCHING_PULLS="$(
+ jq \
+ --arg repository "$GITHUB_REPOSITORY" \
+ '[
+ .[]
+ | select(
+ .merged_at != null
+ and .base.ref == "main"
+ and .head.ref == "develop"
+ and .head.repo.full_name == $repository
+ and (.title | test("^\\[release\\] v[0-9]+\\.[0-9]+\\.[0-9]+$"))
+ )
+ ]' \
+ <<< "$PULLS_JSON"
+ )"
+
+ PULL_COUNT="$(jq 'length' <<< "$MATCHING_PULLS")"
+ if [[ "$PULL_COUNT" -ne 1 ]]; then
+ echo "배포 SHA에 연결된 유효한 릴리즈 PR은 정확히 1개여야 합니다. 발견: $PULL_COUNT" >&2
+ jq -r '.[] | "- #\(.number) \(.title) (\(.head.ref) -> \(.base.ref))"' <<< "$MATCHING_PULLS" >&2
+ exit 1
+ fi
+
+ PR_NUMBER="$(jq -r '.[0].number' <<< "$MATCHING_PULLS")"
+ PR_TITLE="$(jq -r '.[0].title' <<< "$MATCHING_PULLS")"
+ PR_BODY="$(jq -r '.[0].body // ""' <<< "$MATCHING_PULLS")"
+
+ if [[ "$PR_TITLE" =~ ^\[release\]\ (v[0-9]+\.[0-9]+\.[0-9]+)$ ]]; then
+ VERSION="${BASH_REMATCH[1]}"
+ else
+ echo "릴리즈 PR 제목 형식이 올바르지 않습니다: $PR_TITLE" >&2
+ exit 1
+ fi
+
+ BODY_VERSION="$(
+ awk '
+ /^## 릴리즈 버전[[:space:]]*$/ { in_version_section = 1; next }
+ in_version_section && /^## / { exit }
+ in_version_section && match($0, /`v[0-9]+\.[0-9]+\.[0-9]+`/) {
+ print substr($0, RSTART + 1, RLENGTH - 2)
+ exit
+ }
+ ' <<< "$PR_BODY"
+ )"
+
+ if [[ "$BODY_VERSION" != "$VERSION" ]]; then
+ echo "릴리즈 PR 제목과 본문의 버전이 일치하지 않습니다." >&2
+ echo "title_version=$VERSION" >&2
+ echo "body_version=${BODY_VERSION:-missing}" >&2
+ exit 1
+ fi
+
+ START_COUNT="$(grep -cF '' <<< "$PR_BODY" || true)"
+ END_COUNT="$(grep -cF '' <<< "$PR_BODY" || true)"
+ if [[ "$START_COUNT" -ne 1 || "$END_COUNT" -ne 1 ]]; then
+ echo "릴리즈 노트 시작/종료 마커가 각각 정확히 1개 필요합니다." >&2
+ echo "start_count=$START_COUNT" >&2
+ echo "end_count=$END_COUNT" >&2
+ exit 1
+ fi
+
+ START_LINE="$(grep -nF '' <<< "$PR_BODY" | cut -d: -f1)"
+ END_LINE="$(grep -nF '' <<< "$PR_BODY" | cut -d: -f1)"
+ if [[ "$START_LINE" -ge "$END_LINE" ]]; then
+ echo "릴리즈 노트 마커의 순서가 올바르지 않습니다." >&2
+ exit 1
+ fi
+
+ if [[ "$END_LINE" -le "$((START_LINE + 1))" ]]; then
+ echo "릴리즈 노트 마커 사이에 본문이 없습니다." >&2
+ exit 1
+ fi
+
+ sed -n "$((START_LINE + 1)),$((END_LINE - 1))p" <<< "$PR_BODY" > release-notes.md
+ if ! grep -q '[^[:space:]]' release-notes.md; then
+ echo "릴리즈 PR에서 추출한 릴리즈 노트에 비공백 내용이 없습니다." >&2
+ exit 1
+ fi
+
+ echo "릴리즈 PR을 확인했습니다: #$PR_NUMBER $PR_TITLE"
+ echo "pr_number=$PR_NUMBER" >> "$GITHUB_OUTPUT"
+ echo "version=$VERSION" >> "$GITHUB_OUTPUT"
+
+ - name: Create or recover production release
+ if: steps.main.outputs.eligible == 'true'
+ shell: bash
+ env:
+ PR_NUMBER: ${{ steps.release.outputs.pr_number }}
+ VERSION: ${{ steps.release.outputs.version }}
+ run: |
+ set -euo pipefail
+
+ TAG_REF="$(git ls-remote --tags origin "refs/tags/${VERSION}")"
+ if [[ -n "$TAG_REF" ]]; then
+ TAG_EXISTS=true
+ else
+ TAG_EXISTS=false
+ fi
+
+ if gh api --silent "repos/${GITHUB_REPOSITORY}/releases/tags/${VERSION}" 2> release-error.txt; then
+ RELEASE_EXISTS=true
+ elif grep -q 'HTTP 404' release-error.txt; then
+ RELEASE_EXISTS=false
+ else
+ echo "GitHub Release 존재 여부를 확인하지 못했습니다." >&2
+ cat release-error.txt >&2
+ exit 1
+ fi
+
+ if [[ "$TAG_EXISTS" == true ]]; then
+ git fetch origin "refs/tags/${VERSION}:refs/tags/${VERSION}"
+ TAG_SHA="$(git rev-list -n 1 "$VERSION")"
+ if [[ "$TAG_SHA" != "$EVENT_SHA" ]]; then
+ echo "기존 태그가 현재 배포 SHA와 다른 커밋을 가리킵니다." >&2
+ echo "version=$VERSION" >&2
+ echo "tag_sha=$TAG_SHA" >&2
+ echo "event_sha=$EVENT_SHA" >&2
+ exit 1
+ fi
+ fi
+
+ if [[ "$TAG_EXISTS" == true && "$RELEASE_EXISTS" == true ]]; then
+ echo "태그와 GitHub Release가 이미 존재합니다: $VERSION"
+ exit 0
+ fi
+
+ if [[ "$TAG_EXISTS" == false && "$RELEASE_EXISTS" == true ]]; then
+ echo "GitHub Release는 존재하지만 대응하는 Git 태그가 없습니다: $VERSION" >&2
+ exit 1
+ fi
+
+ if [[ "$TAG_EXISTS" == false ]]; then
+ git config user.name "github-actions[bot]"
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
+ git tag -a "$VERSION" "$EVENT_SHA" -m "$VERSION"
+ git push origin "refs/tags/${VERSION}"
+ echo "배포 SHA에 태그를 생성했습니다: $VERSION -> $EVENT_SHA"
+ else
+ echo "기존 태그를 사용해 GitHub Release 생성을 재시도합니다: $VERSION"
+ fi
+
+ gh release create "$VERSION" \
+ --repo "$GITHUB_REPOSITORY" \
+ --title "$VERSION" \
+ --notes-file release-notes.md \
+ --verify-tag
+
+ {
+ echo "### Production release"
+ echo "- Version: \`$VERSION\`"
+ echo "- Commit: \`$EVENT_SHA\`"
+ echo "- Pull Request: #$PR_NUMBER"
+ } >> "$GITHUB_STEP_SUMMARY"
diff --git a/.gitignore b/.gitignore
index 4f8eb38..ada04d4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -40,9 +40,6 @@ yarn-error.log*
*.tsbuildinfo
next-env.d.ts
-# github
-.github
-
# pwa
public/sw.js*
public/workbox-*.js*
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..aef83aa
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,116 @@
+# Repository Guideline
+
+## 프로젝트 개요
+
+이 프로젝트는 구독 공유 통합 관리 서비스 **엔빵**의 프론트엔드 저장소입니다.
+
+- Framework: Next.js (App Router)
+- Language: TypeScript (Strict Mode)
+- Backend: Supabase
+- State Management: Zustand
+- Styling: Tailwind CSS
+
+---
+
+## 개발 원칙
+
+AI는 아래 원칙을 항상 우선적으로 따릅니다.
+
+- 기존 기능이 정상 동작하는 것을 최우선으로 합니다.
+- 필요한 범위만 최소한으로 수정합니다.
+- 큰 변경보다 작은 변경을 선호합니다.
+- 추측하여 구현하지 않습니다.
+- 기존 코드 스타일과 프로젝트 구조를 최대한 유지합니다.
+
+---
+
+## 작업 방식
+
+모든 개발 작업은 아래 순서를 따릅니다.
+
+```
+구현 설계서
+ ↓
+GitHub Issue
+ ↓
+구현
+ ↓
+Pull Request
+ ↓
+Code Review
+```
+
+- 구현 전에 작업 목적과 범위를 먼저 이해합니다.
+- 하나의 Issue는 가능한 하나의 PR로 구현합니다.
+- PR이 너무 커질 경우 Task 단위로 작업을 다시 분리합니다.
+- 구현보다 작업 분해와 영향 범위를 먼저 검토합니다.
+
+---
+
+## 코드 작성 규칙
+
+- TypeScript Strict Mode를 유지합니다.
+- `any` 사용은 지양합니다.
+- 기존 프로젝트의 코드 스타일을 따릅니다.
+- 새로운 구조를 만들기보다 기존 패턴을 우선 재사용합니다.
+- UI 컴포넌트에서 비즈니스 로직을 직접 구현하지 않습니다.
+- 기존 Helper 또는 Lib가 있다면 우선 활용합니다.
+
+---
+
+## 테스트
+
+작업 완료 후 아래 내용을 확인합니다.
+
+- `npm run lint`
+- `npm run build`
+
+Playwright 테스트가 존재하는 경우 영향을 받은 기능을 함께 검증합니다.
+
+---
+
+## Review Guidelines
+
+리뷰의 목적은 코드 스타일을 지적하는 것이 아니라, 변경 사항의 위험 요소와 의사결정을 검증하는 것입니다.
+
+리뷰 시 아래 순서를 우선합니다.
+
+1. Why
+ - 이 작업을 왜 해야 하는가?
+ - 지금 해결해야 하는 이유가 충분한가?
+
+2. Decision
+ - 구현 방식이 문제를 해결하는 데 적절한가?
+ - 더 단순하거나 안전한 대안은 없는가?
+
+3. Impact
+ - 기존 기능에 영향을 주지 않는가?
+ - 회귀(Regression), 데이터 정합성, 성능 저하 가능성은 없는가?
+
+4. Review Points
+ - 작성자가 지정한 위험 영역을 우선 검토한다.
+
+코드 스타일보다 기능 안정성과 사용자 영향도를 우선하며, P0/P1 수준의 문제에 집중합니다.
+
+---
+
+## 보안
+
+- Secret 또는 `.env` 파일은 절대 커밋하지 않습니다.
+- Supabase Schema 변경 시 Type을 최신 상태로 유지합니다.
+- RLS 관련 변경은 특히 신중하게 검토합니다.
+- 운영 환경에서만 사용하는 설정은 유지합니다.
+
+---
+
+## AI 작업 원칙
+
+작업 시 다음 내용을 항상 명확히 설명합니다.
+
+- 왜 이 작업이 필요한가
+- 어떤 방식을 선택했는가
+- 어떤 대안을 검토했는가
+- 어떤 영향 범위를 가지는가
+- 어떤 리스크가 존재하는가
+
+불필요한 리팩토링이나 구조 변경은 제안하지 않습니다.
diff --git a/next-sitemap.config.js b/next-sitemap.config.js
index 9657d56..95c22d8 100644
--- a/next-sitemap.config.js
+++ b/next-sitemap.config.js
@@ -1,6 +1,6 @@
/** @type {import('next-sitemap').IConfig} */
module.exports = {
- siteUrl: process.env.SITE_URL || 'https://nbread-nbread.vercel.app',
+ siteUrl: 'https://www.nbread.co.kr',
generateRobotsTxt: true,
changefreq: 'weekly',
priority: 0.7,
@@ -9,8 +9,11 @@ module.exports = {
'/auth/*',
'/calendar',
'/friendList',
+ '/home',
'/icon.ico',
- '/inviteAccept',
+ '/invite/*',
+ '/invites',
+ '/login',
'/manifest.json',
'/mypage',
'/mypage/*',
@@ -30,7 +33,8 @@ module.exports = {
'/auth/',
'/calendar',
'/friendList',
- '/inviteAccept',
+ '/home',
+ '/invites',
'/mypage',
'/nbread/',
'/notification',
diff --git a/package-lock.json b/package-lock.json
index 40d5e02..a38a982 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "andbread",
- "version": "0.1.0",
+ "version": "2.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "andbread",
- "version": "0.1.0",
+ "version": "2.1.0",
"dependencies": {
"@microsoft/clarity": "^1.0.2",
"@sentry/nextjs": "^10.53.1",
diff --git a/public/assets/logo/nbread-logo.png b/public/assets/logo/nbread-logo.png
new file mode 100644
index 0000000..e6dd982
Binary files /dev/null and b/public/assets/logo/nbread-logo.png differ
diff --git a/public/image/landing/home.png b/public/image/landing/home.png
new file mode 100644
index 0000000..90d15ac
Binary files /dev/null and b/public/image/landing/home.png differ
diff --git a/public/robots.txt b/public/robots.txt
index 223dccc..799d608 100644
--- a/public/robots.txt
+++ b/public/robots.txt
@@ -7,14 +7,15 @@ User-agent: *
Disallow: /auth/
Disallow: /calendar
Disallow: /friendList
-Disallow: /inviteAccept
+Disallow: /home
+Disallow: /invites
Disallow: /mypage
Disallow: /nbread/
Disallow: /notification
Disallow: /terms-agreement
# Host
-Host: https://nbread-nbread.vercel.app
+Host: https://www.nbread.co.kr
# Sitemaps
-Sitemap: https://nbread-nbread.vercel.app/sitemap.xml
+Sitemap: https://www.nbread.co.kr/sitemap.xml
diff --git a/public/sitemap-0.xml b/public/sitemap-0.xml
index 1ada202..5c01073 100644
--- a/public/sitemap-0.xml
+++ b/public/sitemap-0.xml
@@ -1,8 +1,7 @@
-https://nbread-nbread.vercel.app/ios-guide2026-06-04T11:49:35.254Zweekly0.7
-https://nbread-nbread.vercel.app/terms-of-service2026-06-04T11:49:35.257Zweekly0.7
-https://nbread-nbread.vercel.app2026-06-04T11:49:35.257Zweekly0.7
-https://nbread-nbread.vercel.app/privacy-policy2026-06-04T11:49:35.257Zweekly0.7
-https://nbread-nbread.vercel.app/login2026-06-04T11:49:35.257Zweekly0.7
+https://www.nbread.co.kr/ios-guide2026-07-10T09:10:58.779Zweekly0.7
+https://www.nbread.co.kr2026-07-10T09:10:58.780Zweekly0.7
+https://www.nbread.co.kr/privacy-policy2026-07-10T09:10:58.780Zweekly0.7
+https://www.nbread.co.kr/terms-of-service2026-07-10T09:10:58.780Zweekly0.7
\ No newline at end of file
diff --git a/public/sitemap.xml b/public/sitemap.xml
index 0cff401..2d62498 100644
--- a/public/sitemap.xml
+++ b/public/sitemap.xml
@@ -1,4 +1,4 @@
-https://nbread-nbread.vercel.app/sitemap-0.xml
+https://www.nbread.co.kr/sitemap-0.xml
\ No newline at end of file
diff --git a/src/app/home/page.tsx b/src/app/home/page.tsx
new file mode 100644
index 0000000..5da6e5b
--- /dev/null
+++ b/src/app/home/page.tsx
@@ -0,0 +1,113 @@
+'use client'
+import { useEffect, useState } from 'react'
+import { getUserNbreads } from '@/lib/nbread'
+import Header from '@/components/home/Header'
+import MonthlyNbread from '@/components/home/MonthlyNbread'
+import MyNbread from '@/components/home/MyNbread'
+import { Nbread } from '@/types/nbread'
+import useUserStore from '@/stores/useAuthStore'
+import { getParticipants } from '@/lib/participant'
+import Spinner from '@/components/common/spinner/Spinner'
+import NotificationPermissionModal from '@/components/common/modal/NotificationPermissionModal'
+import NotificationDeniedModal from '@/components/common/modal/NotificationDeniedModal'
+import ReceivedInviteBanner from '@/components/home/ReceivedInviteBanner'
+import { getPendingInvites } from '@/lib/invite/getPendingInvites'
+import type { PendingInvite } from '@/lib/invite/getPendingInvites'
+
+// [ ] OS 알림 허용 후 모달 닫힘
+// [ ] OS 알림 허용 상태 확인해서 모달 열기
+
+const HomePage = () => {
+ const user = useUserStore((state) => state.user)
+ const [monthlyNbreadList, setMonthlyNbreadList] = useState([])
+ const [myNbreadList, setMyNbreadList] = useState([])
+ const [pendingInvites, setPendingInvites] = useState([])
+ const [totalAmount, setTotalAmount] = useState(0)
+ const [isLoading, setIsLoading] = useState(true)
+ const [isNotificationDeniedModalOpen, setIsNotificationDeniedModalOpen] =
+ useState(false)
+ const currentMonth = new Date().getMonth() + 1
+
+ // Nbread 및 Participant 정보를 DB로부터 fetch
+ const fetchNbreads = async (userId: string) => {
+ try {
+ const [{ monthlyNbreads, myNbreads }, invites] = await Promise.all([
+ getUserNbreads(userId),
+ // 초대 조회 실패가 기존 홈 엔빵 조회까지 막지 않도록 빈 목록으로 처리한다.
+ getPendingInvites(userId).catch(() => []),
+ ])
+
+ const myNbreadsWithParticipants = await Promise.all(
+ myNbreads.map(async (nbread) => {
+ const participants = await getParticipants(nbread.id)
+ return { ...nbread, participants }
+ }),
+ )
+ const nbreadsById = new Map(
+ myNbreadsWithParticipants.map((nbread) => [nbread.id, nbread]),
+ )
+ const monthlyNbreadsWithParticipants = monthlyNbreads.map(
+ (nbread) => nbreadsById.get(nbread.id) ?? nbread,
+ )
+
+ setMonthlyNbreadList(monthlyNbreadsWithParticipants)
+ setMyNbreadList(myNbreadsWithParticipants)
+ setPendingInvites(invites)
+ } catch {
+ setMonthlyNbreadList([])
+ setMyNbreadList([])
+ setPendingInvites([])
+ } finally {
+ setIsLoading(false)
+ }
+ }
+
+ useEffect(() => {
+ if (!user) return
+ fetchNbreads(user.id)
+ // testSendPaymentNotification()
+ // test()
+ }, [user])
+
+ // NbreadList가 업데이트된 후 totalAmount 계산
+ useEffect(() => {
+ const total = monthlyNbreadList.reduce(
+ (sum: number, nbread: Nbread) =>
+ sum + Math.floor(nbread.amount / Math.max(nbread.participantCount, 1)),
+ 0,
+ )
+ setTotalAmount(total)
+ }, [monthlyNbreadList])
+
+ return (
+