# 코드 아키텍처 원칙 (Code Architecture Principles)
> 버전: 1.0 | 작성일: 2026-03-26 | 작성자: 루(Lugh), 개발3팀

---

## 1. SOLID 원칙

### S — 단일 책임 원칙 (SRP)
**정의**: 클래스/모듈은 하나의 변경 이유만 가져야 한다.

**우리 시스템 적용**:
`utils/` 디렉토리가 SRP의 표준 사례다. `injection_guard.py`는 프롬프트 인젝션 탐지만, `redact.py`는 민감정보 마스킹만, `approval.py`는 위험 명령어 탐지만 담당한다. 각 모듈은 50~120줄 내외로 단일 책임이 명확히 분리되어 있다.

**위반 징후**: 한 파일에 "그리고"로 연결되는 기능이 둘 이상이면 분리를 검토한다.

---

### O — 개방-폐쇄 원칙 (OCP)
**정의**: 확장에는 열려 있고, 수정에는 닫혀 있어야 한다.

**우리 시스템 적용**:
`teams/dev1/qc/qc_verify.py`의 `ALL_CHECKS` 목록이 핵심 예시다. 새 검증 항목(verifier) 추가 시 `verifiers/` 에 독립 모듈을 생성하고 `ALL_CHECKS` 리스트에 이름만 추가하면 된다. `run_check()` 본체는 수정 불필요.

```python
ALL_CHECKS = [
    "api_health", "file_check", "data_integrity",
    "test_runner", "tdd_check", "schema_contract",
    ...  # 새 verifier는 여기에 이름 추가만
]
```

---

### L — 리스코프 치환 원칙 (LSP)
**정의**: 서브타입은 부모 타입으로 대체 가능해야 한다.

**우리 시스템 적용**:
`verifiers/` 내 모든 모듈은 `verify(**kwargs) -> dict` 시그니처를 공유한다. `api_health.verify()`, `file_check.verify()`, `data_integrity.verify()` 모두 `{"status": "PASS"|"FAIL"|..., "details": [...]}` 형태를 반환한다. `run_check()`는 어떤 verifier든 동일하게 호출 가능하다.

---

### I — 인터페이스 분리 원칙 (ISP)
**정의**: 클라이언트는 사용하지 않는 인터페이스에 의존하지 않아야 한다.

**우리 시스템 적용**:
`dispatch.py`의 선택적 임포트 패턴이 ISP의 실용적 구현이다. `_REDACT_AVAILABLE`, `_INJECTION_GUARD_AVAILABLE`, `_APPROVAL_AVAILABLE` 플래그를 통해 필요한 기능만 활성화된다. 불필요한 모듈이 없는 환경에서도 핵심 기능은 동작한다.

---

### D — 의존성 역전 원칙 (DIP)
**정의**: 고수준 모듈은 저수준 구현이 아닌 추상화에 의존해야 한다.

**우리 시스템 적용**:
`dispatch.py`의 봇 라우팅은 `TEAM_BOT` 딕셔너리와 `_find_available_bot()` 함수를 통해 추상화된다. 호출부는 어떤 봇이 선택되는지 알 필요 없다. 마찬가지로 `utils/model_router.py`는 모델 선택 로직을 캡슐화하여 호출부가 특정 모델에 직접 의존하지 않도록 한다.

---

## 2. DRY 원칙

**원칙**: 동일한 로직은 한 곳에만 존재해야 한다.

**공통 모듈화 기준**:
- 동일 코드가 **2개 이상** 파일에 등장하면 `utils/`로 추출한다.
- 원자적 파일 쓰기는 `utils/atomic_write.py`의 `atomic_json_write()` / `atomic_text_write()`를 사용한다. `task-timer.py`와 `dispatch.py`에서 과거 각자 구현했던 `tempfile + os.replace` 패턴이 이 모듈로 통합된 이유다.
- 파일 락 패턴(`fcntl.flock`)도 `_load_timers()` / `_save_timers()` 내부로 캡슐화하여 직접 호출을 금지한다.

**금지 사례**:
- 팀별 스크립트마다 비밀 마스킹 정규식을 개별 정의 — `utils/redact.py` 사용
- 각 검증 스크립트에 HTTP 체크 로직 중복 작성 — `verifiers/api_health.py` 사용

---

## 3. 디자인 패턴

### Strategy 패턴
`qc_verify.py`의 verifier 시스템. `ALL_CHECKS` 이름 목록 → `run_check(name, ...)` → 해당 모듈 호출. 알고리즘(검증 방식)을 교체 가능하도록 분리.

### Router/Factory 패턴
`dispatch.py`의 `TEAM_BOT` 딕셔너리 및 `_find_available_bot()`. 입력(팀명)을 받아 적절한 처리 객체(봇 키)를 반환한다. 동적 봇 선택 로직이 한 함수로 집중되어 있다.

