# Coding Conventions > AI 에이전트는 코드를 작성하기 전 이 컨벤션을 확인합니다. ## 네이밍 ### Python (서버) | 대상 | 규칙 | 예시 | |------|------|------| | 변수/함수 | snake_case | `write_pending_approval()` | | 클래스 | PascalCase | `GravityBot`, `WSHub`, `TokenManager` | | 상수 | UPPER_SNAKE_CASE | `MAX_MSG_SIZE`, `HEARTBEAT_INTERVAL` | | 파일명 | snake_case | `hub.py`, `ws_client.py` | | 로거명 | 모듈명 | `logging.getLogger("hub")` | ### TypeScript (Extension) | 대상 | 규칙 | 예시 | |------|------|------| | 변수/함수 | camelCase | `writePendingApproval()`, `setupMonitor()` | | 클래스 | PascalCase | `WSBridgeClient` | | 인터페이스 | PascalCase | `BridgeContext`, `WSPendingData` | | 상수 | UPPER_SNAKE_CASE | `MAX_QUEUE_SIZE`, `AUTH_TIMEOUT` | | 파일명 | kebab-case | `ws-client.ts`, `step-probe.ts` | | export 함수 | camelCase | `initStepProbe()`, `generateApprovalObserverScript()` | ## 코드 스타일 | 항목 | Python | TypeScript | |------|--------|-----------| | 들여쓰기 | 4 spaces | 4 spaces | | 따옴표 | 쌍따옴표 `"` (f-string 포함) | 작은따옴표 `'` | | 세미콜론 | N/A | 사용 | | 줄바꿈 | LF (Unix) | CRLF (Windows, git 자동 변환) | | 최대 줄 길이 | 120자 권장 | 120자 권장 | | 타입 힌트 | 적극 사용 (`-> list[str]`) | strict (`BridgeContext` 인터페이스) | ## 커밋 메시지 ``` (): type: feat|fix|refactor|test|docs|chore|ci|infra scope: server|extension|hub|bot|gateway|bridge (선택) ``` **예시:** - `feat(hub): WebSocket Hub 구현 + JWT 인증` - `refactor(extension): 모듈 분리 (step-probe, observer-script)` - `fix(bot): auto-approve 세션 간 초기화` - `docs: architecture.md 전면 재작성` 관련 Vikunja 태스크가 있으면: `feat(hub): WS Hub 구현 #task-395` ## 주석 - 한국어/영어 혼용 가능 - TODO 주석: `// TODO: 설명` 형식 - 섹션 구분: `// ─── Section Name ───` (TypeScript), `# ─── Section ───` (Python) - 복잡한 로직에는 반드시 WHY(왜) 주석 추가 - 함수 docstring: Python은 `"""..."""`, TypeScript는 `/** ... */` ## 모듈 분리 패턴 Extension 모듈 분리 시 사용하는 패턴: | 패턴 | 용도 | 예시 | |------|------|------| | **순수 함수 추출** | 외부 상태 참조 없는 함수 | `step-utils.ts` | | **독립 스크립트** | 문자열 반환 함수 | `observer-script.ts` | | **Context 패턴** | 공유 상태가 많은 함수 그룹 | `step-probe.ts` (BridgeContext) | | **클래스 추출** | 자체 상태 + 메서드 | `ws-client.ts` (WSBridgeClient) | ## 테스트 | 항목 | 위치 | 도구 | |------|------|------| | Python 구문 검사 | `tests/test_syntax.py` | `ast.parse` | | WS Hub 연결 | `tests/test_ws_hub.py` | `websockets` | | TypeScript 컴파일 | `npx tsc --noEmit` | TypeScript compiler | | E2E | 수동 (Discord 버튼 클릭) | — | ## 로깅 | 측 | 방식 | 포맷 | |----|------|------| | Python | `logging.getLogger(name)` | `YYYY-MM-DD HH:MM:SS [name] LEVEL: message` | | Extension | `logToFile(msg)` → bridge/log/ | `[HH:MM:SS] message` + `[WS]` prefix | | Hub | `[HUB]` prefix | `[HUB] Auth OK: {conn_id} project={project}` | | Gateway | `[GATEWAY]` prefix | `[GATEWAY] HTTP API started on {host}:{port}` | ## WS / File Bridge 상호 배타 패턴 > [!IMPORTANT] > WS Hub과 파일 bridge는 **항상 상호 배타적**이어야 합니다. > 양쪽에 동시에 쓰면 이중 전달 버그가 발생합니다. (known-issues-archive 참조) **Extension (TypeScript):** ```typescript // ✅ 올바른 패턴 if (ctx.wsBridge?.isConnected()) { ctx.wsBridge.sendPending(data); return; // ← 반드시 return으로 파일 쓰기 건너뛰기 } // File fallback fs.writeFileSync(pendingPath, JSON.stringify(data)); ``` **Bot (Python):** ```python # ✅ 올바른 패턴 if self.hub: await self.hub.send_response(...) else: bridge.write_response(...) ``` **금지 패턴:** ```python # ❌ 이중 쓰기 — 절대 금지 if self.hub: await self.hub.send_response(...) bridge.write_response(...) # ← Hub 성공해도 파일에도 씀 → 이중 처리 ```