Skip to content

Commit 07e0f63

Browse files
trz-21claude
andcommitted
chore: initialize monorepo (Rust backend + Next.js frontend)
Coupang Review AI 프로젝트 초기 세팅. - monorepo: backend/ (Rust + Axum, Hexagonal) + frontend/ (Next.js 14) - specification/ 스펙 문서 6종, CLAUDE.md, ADR, 작업 로그 - .claude/ 워크플로우 규칙 + 자동화 훅 14개 ($CLAUDE_PROJECT_DIR 기반) - CI: .github/workflows backend-ci / frontend-ci (paths 필터) - 검증: cargo check/fmt/clippy/test, FE type-check/lint/vitest 모두 통과 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
0 parents  commit 07e0f63

82 files changed

Lines changed: 15345 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/code-rules.md

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# AI Agent 코드 수정 규칙
2+
3+
코드를 수정할 때 반드시 준수한다.
4+
5+
## 일반 규칙
6+
7+
1. **컴파일 먼저**: `cargo check` 통과 없이 작업 완료 불가
8+
2. **테스트 통과**: `cargo test` 전체 통과 필수
9+
3. **API 계약 유지**: 기존 API 응답 필드 제거/타입 변경 금지 (추가만 허용)
10+
4. **트레이트 계약**: Port trait 변경 시 모든 구현체 동시 수정
11+
5. **테스트 동반**: 새 엔드포인트/기능 추가 시 테스트 필수
12+
6. **스펙 동기화**: 기능/설계 변경 시 `specification/` 업데이트 필수
13+
14+
## 백엔드 새 API 엔드포인트 추가 순서
15+
16+
```
17+
[ ] 1. domain/ports/ 에 필요한 trait 정의 (없으면 추가)
18+
[ ] 2. adapters/ 에 구현체 작성
19+
[ ] 3. application/ 에 서비스 로직 작성
20+
[ ] 4. http/handlers/ 에 핸들러 작성 (매크로 패턴 사용, 아래 참조)
21+
[ ] 5. http/router.rs 의 route_table() 에 한 줄 추가
22+
[ ] 6. 통합 테스트 작성 (TestApp + Mock 주입)
23+
[ ] 7. cargo check && cargo test 통과
24+
[ ] 8. specification/dev/api.md 업데이트
25+
```
26+
27+
### 핸들러 작성 규칙
28+
29+
**표준 케이스 → 매크로 사용 (backend/src/http/macros.rs)**
30+
31+
| 패턴 | 매크로 |
32+
|------|--------|
33+
| POST + JSON body | `post_endpoint!` |
34+
| GET + /:id | `get_endpoint_with_id!` |
35+
| POST + /:id (body 없음) | `post_endpoint_with_id!` |
36+
37+
매크로는 Request/Response 구조체와 핸들러 함수를 함께 생성한다.
38+
잘못된 타입 또는 서비스 메서드 시그니처는 컴파일 에러로 즉시 검출된다.
39+
40+
**비표준 케이스 → 수동 작성** (OptionalUser, Query 파라미터, SSE)
41+
42+
수동 작성 시 반드시 `impl IntoResponse` 대신 `Result<Json<XxxResponse>, AppError>` 를 명시한다.
43+
XxxResponse는 `#[derive(Serialize)]` 구조체여야 한다 (`json!({...})` 매크로 금지).
44+
45+
## 프론트엔드 새 API 함수 추가 패턴
46+
47+
**모든 API 함수는 명명된 타입을 사용한다. 인라인 타입(`Promise<{ field: Type }>`) 금지.**
48+
49+
```
50+
[ ] 1. src/features/{domain}/types.ts 에 {Action}Request / {Action}Response 인터페이스 추가
51+
[ ] 2. src/features/{domain}/api/index.ts 에 함수 추가:
52+
export async function actionName(req: ActionRequest): Promise<ActionResponse>
53+
[ ] 3. npm run type-check && npm test 통과
54+
```
55+
56+
### 타입 네이밍 규칙
57+
58+
| 종류 | 패턴 | 예시 |
59+
|------|------|------|
60+
| 요청 body | `{Action}Request` | `CreateItemRequest` |
61+
| 응답 | `{Action}Response` | `CreateItemResponse` |
62+
| 도메인 모델 | 명사형 | `Item`, `User` |
63+
64+
- `{Action}Request`, `{Action}Response``src/features/{domain}/types.ts` (예외 파일이므로 테스트 불필요)
65+
- BE `XxxResponse` 구조체와 1:1 대응 유지
66+
67+
## 파일별 단위 테스트 요구사항
68+
69+
- `backend/src/application/`, `backend/src/adapters/` 하위 .rs 파일: `#[cfg(test)] mod tests` 필수
70+
- `frontend/src/features/` 하위 .ts/.tsx 파일: 동일 경로 `.test.ts` 파일 필수
71+
- 예외: `mod.rs`, `main.rs`, `lib.rs`, `config.rs`, `error.rs`, `index.ts`, `types.ts`