### Plugin 아키텍처
`verifiers/` 디렉토리. 각 verifier는 독립 파일이며 `verify()` 인터페이스만 구현하면 시스템에 플러그인처럼 추가된다. 새 QC 항목 추가 시 기존 파일 수정 0건.

### 원자적 쓰기 패턴
`utils/atomic_write.py`. `tempfile.mkstemp()` → 쓰기 → `os.fsync()` → `os.replace()` 순서. 부분 쓰기 없음, 프로세스 충돌 시에도 파일 일관성 보장.

### 멱등성 패턴
`task-timer.py`의 `end_task()`. 이미 `completed` 상태면 동일한 결과를 재반환한다. 중복 호출이 상태를 변경하지 않음. 분산 환경/재시도 로직에서 필수.

---

## 4. 결합도/응집도

### 낮은 결합도 판단 기준
- 모듈 간 의존은 **임포트만**으로 제한, 전역 상태 공유 금지
- 선택적 의존성은 `try/except ImportError`로 처리하고 플래그 변수로 관리 (`_REDACT_AVAILABLE` 등)
- 모듈 삭제 시 **다른 모듈 수정 없이** 기능이 graceful하게 저하(degradation)되면 결합도가 낮다고 판단
- 두 모듈이 서로를 임포트하면(순환 의존) 즉시 분리 검토

### 높은 응집도 판단 기준
- 한 모듈의 모든 함수/클래스가 **동일한 데이터**를 다루거나 **동일한 목적**을 가질 때
- `utils/injection_guard.py`: 패턴 정의 → 탐지 → 결과 반환, 모두 "인젝션 탐지"라는 단일 목적
- `TaskTimer` 클래스: 타이머 시작/종료/조회/저장, 모두 task-timers.json 상태 관리 목적
- "이 모듈을 설명하는 한 문장"이 자연스럽게 나오면 응집도가 높다

---

## 5. 코딩 컨벤션

### Python (PEP8 기반)
- 들여쓰기: 공백 4칸
- 줄 최대 길이: 120자 (PEP8 기본 79자보다 완화, 긴 URL/정규식 허용)
- 타입 힌트 필수: 함수 시그니처에 `-> dict`, `-> Optional[str]` 명시
- Docstring: 모듈 상단 + 공개 함수에 작성, 한국어 허용
- 린터: `pyright`로 정적 타입 검사 (verifiers/pyright_check.py가 자동 실행)

### TypeScript
- ESLint 설정 준수 (`.eslintrc` 기준)
- `any` 타입 사용 금지, `unknown` 후 타입 가드 사용
- 인터페이스는 `I` 접두사 없이 PascalCase

### 공통 규칙
- **함수 200줄 이하** — 초과 시 책임 분리 신호
- **클래스 500줄 이하** — 초과 시 SRP 위반 검토
- **주석**: "무엇"이 아닌 "왜"를 설명 (`# 멱등성: 이미 completed면 기존 결과 반환`)
- **매직 넘버 금지**: 상수 이름 부여 (`CURL_TIMEOUT = 5`, `MAX_COMPOSITE_TEAMS`)
- 파일 상단에 `Usage:` 블록 작성 (직접 실행 가능한 스크립트 한정)

---

## 6. 확장 가능 구조 체크리스트

새 기능/모듈 추가 전 다음 항목을 검토한다.

### 하드코딩 금지
- [ ] 팀 이름, 봇 키, 엔드포인트 URL은 환경변수 또는 설정 딕셔너리로 관리 (`BOT_KEYS`, `TEAM_BOT`)
- [ ] 임계값/타임아웃은 모듈 상단 상수로 분리 (`CURL_TIMEOUT`, `MANDATORY_CHECKS`)
- [ ] 파일 경로는 `WORKSPACE_ROOT` 환경변수 기준으로 조립 (`Path(os.environ.get("WORKSPACE_ROOT", ...))`)

### 설정 파일 분리
- [ ] 팀/봇 매핑 변경은 코드 수정 없이 JSON/딕셔너리 변경으로 가능한가
- [ ] 새 verifier 추가 시 `ALL_CHECKS` 리스트 한 줄 추가만으로 완료되는가
- [ ] 민감 설정(API 키, 토큰)은 `.env.keys`에서 로드하고 코드에 직접 기재 금지

### 인터페이스 기반 설계
- [ ] 새 모듈이 기존 모듈과 동일한 시그니처를 가지는가 (verifier: `verify(**kwargs) -> dict`)
- [ ] 선택적 의존 모듈은 `try/except ImportError` + 폴백 함수로 처리했는가
- [ ] 기존 호출부를 수정하지 않고 새 구현체로 교체 가능한가 (OCP 확인)
- [ ] 파일 I/O는 `utils/atomic_write.py`를 통해 일관성을 보장하는가
- [ ] 동시 접근이 가능한 파일은 `fcntl.flock`으로 락을 적용했는가
