-
Notifications
You must be signed in to change notification settings - Fork 3
[공통] SonarCloud 연동 및 Claude Code AI 파이프라인 하네스 구성 #1236
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,213 +1,75 @@ | ||
| # CLAUDE.md | ||
|
|
||
| This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. | ||
| ## 프로젝트 개요 | ||
|
|
||
| ## Project Overview | ||
| KOIN은 한국기술교육대학교(KOREATECH) 재학생을 위한 캠퍼스 서비스 웹앱이다. 시간표, 버스, 식당, 가게, 커뮤니티, 동아리, 분실물, 졸업 계산기를 제공한다. | ||
|
|
||
| KOIN is a campus service web application for Korea University of Technology and Education (KOREATECH). It provides timetable management, bus schedules, cafeteria menus, store/shop listings, community articles, clubs, lost & found, and graduation calculators. | ||
| **기술 스택:** Next.js 15 (Pages Router) · React 19 · TypeScript strict · Yarn 4 (Berry) PnP · Node 20.11.1 | ||
|
|
||
| Built with **Next.js 15 (Pages Router)**, **React 19**, and **TypeScript** (strict mode). | ||
|
|
||
| - Package manager: **Yarn 4 (Berry)** with PnP. Never use `npm install`. | ||
| - Node version: **20.11.1** | ||
|
|
||
| ## Commands | ||
| ## 주요 명령어 | ||
|
|
||
| ```bash | ||
| yarn start # Dev server (next dev) | ||
| yarn build # Type-check (tsc) then production build | ||
| yarn start # 개발 서버 (next dev) | ||
| yarn build # 타입 체크(tsc) + 프로덕션 빌드 | ||
| yarn lint # ESLint + Stylelint | ||
| yarn lint:eslint # ESLint only (src/) | ||
| yarn lint:stylelint # Stylelint only (src/**/*.scss) | ||
| yarn log # Generate analytics logging hooks from Notion spec | ||
| ``` | ||
|
|
||
| ## Architecture | ||
|
|
||
| ### Routing (Pages Router) | ||
|
|
||
| File-based routing in `src/pages/`. Type-safe route builder in `src/static/routes.ts`: | ||
|
|
||
| ```typescript | ||
| ROUTES.StoreDetail({ id: '123' }); // → '/store/123' | ||
| ``` | ||
|
|
||
| Pages can declare static properties: | ||
|
|
||
| ```typescript | ||
| Page.getLayout = (page: React.ReactNode) => <SSRLayout>{page}</SSRLayout>; | ||
| Page.requireAuth = true; | ||
| Page.title = 'Page Title'; | ||
| ``` | ||
|
|
||
| ### API Layer | ||
|
|
||
| `src/api/` uses a class-based pattern with `APIClient` wrapper (`src/utils/ts/apiClient.ts`): | ||
|
|
||
| 1. Define request class in `APIDetail.ts` implementing `APIRequest<ResponseType>` | ||
| 2. Define request/response types in `entity.ts` | ||
| 3. Export callable function via `APIClient.of(DetailClass)` in `index.ts` | ||
|
|
||
| ```typescript | ||
| export class Login implements APIRequest<LoginResponse> { | ||
| path = '/user/login'; | ||
| method = HTTP_METHOD.POST; | ||
| data: LoginRequest; | ||
| } | ||
| // index.ts | ||
| export const login = APIClient.of(Login); | ||
| yarn lint:eslint # ESLint (src/) | ||
| yarn lint:stylelint # Stylelint (src/**/*.scss) | ||
| yarn log # Notion 스펙에서 분석 로깅 훅 생성 | ||
| ``` | ||
|
|
||
| The APIClient handles token refresh (401), maintenance mode (503), and user type verification (403) automatically. | ||
|
|
||
| ### State Management | ||
|
|
||
| - **Server state**: TanStack React Query (`staleTime: 60000`, `retry: false`). SSR via `getServerSideProps` + `HydrationBoundary`. | ||
| - **Client state**: Zustand stores in `src/utils/zustand/`. Many stores separate `State` and `Actions` types — follow the existing pattern of each file. | ||
|
|
||
| Zustand stores export selectors: | ||
|
|
||
| ```typescript | ||
| export const useStateSelector = () => useStore((state) => state.prop); | ||
| export const useActions = () => useStore((state) => state.action); | ||
| ``` | ||
|
|
||
| ### Styling | ||
|
|
||
| SCSS with CSS Modules (`[Component].module.scss`) using BEM methodology. Desktop-first approach with mobile overrides via responsive mixins in `src/utils/scss/`: | ||
|
|
||
| ```scss | ||
| @include media.media-breakpoint(mobile) { | ||
| /* breakpoint: 576px */ | ||
| } | ||
| ``` | ||
|
|
||
| ### Component Organization | ||
|
|
||
| Feature-based: `src/components/[Feature]/` with co-located hooks in `hooks/` subdirectory and styles. Split responsive views into `MobileView/` and `PCView/` directories when layouts differ. Shared UI in `src/components/ui/`, layouts in `src/components/layout/`, modals in `src/components/modal/`. | ||
|
|
||
| ### Layout | ||
|
|
||
| Two layout components in `src/components/layout/`: | ||
|
|
||
| - **`SSRLayout`**: No Suspense wrapping. Used for SSR pages via `getLayout`. | ||
| - **`Layout`** (default): Wraps Header/children in Suspense boundaries. Hides Footer in native WebView. | ||
| **패키지 매니저:** Yarn 4만 사용. `npm install` 금지. | ||
|
|
||
| Misusing these causes hydration errors. | ||
| ## 아키텍처 핵심 포인터 | ||
|
|
||
| ### Custom Hooks | ||
|
|
||
| Located in `src/utils/hooks/`, organized by category: | ||
|
|
||
| - `auth/` — useAuth, useAutoLogin, useLoginRedirect, useLogout | ||
| - `ui/` — useBodyScrollLock, useOutsideClick, useEscapeKeyDown | ||
| - `state/` — useBooleanState, useLocalStorage, useWebStorage, useMount | ||
| - `layout/` — useMediaQuery, useModalPortal | ||
| - `analytics/` — useLogger, useScrollLogging | ||
|
|
||
| ### Internal Packages | ||
|
|
||
| - **`@bcsdlab/koin`**: `isKoinError()` type guard, `sendClientError()` (sends errors to internal Slack). | ||
| - **`@bcsdlab/utils`**: `cn()` (className merger), `sha256()` (Web Crypto hashing). | ||
|
|
||
| These are internal BCSD Lab packages — do not suggest replacing them with external alternatives. | ||
|
|
||
| ### Cookie Management | ||
|
|
||
| Cookie keys are environment-aware via `IS_STAGE` flag in `src/static/url.ts`. Stage and production use different cookie key names (e.g., `STAGE_AUTH_TOKEN_KEY` vs `AUTH_TOKEN_KEY`) and different domains (`.stage.koreatech.in` vs `.koreatech.in`). Always use `COOKIE_KEY` constants and `getCookieDomain()` — never hard-code cookie names or domains. | ||
|
|
||
| ### iOS Native Bridge | ||
|
|
||
| WebKit message handlers for token sync between web and native app via `window.webkit.messageHandlers`. Bridge functions in `src/utils/ts/iosBridge.ts`. | ||
|
|
||
| ### Analytics | ||
|
|
||
| Generated logging hooks in `src/generated/analytics/` from Notion spec via `yarn log`. Google Analytics + GTM + Sentry error tracking. | ||
|
|
||
| ## Code Conventions | ||
|
|
||
| ### Imports | ||
|
|
||
| Absolute imports via `*` → `src/*` path mapping. Use `import X from 'components/...'` not `'../../../components/...'`. Relative parent imports (`../*`) are forbidden by ESLint. | ||
|
|
||
| Import order (enforced): React/Next → builtins → external packages → internal (`@/**`) → parent/sibling → types → styles (`.scss`). | ||
|
|
||
| ### Naming | ||
|
|
||
| - Components/directories: PascalCase (`ArticleList.tsx`) | ||
| - Utilities: camelCase (`apiClient.ts`) | ||
| - Hooks: `use[Name].ts` | ||
| - Styles: `[Component].module.scss` | ||
| - API types: `entity.ts`, API classes: `APIDetail.ts` | ||
|
|
||
| ### Formatting | ||
|
|
||
| Prettier: 120 char width, single quotes, trailing commas, 2-space indent. Stylelint enforces `stylelint-config-standard-scss`. | ||
|
|
||
| ## PR Review Rules (for claude-code-action) | ||
|
|
||
| Write all review comments in Korean. | ||
|
|
||
| Focus on correctness, regression risk, security, and performance before style. | ||
|
|
||
| Use this output format for every finding: | ||
|
|
||
| - Severity: `[P0]` (blocks merge), `[P1]` (should fix), `[P2]` (suggestion) | ||
| - Location: `file:line` | ||
| - Why it matters | ||
| - Minimal fix suggestion | ||
|
|
||
| ### Error Handling | ||
|
|
||
| - Always use `isKoinError()` type guard before accessing error properties on API errors. | ||
| - In ErrorBoundary, use `isAxiosError()` type guard to branch handling. | ||
|
|
||
| ```typescript | ||
| onError: (error) => { | ||
| if (isKoinError(error)) { | ||
| showToast('error', error.message || 'fallback message'); | ||
| } else { | ||
| showToast('error', 'fallback message'); | ||
| } | ||
| }; | ||
| ``` | ||
| | 영역 | 패턴 요약 | 위치 | | ||
| |------|-----------|------| | ||
| | 라우팅 | Pages Router, `ROUTES` 헬퍼 사용 | `src/static/routes.ts` | | ||
| | API | `APIRequest<T>` 클래스 → `APIClient.of()` export | `src/api/[domain]/` | | ||
| | 서버 상태 | React Query (`staleTime: 60000, retry: false`) | `src/api/[domain]/queries.ts` | | ||
| | 클라이언트 상태 | Zustand (`State`/`Actions` 타입 분리) | `src/utils/zustand/` | | ||
| | 스타일 | SCSS Modules + BEM, 데스크톱 우선 | `[Component].module.scss` | | ||
| | 레이아웃 | SSR 페이지 → `SSRLayout`, 클라이언트 → `Layout` | `src/components/layout/` | | ||
| | 내부 패키지 | `@bcsdlab/koin` (isKoinError, sendClientError), `@bcsdlab/utils` (cn, sha256) | 교체 금지 | | ||
|
|
||
| ### React Query | ||
| ## 필수 준수 규칙 | ||
|
|
||
| - Query keys as arrays: `['resource', 'action', params]`. | ||
| - Prefer `useSuspenseQuery` when a Suspense boundary wraps the component for blocking UI. Use `useQuery` for conditional fetching (`enabled`), background refresh, or non-blocking patterns. | ||
| - Invalidate cache via `queryClient.invalidateQueries()` after mutations. | ||
| - Every mutation `onError` must follow the error handling pattern above. | ||
| 1. **임포트:** 절대 경로 사용 (`import X from 'components/...'`). 상위 경로(`../`) ESLint 금지. | ||
| 2. **에러 핸들링:** mutation `onError`에 반드시 `isKoinError()` 타입 가드 → `showToast()` 패턴 적용. | ||
| 3. **쿠키:** `COOKIE_KEY` 상수 + `getCookieDomain()` 사용. 쿠키명/도메인 하드코딩 금지. | ||
| 4. **SSR 안전성:** `window`/`document`/`localStorage` 접근 시 `typeof window !== 'undefined'` 체크 필수. | ||
| 5. **라우팅:** `ROUTES` 헬퍼 사용. 경로 문자열 하드코딩 금지. | ||
| 6. **로깅:** `console.log` 금지. `console.warn`/`console.error`만 허용. | ||
| 7. **토스트:** `showToast(type, message)` 사용. `toast()` 직접 호출 금지. | ||
| 8. **iOS 브릿지:** `window.webkit` optional chaining 유지 (`src/utils/ts/iosBridge.ts`). | ||
| 9. **분석 로깅:** 주요 사용자 인터랙션마다 `useLogger()` 훅 포함. | ||
|
|
||
| ### Analytics Logging | ||
| ## PR 리뷰 규칙 (claude-code-action용) | ||
|
|
||
| - All user interactions (click, swipe, page load) must include logging. | ||
| - Use the `useLogger()` hook with `team`, `event_label`, `value` structure. | ||
| - Define logging constants (`loggingTitle`, `loggingValue`) at the top of the component. | ||
| 리뷰 댓글은 **한국어**로 작성. 정확성 > 회귀 위험 > 보안 > 성능 > 스타일 순으로 검토. | ||
|
|
||
| ### General | ||
| **출력 형식 (모든 발견에 적용):** | ||
| - 심각도: `[P0]` (머지 차단) · `[P1]` (수정 필요) · `[P2]` (제안) | ||
| - 위치: `file:line` | ||
| - 영향과 최소 수정안 | ||
|
|
||
| - No `console.log` (ESLint warn). Only `console.warn` and `console.error` allowed. | ||
| - Import SVGs as React components via `@svgr/webpack`. | ||
| - Use `showToast(type, message)` utility instead of calling `toast()` directly. Type: `'success' | 'error' | 'info' | 'warning' | 'default'`. | ||
| **검토 제외:** | ||
| - lint/import-order 포매팅 노이즈 | ||
| - 자동 생성 파일만 변경 (`src/generated/**`, `analytics.events.json`) | ||
| - `@bcsdlab/koin`, `@bcsdlab/utils` 교체 제안 | ||
|
|
||
| ### Project-Specific Must-Checks | ||
| ## 유효성 검사 정책 | ||
|
|
||
| - **SSR safety**: Guard `window`, `document`, `localStorage`, `sessionStorage` with browser checks. Verify correct layout usage (`SSRLayout` for SSR pages, `Layout` for client pages). | ||
| - **React Query SSR hydration**: Verify prefetch query keys and hydration state keys are consistent. | ||
| - **Auth/API stability**: Do not break token refresh lock, 401/403/503 handling, or retry flow. | ||
| - **Cookie safety**: Must use `COOKIE_KEY` constants and `getCookieDomain()` — never hard-code cookie names or domains. Verify stage/production separation is preserved. | ||
| - **iOS bridge stability**: Preserve `window.webkit` optional chaining and native callback contract. | ||
| - **Routing consistency**: Prefer `ROUTES` helpers over hard-coded path strings. | ||
| - **Next.js performance**: Flag async waterfalls, unnecessary heavy static imports, and missed dynamic imports. | ||
| - 기본: `yarn lint` | ||
| - `yarn build` 실패는 환경 제약(API 접근 불가 등)이 원인일 수 있으므로, 변경 코드가 직접 원인이 아니면 비차단 처리. | ||
|
|
||
| ### Validation Policy | ||
| ## 하네스: KOIN AI 파이프라인 | ||
|
|
||
| - Primary check: `yarn lint`. | ||
| - `yarn build` may fail due to environment constraints (missing API access, sandbox limitations); treat as non-blocking unless the changed code directly caused the failure. | ||
| **목표:** SonarCloud 코드 품질 분석 및 자동 수정, 기능 구현 자동화 PR 생성 | ||
|
|
||
| ### Do Not Review | ||
| **트리거:** "SonarCloud 분석", "SonarCloud 이슈 수정", "코드 품질 개선", "기능 구현해줘", "새 기능 추가해줘", "PR 만들어줘", "파이프라인 실행", "다시 실행", "재실행", "이전 결과 개선", "업데이트" 등의 요청 시 `koin-pipeline` 스킬을 사용하라. | ||
|
|
||
| - Pure formatting/import-order noise already covered by lint. | ||
| - Generated artifacts only (`analytics.events.json`, `src/generated/**`) unless generation logic changed. | ||
| - `@bcsdlab/koin` and `@bcsdlab/utils` are internal packages — do not suggest external replacements. | ||
| **변경 이력:** | ||
| | 날짜 | 변경 내용 | 대상 | 사유 | | ||
| |------|----------|------|------| | ||
| | 2026-04-21 | 초기 구성 | 전체 | SonarCloud 품질 관리 + 기능 구현 자동화 파이프라인 | | ||
| | 2026-04-21 | 전체 재작성 | CLAUDE.md | 영어→한국어, 스킬 중복 제거, 분량 축소 (224줄→80줄) | | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| --- | ||
| name: code-quality-fixer | ||
| description: SonarCloud 이슈 분석 결과를 바탕으로 KOIN 프로젝트의 코드 품질 이슈를 실제로 수정하는 에이전트. TypeScript 타입 오류, React 패턴 개선, 보안 이슈를 KOIN 컨벤션에 맞게 수정한다. | ||
| model: sonnet | ||
| --- | ||
|
|
||
| # Code Quality Fixer | ||
|
|
||
| ## 핵심 역할 | ||
|
|
||
| sonarcloud-analyzer가 생성한 이슈 배치를 처리하여, KOIN 프로젝트 컨벤션에 맞게 코드를 수정한다. `code-quality-fix` 스킬의 가이드를 따른다. | ||
|
|
||
| ## 작업 원칙 | ||
|
|
||
| 1. 반드시 `code-quality-fix` 스킬을 로딩하여 KOIN 컨벤션과 수정 패턴을 확인한다. | ||
| 2. 수정 전 대상 파일을 먼저 읽고, 변경 범위를 최소화한다. | ||
| 3. 수정은 이슈 단위로 진행하며, 각 수정 후 변경 내용을 기록한다. | ||
| 4. `yarn lint:eslint`가 통과하는 수정만 최종 반영한다. | ||
| 5. 기존 기능을 깨지 않는 수정만 한다 — 비즈니스 로직 변경 금지. | ||
| 6. KOIN 프로젝트 특수 규칙을 반드시 지킨다: | ||
| - `isKoinError()` 타입 가드 사용 | ||
| - `COOKIE_KEY` 상수 사용 (하드코딩 금지) | ||
| - `ROUTES` 헬퍼 사용 (경로 하드코딩 금지) | ||
| - SSR 안전성 보장 (`window`, `localStorage` 접근 시 브라우저 체크) | ||
|
|
||
| ## 입력 | ||
|
|
||
| - `_workspace/01_analyzer_issues.json` (sonarcloud-analyzer 출력) | ||
| - 처리할 배치 번호 (없으면 배치 1부터 순차 처리) | ||
|
|
||
|
Comment on lines
+28
to
+30
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 재실행(리트라이) 시 상태 처리 규칙을 명시해 주세요. 현재 계약은 1회 실행 기준으로만 보이며(Line 28-30, 33-47), 오케스트레이터 재호출 시 Also applies to: 33-47, 51-54 🤖 Prompt for AI Agents |
||
| ## 출력 | ||
|
|
||
| `_workspace/02_fixer_result.json` 파일: | ||
| ```json | ||
| { | ||
| "fixed": [ | ||
| { | ||
| "file": "src/components/...", | ||
| "issue_key": "...", | ||
| "description": "수정 내용 요약" | ||
| } | ||
| ], | ||
| "failed": [], | ||
| "skipped": [], | ||
| "changed_files": ["src/components/...", "..."] | ||
| } | ||
| ``` | ||
|
|
||
| ## 에러 핸들링 | ||
|
|
||
| - 파일을 찾을 수 없을 때: skipped 목록에 추가, 계속 진행 | ||
| - 수정 후 lint 실패 시: 원본으로 되돌리고 failed 목록에 추가 | ||
| - 타입 에러 발생 시: 수정 취소하고 수동 검토 필요 표시 | ||
|
|
||
| ## 협업 | ||
|
|
||
| - **입력 출처**: sonarcloud-analyzer (`_workspace/01_analyzer_issues.json`) | ||
| - **출력 대상**: pr-creator (`_workspace/02_fixer_result.json` + 실제 수정된 파일) | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
테이블 앞뒤 공백 줄을 추가해 주세요.
Line 72 표가 빈 줄로 감싸지지 않아 markdownlint(MD058) 경고가 발생합니다. 표 전/후에 한 줄씩 비워 주세요.
🧰 Tools
🪛 markdownlint-cli2 (0.22.0)
[warning] 72-72: Tables should be surrounded by blank lines
(MD058, blanks-around-tables)
🤖 Prompt for AI Agents