# layered-architecture > vibe-commander의 4-레이어 아키텍처 규칙에 따라 코드를 작성합니다. 새 함수, 모듈, 파일 생성, 리팩토링, 의존성 방향 검토 요청 시 사용합니다. - Author: yachaboom - Repository: viilab/vibe-commander - Version: 20260207190114 - Stars: 0 - Forks: 0 - Last Updated: 2026-02-07 - Source: https://github.com/viilab/vibe-commander - Web: https://mule.run/skillshub/@@viilab/vibe-commander~layered-architecture:20260207190114 --- --- name: layered-architecture description: vibe-commander의 4-레이어 아키텍처 규칙에 따라 코드를 작성합니다. 새 함수, 모듈, 파일 생성, 리팩토링, 의존성 방향 검토 요청 시 사용합니다. metadata: version: 1.0.0 category: architecture tags: [layers, pure-functions, adapters, dependency-direction] --- # Layered Architecture Guide vibe-commander는 4-레이어 아키텍처를 사용합니다. 모든 코드는 해당 레이어 규칙을 준수해야 합니다. ## Instructions ### Step 1: 레이어 식별 코드를 작성하기 전에 해당 코드가 속하는 레이어를 먼저 결정합니다. | Layer | 위치 | 책임 | I/O 허용 | |-------|------|------|----------| | **1. Core** | `src/core/` | 파싱, 렌더링, 검증 로직 | 없음 | | **2. Resolver** | `src/config/` | 설정 기반 프로젝트 구조 해석 | 설정 로드만 | | **3. Adapter** | `src/adapters/` | CLI, MCP, Pipe 인터페이스 | 파일 R/W, Git, stdin/stdout | | **4. Consumer** | 외부 | 사람, 에이전트, 오케스트레이터 | 해당 없음 | ### Step 2: 의존성 방향 확인 의존성은 반드시 **상위 → 하위** 방향만 허용합니다. ``` Layer 4 (Consumer) → Layer 3 (Adapter) → Layer 2 (Resolver) → Layer 1 (Core) ``` CRITICAL: 역방향 의존성 금지. Core가 Adapter를 import하거나 Resolver가 Adapter를 import하면 안 됩니다. ### Step 3: 레이어별 코드 작성 레이어에 맞는 패턴으로 코드를 작성합니다 (아래 Rules 참조). ## Rules ### Layer 1: Core — 순수 함수 원칙 Core 함수는 **JSON in → JSON out**이며 부수효과가 없어야 합니다. **Do ✅**: ```typescript // 순수 함수: 입력 → 출력, 외부 의존 없음 export function parseUnitPlan( content: string, schema: PlanParsingConfig ): UnitMeta { const title = extractTitle(content, schema.titleSource); const depends = extractDeps(content, schema); return { unitId: '', unitType: '', title, planPath: '', depends }; } ``` **Don't ❌**: ```typescript // Core에서 파일 직접 읽기 — 부수효과 발생 import { readFileSync } from 'fs'; export function parseUnitPlan(planPath: string): UnitMeta { const content = readFileSync(planPath, 'utf-8'); // ❌ I/O 금지 // ... } ``` **핵심 규칙:** - `fs`, `child_process`, `path.resolve` 등 I/O 모듈 import 금지 - 전역 상태(`process.env`, 싱글톤 등) 참조 금지 - 동일 입력 → 동일 출력 보장 - 에러는 throw 대신 `ToolResult` 구조로 반환 ### Layer 2: Resolver — 설정 기반 추상화 Resolver는 `vibe-commander.config.json`을 읽어 프로젝트별 차이를 흡수합니다. **Do ✅**: ```typescript // 설정 기반으로 경로 결정 — Core에 설정 주입 export function locatePlan( unitId: string, config: ProjectConfig ): string { const unitType = resolveUnitType(unitId, config); const docRoot = config.paths.docRoots[unitType.planDir]; const pattern = config.docDiscovery.plan.pattern; return path.join(docRoot, interpolate(pattern, { unitId })); } ``` **Don't ❌**: ```typescript // 하드코딩된 프로젝트 구조 — 범용성 파괴 export function locatePlan(unitId: string): string { return `vibe/unit-plans/${unitId}.md`; // ❌ 설정 무시 } ``` **핵심 규칙:** - 프로젝트 경로, ID 패턴 등을 절대 하드코딩하지 않음 - 모든 프로젝트별 차이는 `config` 파라미터로 주입 - Core 함수 호출 시 설정에서 추출한 스키마/옵션을 전달 ### Layer 3: Adapter — 외부 인터페이스 Adapter는 I/O를 수행하고 Core/Resolver를 조합합니다. **Do ✅**: ```typescript // CLI Adapter: 파일 읽기 → Resolver → Core → 파일 쓰기 export async function setUnitCommand(unitId: string): Promise> { const config = loadConfig(projectRoot); const planPath = locatePlan(unitId, config); const content = readFileSync(planPath, 'utf-8'); const unitMeta = parseUnitPlan(content, config.planParsing); const section = renderSection(unitMeta, config); writeSection(config.paths.commands, section, config); return { success: true, data: { ...unitMeta } }; } ``` **Don't ❌**: ```typescript // Adapter에 파싱 로직 직접 구현 — 테스트 불가 export async function setUnitCommand(unitId: string) { const content = readFileSync(`vibe/unit-plans/${unitId}.md`, 'utf-8'); const title = content.match(/^# (.+)/m)?.[1]; // ❌ Core 로직이 Adapter에 // ... } ``` **핵심 규칙:** - I/O는 Adapter에서만 수행 (fs, child_process, stdin/stdout) - 비즈니스 로직은 Core/Resolver에 위임 - 에러를 `ToolResult` 구조로 감싸서 반환 ### 공통: ToolResult 반환 표준 모든 공개 API는 `ToolResult` 구조로 반환합니다. **Do ✅**: ```typescript interface ToolResult { success: boolean; data?: T; error?: { code: string; message: string; details?: string }; } // 성공 return { success: true, data: { unitId, title, depsFound } }; // 실패 return { success: false, error: { code: 'PLAN_NOT_FOUND', message: `계획서를 찾을 수 없음: ${planPath}` } }; ``` **Don't ❌**: ```typescript // 비구조화된 에러 — 소비자가 처리 불가 throw new Error('계획서가 없습니다'); return null; // ❌ 실패 이유 불명 ``` ## Examples ### Example 1: 새 파서 함수 추가 User says: "로드맵 파싱 함수 만들어줘" Actions: 1. `src/core/parsers/` 디렉토리에 파일 생성 2. 순수 함수로 구현 (content + schema 파라미터) 3. `ToolResult` 반환 4. `src/config/resolver.ts`에서 설정 기반 스키마 추출 로직 추가 5. `src/adapters/cli/`에서 파일 읽기 → 파서 호출 → 결과 출력 ### Example 2: 새 CLI 커맨드 추가 User says: "vc validate 커맨드 추가해줘" Actions: 1. Core에 검증 로직 (`src/core/validators/`) 2. Resolver에 설정 기반 경로 확인 로직 3. Adapter에 CLI 인터페이스 (`src/adapters/cli/validate.ts`) 4. 각 레이어가 올바른 의존 방향인지 확인 ## Troubleshooting ### Error: Core 함수에서 fs import 감지 Cause: 순수 함수 원칙 위반 Solution: 파일 읽기를 Adapter로 이동하고, Core 함수에는 `content: string` 파라미터로 전달 ### Error: 순환 의존성 감지 Cause: 레이어 간 역방향 import Solution: 공유 타입은 `src/types/`에 정의하고, 각 레이어에서 타입만 import