시리즈 전체를 종합하여 실전 컨텍스트 엔지니어링 시스템을 구축합니다. CLAUDE.md 작성, MCP 서버 구현, 멀티에이전트 파이프라인, 측정 대시보드를 실습합니다.
이번 장에서는 이 시리즈에서 다룬 모든 개념을 종합하여, 실제 프로젝트에 적용할 수 있는 컨텍스트 엔지니어링 시스템을 단계별로 구축합니다. 대상 프로젝트는 TypeScript 기반 웹 애플리케이션을 가정합니다.
3장에서 배운 원칙을 적용하여 CLAUDE.md를 작성합니다.
# My Project
## Commands
npm run dev # dev server (port 3000)
npm run build # production build (MUST pass before commit)
npm run test # run tests
npm run lint # ESLint + Prettier
## Critical Rules
1. TypeScript strict mode, NO 'any' or 'as any'
2. All components: named function declarations
3. Styling: Tailwind CSS only, use cn() for merging
4. Error handling: always use AppError class hierarchy
5. API responses: use ApiResponse<T> wrapper type
6. Database: all queries through repository pattern
## Architecture
src/
app/ - Next.js App Router pages
components/
ui/ - Reusable UI primitives (Button, Input, Modal)
features/ - Feature-specific components
lib/
db/ - Database client, repositories
auth/ - Authentication utilities
api/ - API client, types
types/ - Shared TypeScript types
## Patterns (follow existing code)
- API route: src/app/api/users/route.ts (reference)
- Component: src/components/ui/Button.tsx (reference)
- Repository: src/lib/db/user-repository.ts (reference)
- Test: src/lib/db/__tests__/user-repository.test.ts (reference)
## Detailed Docs
- docs/architecture.md - Full architecture, data flow
- docs/api-guide.md - API conventions, error codes
- docs/testing.md - Test patterns, fixtures작성한 CLAUDE.md를 다음 기준으로 검증합니다.
| 항목 | 확인 | 근거 |
|---|---|---|
| 300줄 미만인가 | 약 40줄 | 모든 대화에 로드되므로 간결해야 함 |
| 빌드 명령어가 정확한가 | npm run build | 에이전트가 검증에 사용 |
| 모든 규칙이 프로젝트 특화인가 | 확인 | "좋은 코드를 작성하라" 같은 일반 규칙 없음 |
| 참조 패턴이 실제 파일을 가리키는가 | 확인 | 구체적 파일 경로 제공 |
| 상세 문서 경로가 명시되어 있는가 | 확인 | 필요 시 추가 읽기 가능 |
CLAUDE.md의 "Patterns" 섹션이 특히 중요합니다. 에이전트에게 "이 파일처럼 작성하라"고 참조 파일을 지정하면, 장황한 규칙 설명 없이도 일관된 코드가 생성됩니다. 하나의 좋은 예시가 열 줄의 규칙보다 효과적입니다.
프로젝트 루트의 CLAUDE.md 외에, 하위 디렉토리에 특화된 컨텍스트를 추가합니다.
# Database Layer
## Repository Pattern
- All database access through repository classes
- Each repository implements IRepository<T> interface
- Use Drizzle ORM query builder, NOT raw SQL
- All queries must have proper error handling with DBError
## Naming
- File: {entity}-repository.ts
- Class: {Entity}Repository
- Method: findById, findMany, create, update, delete
## Testing
- Use test fixtures from src/lib/db/__fixtures__/
- Mock database with in-memory SQLite
- Every repository method needs a test# Components
## Structure
- UI primitives in ui/ (no business logic)
- Feature components in features/ (can use hooks, API calls)
- Each component: one file, named export
## Styling
- Use cn() for className merging (import from "@/lib/utils")
- Follow design tokens in globals.css
- Dark mode: use CSS custom properties, NOT conditional classes
- Responsive: mobile-first approach
## Accessibility
- All interactive elements: keyboard navigable
- Images: always include alt text
- Form inputs: always include label8장에서 다룬 MCP 서버를 실제로 구현합니다. 이 서버는 코드베이스 바깥의 정보를 에이전트에게 동적으로 제공합니다.
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { execSync } from "child_process";
import { readFileSync, existsSync } from "fs";
import { join } from "path";
const server = new McpServer({
name: "project-context-server",
version: "1.0.0",
});
// 도구 1: 모듈 아키텍처 정보
server.tool(
"get-module-info",
"특정 모듈의 아키텍처, 의존성, 주요 파일 정보를 반환합니다",
{
modulePath: z.string().describe("모듈 경로 (예: src/lib/auth)"),
},
async ({ modulePath }) => {
const info = analyzeModule(modulePath);
return {
content: [{
type: "text",
text: JSON.stringify(info, null, 2),
}],
};
}
);
// 도구 2: 최근 변경 영향 분석
server.tool(
"analyze-recent-changes",
"최근 N개 커밋의 변경 파일과 영향 범위를 분석합니다",
{
count: z.number().default(5).describe("분석할 커밋 수"),
path: z.string().optional().describe("특정 경로로 필터링"),
},
async ({ count, path }) => {
const gitLog = execSync(
`git log --oneline --name-only -${count} ${path ? `-- ${path}` : ""}`,
{ encoding: "utf-8" }
);
return {
content: [{
type: "text",
text: `최근 ${count}개 커밋의 변경 파일:\n\n${gitLog}`,
}],
};
}
);
// 도구 3: 의존성 그래프
server.tool(
"get-dependency-graph",
"파일의 import/export 의존성 그래프를 반환합니다",
{
filePath: z.string().describe("분석할 파일 경로"),
depth: z.number().default(2).describe("추적 깊이"),
},
async ({ filePath, depth }) => {
const graph = buildDependencyGraph(filePath, depth);
return {
content: [{
type: "text",
text: formatDependencyGraph(graph),
}],
};
}
);
// 도구 4: 테스트 커버리지 정보
server.tool(
"get-test-coverage",
"특정 파일이나 모듈의 테스트 커버리지 정보를 반환합니다",
{
path: z.string().describe("파일 또는 모듈 경로"),
},
async ({ path }) => {
const coverage = getTestCoverage(path);
return {
content: [{
type: "text",
text: formatCoverage(coverage),
}],
};
}
);
// 헬퍼 함수들
function analyzeModule(modulePath: string) {
const fullPath = join(process.cwd(), modulePath);
if (!existsSync(fullPath)) {
return { error: `경로를 찾을 수 없습니다: ${modulePath}` };
}
const files = execSync(`find ${fullPath} -name "*.ts" -o -name "*.tsx"`, {
encoding: "utf-8",
}).trim().split("\n");
const exports = files.flatMap(file => {
const content = readFileSync(file, "utf-8");
const matches = content.match(/export\s+(function|class|const|interface|type)\s+(\w+)/g);
return (matches ?? []).map(m => ({
file: file.replace(process.cwd() + "/", ""),
export: m,
}));
});
return {
path: modulePath,
fileCount: files.length,
exports,
hasTests: existsSync(join(fullPath, "__tests__")),
hasCLAUDEmd: existsSync(join(fullPath, "CLAUDE.md")),
};
}
function buildDependencyGraph(filePath: string, depth: number) {
// 간략화된 구현 - 실제로는 TypeScript Compiler API 사용
const content = readFileSync(filePath, "utf-8");
const imports = [...content.matchAll(/from\s+["']([^"']+)["']/g)]
.map(m => m[1])
.filter(imp => imp.startsWith(".") || imp.startsWith("@/"));
return { file: filePath, imports, depth };
}
function formatDependencyGraph(graph: { file: string; imports: string[] }) {
let result = `${graph.file}\n`;
for (const imp of graph.imports) {
result += ` -> ${imp}\n`;
}
return result;
}
function getTestCoverage(path: string) {
// 실제 구현에서는 coverage report 파싱
return { path, coverage: "N/A - run npm run test:coverage first" };
}
function formatCoverage(coverage: { path: string; coverage: string }) {
return `${coverage.path}: ${coverage.coverage}`;
}
// 서버 시작
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
}
main().catch(console.error);{
"mcpServers": {
"project-context": {
"command": "npx",
"args": ["tsx", "mcp-servers/project-context/index.ts"]
}
}
}7장의 격리 전략을 적용한 멀티에이전트 파이프라인을 설계합니다.
const agentContexts = {
coder: {
shared: ["CLAUDE.md", "프로젝트 구조"],
dedicated: {
files: ["대상 모듈 코드", "관련 타입 정의", "참조 패턴 파일"],
tools: ["read", "write", "edit", "grep", "glob", "terminal"],
instructions: "코드를 작성/수정합니다. 기존 패턴을 따르세요.",
},
},
tester: {
shared: ["CLAUDE.md", "테스트 컨벤션"],
dedicated: {
files: ["테스트 대상 코드(인터페이스)", "기존 테스트 패턴", "테스트 픽스처"],
tools: ["read", "write", "terminal"],
instructions: "단위 테스트를 작성합니다. 기존 테스트 패턴을 따르세요.",
},
},
reviewer: {
shared: ["CLAUDE.md", "코딩 컨벤션"],
dedicated: {
files: ["변경된 코드 diff", "아키텍처 문서", "관련 모듈 인터페이스"],
tools: ["read", "grep"],
instructions: "코드를 리뷰하고 개선사항을 제안합니다.",
},
},
analyzer: {
shared: ["CLAUDE.md", "프로젝트 구조"],
dedicated: {
files: ["대상 모듈 전체", "의존성 그래프"],
tools: ["read", "grep", "glob", "get-dependency-graph", "analyze-recent-changes"],
instructions: "변경 영향을 분석하고 리팩토링 계획을 수립합니다.",
},
},
};9장의 메트릭을 수집하고 시각화하는 시스템입니다.
interface ContextMetric {
timestamp: Date;
taskId: string;
taskType: string;
// 컨텍스트 메트릭
contextTokens: number;
contextFiles: number;
compressionRatio: number;
// 결과 메트릭
success: boolean;
firstAttempt: boolean;
iterationCount: number;
buildPassed: boolean;
testsPassed: boolean;
// 효율성 메트릭
totalCost: number;
responseTimeMs: number;
}
class MetricsCollector {
private metrics: ContextMetric[] = [];
private storagePath: string;
constructor(storagePath: string = ".claude/metrics.jsonl") {
this.storagePath = storagePath;
}
record(metric: ContextMetric): void {
this.metrics.push(metric);
// JSONL 형식으로 추가 기록
appendFileSync(
this.storagePath,
JSON.stringify(metric) + "\n"
);
}
generateReport(period: "day" | "week" | "month"): Report {
const filtered = this.filterByPeriod(period);
return {
period,
totalTasks: filtered.length,
successRate: this.calcRate(filtered, m => m.success),
firstAttemptRate: this.calcRate(filtered, m => m.firstAttempt),
avgCost: this.calcAvg(filtered, m => m.totalCost),
avgTokens: this.calcAvg(filtered, m => m.contextTokens),
avgIterations: this.calcAvg(filtered, m => m.iterationCount),
costPerSuccess: this.calcCostPerSuccess(filtered),
// 작업 유형별 분석
byTaskType: this.groupBy(filtered, m => m.taskType),
// 트렌드
trend: this.calculateTrend(filtered),
};
}
private calcRate(metrics: ContextMetric[], predicate: (m: ContextMetric) => boolean): number {
if (metrics.length === 0) return 0;
return metrics.filter(predicate).length / metrics.length;
}
private calcAvg(metrics: ContextMetric[], selector: (m: ContextMetric) => number): number {
if (metrics.length === 0) return 0;
return metrics.reduce((sum, m) => sum + selector(m), 0) / metrics.length;
}
private calcCostPerSuccess(metrics: ContextMetric[]): number {
const successful = metrics.filter(m => m.success);
if (successful.length === 0) return 0;
return successful.reduce((sum, m) => sum + m.totalCost, 0) / successful.length;
}
private filterByPeriod(period: string): ContextMetric[] {
// 기간별 필터링 구현
return this.metrics;
}
private groupBy(metrics: ContextMetric[], key: (m: ContextMetric) => string) {
const groups = new Map<string, ContextMetric[]>();
for (const m of metrics) {
const k = key(m);
if (!groups.has(k)) groups.set(k, []);
groups.get(k)?.push(m);
}
return Object.fromEntries(groups);
}
private calculateTrend(metrics: ContextMetric[]) {
// 시간에 따른 성공률 변화 추적
return { direction: "stable" as const };
}
}=== 컨텍스트 엔지니어링 주간 리포트 ===
기간: 2026-03-29 ~ 2026-04-05
총 태스크: 47건
성공률: 87.2% (41/47)
첫 시도 성공률: 72.3% (34/47)
평균 컨텍스트 토큰: 28,450
평균 비용: $0.12/태스크
성공 건당 비용: $0.14
작업 유형별:
create: 93.3% (14/15)
modify: 85.0% (17/20)
fix: 83.3% (10/12)
전주 대비:
성공률: +3.2%p
비용: -8.5%
첫 시도 성공률: +5.1%p
개선 제안:
- fix 유형 성공률이 낮음 -> 에러 컨텍스트 강화 권장
- modify 유형에서 테스트 관련 실패 빈발 -> 테스트 컨텍스트 추가 권장프로젝트에 컨텍스트 엔지니어링을 도입할 때 참고할 체크리스트입니다.
한 번에 모든 것을 구축하려 하지 마십시오. Phase 1의 기초만으로도 상당한 개선을 경험할 수 있습니다. 이후 단계는 메트릭에 기반하여 필요한 부분부터 점진적으로 도입하는 것이 효과적입니다.
이 시리즈 전체를 관통하는 핵심 원칙을 정리합니다.
컨텍스트가 결과를 결정한다: 모델의 출력 품질은 프롬프트보다 컨텍스트에 더 크게 좌우됩니다.
적을수록 좋다: 300줄 미만의 집중된 CLAUDE.md가 500줄 이상의 비대한 파일보다 낫습니다. 더 많은 규칙이 더 나은 성능을 만들지 않습니다.
측정하고 개선한다: 주관적 판단이 아닌 태스크 성공률과 토큰 효율성으로 평가합니다.
코드화한다: 컨텍스트를 일급 엔지니어링 산출물로 다룹니다. 버전 관리하고, 테스트하고, 리뷰합니다.
동적으로 조립한다: 모든 작업에 동일한 컨텍스트를 사용하지 않습니다. 작업 유형과 복잡도에 따라 맞춤형 컨텍스트를 구성합니다.
10개 장에 걸쳐 컨텍스트 엔지니어링의 개념, 전략, 도구, 측정, 그리고 실전 적용을 살펴보았습니다.
프롬프트 엔지니어링이 "모델에게 잘 말하는 기술"이었다면, 컨텍스트 엔지니어링은 "모델이 잘 일할 수 있는 환경을 체계적으로 구축하는 기술"입니다. 이 분야는 아직 빠르게 발전하고 있으며, 모델의 능력이 향상될수록 컨텍스트의 품질이 차별화 요소가 됩니다.
컨텍스트 엔지니어링은 특정 도구에 종속되지 않는 범용 기술입니다. 오늘 이 시리즈에서 배운 원칙들은 내일 등장할 새로운 도구와 모델에서도 유효합니다. 선택, 압축, 정렬, 격리, 포맷이라는 다섯 가지 전략을 기억하고, 측정을 통해 지속적으로 개선해 나가시기 바랍니다.
이 글이 도움이 되셨나요?
관련 주제 더 보기
컨텍스트 엔지니어링의 효과를 정량적으로 측정하는 방법을 다룹니다. 태스크 성공률, 토큰 효율성, A/B 테스트, 컨텍스트 드리프트 감지를 분석합니다.
Claude Code, Cursor, GitHub Copilot에서 컨텍스트를 최적화하는 구체적 방법과, MCP 서버를 통한 동적 컨텍스트 확장 기법을 다룹니다.
멀티에이전트 시스템에서 에이전트별 컨텍스트를 격리하고, 크로스 오염을 방지하며, 공유 컨텍스트를 효과적으로 관리하는 전략을 다룹니다.