From 9135dbe3f84723307ed5e8da84e4106c00de918c Mon Sep 17 00:00:00 2001 From: zaman Date: Wed, 13 May 2026 23:33:34 +0900 Subject: [PATCH 01/16] =?UTF-8?q?docs(global):=20AGENTS.md=EB=A5=BC=20SOT?= =?UTF-8?q?=EB=A1=9C=20=EC=9E=AC=EA=B5=AC=EC=84=B1=ED=95=98=EA=B3=A0=20CLA?= =?UTF-8?q?UDE.md=20import=20=EB=B0=A9=EC=8B=9D=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 114 ++++++++++++++++++++---------------------------------- CLAUDE.md | 60 ++++------------------------ 2 files changed, 49 insertions(+), 125 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 9c4f30963..64d86f6cf 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,99 +2,69 @@ ## Project Overview -DataGSM is a Spring Boot REST API service providing school information (students, clubs, meals, schedules) for Gwangju Software Meister High School. The system uses Google OAuth2 authentication with JWT token and API key management. +DataGSM은 광주소프트웨어마이스터고등학교의 학교 정보(학생, 동아리, 급식, 시간표 등)를 제공하는 Spring Boot REST API 서버입니다. Google OAuth2(JWT) 인증과 API Key 기반 외부 공개 API를 함께 제공합니다. ## Tech Stack -- **Backend**: Kotlin, Spring Boot 4.0, Spring Security, Spring Data JPA -- **Database**: MySQL (main data), Redis (caching, sessions) -- **Query & Integration**: QueryDSL for complex queries, OpenFeign for external APIs -- **Serialization**: Jackson 3.0 for JSON processing -- **Testing**: Kotest + MockK + JUnit 5 (Given-When-Then style) +- **Language / Framework**: Kotlin 2.3.10, Spring Boot 4.0.3, Spring Security, Spring Data JPA +- **Build**: Gradle (Java 25 toolchain), multi-module +- **Database**: MySQL (main), Redis (cache, session) +- **Query / Integration**: QueryDSL, OpenFeign +- **Serialization**: Jackson 3.0 +- **Testing**: Kotest (`DescribeSpec`) + MockK + JUnit 5 -## Project Structure (Multi-module) +## Project Structure ``` datagsm-server/ -├── datagsm-common/ # Shared library (Entity, DTO, Repository, Config, Health API) -├── datagsm-oauth-authorization/ # OAuth2 authentication, account lifecycle (signup, password reset) -├── datagsm-oauth-userinfo/ # OAuth2 UserInfo endpoint (external clients) -├── datagsm-openapi/ # Public read-only API (students, clubs, NEIS) -└── datagsm-web/ # Web service API (user features, admin features, Excel) +├── datagsm-common/ # 공유 Entity/DTO/Repository/Config, Health API (실행 모듈 아님) +├── datagsm-oauth-authorization/ # OAuth2 인증, 계정 라이프사이클(회원가입, 비밀번호 재설정) +├── datagsm-oauth-userinfo/ # OAuth2 UserInfo 엔드포인트 (외부 클라이언트용) +├── datagsm-openapi/ # 외부 공개 API (API Key 인증): student, club, project, webhook, neis +└── datagsm-web/ # 웹 서비스 API: account, auth, application, client, student, club, project, utility (Excel 처리 포함) ``` -Each module follows: `controller/`, `service/`, `repository/`, `entity/`, `dto/` +각 모듈은 도메인별로 `controller/ → service/ → repository/` + `entity/`, `dto/` 구조를 따릅니다. -**Note**: `/v1/health` endpoint is provided by `HealthController` in `datagsm-common/global/controller/` and is shared across all modules. +**Key Paths** + +- `/v1/health` 엔드포인트는 `datagsm-common`의 `HealthController`가 제공하며 모든 실행 모듈에 공유됩니다. +- 공통 Entity: `datagsm-common/src/main/kotlin/team/themoment/datagsm/common/domain/` +- 공통 예외 핸들러: `datagsm-common/src/main/kotlin/team/themoment/datagsm/common/global/common/error/` +- API 응답 래퍼: `CommonApiResponse`를 사용하세요. ## Commands - Build: `./gradlew build` - Test: `./gradlew test` -- Format: `./gradlew ktlintFormat` -- Run: `./gradlew ::bootRun` - -## Coding Conventions - -### Kotlin Style - -- Prefer `val` over `var`. Use `var` only when reassignment is strictly required. -- Always use constructor injection — never `@Autowired` field injection. -- Use Kotlin null-safety features (`?.`, `?:`) instead of `!!`. -- Do NOT add excessive comments — only where logic is not self-evident. - -### DTO Annotations - -- Jackson: always use `@field:` target — never `@param:` (e.g., `@field:JsonProperty("user_name")`) -- Swagger/OpenAPI: - - Request DTOs (`*ReqDto`): use `@param:Schema` - - Response DTOs (`*ResDto`): use `@field:Schema` - -### API Conventions - -- 1–2 query params: use `@RequestParam`; 3+ or with validation: use `@ModelAttribute` + DTO -- `@RequestBody` variable: `reqDto`; `@ModelAttribute` query: `queryReq` -- `@Transactional` must be at **method level only** — never class level -- Read operations: `@Transactional(readOnly = true)` / Write operations: `@Transactional` -- Use `CommonApiResponse` wrapper for all API responses - -### Logging - -- English only — verb-led sentences -- SLF4J `{}` placeholder only — no Kotlin string interpolation, no colon separators -- Correct: `logger().info("Deleted {} expired API keys", deletedCount)` -- Wrong: `logger().error("에러 발생: $message")` or `logger().error("Failed: {}", msg)` - -### Exception Handling - -- Use `ExpectedException` directly — do NOT subclass it -- Message: Korean (합쇼체) + period, no dynamic data (IDs, names, variables) -- Correct: `ExpectedException("학생을 찾을 수 없습니다.", HttpStatus.NOT_FOUND)` -- Wrong: `ExpectedException("학생 ID: $id 없음", HttpStatus.NOT_FOUND)` +- Format (commit 전 필수): `./gradlew ktlintFormat` +- Run a module: `./gradlew ::bootRun` (실행 가능 모듈: `datagsm-oauth-authorization`, `datagsm-oauth-userinfo`, `datagsm-openapi`, `datagsm-web`) -### Commit Conventions +## Core Rules (요약) -Format: `type(scope): 설명` +상세 규칙은 `.claude/rules/`에 정의되어 있습니다. 아래는 매 세션 적용되는 최소 규칙입니다. -- Types: `add` / `update` / `fix` / `refactor` / `ci/cd` / `docs` / `test` / `merge` -- Scope: domain name (`auth`, `student`, `club`, `application`, etc.) — NOT module names -- Cross-cutting only: `global`, `ci/cd`, or module names (`web`, `openapi`, `oauth`) -- Description: Korean, no period +- **계층**: Controller → Service → Repository 패턴을 유지하세요. Service 인터페이스(`*Service`)와 구현체(`*ServiceImpl`)를 분리하세요. +- **DI**: 항상 생성자 주입을 사용하세요. `@Autowired` 필드 주입은 금지입니다. +- **불변성**: `val`을 우선 사용하세요. `var`는 재할당이 반드시 필요한 경우(루프 누적, Logback 주입 등)에만 사용하세요. +- **Null 안전성**: `!!`를 사용하지 말고 `?.`, `?:`, `requireNotNull`을 사용하세요. +- **트랜잭션**: `@Transactional`은 **메서드 레벨에만** 붙이세요. 읽기는 `readOnly = true`, 쓰기는 기본 `@Transactional`을 사용하세요. +- **JPA**: N+1을 피하기 위해 Fetch Join 또는 `@EntityGraph`를 사용하세요. +- **테스트**: Kotest `DescribeSpec` + MockK + Given-When-Then 구조를 사용하세요. 테스트 이름은 한국어로 작성하세요 (`describe("클래스명 클래스의")`). +- **주석**: 로직이 자명하지 않은 경우에만 작성하세요. 과도한 주석을 추가하지 마세요. -## Key Practices +## Detailed Rules (`.claude/rules/`) -### JPA -- Avoid N+1 problems — use Fetch Join or `@EntityGraph` -- Use `@Transactional(readOnly = true)` for read operations +해당 파일을 작업할 때 자동 로드됩니다. -### Testing -- Write Kotest tests for business logic -- Use Kotest `DescribeSpec` with `describe/context/it` blocks -- Use MockK for mocking; Given-When-Then structure inside `it` blocks -- Test names in Korean: `describe("클래스명 클래스의")`, `describe("메서드명 메서드는")` +- `kotlin-style.md` — `val/var`, 생성자 주입, null 안전성 +- `dto-annotations.md` — Jackson/Swagger의 `@field:` vs `@param:` 규칙 +- `api-conventions.md` — `@RequestParam` vs `@ModelAttribute`, DTO 명명, `@Transactional` 배치 +- `logging.md` — 영어 only, SLF4J `{}` 플레이스홀더, 콜론 구분자 금지 +- `exception.md` — `ExpectedException` 사용 규칙과 메시지 포맷 +- `commit-conventions.md` — 커밋 `type(scope): 설명` 규칙 (scope는 도메인명) ## Notes -- This project uses Java 25 for Gradle builds -- Always check `.gitignore` and `.geminiignore` when suggesting file changes -- When analyzing code, consider the multi-module structure +- 파일 변경을 제안할 때는 `.gitignore`와 `.geminiignore`를 항상 확인하세요. +- 코드를 분석할 때는 다중 모듈 구조와 모듈 간 의존을 고려하세요 (`datagsm-common`은 모든 실행 모듈의 base). \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index b42b308e5..1611a4cf8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,55 +1,9 @@ -## Project Overview +@AGENTS.md -School information API server for Gwangju Software Meister High School (students, clubs, meals, schedules) +## Claude Code Specific -## Module Structure - -- datagsm-common: Shared Entity/DTO/Repository, Health API -- datagsm-oauth-authorization: OAuth2 authentication, account lifecycle (signup, password reset) -- datagsm-oauth-userinfo: OAuth2 UserInfo endpoint (external clients) -- datagsm-openapi: Public read-only API (students, clubs, NEIS integration) -- datagsm-web: Web service API (user features, admin features, Excel processing) - -**Note**: `/v1/health` endpoint is provided by `HealthController` in `datagsm-common` module and is shared across all modules. - -## Commands - -- Build: `./gradlew build` -- Test: `./gradlew test` -- Format: `./gradlew ktlintFormat` -- Run: `./gradlew ::bootRun` (modules: `datagsm-oauth-authorization`, `datagsm-openapi`, `datagsm-oauth-userinfo`, `datagsm-web`) - -## Tech Stack - -Kotlin, Spring Boot 4.0, Spring Data JPA, QueryDSL, Redis, MySQL - -## Coding Rules - -- Controller → Service → Repository pattern -- Use constructor injection -- Test: Kotest + MockK (Given-When-Then) -- Do NOT add excessive comments - only add comments where logic is not self-evident - -Detailed rules are split into `.claude/rules/`: -- `dto-annotations.md` — `@field:` vs `@param:` rules for Jackson and Swagger -- `logging.md` — English-only, SLF4J `{}` placeholders, no colon separators -- `exception.md` — `ExpectedException` usage and message format -- `kotlin-style.md` — `val/var`, constructor injection, null safety -- `api-conventions.md` — `@RequestParam` vs `@ModelAttribute`, DTO naming, `@Transactional` placement -- `commit-conventions.md` — commit type/scope rules - -## Context Compaction Rules - -Priority order when compressing conversation history: -1. Project Overview (module structure) -2. Common Mistakes section -3. DTO Annotations rules -4. Commit/PR conventions -5. Tech Stack -6. Reduce: Commands, Key Paths - -## Key Paths - -- Common Entity: `datagsm-common/src/main/kotlin/.../domain/` -- Exception Handler: `datagsm-common/.../global/common/error/` -- API Response: Use `CommonApiResponse` wrapper +- 항상 **한국어**로 응답하세요 (이미 AGENTS.md에서 요청됨 — 일관성 유지). +- 코드 변경 전 관련 도메인의 `.claude/rules/` 파일이 자동 로드되었는지 확인하세요. 누락된 규칙은 추측하지 말고 해당 파일을 직접 읽으세요. +- 컨벤션 위반이 발생했을 때 단순히 수정만 하지 말고, 그 규칙이 `.claude/rules/`에 명문화되어 있는지 확인하고 빠져 있다면 추가를 제안하세요. +- 커밋·PR 전에는 `commit` 스킬을 통해 `./gradlew ktlintFormat`이 적용된 상태인지 확인하세요. +- 코드 리뷰가 필요할 때는 `code-review` 스킬을, 보안 점검이 필요할 때는 `security-checklist` 스킬을 우선 호출하세요. \ No newline at end of file From 75a197b245abe9535f9e38fa8fbe03cae768b297 Mon Sep 17 00:00:00 2001 From: zaman Date: Wed, 13 May 2026 23:39:12 +0900 Subject: [PATCH 02/16] =?UTF-8?q?fix(global):=20=EC=A1=B4=EC=9E=AC?= =?UTF-8?q?=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20.geminiignore=20?= =?UTF-8?q?=EC=96=B8=EA=B8=89=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index 64d86f6cf..548ae4f0c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -66,5 +66,5 @@ datagsm-server/ ## Notes -- 파일 변경을 제안할 때는 `.gitignore`와 `.geminiignore`를 항상 확인하세요. +- 파일 변경을 제안할 때는 `.gitignore`를 항상 확인하세요. - 코드를 분석할 때는 다중 모듈 구조와 모듈 간 의존을 고려하세요 (`datagsm-common`은 모든 실행 모듈의 base). \ No newline at end of file From 8caf9b1ebe622b84ad2efe8dabc90757c4b218cf Mon Sep 17 00:00:00 2001 From: zaman Date: Wed, 13 May 2026 23:39:24 +0900 Subject: [PATCH 03/16] =?UTF-8?q?fix(global):=20AGENTS.md=EC=99=80=20?= =?UTF-8?q?=EC=A4=91=EB=B3=B5=EB=90=98=EB=8A=94=20=ED=95=9C=EA=B5=AD?= =?UTF-8?q?=EC=96=B4=20=EC=9D=91=EB=8B=B5=20=EC=A7=80=EC=8B=9C=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CLAUDE.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index 1611a4cf8..e0cf3dd81 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,7 +2,6 @@ ## Claude Code Specific -- 항상 **한국어**로 응답하세요 (이미 AGENTS.md에서 요청됨 — 일관성 유지). - 코드 변경 전 관련 도메인의 `.claude/rules/` 파일이 자동 로드되었는지 확인하세요. 누락된 규칙은 추측하지 말고 해당 파일을 직접 읽으세요. - 컨벤션 위반이 발생했을 때 단순히 수정만 하지 말고, 그 규칙이 `.claude/rules/`에 명문화되어 있는지 확인하고 빠져 있다면 추가를 제안하세요. - 커밋·PR 전에는 `commit` 스킬을 통해 `./gradlew ktlintFormat`이 적용된 상태인지 확인하세요. From 266070c0d481af160653d98ee79f0e484b520f2a Mon Sep 17 00:00:00 2001 From: zaman Date: Wed, 13 May 2026 23:39:36 +0900 Subject: [PATCH 04/16] =?UTF-8?q?fix(global):=20=EC=9E=90=EB=8F=99=20?= =?UTF-8?q?=EB=A1=9C=EB=93=9C=20=ED=99=95=EC=9D=B8=20=EC=A7=80=EC=8B=9C?= =?UTF-8?q?=EB=A5=BC=20=EA=B2=80=EC=A6=9D=20=EA=B0=80=EB=8A=A5=ED=95=9C=20?= =?UTF-8?q?=ED=96=89=EB=8F=99=20=EC=A7=80=EC=8B=9C=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CLAUDE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index e0cf3dd81..a77e3b59e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,7 +2,7 @@ ## Claude Code Specific -- 코드 변경 전 관련 도메인의 `.claude/rules/` 파일이 자동 로드되었는지 확인하세요. 누락된 규칙은 추측하지 말고 해당 파일을 직접 읽으세요. +- 컨벤션이 불확실할 때는 추측하지 말고 `.claude/rules/`의 해당 규칙 파일을 직접 읽으세요. - 컨벤션 위반이 발생했을 때 단순히 수정만 하지 말고, 그 규칙이 `.claude/rules/`에 명문화되어 있는지 확인하고 빠져 있다면 추가를 제안하세요. - 커밋·PR 전에는 `commit` 스킬을 통해 `./gradlew ktlintFormat`이 적용된 상태인지 확인하세요. - 코드 리뷰가 필요할 때는 `code-review` 스킬을, 보안 점검이 필요할 때는 `security-checklist` 스킬을 우선 호출하세요. \ No newline at end of file From 4c716a870c78a009277d0ab9afe83839c45f9b41 Mon Sep 17 00:00:00 2001 From: zaman Date: Wed, 13 May 2026 23:39:47 +0900 Subject: [PATCH 05/16] =?UTF-8?q?fix(global):=20ktlintFormat=EC=9D=84=20co?= =?UTF-8?q?mmit=20=EC=8A=A4=ED=82=AC=EC=9D=B4=20=EC=9E=90=EB=8F=99=20?= =?UTF-8?q?=EC=8B=A4=ED=96=89=ED=95=98=EB=8A=94=20=EA=B2=83=EC=B2=98?= =?UTF-8?q?=EB=9F=BC=20=EC=93=B4=20=EB=B6=80=EB=B6=84=20=EC=A0=95=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CLAUDE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index a77e3b59e..9f9c837bb 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,5 +4,5 @@ - 컨벤션이 불확실할 때는 추측하지 말고 `.claude/rules/`의 해당 규칙 파일을 직접 읽으세요. - 컨벤션 위반이 발생했을 때 단순히 수정만 하지 말고, 그 규칙이 `.claude/rules/`에 명문화되어 있는지 확인하고 빠져 있다면 추가를 제안하세요. -- 커밋·PR 전에는 `commit` 스킬을 통해 `./gradlew ktlintFormat`이 적용된 상태인지 확인하세요. +- 커밋·PR 전에는 `./gradlew ktlintFormat`을 실행해 포맷을 맞추세요. - 코드 리뷰가 필요할 때는 `code-review` 스킬을, 보안 점검이 필요할 때는 `security-checklist` 스킬을 우선 호출하세요. \ No newline at end of file From 8a067f9f588aeb3b2cd0dff87d344a42483630da Mon Sep 17 00:00:00 2001 From: zaman Date: Wed, 13 May 2026 23:40:18 +0900 Subject: [PATCH 06/16] =?UTF-8?q?fix(global):=20=EC=8A=A4=ED=82=AC=20?= =?UTF-8?q?=EC=9E=90=EB=8F=99=20=ED=8A=B8=EB=A6=AC=EA=B1=B0=EC=99=80=20?= =?UTF-8?q?=EC=A4=91=EB=B3=B5=EB=90=98=EB=8A=94=20=EC=9A=B0=EC=84=A0=20?= =?UTF-8?q?=ED=98=B8=EC=B6=9C=20=EC=A7=80=EC=8B=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CLAUDE.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 9f9c837bb..5c702dec2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,5 +4,4 @@ - 컨벤션이 불확실할 때는 추측하지 말고 `.claude/rules/`의 해당 규칙 파일을 직접 읽으세요. - 컨벤션 위반이 발생했을 때 단순히 수정만 하지 말고, 그 규칙이 `.claude/rules/`에 명문화되어 있는지 확인하고 빠져 있다면 추가를 제안하세요. -- 커밋·PR 전에는 `./gradlew ktlintFormat`을 실행해 포맷을 맞추세요. -- 코드 리뷰가 필요할 때는 `code-review` 스킬을, 보안 점검이 필요할 때는 `security-checklist` 스킬을 우선 호출하세요. \ No newline at end of file +- 커밋·PR 전에는 `./gradlew ktlintFormat`을 실행해 포맷을 맞추세요. \ No newline at end of file From 85bb22710059a313c25adb50c5b749dbf9ac7207 Mon Sep 17 00:00:00 2001 From: zaman Date: Wed, 13 May 2026 23:40:56 +0900 Subject: [PATCH 07/16] =?UTF-8?q?fix(global):=20CommonApiResponse=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=20=EC=84=A4=EB=AA=85=EC=9D=84=20=EC=8B=A4?= =?UTF-8?q?=EC=A0=9C=20=EB=8F=99=EC=9E=91=EC=97=90=20=EB=A7=9E=EA=B2=8C=20?= =?UTF-8?q?=EC=A0=95=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index 548ae4f0c..323636550 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -31,7 +31,7 @@ datagsm-server/ - `/v1/health` 엔드포인트는 `datagsm-common`의 `HealthController`가 제공하며 모든 실행 모듈에 공유됩니다. - 공통 Entity: `datagsm-common/src/main/kotlin/team/themoment/datagsm/common/domain/` - 공통 예외 핸들러: `datagsm-common/src/main/kotlin/team/themoment/datagsm/common/global/common/error/` -- API 응답 래퍼: `CommonApiResponse`를 사용하세요. +- API 응답: 성공 응답은 DTO를 직접 반환합니다. 예외 응답은 `GlobalExceptionHandler`가 `CommonApiResponse`로 래핑합니다. ## Commands From 93c88c17962514648f4bf5a00cddac31463bfff7 Mon Sep 17 00:00:00 2001 From: zaman Date: Wed, 13 May 2026 23:41:13 +0900 Subject: [PATCH 08/16] =?UTF-8?q?refactor(global):=20=EB=AA=A8=EB=93=88=20?= =?UTF-8?q?=EC=84=A4=EB=AA=85=20=EB=9D=BC=EC=9D=B8=EC=9D=84=20=EA=B0=80?= =?UTF-8?q?=EB=8F=85=EC=84=B1=20=EC=9C=84=ED=95=B4=20=EC=A4=84=EB=B0=94?= =?UTF-8?q?=EA=BF=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 323636550..c9a9bbe6e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -20,8 +20,10 @@ datagsm-server/ ├── datagsm-common/ # 공유 Entity/DTO/Repository/Config, Health API (실행 모듈 아님) ├── datagsm-oauth-authorization/ # OAuth2 인증, 계정 라이프사이클(회원가입, 비밀번호 재설정) ├── datagsm-oauth-userinfo/ # OAuth2 UserInfo 엔드포인트 (외부 클라이언트용) -├── datagsm-openapi/ # 외부 공개 API (API Key 인증): student, club, project, webhook, neis -└── datagsm-web/ # 웹 서비스 API: account, auth, application, client, student, club, project, utility (Excel 처리 포함) +├── datagsm-openapi/ # 외부 공개 API (API Key 인증) +│ # 도메인: student, club, project, webhook, neis +└── datagsm-web/ # 웹 서비스 API (Excel 처리 포함) + # 도메인: account, auth, application, client, student, club, project, utility ``` 각 모듈은 도메인별로 `controller/ → service/ → repository/` + `entity/`, `dto/` 구조를 따릅니다. From e5eeef194ac7de385ec1e698d15fcbf828eca6db Mon Sep 17 00:00:00 2001 From: zaman Date: Wed, 13 May 2026 23:49:31 +0900 Subject: [PATCH 09/16] =?UTF-8?q?fix(global):=20API=20=EC=9D=91=EB=8B=B5?= =?UTF-8?q?=20=EC=9E=90=EB=8F=99=20=EB=9E=98=ED=95=91=20=EB=8F=99=EC=9E=91?= =?UTF-8?q?=EC=9D=84=20=EC=A0=95=ED=99=95=ED=9E=88=20=EB=B0=98=EC=98=81=20?= =?UTF-8?q?(the-sdk=20ResponseBodyAdvice)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index c9a9bbe6e..4f980ca8a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -33,7 +33,7 @@ datagsm-server/ - `/v1/health` 엔드포인트는 `datagsm-common`의 `HealthController`가 제공하며 모든 실행 모듈에 공유됩니다. - 공통 Entity: `datagsm-common/src/main/kotlin/team/themoment/datagsm/common/domain/` - 공통 예외 핸들러: `datagsm-common/src/main/kotlin/team/themoment/datagsm/common/global/common/error/` -- API 응답: 성공 응답은 DTO를 직접 반환합니다. 예외 응답은 `GlobalExceptionHandler`가 `CommonApiResponse`로 래핑합니다. +- API 응답: 컨트롤러는 DTO를 직접 반환하며, `the-sdk`의 `ResponseBodyAdvice`가 자동으로 `CommonApiResponse`로 래핑합니다. 데이터 없이 메시지만 반환하는 경우(예: 삭제 작업)에만 `CommonApiResponse`을 명시적으로 사용하세요. 예외 응답은 `GlobalExceptionHandler`가 `CommonApiResponse`로 래핑합니다. ## Commands From c08dfa4cfa5f6395aabad0426e592ea8a9bee63f Mon Sep 17 00:00:00 2001 From: zaman Date: Wed, 13 May 2026 23:49:48 +0900 Subject: [PATCH 10/16] =?UTF-8?q?fix(global):=20CONTRIBUTING.md=20Kotlin?= =?UTF-8?q?=20=EB=B2=84=EC=A0=84=EC=9D=84=20=EC=8B=A4=EC=A0=9C=20=EB=B9=8C?= =?UTF-8?q?=EB=93=9C=20=EC=84=A4=EC=A0=95(2.3.10)=EC=97=90=20=EB=A7=9E?= =?UTF-8?q?=EC=B6=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cdc856ea6..f873c6da5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,7 +23,7 @@ DataGSM 프로젝트에 기여해주셔서 감사합니다! 이 문서는 프로 프로젝트 개발을 위해 다음 환경이 필요합니다: - **JDK**: Java 25 (Temurin 권장) -- **Kotlin**: 2.3.0 (자동 설정) +- **Kotlin**: 2.3.10 (자동 설정) - **Gradle**: 최신 버전 (래퍼 사용) - **Docker & Docker Compose**: 로컬 개발용 - **MySQL**: 8.0 From cc24200fbe22d53409982f0994c5df10cc7dc72f Mon Sep 17 00:00:00 2001 From: zaman Date: Mon, 18 May 2026 03:15:37 +0900 Subject: [PATCH 11/16] =?UTF-8?q?refactor(global):=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=C2=B7=EC=A3=BC=EC=84=9D=20=EC=BB=A8=EB=B2=A4=EC=85=98?= =?UTF-8?q?=EC=9D=84=20.claude/rules/=EB=A1=9C=20=EC=9D=B4=EA=B4=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AGENTS.md Core Rules에 있던 테스트(Kotest 구조)와 주석 작성 기준을 별도 rules 파일로 분리한다. AGENTS.md는 요약을 들고 있지 않고, rules 파일이 Single Source of Truth가 되도록 한다. - testing.md: Kotest DescribeSpec + MockK + Given-When-Then 구조와 한국어 명명 - comments.md: 주석 작성 기준과 안티패턴 예시 --- .claude/rules/comments.md | 25 +++++++++++++++++++++++++ .claude/rules/testing.md | 35 +++++++++++++++++++++++++++++++++++ AGENTS.md | 4 ++-- 3 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 .claude/rules/comments.md create mode 100644 .claude/rules/testing.md diff --git a/.claude/rules/comments.md b/.claude/rules/comments.md new file mode 100644 index 000000000..9b191f4b5 --- /dev/null +++ b/.claude/rules/comments.md @@ -0,0 +1,25 @@ +# Comment Rules + +Write comments only when the logic is not self-evident. Do NOT add excessive comments. + +## When to write + +- Non-obvious business rules or domain constraints +- Workarounds for specific bugs or framework quirks +- Hidden invariants that a future reader could not infer from the code + +## When NOT to write + +- Restating what well-named identifiers already convey +- Narrating step-by-step what the code does +- Marking referenced tickets, callers, or the current task (those belong in the commit/PR description) + +```kotlin +// WRONG — restates the obvious +// Find student by id +val student = studentRepository.findById(id).orElseThrow { ... } + +// CORRECT — surfaces a non-obvious constraint +// NEIS API returns 0 instead of null for "no allergy", so treat 0 as absent. +val allergy = response.allergyCode.takeIf { it != 0 } +``` diff --git a/.claude/rules/testing.md b/.claude/rules/testing.md new file mode 100644 index 000000000..b64f3e75a --- /dev/null +++ b/.claude/rules/testing.md @@ -0,0 +1,35 @@ +# Testing Rules + +## Framework + +Use **Kotest `DescribeSpec` + MockK + JUnit 5** with the **Given-When-Then** structure. + +## Naming + +Test names are written in Korean. Use `describe("클래스명 클래스의")` for the top-level scope. + +```kotlin +class StudentServiceImplTest : DescribeSpec({ + describe("StudentServiceImpl 클래스의") { + context("execute 메서드를 호출할 때") { + it("학생을 조회한다") { + // Given + val id = 1L + every { studentRepository.findById(id) } returns Optional.of(student) + + // When + val result = service.execute(id) + + // Then + result.id shouldBe id + } + } + } +}) +``` + +## Structure + +- **Given**: prepare data, configure mocks (`every { } returns ...`) +- **When**: invoke the system under test +- **Then**: assert results (`shouldBe`, `shouldThrow`, `verify { }`) diff --git a/AGENTS.md b/AGENTS.md index 4f980ca8a..f3dcae493 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -52,8 +52,6 @@ datagsm-server/ - **Null 안전성**: `!!`를 사용하지 말고 `?.`, `?:`, `requireNotNull`을 사용하세요. - **트랜잭션**: `@Transactional`은 **메서드 레벨에만** 붙이세요. 읽기는 `readOnly = true`, 쓰기는 기본 `@Transactional`을 사용하세요. - **JPA**: N+1을 피하기 위해 Fetch Join 또는 `@EntityGraph`를 사용하세요. -- **테스트**: Kotest `DescribeSpec` + MockK + Given-When-Then 구조를 사용하세요. 테스트 이름은 한국어로 작성하세요 (`describe("클래스명 클래스의")`). -- **주석**: 로직이 자명하지 않은 경우에만 작성하세요. 과도한 주석을 추가하지 마세요. ## Detailed Rules (`.claude/rules/`) @@ -64,6 +62,8 @@ datagsm-server/ - `api-conventions.md` — `@RequestParam` vs `@ModelAttribute`, DTO 명명, `@Transactional` 배치 - `logging.md` — 영어 only, SLF4J `{}` 플레이스홀더, 콜론 구분자 금지 - `exception.md` — `ExpectedException` 사용 규칙과 메시지 포맷 +- `testing.md` — Kotest `DescribeSpec` + MockK + Given-When-Then 구조 +- `comments.md` — 주석 작성 기준 (자명하지 않은 로직에만) - `commit-conventions.md` — 커밋 `type(scope): 설명` 규칙 (scope는 도메인명) ## Notes From 9a32871386664135988157619e1cfb2508820da7 Mon Sep 17 00:00:00 2001 From: zaman Date: Mon, 18 May 2026 03:16:11 +0900 Subject: [PATCH 12/16] =?UTF-8?q?fix(global):=20=EB=AA=A8=EB=8D=B8?= =?UTF-8?q?=EC=9D=B4=20=EC=9D=B4=EB=AF=B8=20=EC=95=84=EB=8A=94=20=EC=9D=BC?= =?UTF-8?q?=EB=B0=98=20=EC=BB=A8=EB=B2=A4=EC=85=98=20=EC=9A=94=EC=95=BD=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AGENTS.md의 Core Rules 요약 섹션은 계층/DI/불변성/null safety/트랜잭션/JPA처럼 Kotlin·Spring 모델이 이미 학습한 일반 컨벤션을 재선언하는 형태였다. 세부 규칙은 .claude/rules/에 있으므로 요약 섹션을 제거하고 Detailed Rules 참조만 남긴다. --- AGENTS.md | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index f3dcae493..4789babe5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -42,17 +42,6 @@ datagsm-server/ - Format (commit 전 필수): `./gradlew ktlintFormat` - Run a module: `./gradlew ::bootRun` (실행 가능 모듈: `datagsm-oauth-authorization`, `datagsm-oauth-userinfo`, `datagsm-openapi`, `datagsm-web`) -## Core Rules (요약) - -상세 규칙은 `.claude/rules/`에 정의되어 있습니다. 아래는 매 세션 적용되는 최소 규칙입니다. - -- **계층**: Controller → Service → Repository 패턴을 유지하세요. Service 인터페이스(`*Service`)와 구현체(`*ServiceImpl`)를 분리하세요. -- **DI**: 항상 생성자 주입을 사용하세요. `@Autowired` 필드 주입은 금지입니다. -- **불변성**: `val`을 우선 사용하세요. `var`는 재할당이 반드시 필요한 경우(루프 누적, Logback 주입 등)에만 사용하세요. -- **Null 안전성**: `!!`를 사용하지 말고 `?.`, `?:`, `requireNotNull`을 사용하세요. -- **트랜잭션**: `@Transactional`은 **메서드 레벨에만** 붙이세요. 읽기는 `readOnly = true`, 쓰기는 기본 `@Transactional`을 사용하세요. -- **JPA**: N+1을 피하기 위해 Fetch Join 또는 `@EntityGraph`를 사용하세요. - ## Detailed Rules (`.claude/rules/`) 해당 파일을 작업할 때 자동 로드됩니다. From 66986d14a78dfa073dcaa39fc337729809e8e84a Mon Sep 17 00:00:00 2001 From: zaman Date: Mon, 18 May 2026 03:16:31 +0900 Subject: [PATCH 13/16] =?UTF-8?q?fix(global):=20Commands=20=EC=84=B9?= =?UTF-8?q?=EC=85=98=20=EC=B6=95=EC=95=BD=20(Build/Test/Format=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Build/Test/Format은 일반 Gradle 사용법으로 모델이 이미 안다. ktlintFormat은 .claude/hooks/postToolUse.sh가 .kt 파일 저장 시 자동 실행하므로 명시 지시가 불필요하다. 프로젝트 고유 정보인 '실행 가능 모듈 목록'만 Runnable Modules 섹션으로 남긴다. --- AGENTS.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 4789babe5..e66944696 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -35,12 +35,9 @@ datagsm-server/ - 공통 예외 핸들러: `datagsm-common/src/main/kotlin/team/themoment/datagsm/common/global/common/error/` - API 응답: 컨트롤러는 DTO를 직접 반환하며, `the-sdk`의 `ResponseBodyAdvice`가 자동으로 `CommonApiResponse`로 래핑합니다. 데이터 없이 메시지만 반환하는 경우(예: 삭제 작업)에만 `CommonApiResponse`을 명시적으로 사용하세요. 예외 응답은 `GlobalExceptionHandler`가 `CommonApiResponse`로 래핑합니다. -## Commands +## Runnable Modules -- Build: `./gradlew build` -- Test: `./gradlew test` -- Format (commit 전 필수): `./gradlew ktlintFormat` -- Run a module: `./gradlew ::bootRun` (실행 가능 모듈: `datagsm-oauth-authorization`, `datagsm-oauth-userinfo`, `datagsm-openapi`, `datagsm-web`) +`./gradlew ::bootRun`으로 실행하는 모듈: `datagsm-oauth-authorization`, `datagsm-oauth-userinfo`, `datagsm-openapi`, `datagsm-web`. (`datagsm-common`은 라이브러리 모듈로 실행 대상이 아닙니다.) ## Detailed Rules (`.claude/rules/`) From f7eb910f1d0d611c46aaf2d33bf6efd63d97f44a Mon Sep 17 00:00:00 2001 From: zaman Date: Mon, 18 May 2026 03:16:56 +0900 Subject: [PATCH 14/16] =?UTF-8?q?fix(global):=20CLAUDE.md=20ktlintFormat?= =?UTF-8?q?=20=EC=A7=80=EC=8B=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit postToolUse hook이 .kt 파일 Edit/Write 시 자동으로 ktlintFormat을 실행한다. 명시 프롬프트로 두면 토큰만 차지하고 실효성이 없으므로 제거한다. --- CLAUDE.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 5c702dec2..c47e989b8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -3,5 +3,4 @@ ## Claude Code Specific - 컨벤션이 불확실할 때는 추측하지 말고 `.claude/rules/`의 해당 규칙 파일을 직접 읽으세요. -- 컨벤션 위반이 발생했을 때 단순히 수정만 하지 말고, 그 규칙이 `.claude/rules/`에 명문화되어 있는지 확인하고 빠져 있다면 추가를 제안하세요. -- 커밋·PR 전에는 `./gradlew ktlintFormat`을 실행해 포맷을 맞추세요. \ No newline at end of file +- 컨벤션 위반이 발생했을 때 단순히 수정만 하지 말고, 그 규칙이 `.claude/rules/`에 명문화되어 있는지 확인하고 빠져 있다면 추가를 제안하세요. \ No newline at end of file From bc81e1973041a7039c6f35a45d1297c0d7308e81 Mon Sep 17 00:00:00 2001 From: zaman Date: Mon, 18 May 2026 03:17:31 +0900 Subject: [PATCH 15/16] =?UTF-8?q?fix(global):=20rules=20=EC=9E=90=EB=8F=99?= =?UTF-8?q?=20=EC=A3=BC=EC=9E=85=EA=B3=BC=20=EC=A4=91=EB=B3=B5=EB=90=98?= =?UTF-8?q?=EB=8A=94=20=EC=A7=81=EC=A0=91=20=EC=9D=BD=EA=B8=B0=20=EC=A7=80?= =?UTF-8?q?=EC=8B=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Claude Code는 .claude/rules/ 파일을 작업 컨텍스트에 따라 자동 로드한다. '규칙 파일을 직접 읽으세요'는 자동 주입과 이중 지시가 되어 rules 메커니즘의 의미를 퇴색시키므로 제거한다. 자동 로드된 규칙과 코드가 충돌할 때 규칙 파일을 보완 제안하라는 지시만 남긴다. --- CLAUDE.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index c47e989b8..a22182026 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,5 +2,4 @@ ## Claude Code Specific -- 컨벤션이 불확실할 때는 추측하지 말고 `.claude/rules/`의 해당 규칙 파일을 직접 읽으세요. -- 컨벤션 위반이 발생했을 때 단순히 수정만 하지 말고, 그 규칙이 `.claude/rules/`에 명문화되어 있는지 확인하고 빠져 있다면 추가를 제안하세요. \ No newline at end of file +- `.claude/rules/`의 세부 규칙은 Claude Code가 작업 컨텍스트에 맞춰 자동 로드합니다. 자동 로드된 규칙과 충돌하는 코드를 발견하면 단순 수정에 그치지 말고 규칙 파일에 누락된 항목이 있는지 점검하고 보완을 제안하세요. \ No newline at end of file From 6c12801157a7a2c99790487741c688e79b72d89d Mon Sep 17 00:00:00 2001 From: zaman Date: Mon, 18 May 2026 03:33:45 +0900 Subject: [PATCH 16/16] =?UTF-8?q?refactor(global):=20AGENTS.md=20=EB=B3=B8?= =?UTF-8?q?=EB=AC=B8=EC=9D=84=20=EC=98=81=EC=96=B4=EB=A1=9C=20=EC=9E=AC?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 평균적으로 한국어 입력이 영어 대비 토큰 사용량이 2배 이상 증가한다. 사용자 상호작용 언어 정책(line 1 한국어 응답 지시)은 그대로 유지하면서 본문 설명만 영어로 옮겨 토큰 효율을 개선한다. --- AGENTS.md | 50 +++++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index e66944696..8cba3b41c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,7 +2,7 @@ ## Project Overview -DataGSM은 광주소프트웨어마이스터고등학교의 학교 정보(학생, 동아리, 급식, 시간표 등)를 제공하는 Spring Boot REST API 서버입니다. Google OAuth2(JWT) 인증과 API Key 기반 외부 공개 API를 함께 제공합니다. +DataGSM is a Spring Boot REST API server that exposes Gwangju Software Meister High School data (students, clubs, meals, timetable, etc.). It provides Google OAuth2 (JWT) authentication for internal clients and API-Key authentication for external public APIs. ## Tech Stack @@ -17,42 +17,42 @@ DataGSM은 광주소프트웨어마이스터고등학교의 학교 정보(학생 ``` datagsm-server/ -├── datagsm-common/ # 공유 Entity/DTO/Repository/Config, Health API (실행 모듈 아님) -├── datagsm-oauth-authorization/ # OAuth2 인증, 계정 라이프사이클(회원가입, 비밀번호 재설정) -├── datagsm-oauth-userinfo/ # OAuth2 UserInfo 엔드포인트 (외부 클라이언트용) -├── datagsm-openapi/ # 외부 공개 API (API Key 인증) -│ # 도메인: student, club, project, webhook, neis -└── datagsm-web/ # 웹 서비스 API (Excel 처리 포함) - # 도메인: account, auth, application, client, student, club, project, utility +├── datagsm-common/ # Shared Entity/DTO/Repository/Config, Health API (library module) +├── datagsm-oauth-authorization/ # OAuth2 authentication, account lifecycle (signup, password reset) +├── datagsm-oauth-userinfo/ # OAuth2 UserInfo endpoint (for external clients) +├── datagsm-openapi/ # External public API (API-Key auth) +│ # Domains: student, club, project, webhook, neis +└── datagsm-web/ # Web service API (includes Excel processing) + # Domains: account, auth, application, client, student, club, project, utility ``` -각 모듈은 도메인별로 `controller/ → service/ → repository/` + `entity/`, `dto/` 구조를 따릅니다. +Each module follows the `controller/ → service/ → repository/` layering with `entity/` and `dto/` packages per domain. **Key Paths** -- `/v1/health` 엔드포인트는 `datagsm-common`의 `HealthController`가 제공하며 모든 실행 모듈에 공유됩니다. -- 공통 Entity: `datagsm-common/src/main/kotlin/team/themoment/datagsm/common/domain/` -- 공통 예외 핸들러: `datagsm-common/src/main/kotlin/team/themoment/datagsm/common/global/common/error/` -- API 응답: 컨트롤러는 DTO를 직접 반환하며, `the-sdk`의 `ResponseBodyAdvice`가 자동으로 `CommonApiResponse`로 래핑합니다. 데이터 없이 메시지만 반환하는 경우(예: 삭제 작업)에만 `CommonApiResponse`을 명시적으로 사용하세요. 예외 응답은 `GlobalExceptionHandler`가 `CommonApiResponse`로 래핑합니다. +- The `/v1/health` endpoint is served by `HealthController` in `datagsm-common` and is shared across all runnable modules. +- Shared entities: `datagsm-common/src/main/kotlin/team/themoment/datagsm/common/domain/` +- Global exception handling: `datagsm-common/src/main/kotlin/team/themoment/datagsm/common/global/common/error/` +- API response: controllers return DTOs directly — `the-sdk`'s `ResponseBodyAdvice` automatically wraps them in `CommonApiResponse`. Use `CommonApiResponse` explicitly only when no data is returned (e.g. delete operations). Exception responses are wrapped by `GlobalExceptionHandler`. ## Runnable Modules -`./gradlew ::bootRun`으로 실행하는 모듈: `datagsm-oauth-authorization`, `datagsm-oauth-userinfo`, `datagsm-openapi`, `datagsm-web`. (`datagsm-common`은 라이브러리 모듈로 실행 대상이 아닙니다.) +Modules launched via `./gradlew ::bootRun`: `datagsm-oauth-authorization`, `datagsm-oauth-userinfo`, `datagsm-openapi`, `datagsm-web`. (`datagsm-common` is a library module and is not run directly.) ## Detailed Rules (`.claude/rules/`) -해당 파일을 작업할 때 자동 로드됩니다. +The following rule files are auto-loaded when working on related code: -- `kotlin-style.md` — `val/var`, 생성자 주입, null 안전성 -- `dto-annotations.md` — Jackson/Swagger의 `@field:` vs `@param:` 규칙 -- `api-conventions.md` — `@RequestParam` vs `@ModelAttribute`, DTO 명명, `@Transactional` 배치 -- `logging.md` — 영어 only, SLF4J `{}` 플레이스홀더, 콜론 구분자 금지 -- `exception.md` — `ExpectedException` 사용 규칙과 메시지 포맷 -- `testing.md` — Kotest `DescribeSpec` + MockK + Given-When-Then 구조 -- `comments.md` — 주석 작성 기준 (자명하지 않은 로직에만) -- `commit-conventions.md` — 커밋 `type(scope): 설명` 규칙 (scope는 도메인명) +- `kotlin-style.md` — `val/var`, constructor injection, null safety +- `dto-annotations.md` — Jackson/Swagger `@field:` vs `@param:` targets +- `api-conventions.md` — `@RequestParam` vs `@ModelAttribute`, DTO naming, `@Transactional` placement +- `logging.md` — English only, SLF4J `{}` placeholder, no colon separators +- `exception.md` — `ExpectedException` usage and message format +- `testing.md` — Kotest `DescribeSpec` + MockK + Given-When-Then structure +- `comments.md` — when to write comments (only for non-obvious logic) +- `commit-conventions.md` — commit `type(scope): description` rules (scope = domain name) ## Notes -- 파일 변경을 제안할 때는 `.gitignore`를 항상 확인하세요. -- 코드를 분석할 때는 다중 모듈 구조와 모듈 간 의존을 고려하세요 (`datagsm-common`은 모든 실행 모듈의 base). \ No newline at end of file +- Always check `.gitignore` before proposing file changes. +- When analyzing code, consider the multi-module structure and inter-module dependencies (`datagsm-common` is the base for every runnable module).