.claude/commit-rules.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# 커밋 규칙
2+
3+
## 타이밍
4+
5+
- DoD (`.claude/dod.md`) 전체 충족 시점에 커밋
6+
- 논리적으로 완결된 단계마다 커밋 (한 작업을 여러 단계로 나눌 때)
7+
- WIP 상태 (컴파일/테스트 실패) 는 커밋하지 않는다
8+
9+
## 메시지 형식
10+
11+
```
12+
<type>: <subject>
13+
14+
[optional body]
15+
16+
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
17+
```
18+
19+
## Type 목록
20+
21+
| type | 사용 상황 |
22+
|------|-----------|
23+
| `feat` | 새 기능 추가 |
24+
| `fix` | 버그 수정 |
25+
| `refactor` | 기능 변경 없는 코드 개선 |
26+
| `test` | 테스트 추가/수정 |
27+
| `docs` | 문서만 변경 |
28+
| `infra` | 인프라/배포 설정 변경 |
29+
| `chore` | 의존성, 설정, 빌드 스크립트 |
30+
31+
## 범위
32+
33+
- 백엔드 + 프론트엔드 + 문서가 하나의 기능을 위한 변경이면 **한 커밋**
34+
- 서로 독립적인 변경은 **별도 커밋**
35+
- `.env`, `.env.prod`, 시크릿 파일은 **절대 커밋 금지**
36+
37+
## 푸시
38+
39+
사용자가 명시적으로 요청할 때만 푸시한다.

.claude/dod.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# 작업 완료 기준 (Definition of Done)
2+
3+
코드 수정이 포함된 모든 작업은 아래가 **모두** 충족돼야 "완료"다.
4+
5+
## 체크리스트
6+
7+
- [ ] **컴파일/타입**: `cargo check` (BE) / `npm run type-check` (FE) 통과
8+
- [ ] **테스트**: `cargo test` (BE) / `npm test -- --run` (FE) 전체 통과
9+
- [ ] **작업 로그**: `docs/works/YYYYMMDD-[작업명].md` 작성
10+
- [ ] **스펙 동기화**: 기능/설계 변경 시 `specification/` 해당 파일 업데이트
11+
- [ ] **커밋**: 작업 단위로 git commit (`.claude/commit-rules.md` 참조)
12+
13+
## 검증 명령어
14+
15+
```bash
16+
# 백엔드
17+
cd backend
18+
cargo check
19+
cargo test
20+
21+
# 프론트엔드
22+
cd frontend
23+
npm run type-check
24+
npm test -- --run
25+
```
26+
27+
## 서브에이전트 주의사항
28+
29+
훅은 메인 Claude에만 적용된다. 서브에이전트에게 작업을 위임할 경우,
30+
메인이 서브에이전트 완료 후 직접 검증해야 한다:
31+
32+
1. 해당 명령어 실행 (cargo check, npm run type-check, cargo test, npm test -- --run)
33+
2. `docs/works/YYYYMMDD-*.md` 오늘 날짜 파일 존재 확인
34+
3. 모두 통과 시에만 작업 완료 처리

.claude/hooks/block-git-push.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/bin/bash
2+
COMMAND=$(jq -r '.tool_input.command // ""' 2>/dev/null)
3+
if echo "$COMMAND" | grep -qE '^\s*git push'; then
4+
echo '{"continue": false, "stopReason": "git push는 사용자가 명시적으로 요청할 때만 실행합니다."}'
5+
exit 0
6+
fi
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#!/bin/bash
2+
INPUT=$(cat)
3+
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.path // ""')
4+
PROJECT_ROOT="${CLAUDE_PROJECT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}"
5+
6+
if ! echo "$FILE_PATH" | grep -qE '\.rs$'; then exit 0; fi
7+
if ! echo "$FILE_PATH" | grep -q 'backend/'; then exit 0; fi
8+
9+
BACKEND_DIR="${PROJECT_ROOT}/backend"
10+
cd "$BACKEND_DIR" || exit 0
11+
12+
CHECK_OUTPUT=$(cargo check 2>&1)
13+
if [ $? -ne 0 ]; then echo "$CHECK_OUTPUT" >&2; exit 2; fi
14+
15+
BASENAME=$(basename "$FILE_PATH")
16+
17+
# mod.rs, lib.rs, main.rs → 전체 단위 테스트
18+
if echo "$BASENAME" | grep -qE '^(mod|lib|main)\.rs$'; then
19+
cargo nextest run --lib 2>&1; exit $?
20+
fi
21+
22+
# tests/ 하위 → 해당 integration test만
23+
if echo "$FILE_PATH" | grep -qE 'backend/tests/[^/]+\.rs$'; then
24+
TEST_NAME=$(basename "$FILE_PATH" .rs)
25+
cargo nextest run --test "$TEST_NAME" 2>&1; exit $?
26+
fi
27+
28+
# src/ 하위 → 모듈 경로로 변환하여 단위 테스트 필터
29+
if echo "$FILE_PATH" | grep -qE 'backend/src/'; then
30+
MODULE=$(echo "$FILE_PATH" | sed 's|.*/backend/src/||' | sed 's|\.rs$||' | sed 's|/|::|g')
31+
cargo nextest run --lib --filter-expr "test(~$MODULE)" 2>&1; exit $?
32+
fi
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#!/bin/bash
2+
INPUT=$(cat)
3+
STOP_HOOK_ACTIVE=$(echo "$INPUT" | jq -r '.stop_hook_active // false')
4+
if [ "$STOP_HOOK_ACTIVE" = "true" ]; then exit 0; fi
5+
PROJECT_ROOT="${CLAUDE_PROJECT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}"
6+
7+
RUST_MARKER="${PROJECT_ROOT}/.claude/.pending-rust-check"
8+
SPEC_MARKER="${PROJECT_ROOT}/.claude/.pending-spec-check"
9+
MARKER="${PROJECT_ROOT}/.claude/.pending-doc-check"
10+
11+
if [ -f "$RUST_MARKER" ]; then
12+
BACKEND_DIR="${PROJECT_ROOT}/backend"
13+
CHECK_OUTPUT=$(cd "$BACKEND_DIR" && cargo check 2>&1)
14+
if [ $? -ne 0 ]; then
15+
echo "{\"decision\": \"block\", \"reason\": \"cargo check 실패. 컴파일 오류를 수정한 후 완료하세요.\"}"
16+
exit 0
17+
fi
18+
fi
19+
20+
if [ -f "$SPEC_MARKER" ]; then
21+
echo "{\"decision\": \"block\", \"reason\": \"구조적 BE 파일이 변경됐는데 specification/ 업데이트가 없습니다.\"}"
22+
exit 0
23+
fi
24+
25+
if [ -f "$MARKER" ]; then
26+
TODAY=$(date +%Y%m%d)
27+
echo "{\"decision\": \"block\", \"reason\": \"소스 코드가 변경됐는데 작업 로그가 없습니다. docs/works/${TODAY}-[작업명].md 를 작성한 후 완료하세요.\"}"
28+
exit 0
29+
fi
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#!/bin/bash
2+
INPUT=$(cat)
3+
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.path // ""')
4+
PROJECT_ROOT="${CLAUDE_PROJECT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}"
5+
6+
# docs/plans/ 작성 자체는 허용
7+
if echo "$FILE_PATH" | grep -qE 'docs/plans/'; then exit 0; fi
8+
9+
# 소스 파일이 아니면 통과
10+
if ! echo "$FILE_PATH" | grep -qE '(backend/src/|frontend/src/|frontend/app/|backend/tests/|backend/migrations/)'; then
11+
exit 0
12+
fi
13+
14+
PLAN_MARKER="${PROJECT_ROOT}/.claude/.plan-exists"
15+
if [ -f "$PLAN_MARKER" ]; then exit 0; fi
16+
17+
echo '{"decision": "block", "reason": "소스 코드를 수정하기 전에 작업 플랜을 먼저 작성하세요.\n\ndocs/plans/YYYYMMDD-[작업명].md 파일을 작성한 뒤 다시 시도하세요."}'
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/bin/bash
2+
PROJECT_ROOT="${CLAUDE_PROJECT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}"
3+
rm -f "${PROJECT_ROOT}/.claude/.pending-doc-check"
4+
rm -f "${PROJECT_ROOT}/.claude/.pending-rust-check"
5+
rm -f "${PROJECT_ROOT}/.claude/.pending-spec-check"
6+
rm -f "${PROJECT_ROOT}/.claude/.plan-exists"

.claude/hooks/coverage-check-fe.sh

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#!/bin/bash
2+
PROJECT_ROOT="${CLAUDE_PROJECT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}"
3+
FE_DIR="${PROJECT_ROOT}/frontend"
4+
[ ! -d "$FE_DIR" ] && exit 0
5+
[ ! -d "$FE_DIR/node_modules" ] && exit 0
6+
7+
cd "$FE_DIR" || exit 0
8+
npm test -- --run --coverage --silent 2>&1 | grep -E "^(All files|src/|Coverage|Threshold)" >&2
9+
exit ${PIPESTATUS[0]}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/bin/bash
2+
INPUT=$(cat)
3+
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // ""')
4+
5+
if ! echo "$COMMAND" | grep -qE 'cargo (test|nextest)'; then exit 0; fi
6+
if ! docker info > /dev/null 2>&1; then exit 0; fi
7+
8+
TC_CONTAINERS=$(docker ps -aq --filter "label=org.testcontainers.managed-by=testcontainers" 2>/dev/null)
9+
if [ -n "$TC_CONTAINERS" ]; then
10+
docker stop $TC_CONTAINERS > /dev/null 2>&1
11+
docker rm $TC_CONTAINERS > /dev/null 2>&1
12+
fi
13+
docker system prune -f > /dev/null 2>&1

0 commit comments

Comments
 (0)