장기 실행 AI 에이전트 개발 가이드
목차
- 개요
- 핵심 문제와 해결책
- 아키텍처 패턴
- Initializer Agent
- Coding Agent
- 세션 시작 루틴
- 품질 관리 시스템
- 실전 구현 가이드
- 모범 사례
- 고급 패턴
- 문제 해결 가이드
- 성능 최적화
- 보안 및 권한 관리
- 모니터링 및 디버깅
- 배포 및 프로덕션
- 결론
- 부록 A: 전체 프로젝트 템플릿
- 부록 B: 문제 해결 플로우차트
- 부록 C: 에이전트 프롬프트 라이브러리
- 맺음말
- 부록 D: FAQ (자주 묻는 질문)
- 부록 E: 실전 사례 연구
개요
장기 실행 AI 에이전트는 수 시간에서 며칠에 걸쳐 복잡한 작업을 자율적으로 수행할 수 있는 시스템입니다. Anthropic의 Claude Agent SDK는 이러한 장기 실행 에이전트를 구축하기 위한 효과적인 하네스(harness)를 제공합니다. 하네스란 에이전트가 안정적으로 작동할 수 있도록 지원하는 외부 구조와 시스템을 의미합니다.
왜 중요한가?
전통적인 AI 에이전트는 단일 컨텍스트 윈도우 내에서만 작동하며, 세션이 종료되면 모든 작업 기록을 잃어버립니다. 이는 마치 매 교대마다 이전 작업 내용을 전혀 모르는 새로운 엔지니어가 투입되는 것과 같습니다. 장기 프로젝트를 완수하기 위해서는 세션 간 격차를 메울 수 있는 메커니즘이 필요합니다.
핵심 문제와 해결책
주요 실패 패턴
장기 실행 에이전트에서 흔히 발생하는 두 가지 치명적인 실패 패턴이 있습니다.
1. One-Shot 실패 에이전트가 한 번에 모든 것을 끝내려다 컨텍스트 윈도우를 초과하여 작업 중간에 멈추는 현상입니다. 이 경우 다음 세션의 에이전트는 이전에 무엇을 했는지 추측해야 하며, 명확한 인계가 불가능합니다.
2. Early Victory (조기 승리 선언) 일부 기능이 구현된 후 에이전트가 프로젝트를 완료했다고 잘못 판단하는 현상입니다. 실제로는 더 많은 작업이 남아있음에도 불구하고 작업을 중단합니다.
Anthropic의 해결책
Anthropic은 이러한 문제를 해결하기 위해 이중 에이전트 패턴을 개발했습니다.
- Initializer Agent: 첫 세션에서 환경을 설정하고 작업 범위를 명확히 정의
- Coding Agent: 이후 세션에서 점진적으로 기능을 구현하고 명확한 인계 자료 생성
이 패턴의 핵심은 작업을 작은 단위로 쪼개고, 구조화된 체크리스트를 통해 범위를 강제하는 것입니다.
아키텍처 패턴
프로젝트 구조
1
2
3
4
5
6
7
8
my_project/
├── feature_list.json # 작업의 단일 진실 공급원(SSOT)
├── app_spec.txt # 애플리케이션 사양
├── init.sh # 환경 설정 스크립트
├── claude-progress.txt # 세션별 진행 로그
├── .claude_settings.json # 보안 설정
├── .git/ # Git 저장소
└── [application files] # 생성된 애플리케이션 코드
핵심 구성 요소
1. feature_list.json 모든 작업의 기준이 되는 JSON 파일입니다. 각 기능은 다음과 같은 구조를 가집니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"features": [
{
"id": 1,
"title": "사용자 로그인 구현",
"description": "이메일과 비밀번호를 사용한 기본 로그인 기능",
"steps": [
"로그인 폼 UI 생성",
"백엔드 인증 API 구현",
"세션 관리 추가",
"에러 핸들링 구현"
],
"passes": false,
"priority": "high"
}
]
}
2. claude-progress.txt 각 세션의 작업 내용을 기록하는 로그 파일입니다. 다음 세션의 에이전트가 이 파일을 읽어 이전 작업 내용을 파악합니다.
1
2
3
4
5
6
7
8
=== Session 2024-01-15 14:30 ===
Completed: Feature #1 - User Login
- Implemented login form with email/password validation
- Added JWT authentication to backend
- Tested with Puppeteer automation
- Committed: "feat: implement user login system"
Next: Feature #2 - User Registration
3. Git Repository Git은 에이전트의 영구 메모리 역할을 합니다. 각 기능 구현 후 상세한 커밋 메시지와 함께 변경사항을 저장합니다.
Initializer Agent
역할과 책임
Initializer Agent는 프로젝트의 첫 세션에서 실행되며, 이후 모든 Coding Agent가 효과적으로 작업할 수 있는 환경을 구축합니다.
주요 작업
1. 사양 분석
1
2
# app_spec.txt 파일을 읽고 요구사항 파악
cat app_spec.txt
2. feature_list.json 생성 애플리케이션 사양을 바탕으로 200개 이상의 상세한 테스트 케이스를 작성합니다. 이 과정은 10-20분 정도 소요될 수 있으며, 에이전트가 멈춘 것처럼 보일 수 있지만 정상적인 동작입니다.
1
2
3
4
5
6
7
8
9
10
11
12
{
"features": [
{
"id": 1,
"title": "홈페이지 레이아웃",
"description": "반응형 홈페이지 레이아웃 구현",
"steps": ["헤더 생성", "네비게이션 바", "푸터 구현"],
"passes": false
},
// ... 199개 더
]
}
3. 프로젝트 구조 설정
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Git 저장소 초기화
git init
# 기본 디렉토리 구조 생성
mkdir -p src/{frontend,backend,tests}
# 환경 설정 스크립트 생성
cat > init.sh << 'EOF'
#!/bin/bash
npm install
npm run dev
EOF
chmod +x init.sh
# 첫 커밋
git add .
git commit -m "Initial setup: project structure and feature list"
4. 보안 설정
1
2
3
4
5
6
7
8
9
10
11
12
// .claude_settings.json
{
"allowed_commands": [
"npm", "node", "git", "cat", "ls",
"mkdir", "cp", "mv", "rm"
],
"blocked_patterns": [
"rm -rf /",
"sudo",
"chmod 777"
]
}
Coding Agent
운영 원칙
Coding Agent는 매 세션마다 단 하나의 기능만 구현합니다. 이는 작업을 관리 가능한 크기로 유지하고, 각 세션이 명확한 결과물을 남기도록 보장합니다.
작업 흐름
1. 컨텍스트 수집
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 1. 현재 위치 확인
pwd
# 2. 프로젝트 구조 파악
ls -la
# 3. 사양 파일 읽기
cat app_spec.txt
# 4. 기능 목록 확인
cat feature_list.json | head -50
# 5. 이전 세션 진행 상황 확인
cat claude-progress.txt
# 6. Git 히스토리 확인
git log --oneline -20
# 7. 남은 작업 카운트
cat feature_list.json | grep '"passes": false' | wc -l
2. 기능 선택 feature_list.json에서 "passes": false인 최우선 순위 기능을 선택합니다.
3. 개발 서버 실행
1
2
3
4
5
6
# init.sh 스크립트로 자동 실행
./init.sh
# 또는 수동으로
npm install
npm run dev
4. 기능 구현 선택한 기능의 모든 단계를 완료합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 예: 로그인 기능 구현
// src/components/LoginForm.jsx
import React, { useState } from 'react';
export default function LoginForm() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const handleSubmit = async (e) => {
e.preventDefault();
try {
const response = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password })
});
// 로그인 로직...
} catch (err) {
setError('로그인 실패');
}
};
return (
<form onSubmit={handleSubmit}>
{/* 폼 필드... */}
</form>
);
}
5. E2E 테스트 Puppeteer를 사용하여 실제 사용자 시나리오를 테스트합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// tests/e2e/login.test.js
const puppeteer = require('puppeteer');
describe('Login Feature', () => {
let browser;
let page;
beforeAll(async () => {
browser = await puppeteer.launch();
page = await browser.newPage();
});
test('사용자가 올바른 자격증명으로 로그인할 수 있어야 함', async () => {
await page.goto('http://localhost:3000/login');
// 이메일 입력
await page.type('#email', 'test@example.com');
// 비밀번호 입력
await page.type('#password', 'password123');
// 로그인 버튼 클릭
await page.click('button[type="submit"]');
// 대시보드로 리다이렉트 확인
await page.waitForNavigation();
expect(page.url()).toContain('/dashboard');
// 스크린샷 저장
await page.screenshot({
path: 'verification/login-success.png'
});
});
afterAll(async () => {
await browser.close();
});
});
6. Git 커밋
1
2
3
4
5
6
7
8
9
10
11
12
git add .
git commit -m "feat: implement user login feature (#1)
- Created LoginForm component with email/password inputs
- Implemented authentication API endpoint
- Added JWT session management
- Tested with E2E automation using Puppeteer
- Verified UI with screenshots in verification/ directory
- Updated feature_list.json: marked feature #1 as passing
Tests: All E2E tests passing
Screenshots: verification/login-*.png"
7. feature_list.json 업데이트 테스트가 완전히 통과한 후에만 passes 필드를 true로 변경합니다.
1
2
3
4
5
6
{
"id": 1,
"title": "사용자 로그인 구현",
"passes": true, // ← 테스트 통과 후에만 변경
"completed_at": "2024-01-15T14:45:00Z"
}
8. 진행 상황 기록
1
2
3
4
5
6
7
8
# claude-progress.txt에 세션 요약 추가
echo "
=== Session $(date '+%Y-%m-%d %H:%M') ===
Completed: Feature #1 - User Login
Status: ✓ All tests passing
Time spent: ~45 minutes
Next: Feature #2 - User Registration
" >> claude-progress.txt
세션 시작 루틴
매 세션 시작 시 Coding Agent는 일관된 루틴을 따릅니다. 이는 “어제 뭐 했지?” 문제를 완전히 해결합니다.
표준 체크리스트
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#!/bin/bash
# session-start.sh - 세션 시작 루틴
echo "=== 세션 시작 루틴 ==="
# 1. 위치 확인
echo "1. 작업 디렉토리 확인"
pwd
# 2. 프로젝트 현황 파악
echo "2. 프로젝트 구조 확인"
ls -la
# 3. 사양 리뷰
echo "3. 애플리케이션 사양 검토"
head -20 app_spec.txt
# 4. 진행 로그 확인
echo "4. 이전 세션 진행 상황"
tail -50 claude-progress.txt
# 5. Git 히스토리
echo "5. 최근 커밋 히스토리"
git log --oneline -10
# 6. 다음 작업 식별
echo "6. 다음 미완료 기능 찾기"
node -pe "
const features = require('./feature_list.json').features;
const next = features.find(f => !f.passes);
next ? \`Feature #\${next.id}: \${next.title}\` : 'All done!'
"
# 7. 개발 환경 실행
echo "7. 개발 서버 시작"
./init.sh &
# 8. 기본 테스트 실행
echo "8. 기존 테스트 실행"
npm test
echo "=== 루틴 완료 ==="
품질 관리 시스템
3단계 검증 프로세스
1. Rule-Based 검증 코드가 기본 규칙을 준수하는지 자동으로 확인합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// tools/validators.js
class FeatureValidator {
validate(code, tests) {
const checks = {
hasTests: tests.length > 0,
hasDocumentation: code.includes('/**'),
hasErrorHandling: code.includes('try') || code.includes('catch'),
followsNaming: this.checkNamingConvention(code),
hasTypeChecks: code.includes('PropTypes') || code.includes('TypeScript')
};
return Object.values(checks).every(Boolean);
}
}
2. Visual 검증 Puppeteer를 통해 실제 UI를 테스트하고 스크린샷을 저장합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// tests/visual/feature-verification.js
async function verifyFeature(featureId, scenarios) {
const browser = await puppeteer.launch({ headless: false });
const page = await browser.newPage();
const results = [];
for (const scenario of scenarios) {
try {
// 시나리오 실행
await scenario.run(page);
// 스크린샷 저장
const screenshotPath = `verification/feature-${featureId}-${scenario.name}.png`;
await page.screenshot({ path: screenshotPath, fullPage: true });
// 결과 기록
results.push({
scenario: scenario.name,
passed: true,
screenshot: screenshotPath
});
} catch (error) {
results.push({
scenario: scenario.name,
passed: false,
error: error.message
});
}
}
await browser.close();
return results;
}
3. LLM 검증 (선택사항) 복잡한 경우 Claude를 사용하여 추가 검증을 수행할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# tools/llm_validator.py
from claude_agent_sdk import query, ClaudeAgentOptions
async def validate_implementation(feature_desc, code, test_results):
prompt = f"""
다음 기능 구현을 검증해주세요:
기능 설명: {feature_desc}
구현 코드:
```
{code}
```
테스트 결과:
{test_results}
다음 기준으로 평가해주세요:
1. 기능이 사양을 완전히 충족하는가?
2. 코드 품질이 프로덕션 수준인가?
3. 엣지 케이스가 처리되었는가?
4. 보안 문제가 없는가?
JSON 형식으로 응답:
approved
"""
async for message in query(prompt=prompt):
if hasattr(message, 'result'):
return json.loads(message.result)
커밋 메시지 템플릿
명확한 커밋 메시지는 미래 세션의 에이전트에게 중요한 컨텍스트를 제공합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
feat: implement [기능명] (#[기능번호])
[구현 세부사항]
- 변경사항 1
- 변경사항 2
- 변경사항 3
[테스트]
- 테스트 시나리오 1: 통과
- 테스트 시나리오 2: 통과
- E2E 테스트: verification/screenshots/
[업데이트]
- feature_list.json: #[번호] passes: true로 변경
[다음 단계]
Feature #[다음번호] - [다음 기능명]
실전 구현 가이드
Claude Agent SDK 설치
1
2
3
4
5
6
7
8
# Node.js 환경
npm install -g @anthropic-ai/claude-code
# Python 환경
pip install claude-agent-sdk
# 인증 설정
export ANTHROPIC_API_KEY="your-api-key"
기본 구조 구현
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# autonomous_agent.py
import asyncio
from pathlib import Path
from claude_agent_sdk import query, ClaudeAgentOptions
async def run_initializer_agent(project_dir: Path):
"""첫 세션: 환경 설정 및 feature_list.json 생성"""
# 프롬프트 로드
with open('prompts/initializer_prompt.md') as f:
system_prompt = f.read()
options = ClaudeAgentOptions(
cwd=str(project_dir),
system_prompt=system_prompt,
allowed_tools=["Read", "Write", "Bash", "Glob"],
permission_mode='acceptEdits',
model='claude-sonnet-4-20250514'
)
async for message in query(
prompt="app_spec.txt를 읽고 프로젝트를 초기화하세요.",
options=options
):
if hasattr(message, 'result'):
print(f"Initializer: {message.result}")
async def run_coding_agent(project_dir: Path):
"""이후 세션: 기능 구현"""
with open('prompts/coding_prompt.md') as f:
system_prompt = f.read()
options = ClaudeAgentOptions(
cwd=str(project_dir),
system_prompt=system_prompt,
allowed_tools=["Read", "Write", "Bash", "Glob", "WebSearch"],
permission_mode='acceptEdits'
)
async for message in query(
prompt="세션 시작 루틴을 실행하고 다음 기능을 구현하세요.",
options=options
):
if hasattr(message, 'result'):
print(f"Coding Agent: {message.result}")
async def main():
project_dir = Path("./generations/my_project")
project_dir.mkdir(parents=True, exist_ok=True)
feature_list = project_dir / "feature_list.json"
if not feature_list.exists():
print("첫 세션: Initializer Agent 실행")
await run_initializer_agent(project_dir)
else:
print("계속 세션: Coding Agent 실행")
await run_coding_agent(project_dir)
if __name__ == "__main__":
asyncio.run(main())
프롬프트 템플릿
initializer_prompt.md
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# Initializer Agent 시스템 프롬프트
당신은 장기 실행 자율 개발 프로세스의 **첫 번째 에이전트**입니다.
미래의 모든 Coding Agent가 효과적으로 작업할 수 있는 기반을 구축하는 것이 당신의 임무입니다.
## 작업 순서
1. **사양 읽기**: app_spec.txt를 신중히 읽어 요구사항을 완전히 이해하세요.
2. **feature_list.json 생성**: 200개의 상세한 E2E 테스트 케이스를 작성하세요.
- 각 기능은 구체적이고 테스트 가능해야 합니다
- 우선순위를 명확히 표시하세요
- 모든 기능은 초기에 `"passes": false`로 설정
3. **프로젝트 구조 설정**:
- 기술 스택에 맞는 디렉토리 구조 생성
- package.json 또는 requirements.txt 설정
- init.sh 스크립트 작성 (개발 서버 자동 실행)
4. **Git 초기화**:
git init
git add .
git commit -m "Initial setup: complete feature list and project structure"
5. **진행 상황 기록**: claude-progress.txt에 초기 설정 내용 기록
## 중요 원칙
- 완벽주의보다 **완성도**를 추구하세요
- 다음 에이전트를 위한 명확한 **인계 자료**를 남기세요
- 시간이 남으면 최우선 순위 기능 구현을 시작할 수 있습니다
coding_prompt.md
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# Coding Agent 시스템 프롬프트
당신은 장기 실행 프로젝트를 계속하는 Coding Agent입니다.
이것은 **새로운 컨텍스트 윈도우**이므로 이전 세션의 기억이 없습니다.
## 필수 시작 루틴
매 세션 시작 시 다음 명령을 **반드시** 실행하세요:
# 1. 위치 확인
pwd
# 2. 프로젝트 구조 파악
ls -la
# 3. 사양 확인
cat app_spec.txt
# 4. 기능 목록 (처음 50개)
cat feature_list.json | head -50
# 5. 이전 진행 상황
cat claude-progress.txt
# 6. Git 히스토리
git log --oneline -20
# 7. 남은 작업 확인
cat feature_list.json | grep '"passes": false' | wc -l
## 작업 흐름
1. **기능 선택**: feature_list.json에서 `"passes": false`인 최우선 순위 기능 선택
2. **환경 확인**:
./init.sh # 또는 npm run dev
3. **기존 테스트 실행**: 이전 세션이 버그를 남겼을 수 있습니다
4. **기능 구현**: 선택한 기능의 모든 단계 완료
5. **E2E 테스트**: Puppeteer로 실제 UI 테스트
- 마우스와 키보드로 사람처럼 테스트
- JavaScript eval 사용 금지
- 스크린샷을 verification/ 디렉토리에 저장
6. **검증 후 업데이트**:
- 테스트가 **완전히 통과**한 후에만 `"passes": true`로 변경
- 스크린샷 증거 없이는 passes 변경 금지
7. **Git 커밋**:
git add .
git commit -m "feat: [기능명] (#번호)
- 구체적 변경사항
- E2E 테스트: 통과
- 스크린샷: verification/
- feature_list.json 업데이트
"
8. **진행 기록**: claude-progress.txt 업데이트
## 중요 제약사항
- **한 세션에 한 기능만**: 욕심내지 마세요
- **검증 없이 passes 변경 금지**: 반드시 스크린샷 증거 필요
- **명확한 커밋 메시지**: 다음 에이전트를 위해
- **프로덕션 품질**: 임시방편 금지
## 목표
200+ 테스트가 모두 통과하는 프로덕션 품질의 애플리케이션
모범 사례
1. 작업 범위 관리
좋은 예시:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"id": 5,
"title": "로그인 폼 유효성 검사",
"description": "이메일 형식과 비밀번호 길이 검증",
"steps": [
"이메일 정규식 검증 구현",
"비밀번호 8자 이상 확인",
"에러 메시지 표시"
],
"acceptance_criteria": [
"잘못된 이메일 형식 입력 시 에러 표시",
"8자 미만 비밀번호 입력 시 에러 표시",
"올바른 입력 시 에러 없음"
]
}
나쁜 예시:
1
2
3
4
5
6
{
"id": 5,
"title": "사용자 관리 시스템",
"description": "전체 사용자 관리 기능"
// 너무 광범위함!
}
2. 테스트 자동화
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
// tests/e2e-automation.js
const { chromium } = require('playwright');
async function runFeatureTest(featureId, testScenarios) {
const browser = await chromium.launch({ headless: false });
const context = await browser.newContext({
viewport: { width: 1280, height: 720 },
recordVideo: { dir: `verification/videos/feature-${featureId}/` }
});
const page = await context.newPage();
const results = [];
for (const scenario of testScenarios) {
console.log(`Testing: ${scenario.name}`);
try {
// 시나리오 실행
await page.goto('http://localhost:3000');
await scenario.execute(page);
// 스크린샷 캡처
const screenshotPath = `verification/feature-${featureId}-${scenario.name}.png`;
await page.screenshot({
path: screenshotPath,
fullPage: true
});
// 결과 저장
results.push({
feature: featureId,
scenario: scenario.name,
passed: true,
screenshot: screenshotPath,
timestamp: new Date().toISOString()
});
console.log(`✓ ${scenario.name} passed`);
} catch (error) {
results.push({
feature: featureId,
scenario: scenario.name,
passed: false,
error: error.message,
timestamp: new Date().toISOString()
});
console.error(`✗ ${scenario.name} failed: ${error.message}`);
}
}
await context.close();
await browser.close();
// 테스트 결과 저장
const fs = require('fs');
fs.writeFileSync(
`verification/feature-${featureId}-results.json`,
JSON.stringify(results, null, 2)
);
return results.every(r => r.passed);
}
module.exports = { runFeatureTest };
3. 진행 상황 시각화
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
// tools/progress-tracker.js
const fs = require('fs');
class ProgressTracker {
constructor(featureListPath) {
this.featureListPath = featureListPath;
this.features = JSON.parse(fs.readFileSync(featureListPath, 'utf8')).features;
}
getStats() {
const total = this.features.length;
const completed = this.features.filter(f => f.passes).length;
const percentage = ((completed / total) * 100).toFixed(1);
return {
total,
completed,
remaining: total - completed,
percentage,
estimatedHoursRemaining: (total - completed) * 0.75 // 기능당 45분 가정
};
}
generateReport() {
const stats = this.getStats();
const byPriority = {
high: this.features.filter(f => !f.passes && f.priority === 'high').length,
medium: this.features.filter(f => !f.passes && f.priority === 'medium').length,
low: this.features.filter(f => !f.passes && f.priority === 'low').length
};
return `
# 프로젝트 진행 상황
## 전체 현황
- 총 기능: ${stats.total}
- 완료: ${stats.completed} (${stats.percentage}%)
- 남은 작업: ${stats.remaining}
- 예상 소요 시간: ${stats.estimatedHoursRemaining.toFixed(1)}시간
## 우선순위별 남은 작업
- High: ${byPriority.high}
- Medium: ${byPriority.medium}
- Low: ${byPriority.low}
## 진행 바
${'█'.repeat(Math.floor(stats.percentage / 5))}${'░'.repeat(20 - Math.floor(stats.percentage / 5))} ${stats.percentage}%
`;
}
getNextFeature() {
// 우선순위 순으로 다음 미완료 기능 반환
const priorities = ['high', 'medium', 'low'];
for (const priority of priorities) {
const next = this.features.find(
f => !f.passes && f.priority === priority
);
if (next) return next;
}
return null;
}
}
// 사용 예시
const tracker = new ProgressTracker('./feature_list.json');
console.log(tracker.generateReport());
const nextFeature = tracker.getNextFeature();
if (nextFeature) {
console.log(`\n다음 작업: Feature #${nextFeature.id} - ${nextFeature.title}`);
} else {
console.log('\n🎉 모든 기능이 완료되었습니다!');
}
4. 에러 복구 메커니즘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# tools/error_recovery.py
import json
import subprocess
from pathlib import Path
class ErrorRecovery:
def __init__(self, project_dir: Path):
self.project_dir = project_dir
self.feature_list_path = project_dir / "feature_list.json"
self.backup_dir = project_dir / ".backups"
self.backup_dir.mkdir(exist_ok=True)
def create_checkpoint(self, feature_id: int):
"""기능 구현 전 체크포인트 생성"""
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
# Git 스태시 생성
subprocess.run(
["git", "stash", "push", "-m", f"checkpoint_feature_{feature_id}"],
cwd=self.project_dir
)
# feature_list.json 백업
backup_path = self.backup_dir / f"feature_list_{timestamp}.json"
shutil.copy(self.feature_list_path, backup_path)
return timestamp
def rollback_to_checkpoint(self, timestamp: str):
"""체크포인트로 롤백"""
# Git 스태시 복원
subprocess.run(["git", "stash", "pop"], cwd=self.project_dir)
# feature_list.json 복원
backup_path = self.backup_dir / f"feature_list_{timestamp}.json"
if backup_path.exists():
shutil.copy(backup_path, self.feature_list_path)
def validate_repository_state(self):
"""저장소 상태 검증"""
issues = []
# 1. Git 저장소 확인
if not (self.project_dir / ".git").exists():
issues.append("Git 저장소가 초기화되지 않았습니다")
# 2. feature_list.json 존재 확인
if not self.feature_list_path.exists():
issues.append("feature_list.json이 없습니다")
# 3. feature_list.json 유효성 검증
try:
with open(self.feature_list_path) as f:
data = json.load(f)
if "features" not in data:
issues.append("feature_list.json에 features 키가 없습니다")
except json.JSONDecodeError:
issues.append("feature_list.json이 유효한 JSON이 아닙니다")
# 4. 변경사항 미커밋 확인
result = subprocess.run(
["git", "status", "--porcelain"],
cwd=self.project_dir,
capture_output=True,
text=True
)
if result.stdout.strip():
issues.append("커밋되지 않은 변경사항이 있습니다")
return issues
def auto_fix(self):
"""자동 수정 시도"""
issues = self.validate_repository_state()
for issue in issues:
if "Git 저장소" in issue:
subprocess.run(["git", "init"], cwd=self.project_dir)
print("✓ Git 저장소 초기화 완료")
elif "커밋되지 않은" in issue:
subprocess.run(["git", "add", "."], cwd=self.project_dir)
subprocess.run(
["git", "commit", "-m", "Auto-commit: recovery"],
cwd=self.project_dir
)
print("✓ 미커밋 변경사항 자동 커밋 완료")
remaining = self.validate_repository_state()
return len(remaining) == 0
5. 세션 간 일관성 보장
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
// tools/session-validator.js
const fs = require('fs');
const path = require('path');
class SessionValidator {
constructor(projectDir) {
this.projectDir = projectDir;
this.requiredFiles = [
'feature_list.json',
'app_spec.txt',
'claude-progress.txt',
'init.sh'
];
}
validateSessionStart() {
const errors = [];
const warnings = [];
// 1. 필수 파일 존재 확인
for (const file of this.requiredFiles) {
const filePath = path.join(this.projectDir, file);
if (!fs.existsSync(filePath)) {
errors.push(`필수 파일 누락: ${file}`);
}
}
// 2. feature_list.json 무결성 검사
try {
const featureList = JSON.parse(
fs.readFileSync(path.join(this.projectDir, 'feature_list.json'))
);
if (!Array.isArray(featureList.features)) {
errors.push('feature_list.json의 features가 배열이 아닙니다');
}
// 중복 ID 확인
const ids = featureList.features.map(f => f.id);
const duplicates = ids.filter((id, index) => ids.indexOf(id) !== index);
if (duplicates.length > 0) {
errors.push(`중복된 feature ID: ${duplicates.join(', ')}`);
}
// passes:true인데 커밋이 없는 경우
const { execSync } = require('child_process');
const gitLog = execSync('git log --oneline', {
cwd: this.projectDir
}).toString();
for (const feature of featureList.features) {
if (feature.passes && !gitLog.includes(`#${feature.id}`)) {
warnings.push(
`Feature #${feature.id}가 passes:true이지만 Git 커밋이 없습니다`
);
}
}
} catch (error) {
errors.push(`feature_list.json 파싱 실패: ${error.message}`);
}
// 3. Git 상태 확인
try {
const { execSync } = require('child_process');
const status = execSync('git status --porcelain', {
cwd: this.projectDir
}).toString();
if (status.trim()) {
warnings.push('이전 세션의 미커밋 변경사항이 있습니다');
}
} catch (error) {
errors.push('Git 저장소 확인 실패');
}
return { errors, warnings, valid: errors.length === 0 };
}
generateSessionReport() {
const validation = this.validateSessionStart();
let report = '# 세션 검증 보고서\n\n';
if (validation.valid) {
report += '✅ 모든 검증 통과\n\n';
} else {
report += '❌ 검증 실패\n\n';
report += '## 에러\n';
validation.errors.forEach(error => {
report += `- ${error}\n`;
});
report += '\n';
}
if (validation.warnings.length > 0) {
report += '## 경고\n';
validation.warnings.forEach(warning => {
report += `- ${warning}\n`;
});
}
return report;
}
}
module.exports = SessionValidator;
고급 패턴
1. 멀티 에이전트 협업
여러 전문화된 에이전트를 동시에 실행할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# multi_agent_system.py
from claude_agent_sdk import query, ClaudeAgentOptions
import asyncio
class MultiAgentOrchestrator:
def __init__(self, project_dir):
self.project_dir = project_dir
async def run_frontend_agent(self, feature):
"""프론트엔드 전문 에이전트"""
system_prompt = """
당신은 React/Vue 프론트엔드 전문 에이전트입니다.
UI/UX, 반응형 디자인, 접근성에 집중하세요.
"""
options = ClaudeAgentOptions(
cwd=str(self.project_dir),
system_prompt=system_prompt,
allowed_tools=["Read", "Write", "Bash"]
)
async for msg in query(
prompt=f"다음 UI 기능을 구현하세요: {feature['title']}",
options=options
):
yield msg
async def run_backend_agent(self, feature):
"""백엔드 전문 에이전트"""
system_prompt = """
당신은 API/데이터베이스 백엔드 전문 에이전트입니다.
성능, 보안, 확장성에 집중하세요.
"""
options = ClaudeAgentOptions(
cwd=str(self.project_dir),
system_prompt=system_prompt,
allowed_tools=["Read", "Write", "Bash"]
)
async for msg in query(
prompt=f"다음 API를 구현하세요: {feature['title']}",
options=options
):
yield msg
async def run_qa_agent(self, feature):
"""QA/테스트 전문 에이전트"""
system_prompt = """
당신은 품질 보증 전문 에이전트입니다.
E2E 테스트, 통합 테스트, 성능 테스트를 작성하세요.
"""
options = ClaudeAgentOptions(
cwd=str(self.project_dir),
system_prompt=system_prompt,
allowed_tools=["Read", "Write", "Bash"]
)
async for msg in query(
prompt=f"다음 기능을 테스트하세요: {feature['title']}",
options=options
):
yield msg
async def orchestrate_feature(self, feature):
"""기능 구현을 위해 여러 에이전트 조율"""
# 1단계: 백엔드 구현
print("🔧 백엔드 구현 중...")
async for msg in self.run_backend_agent(feature):
pass
# 2단계: 프론트엔드 구현
print("🎨 프론트엔드 구현 중...")
async for msg in self.run_frontend_agent(feature):
pass
# 3단계: QA 테스트
print("🧪 테스트 실행 중...")
async for msg in self.run_qa_agent(feature):
pass
print(f"✅ Feature #{feature['id']} 완료")
2. 지속적 학습 시스템
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# learning_system.py
import json
from pathlib import Path
from collections import defaultdict
class LearningSystem:
def __init__(self, project_dir):
self.project_dir = Path(project_dir)
self.knowledge_base = self.project_dir / "agent_knowledge.json"
self.load_knowledge()
def load_knowledge(self):
"""축적된 지식 로드"""
if self.knowledge_base.exists():
with open(self.knowledge_base) as f:
self.knowledge = json.load(f)
else:
self.knowledge = {
"common_patterns": {},
"error_solutions": {},
"best_practices": {},
"time_estimates": defaultdict(list)
}
def record_feature_completion(self, feature_id, time_spent, issues_encountered):
"""기능 완료 후 학습"""
feature_type = self.classify_feature(feature_id)
# 시간 예측 개선
self.knowledge["time_estimates"][feature_type].append(time_spent)
# 발생한 문제와 해결책 기록
for issue in issues_encountered:
key = issue['error_type']
if key not in self.knowledge["error_solutions"]:
self.knowledge["error_solutions"][key] = []
self.knowledge["error_solutions"][key].append({
"solution": issue['solution'],
"context": issue['context'],
"feature_id": feature_id
})
self.save_knowledge()
def get_time_estimate(self, feature_type):
"""과거 데이터 기반 시간 예측"""
if feature_type in self.knowledge["time_estimates"]:
times = self.knowledge["time_estimates"][feature_type]
return sum(times) / len(times)
return 45 # 기본값: 45분
def get_relevant_solutions(self, error_message):
"""에러 메시지에 대한 과거 해결책 검색"""
relevant = []
for error_type, solutions in self.knowledge["error_solutions"].items():
if error_type.lower() in error_message.lower():
relevant.extend(solutions)
return relevant
def save_knowledge(self):
"""지식 베이스 저장"""
with open(self.knowledge_base, 'w') as f:
json.dump(self.knowledge, f, indent=2)
3. 동적 우선순위 조정
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
// tools/priority-adjuster.js
class DynamicPriorityAdjuster {
constructor(featureListPath) {
this.featureListPath = featureListPath;
this.features = this.loadFeatures();
}
loadFeatures() {
const fs = require('fs');
const data = JSON.parse(fs.readFileSync(this.featureListPath, 'utf8'));
return data.features;
}
adjustPriorities() {
// 1. 의존성 분석
const dependencyGraph = this.buildDependencyGraph();
// 2. 차단된 기능 우선순위 상승
for (const feature of this.features) {
if (!feature.passes) {
const blockingCount = this.countBlockedFeatures(feature.id, dependencyGraph);
if (blockingCount > 5) {
feature.priority = 'high';
feature.adjustmentReason = `${blockingCount}개 기능을 차단 중`;
}
}
}
// 3. 빠른 승리(Quick Wins) 식별
for (const feature of this.features) {
if (!feature.passes && feature.estimatedMinutes < 30) {
if (feature.priority === 'low') {
feature.priority = 'medium';
feature.adjustmentReason = '빠른 승리 가능';
}
}
}
// 4. 장기 미완료 기능 우선순위 상승
const now = new Date();
for (const feature of this.features) {
if (feature.createdAt) {
const daysSinceCreation = (now - new Date(feature.createdAt)) / (1000 * 60 * 60 * 24);
if (daysSinceCreation > 7 && !feature.passes) {
feature.priority = 'high';
feature.adjustmentReason = `${Math.floor(daysSinceCreation)}일째 미완료`;
}
}
}
this.saveFeatures();
}
buildDependencyGraph() {
const graph = {};
for (const feature of this.features) {
graph[feature.id] = {
dependsOn: feature.dependsOn || [],
blockedBy: []
};
}
// 역방향 의존성 계산
for (const feature of this.features) {
if (feature.dependsOn) {
for (const depId of feature.dependsOn) {
if (graph[depId]) {
graph[depId].blockedBy.push(feature.id);
}
}
}
}
return graph;
}
countBlockedFeatures(featureId, graph) {
if (!graph[featureId]) return 0;
let count = graph[featureId].blockedBy.length;
// 재귀적으로 간접 차단된 기능도 카운트
for (const blockedId of graph[featureId].blockedBy) {
count += this.countBlockedFeatures(blockedId, graph);
}
return count;
}
saveFeatures() {
const fs = require('fs');
const data = { features: this.features };
fs.writeFileSync(
this.featureListPath,
JSON.stringify(data, null, 2)
);
}
}
// 사용 예시
const adjuster = new DynamicPriorityAdjuster('./feature_list.json');
adjuster.adjustPriorities();
console.log('우선순위 조정 완료');
문제 해결 가이드
일반적인 문제와 해결책
문제 1: 에이전트가 이전 작업을 기억하지 못함
증상: 매 세션마다 같은 작업을 반복하거나 이전에 완료한 기능을 다시 시도합니다.
해결책:
- claude-progress.txt가 제대로 업데이트되고 있는지 확인
- 세션 시작 루틴이 실행되는지 확인
- Git 커밋 메시지가 충분히 상세한지 검토
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 진단 스크립트
echo "=== 메모리 시스템 진단 ==="
# 1. 진행 로그 확인
echo "진행 로그 최신 항목:"
tail -20 claude-progress.txt
# 2. Git 히스토리 확인
echo -e "\nGit 커밋 히스토리:"
git log --oneline -10
# 3. feature_list.json 상태
echo -e "\n완료된 기능 수:"
cat feature_list.json | grep '"passes": true' | wc -l
echo -e "\n미완료 기능 수:"
cat feature_list.json | grep '"passes": false' | wc -l
문제 2: 테스트가 통과하지 않음
증상: E2E 테스트가 계속 실패하거나 타임아웃이 발생합니다.
해결책:
- 개발 서버가 제대로 실행 중인지 확인
- 테스트 대기 시간을 충분히 설정
- 헤드리스 모드를 끄고 실제 브라우저에서 확인
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// tests/debug-test.js
const puppeteer = require('puppeteer');
async function debugTest() {
// 헤드리스 모드 끄기
const browser = await puppeteer.launch({
headless: false,
slowMo: 250, // 동작을 천천히
devtools: true // DevTools 자동 열기
});
const page = await browser.newPage();
// 콘솔 메시지 캡처
page.on('console', msg => console.log('PAGE LOG:', msg.text()));
// 에러 캡처
page.on('pageerror', error => console.log('PAGE ERROR:', error.message));
try {
await page.goto('http://localhost:3000', {
waitUntil: 'networkidle2',
timeout: 30000
});
// 단계별 스크린샷
await page.screenshot({ path: 'debug-1-initial.png' });
// 요소가 나타날 때까지 대기
await page.waitForSelector('#login-form', { timeout: 10000 });
await page.screenshot({ path: 'debug-2-form-visible.png' });
// 입력
await page.type('#email', 'test@example.com');
await page.screenshot({ path: 'debug-3-email-entered.png' });
await page.type('#password', 'password123');
await page.screenshot({ path: 'debug-4-password-entered.png' });
// 제출
await page.click('button[type="submit"]');
await page.waitForNavigation({ timeout: 10000 });
await page.screenshot({ path: 'debug-5-after-submit.png' });
console.log('✓ 테스트 성공');
} catch (error) {
console.error('✗ 테스트 실패:', error.message);
await page.screenshot({ path: 'debug-error.png' });
}
// 브라우저를 자동으로 닫지 않음
// await browser.close();
}
debugTest();
문제 3: 에이전트가 feature_list.json을 임의로 수정함
증상: 테스트 없이 passes: true로 변경하거나 기능을 임의로 추가/삭제합니다.
해결책:
- 시스템 프롬프트에 명확한 제약사항 추가
- Git 훅으로 무단 수정 감지
- 검증 스크립트 자동 실행
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#!/bin/bash
# .git/hooks/pre-commit
echo "=== feature_list.json 검증 중 ==="
# Python으로 검증 스크립트 실행
python3 << 'EOF'
import json
import sys
import subprocess
# feature_list.json 로드
with open('feature_list.json') as f:
data = json.load(f)
# passes: true인 기능 확인
for feature in data['features']:
if feature.get('passes'):
feature_id = feature['id']
# 해당 기능의 커밋이 있는지 확인
result = subprocess.run(
['git', 'log', '--all', '--grep', f'#{feature_id}'],
capture_output=True,
text=True
)
if not result.stdout.strip():
print(f"❌ Feature #{feature_id}가 passes:true이지만 커밋이 없습니다!")
sys.exit(1)
# 스크린샷이 있는지 확인
screenshot_path = f'verification/feature-{feature_id}-*.png'
result = subprocess.run(
['ls', screenshot_path],
capture_output=True,
shell=True
)
if result.returncode != 0:
print(f"❌ Feature #{feature_id}의 스크린샷이 없습니다!")
sys.exit(1)
print("✓ feature_list.json 검증 통과")
EOF
if [ $? -ne 0 ]; then
echo "검증 실패: 커밋을 중단합니다"
exit 1
fi
echo "✓ 사전 커밋 검증 완료"
문제 4: 세션 타임아웃
증상: 긴 작업 중 세션이 만료됩니다.
해결책:
- 작업을 더 작은 단위로 쪼개기
- 주기적으로 중간 커밋 생성
- 체크포인트 시스템 구현
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# tools/checkpoint_system.py
import time
from datetime import datetime
class CheckpointSystem:
def __init__(self, project_dir):
self.project_dir = project_dir
self.start_time = time.time()
self.checkpoint_interval = 20 * 60 # 20분마다
self.last_checkpoint = self.start_time
def should_checkpoint(self):
"""체크포인트 생성 시점인지 확인"""
elapsed = time.time() - self.last_checkpoint
return elapsed >= self.checkpoint_interval
def create_checkpoint(self, message=""):
"""중간 체크포인트 생성"""
if not self.should_checkpoint():
return False
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
# Git 스태시 생성
subprocess.run(
["git", "add", "."],
cwd=self.project_dir
)
subprocess.run(
["git", "commit", "-m", f"checkpoint: {timestamp} - {message}"],
cwd=self.project_dir
)
self.last_checkpoint = time.time()
print(f"✓ 체크포인트 생성: {timestamp}")
return True
def get_session_duration(self):
"""세션 지속 시간 반환"""
elapsed = time.time() - self.start_time
return {
'minutes': elapsed / 60,
'remaining': (60 - (elapsed / 60)) # 60분 세션 가정
}
성능 최적화
1. 병렬 작업 처리
독립적인 기능들을 동시에 처리하여 전체 완료 시간을 단축할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# parallel_execution.py
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions
class ParallelExecutor:
def __init__(self, project_dir):
self.project_dir = project_dir
self.max_concurrent = 3 # 동시 실행 최대 수
def identify_independent_features(self, features):
"""의존성이 없는 독립적인 기능 식별"""
independent = []
for feature in features:
if not feature.get('passes'):
# 의존성 확인
depends_on = feature.get('dependsOn', [])
all_deps_complete = all(
self.is_feature_complete(dep_id, features)
for dep_id in depends_on
)
if all_deps_complete:
independent.append(feature)
return independent
def is_feature_complete(self, feature_id, features):
"""기능이 완료되었는지 확인"""
for f in features:
if f['id'] == feature_id:
return f.get('passes', False)
return False
async def execute_feature(self, feature):
"""단일 기능 실행"""
options = ClaudeAgentOptions(
cwd=str(self.project_dir),
system_prompt=self.get_coding_prompt(),
allowed_tools=["Read", "Write", "Bash", "Glob"]
)
print(f"🔄 시작: Feature #{feature['id']} - {feature['title']}")
async for message in query(
prompt=f"Feature #{feature['id']}를 구현하세요: {feature['description']}",
options=options
):
if hasattr(message, 'result'):
print(f"✓ 완료: Feature #{feature['id']}")
return True
return False
async def execute_parallel(self, features):
"""여러 기능을 병렬로 실행"""
independent = self.identify_independent_features(features)
# 배치 단위로 처리
for i in range(0, len(independent), self.max_concurrent):
batch = independent[i:i + self.max_concurrent]
print(f"\n=== 배치 {i // self.max_concurrent + 1} 실행 ===")
print(f"동시 처리: {[f['id'] for f in batch]}")
# 병렬 실행
tasks = [self.execute_feature(f) for f in batch]
results = await asyncio.gather(*tasks, return_exceptions=True)
# 결과 확인
for feature, result in zip(batch, results):
if isinstance(result, Exception):
print(f"✗ 실패: Feature #{feature['id']} - {result}")
elif result:
print(f"✓ 성공: Feature #{feature['id']}")
2. 캐싱 시스템
반복적인 작업을 캐시하여 속도를 향상시킵니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
// tools/cache-system.js
const fs = require('fs');
const crypto = require('crypto');
const path = require('path');
class CacheSystem {
constructor(cacheDir = '.cache') {
this.cacheDir = cacheDir;
this.ensureCacheDir();
}
ensureCacheDir() {
if (!fs.existsSync(this.cacheDir)) {
fs.mkdirSync(this.cacheDir, { recursive: true });
}
}
generateKey(input) {
// 입력값으로 캐시 키 생성
return crypto
.createHash('sha256')
.update(JSON.stringify(input))
.digest('hex');
}
get(key) {
const cachePath = path.join(this.cacheDir, `${key}.json`);
if (fs.existsSync(cachePath)) {
const cached = JSON.parse(fs.readFileSync(cachePath, 'utf8'));
// 만료 시간 확인 (24시간)
const age = Date.now() - cached.timestamp;
if (age < 24 * 60 * 60 * 1000) {
console.log(`✓ 캐시 히트: ${key}`);
return cached.data;
}
}
console.log(`✗ 캐시 미스: ${key}`);
return null;
}
set(key, data) {
const cachePath = path.join(this.cacheDir, `${key}.json`);
const cached = {
timestamp: Date.now(),
data: data
};
fs.writeFileSync(cachePath, JSON.stringify(cached, null, 2));
console.log(`✓ 캐시 저장: ${key}`);
}
// 특정 작업을 캐시와 함께 실행
async cached(key, operation) {
const cacheKey = this.generateKey(key);
const cached = this.get(cacheKey);
if (cached !== null) {
return cached;
}
const result = await operation();
this.set(cacheKey, result);
return result;
}
clear() {
// 캐시 디렉토리 전체 삭제
if (fs.existsSync(this.cacheDir)) {
fs.rmSync(this.cacheDir, { recursive: true });
this.ensureCacheDir();
console.log('✓ 캐시 클리어 완료');
}
}
}
// 사용 예시
const cache = new CacheSystem();
// 무거운 테스트를 캐시
async function runHeavyTest(featureId) {
return await cache.cached(
{ type: 'test', featureId },
async () => {
console.log('실제 테스트 실행 중...');
// 실제 테스트 로직
return { passed: true, duration: 45 };
}
);
}
module.exports = CacheSystem;
3. 점진적 테스트
모든 테스트를 매번 실행하는 대신 변경된 부분만 테스트합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
// tools/incremental-testing.js
const { execSync } = require('child_process');
const fs = require('fs');
class IncrementalTesting {
constructor(projectDir) {
this.projectDir = projectDir;
}
getChangedFiles() {
// Git으로 변경된 파일 목록 가져오기
try {
const output = execSync(
'git diff --name-only HEAD',
{ cwd: this.projectDir }
).toString();
return output.split('\n').filter(Boolean);
} catch (error) {
return [];
}
}
identifyAffectedTests(changedFiles) {
const affectedTests = new Set();
// 파일 변경에 따른 테스트 매핑
const testMapping = {
'src/components/Login': ['tests/e2e/login.test.js', 'tests/unit/login.test.js'],
'src/api/auth': ['tests/api/auth.test.js'],
'src/components/': ['tests/e2e/ui.test.js']
};
for (const file of changedFiles) {
for (const [pattern, tests] of Object.entries(testMapping)) {
if (file.includes(pattern)) {
tests.forEach(test => affectedTests.add(test));
}
}
}
return Array.from(affectedTests);
}
runIncrementalTests() {
const changedFiles = this.getChangedFiles();
if (changedFiles.length === 0) {
console.log('변경사항 없음 - 모든 테스트 스킵');
return true;
}
console.log(`변경된 파일: ${changedFiles.length}개`);
const affectedTests = this.identifyAffectedTests(changedFiles);
if (affectedTests.length === 0) {
console.log('영향받는 테스트 없음');
return true;
}
console.log(`실행할 테스트: ${affectedTests.length}개`);
for (const test of affectedTests) {
console.log(`실행 중: ${test}`);
try {
execSync(`npm test ${test}`, {
cwd: this.projectDir,
stdio: 'inherit'
});
} catch (error) {
console.error(`✗ 실패: ${test}`);
return false;
}
}
console.log('✓ 모든 증분 테스트 통과');
return true;
}
}
module.exports = IncrementalTesting;
보안 및 권한 관리
1. 안전한 명령어 실행
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
// tools/safe-executor.js
class SafeCommandExecutor {
constructor() {
// 허용된 명령어 화이트리스트
this.allowedCommands = [
'npm', 'node', 'git', 'cat', 'ls', 'pwd',
'mkdir', 'touch', 'cp', 'mv', 'rm'
];
// 금지된 패턴
this.blockedPatterns = [
/rm\s+-rf\s+\//, // rm -rf /
/sudo/, // sudo 명령
/chmod\s+777/, // 과도한 권한
/eval\(/, // eval 함수
/>\s*\/dev\//, // 시스템 디바이스 접근
/curl.*\|\s*bash/, // 파이프라인 실행
];
}
isCommandSafe(command) {
// 기본 명령어 추출
const baseCommand = command.trim().split(/\s+/)[0];
// 1. 화이트리스트 확인
if (!this.allowedCommands.includes(baseCommand)) {
return {
safe: false,
reason: `명령어 '${baseCommand}'는 허용되지 않습니다`
};
}
// 2. 위험한 패턴 확인
for (const pattern of this.blockedPatterns) {
if (pattern.test(command)) {
return {
safe: false,
reason: `위험한 패턴 감지: ${pattern}`
};
}
}
// 3. 파일 시스템 접근 범위 확인
if (command.includes('..')) {
return {
safe: false,
reason: '상위 디렉토리 접근 금지'
};
}
return { safe: true };
}
execute(command, options = {}) {
const safety = this.isCommandSafe(command);
if (!safety.safe) {
console.error(`❌ 명령어 차단: ${safety.reason}`);
console.error(`차단된 명령어: ${command}`);
throw new Error(safety.reason);
}
console.log(`✓ 안전한 명령어 실행: ${command}`);
const { execSync } = require('child_process');
return execSync(command, {
...options,
timeout: 30000, // 30초 타임아웃
maxBuffer: 10 * 1024 * 1024 // 10MB 버퍼
});
}
}
module.exports = SafeCommandExecutor;
2. 파일 접근 제어
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# tools/file_access_control.py
import os
from pathlib import Path
class FileAccessControl:
def __init__(self, project_root):
self.project_root = Path(project_root).resolve()
# 읽기 전용 파일/디렉토리
self.readonly_paths = {
'.git/config',
'.claude_settings.json',
'app_spec.txt'
}
# 접근 금지 디렉토리
self.forbidden_dirs = {
'/etc',
'/usr',
'/bin',
os.path.expanduser('~/.ssh')
}
def is_path_safe(self, file_path):
"""경로가 안전한지 확인"""
try:
resolved = Path(file_path).resolve()
# 1. 프로젝트 디렉토리 내부인지 확인
if not str(resolved).startswith(str(self.project_root)):
return False, "프로젝트 디렉토리 외부 접근 금지"
# 2. 금지된 디렉토리 확인
for forbidden in self.forbidden_dirs:
if str(resolved).startswith(forbidden):
return False, f"접근 금지 디렉토리: {forbidden}"
return True, "안전한 경로"
except Exception as e:
return False, f"경로 검증 실패: {str(e)}"
def can_write(self, file_path):
"""쓰기 가능한지 확인"""
rel_path = os.path.relpath(file_path, self.project_root)
if rel_path in self.readonly_paths:
return False, f"읽기 전용 파일: {rel_path}"
safe, message = self.is_path_safe(file_path)
return safe, message
def can_read(self, file_path):
"""읽기 가능한지 확인"""
return self.is_path_safe(file_path)
def safe_read(self, file_path):
"""안전하게 파일 읽기"""
can_read, message = self.can_read(file_path)
if not can_read:
raise PermissionError(message)
with open(file_path, 'r') as f:
return f.read()
def safe_write(self, file_path, content):
"""안전하게 파일 쓰기"""
can_write, message = self.can_write(file_path)
if not can_write:
raise PermissionError(message)
# 백업 생성
if os.path.exists(file_path):
backup_path = f"{file_path}.backup"
import shutil
shutil.copy2(file_path, backup_path)
with open(file_path, 'w') as f:
f.write(content)
모니터링 및 디버깅
1. 실시간 진행 상황 대시보드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
// tools/dashboard.js
const blessed = require('blessed');
const contrib = require('blessed-contrib');
class ProgressDashboard {
constructor(featureListPath) {
this.featureListPath = featureListPath;
this.screen = blessed.screen();
this.setupUI();
this.startUpdates();
}
setupUI() {
const grid = new contrib.grid({ rows: 12, cols: 12, screen: this.screen });
// 전체 진행률 게이지
this.progressGauge = grid.set(0, 0, 2, 6, contrib.gauge, {
label: '전체 진행률',
stroke: 'green',
fill: 'white'
});
// 우선순위별 현황
this.priorityDonut = grid.set(0, 6, 4, 6, contrib.donut, {
label: '우선순위별 남은 작업',
radius: 8,
arcWidth: 3,
remainColor: 'black',
yPadding: 2
});
// 최근 완료 기능 로그
this.logBox = grid.set(4, 0, 8, 12, contrib.log, {
fg: 'green',
selectedFg: 'green',
label: '최근 활동'
});
// 남은 작업 목록
this.todoTable = grid.set(2, 0, 2, 6, contrib.table, {
keys: true,
fg: 'white',
selectedFg: 'white',
selectedBg: 'blue',
interactive: false,
label: '다음 작업',
width: '50%',
height: '30%',
columnSpacing: 3,
columnWidth: [4, 30, 10]
});
this.screen.key(['escape', 'q', 'C-c'], () => {
return process.exit(0);
});
this.screen.render();
}
update() {
const fs = require('fs');
const data = JSON.parse(fs.readFileSync(this.featureListPath, 'utf8'));
const features = data.features;
// 진행률 계산
const total = features.length;
const completed = features.filter(f => f.passes).length;
const percentage = Math.round((completed / total) * 100);
this.progressGauge.setPercent(percentage);
// 우선순위별 현황
const remaining = features.filter(f => !f.passes);
const byPriority = {
high: remaining.filter(f => f.priority === 'high').length,
medium: remaining.filter(f => f.priority === 'medium').length,
low: remaining.filter(f => f.priority === 'low').length
};
this.priorityDonut.setData([
{ percent: byPriority.high / remaining.length, label: 'High', color: 'red' },
{ percent: byPriority.medium / remaining.length, label: 'Medium', color: 'yellow' },
{ percent: byPriority.low / remaining.length, label: 'Low', color: 'green' }
]);
// 다음 작업 목록
const nextFeatures = remaining.slice(0, 5);
this.todoTable.setData({
headers: ['ID', 'Title', 'Priority'],
data: nextFeatures.map(f => [
f.id.toString(),
f.title.substring(0, 28),
f.priority
])
});
// Git 로그 읽기
const { execSync } = require('child_process');
try {
const gitLog = execSync('git log --oneline -10', { encoding: 'utf8' });
const lines = gitLog.split('\n').filter(Boolean);
lines.forEach(line => this.logBox.log(line));
} catch (error) {
// Git 히스토리가 없을 수 있음
}
this.screen.render();
}
startUpdates() {
this.update();
setInterval(() => this.update(), 5000); // 5초마다 업데이트
}
}
// 사용 예시
const dashboard = new ProgressDashboard('./feature_list.json');
2. 상세 로깅 시스템
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# tools/detailed_logger.py
import logging
import json
from datetime import datetime
from pathlib import Path
class DetailedLogger:
def __init__(self, project_dir):
self.project_dir = Path(project_dir)
self.log_dir = self.project_dir / "logs"
self.log_dir.mkdir(exist_ok=True)
# 로그 파일 설정
self.session_log = self.log_dir / f"session_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log"
# 로거 설정
self.logger = logging.getLogger('AgentLogger')
self.logger.setLevel(logging.DEBUG)
# 파일 핸들러
fh = logging.FileHandler(self.session_log)
fh.setLevel(logging.DEBUG)
# 콘솔 핸들러
ch = logging.StreamHandler()
ch.setLevel(logging.INFO)
# 포맷터
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
fh.setFormatter(formatter)
ch.setFormatter(formatter)
self.logger.addHandler(fh)
self.logger.addHandler(ch)
def log_feature_start(self, feature):
"""기능 시작 로그"""
self.logger.info(f"========== Feature #{feature['id']} 시작 ==========")
self.logger.info(f"Title: {feature['title']}")
self.logger.info(f"Description: {feature['description']}")
self.logger.info(f"Priority: {feature.get('priority', 'medium')}")
def log_feature_complete(self, feature, duration_minutes):
"""기능 완료 로그"""
self.logger.info(f"========== Feature #{feature['id']} 완료 ==========")
self.logger.info(f"Duration: {duration_minutes:.1f} minutes")
# JSON 로그도 저장
completion_data = {
'feature_id': feature['id'],
'title': feature['title'],
'completed_at': datetime.now().isoformat(),
'duration_minutes': duration_minutes
}
json_log = self.log_dir / "completions.jsonl"
with open(json_log, 'a') as f:
f.write(json.dumps(completion_data) + '\n')
def log_test_result(self, test_name, passed, error=None):
"""테스트 결과 로그"""
if passed:
self.logger.info(f"✓ Test passed: {test_name}")
else:
self.logger.error(f"✗ Test failed: {test_name}")
if error:
self.logger.error(f"Error: {error}")
def log_command(self, command, output, return_code):
"""명령어 실행 로그"""
self.logger.debug(f"Command: {command}")
self.logger.debug(f"Return code: {return_code}")
if output:
self.logger.debug(f"Output: {output[:500]}") # 처음 500자만
def log_error(self, error_message, context=None):
"""에러 로그"""
self.logger.error(f"ERROR: {error_message}")
if context:
self.logger.error(f"Context: {json.dumps(context, indent=2)}")
def generate_session_summary(self):
"""세션 요약 생성"""
summary = {
'session_start': self.session_log.stat().st_ctime,
'session_end': datetime.now().timestamp(),
'log_file': str(self.session_log)
}
summary_file = self.log_dir / f"summary_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
with open(summary_file, 'w') as f:
json.dump(summary, f, indent=2)
return summary
배포 및 프로덕션
1. 최종 검증 체크리스트
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
#!/bin/bash
# final-validation.sh
echo "========== 최종 배포 전 검증 =========="
# 색상 정의
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
PASSED=0
FAILED=0
check_pass() {
echo -e "${GREEN}✓${NC} $1"
((PASSED++))
}
check_fail() {
echo -e "${RED}✗${NC} $1"
((FAILED++))
}
check_warn() {
echo -e "${YELLOW}⚠${NC} $1"
}
# 1. 모든 기능 완료 확인
echo -e "\n1. 기능 완료 상태 확인"
INCOMPLETE=$(cat feature_list.json | grep '"passes": false' | wc -l)
if [ $INCOMPLETE -eq 0 ]; then
check_pass "모든 기능 완료 ($INCOMPLETE개 미완료)"
else
check_fail "$INCOMPLETE개 기능 미완료"
fi
# 2. 테스트 실행
echo -e "\n2. 전체 테스트 실행"
if npm test > /dev/null 2>&1; then
check_pass "모든 테스트 통과"
else
check_fail "일부 테스트 실패"
fi
# 3. 코드 품질 검사
echo -e "\n3. 코드 품질 검사"
if command -v eslint &> /dev/null; then
if npm run lint > /dev/null 2>&1; then
check_pass "Lint 검사 통과"
else
check_warn "Lint 경고 있음"
fi
else
check_warn "ESLint 미설치"
fi
# 4. 보안 취약점 검사
echo -e "\n4. 보안 취약점 검사"
if npm audit --audit-level=high 2>&1 | grep -q "found 0 vulnerabilities"; then
check_pass "심각한 보안 취약점 없음"
else
check_fail "보안 취약점 발견"
fi
# 5. 빌드 테스트
echo -e "\n5. 프로덕션 빌드"
if npm run build > /dev/null 2>&1; then
check_pass "빌드 성공"
else
check_fail "빌드 실패"
fi
# 6. Git 상태 확인
echo -e "\n6. Git 상태 확인"
if [ -z "$(git status --porcelain)" ]; then
check_pass "모든 변경사항 커밋됨"
else
check_fail "미커밋 변경사항 있음"
fi
# 7. 문서 확인
echo -e "\n7. 문서 완성도 확인"
if [ -f "README.md" ] && [ -s "README.md" ]; then
check_pass "README.md 존재"
else
check_warn "README.md 업데이트 필요"
fi
# 최종 결과
echo -e "\n========== 검증 결과 =========="
echo -e "${GREEN}통과: $PASSED${NC}"
echo -e "${RED}실패: $FAILED${NC}"
if [ $FAILED -eq 0 ]; then
echo -e "\n${GREEN}🎉 배포 가능!${NC}"
exit 0
else
echo -e "\n${RED}❌ 문제 해결 후 재시도${NC}"
exit 1
fi
2. 프로덕션 배포 가이드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# .github/workflows/deploy.yml
name: Deploy to Production
on:
push:
branches: [ main ]
workflow_dispatch:
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Validate feature list
run: |
INCOMPLETE=$(cat feature_list.json | grep '"passes": false' | wc -l)
if [ $INCOMPLETE -ne 0 ]; then
echo "❌ $INCOMPLETE features incomplete"
exit 1
fi
- name: Build
run: npm run build
- name: Run final validation
run: bash final-validation.sh
deploy:
needs: validate
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Deploy to production
run: |
# 배포 스크립트 실행
echo "Deploying to production..."
# 실제 배포 명령어
결론
장기 실행 AI 에이전트의 성공은 프롬프트 엔지니어링이 아니라 하네스 설계에 달려 있습니다. 이 가이드에서 다룬 핵심 원칙을 정리하면:
핵심 원칙
- Git은 메모리: 상세한 커밋 메시지로 세션 간 컨텍스트 연결
- JSON은 구조: feature_list.json으로 범위를 명확히 정의하고 임의 수정 방지
- 테스트는 검증: Puppeteer E2E 테스트와 스크린샷으로 실제 작동 확인
- 한 세션 = 한 기능: 작업을 관리 가능한 단위로 분할
- 명확한 인계: 다음 에이전트가 쉽게 이해할 수 있도록 문서화
성공을 위한 체크리스트
프로젝트 시작 시
- app_spec.txt 작성 (명확한 요구사항)
- Initializer Agent로 feature_list.json 생성
- Git 저장소 초기화
- init.sh 스크립트 설정
- 보안 설정 (.claude_settings.json)
매 세션 시작 시
- 세션 시작 루틴 실행 (위치 확인, 로그 읽기, Git 히스토리)
- 다음 미완료 기능 식별
- 개발 서버 실행 및 기존 테스트 확인
기능 구현 중
- 단계별 구현 (feature의 steps 따르기)
- 주기적 중간 커밋 (긴 작업 시)
- 실시간 테스트로 문제 조기 발견
기능 완료 시
- E2E 테스트 작성 및 실행
- 스크린샷 저장 (verification/ 디렉토리)
- 모든 테스트 통과 확인
- feature_list.json 업데이트 (passes: true)
- 상세한 Git 커밋
- claude-progress.txt 업데이트
프로젝트 완료 시
- 모든 기능 passes: true 확인
- 전체 테스트 스위트 실행
- 최종 검증 스크립트 실행
- 프로덕션 빌드 테스트
- 문서 완성도 확인
확장 가능성
이 패턴은 웹 애플리케이션뿐만 아니라 다음 분야에도 적용 가능합니다:
연구 프로젝트
- 문헌 조사 → feature_list.json의 “논문”으로 매핑
- 실험 실행 → Git으로 실험 결과 추적
- 데이터 분석 → 단계별 분석 파이프라인
자동화 시스템
- 작업 체크리스트 → feature_list.json
- 실행 로그 → claude-progress.txt
- 성공 검증 → 자동화 테스트
데이터 파이프라인
- ETL 단계 → 개별 기능으로 분할
- 데이터 품질 검사 → 테스트로 구현
- 버전 관리 → Git으로 데이터 스키마 추적
추가 리소스
공식 문서
- Anthropic 공식 블로그: https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents
- Claude Quickstarts: https://github.com/anthropics/claude-quickstarts/tree/main/autonomous-coding
- Claude SDK 문서: https://docs.anthropic.com
도구 및 라이브러리
- Puppeteer: https://pptr.dev/
- Playwright: https://playwright.dev/
- Jest: https://jestjs.io/
- Cypress: https://www.cypress.io/
커뮤니티
- Anthropic Discord: https://discord.gg/anthropic
- GitHub Discussions: https://github.com/anthropics/claude-quickstarts/discussions
부록 A: 전체 프로젝트 템플릿
디렉토리 구조
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
my_autonomous_project/
├── .git/ # Git 저장소
├── .cache/ # 캐시 디렉토리
├── .backups/ # 백업 파일
├── logs/ # 상세 로그
│ ├── session_20240115_140000.log
│ ├── completions.jsonl
│ └── summary_20240115_180000.json
├── verification/ # 테스트 증거
│ ├── feature-1-login.png
│ ├── feature-2-signup.png
│ └── videos/
├── tests/ # 테스트 스위트
│ ├── e2e/
│ │ ├── login.test.js
│ │ └── signup.test.js
│ ├── unit/
│ └── integration/
├── tools/ # 유틸리티 스크립트
│ ├── progress-tracker.js
│ ├── session-validator.js
│ ├── cache-system.js
│ └── safe-executor.js
├── prompts/ # 에이전트 프롬프트
│ ├── initializer_prompt.md
│ └── coding_prompt.md
├── src/ # 애플리케이션 코드
│ ├── components/
│ ├── api/
│ └── utils/
├── app_spec.txt # 애플리케이션 사양
├── feature_list.json # 기능 체크리스트
├── claude-progress.txt # 진행 로그
├── init.sh # 환경 설정 스크립트
├── .claude_settings.json # 보안 설정
├── package.json # 의존성
└── README.md # 프로젝트 문서
package.json 템플릿
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
{
"name": "autonomous-project",
"version": "1.0.0",
"description": "AI-built application",
"scripts": {
"dev": "vite",
"build": "vite build",
"test": "jest",
"test:e2e": "jest --testPathPattern=e2e",
"test:unit": "jest --testPathPattern=unit",
"lint": "eslint src/",
"validate": "bash tools/final-validation.sh",
"progress": "node tools/progress-tracker.js",
"dashboard": "node tools/dashboard.js"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@types/jest": "^29.5.0",
"eslint": "^8.40.0",
"jest": "^29.5.0",
"puppeteer": "^21.0.0",
"playwright": "^1.40.0",
"vite": "^5.0.0",
"blessed": "^0.1.81",
"blessed-contrib": "^4.11.0"
}
}
.claude_settings.json 템플릿
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
{
"version": "1.0",
"security": {
"allowed_commands": [
"npm", "node", "git", "cat", "ls", "pwd",
"mkdir", "touch", "cp", "mv", "rm", "echo"
],
"allowed_directories": [
"src/", "tests/", "public/", "verification/"
],
"readonly_files": [
"app_spec.txt",
".claude_settings.json"
],
"max_file_size_mb": 10,
"command_timeout_seconds": 30
},
"testing": {
"required_before_passes": true,
"screenshot_required": true,
"screenshot_directory": "verification/"
},
"git": {
"auto_commit": false,
"require_commit_message": true,
"min_commit_message_length": 20
},
"features": {
"max_per_session": 1,
"require_priority": true,
"allowed_priorities": ["high", "medium", "low"]
}
}
app_spec.txt 템플릿
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# 애플리케이션 사양
## 프로젝트 개요
프로젝트명: [프로젝트 이름]
목적: [프로젝트의 목적과 목표]
대상 사용자: [타겟 사용자]
## 기술 스택
- 프론트엔드: React + Vite
- 스타일링: Tailwind CSS
- 백엔드: Node.js + Express
- 데이터베이스: PostgreSQL
- 테스팅: Jest + Puppeteer
## 핵심 기능
### 1. 사용자 인증
- 이메일/비밀번호 로그인
- 회원가입 (이메일 검증 포함)
- 비밀번호 재설정
- 소셜 로그인 (Google, GitHub)
### 2. 사용자 대시보드
- 개인 프로필 관리
- 활동 히스토리 조회
- 설정 페이지
### 3. [추가 기능들...]
## 비기능적 요구사항
### 성능
- 페이지 로드 시간 < 2초
- API 응답 시간 < 500ms
- 동시 사용자 1000명 지원
### 보안
- HTTPS 필수
- JWT 인증
- XSS/CSRF 방어
- Rate limiting
### 접근성
- WCAG 2.1 AA 준수
- 키보드 네비게이션 지원
- 스크린 리더 호환
### 반응형 디자인
- 모바일 (320px~)
- 태블릿 (768px~)
- 데스크톱 (1024px~)
## 제약사항
- 예산: [예산]
- 기간: [기간]
- 팀 규모: AI 에이전트 단독
## 성공 지표
- [ ] 모든 E2E 테스트 통과
- [ ] 200+ 기능 구현 완료
- [ ] 코드 커버리지 > 80%
- [ ] 성능 벤치마크 달성
- [ ] 보안 취약점 0건
부록 B: 문제 해결 플로우차트
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
세션 시작
│
├─> 세션 시작 루틴 실행
│ ├─> pwd, ls 실행
│ ├─> app_spec.txt 읽기
│ ├─> feature_list.json 읽기
│ ├─> claude-progress.txt 읽기
│ └─> git log 확인
│
├─> 다음 기능 선택
│ ├─> passes: false 찾기
│ └─> 우선순위 확인
│
├─> 개발 환경 실행
│ ├─> ./init.sh 또는 npm run dev
│ └─> 기존 테스트 실행
│ ├─> 통과? → 계속
│ └─> 실패? → 이전 버그 수정
│
├─> 기능 구현
│ ├─> 단계별 구현
│ ├─> 중간 테스트
│ └─> 코드 리뷰
│
├─> E2E 테스트
│ ├─> Puppeteer 테스트 작성
│ ├─> 테스트 실행
│ └─> 스크린샷 저장
│ ├─> 통과? → 다음 단계
│ └─> 실패? → 디버깅
│
├─> 검증 및 커밋
│ ├─> 모든 테스트 통과 확인
│ ├─> feature_list.json 업데이트
│ ├─> Git 커밋 (상세 메시지)
│ └─> claude-progress.txt 업데이트
│
└─> 세션 종료
├─> 다음 기능 남음? → 새 세션 시작
└─> 모든 기능 완료? → 최종 검증
부록 C: 에이전트 프롬프트 라이브러리
초기화 전용 프롬프트
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# Initializer Agent - 고급 버전
당신은 장기 실행 자율 개발 시스템의 설계자입니다.
미래의 Coding Agent들이 효율적으로 작업할 수 있는 완벽한 기반을 구축하세요.
## Phase 1: 요구사항 분석 (10분)
1. app_spec.txt를 3번 읽으세요:
- 1번째: 전체 개요 파악
- 2번째: 핵심 기능 식별
- 3번째: 비기능적 요구사항 확인
2. 불명확한 사항 질문:
- 기술 스택 불명확?
- 기능 우선순위 미정의?
- 성능/보안 요구사항 누락?
## Phase 2: 아키텍처 설계 (15분)
1. 기술 스택 결정:
Frontend: [선택 + 이유]
Backend: [선택 + 이유]
Database: [선택 + 이유]
Testing: [선택 + 이유]
2. 디렉토리 구조 설계:
- 확장 가능한 구조
- 테스트 용이성
- 명확한 책임 분리
## Phase 3: Feature Breakdown (30-45분)
200+ 상세한 E2E 테스트 케이스 작성.
각 기능은 반드시 포함:
{
"id": 1,
"title": "명확하고 구체적인 제목",
"description": "구현해야 할 내용의 상세 설명",
"steps": [
"단계 1: 구체적 작업",
"단계 2: 구체적 작업"
],
"acceptance_criteria": [
"테스트 가능한 조건 1",
"테스트 가능한 조건 2"
],
"priority": "high|medium|low",
"estimatedMinutes": 45,
"dependsOn": [/* 의존 기능 ID */],
"tags": ["auth", "ui", "api"],
"passes": false
}
**중요한 분할 원칙:**
- 각 기능은 45분 이내 완료 가능해야 함
- 기능 간 의존성 명시
- 우선순위 명확히 (의존성 고려)
- 테스트 가능한 acceptance criteria
## Phase 4: 환경 설정 (10분)
1. Git 저장소:
git init
git config user.name "Claude Agent"
git config user.email "agent@anthropic.com"
2. init.sh 스크립트:
#!/bin/bash
set -e
echo "🚀 개발 환경 시작"
# 의존성 설치 (첫 실행 시)
if [ ! -d "node_modules" ]; then
npm install
fi
# 데이터베이스 마이그레이션
npm run db:migrate
# 개발 서버 시작
npm run dev &
# 서버 준비 대기
sleep 5
echo "✓ 개발 서버 실행 중: http://localhost:3000"
3. 첫 커밋:
git add .
git commit -m "Initial setup: complete project structure
- Created 200+ feature test cases in feature_list.json
- Set up development environment with [기술스택]
- Configured testing framework
- Added init.sh for automated environment setup
Project structure:
- src/: Application source code
- tests/: E2E, unit, and integration tests
- tools/: Utility scripts for validation and monitoring
- verification/: Test evidence (screenshots, videos)
Next: Feature #1 - [첫 번째 기능명]"
## Phase 5: 문서화 (5분)
README.md 생성:
# [프로젝트명]
## 현재 상태
- 총 기능: 200+
- 완료: 0
- 진행률: 0%
## 개발 시작하기
\`\`\`bash
./init.sh
\`\`\`
## 다음 작업
Feature #1: [제목]
## 에이전트 노트
- 모든 기능은 feature_list.json에 정의됨
- 각 세션은 한 기능만 구현
- 테스트 없이 passes:true 금지
## Phase 6: 검증 및 인계
1. 자체 검증:
- [ ] feature_list.json 유효성
- [ ] Git 저장소 초기화
- [ ] init.sh 실행 가능
- [ ] 모든 필수 파일 존재
2. 인계 메시지:
✓ 초기화 완료
생성된 파일:
- feature_list.json (200개 기능)
- init.sh (환경 설정)
- README.md
- .claude_settings.json
다음 Coding Agent를 위한 노트:
- Feature #1부터 시작
- 반드시 세션 시작 루틴 실행
- 테스트 통과 후에만 passes:true
첫 번째 기능: [상세 설명]
코딩 전용 프롬프트 (고급)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
# Coding Agent - Production Grade
당신은 이전 세션의 작업을 이어받는 프로페셔널 개발자입니다.
## 필수 시작 루틴 (5분, 절대 스킵 불가)
# 1. 컨텍스트 수집
pwd
ls -la
cat app_spec.txt | head -30
cat feature_list.json | head -100
tail -100 claude-progress.txt
git log --oneline -20
git diff HEAD~1
# 2. 상태 확인
node << 'EOF'
const fs = require('fs');
const data = JSON.parse(fs.readFileSync('feature_list.json'));
const total = data.features.length;
const done = data.features.filter(f => f.passes).length;
const next = data.features.find(f => !f.passes);
console.log(`진행률: ${done}/${total} (${(done/total*100).toFixed(1)}%)`);
console.log(`다음 작업: Feature #${next.id} - ${next.title}`);
console.log(`우선순위: ${next.priority}`);
console.log(`예상 시간: ${next.estimatedMinutes}분`);
EOF
# 3. 환경 실행
./init.sh
# 4. 기존 테스트 (회귀 방지)
npm test
## 기능 구현 프로세스
### Step 1: 계획 (5분)
선택한 기능을 분석하고 구현 계획 수립:
Feature #[ID]: [제목]
구현 전 체크리스트:
- [ ] Description 이해
- [ ] Steps 검토
- [ ] Acceptance criteria 확인
- [ ] 의존성 확인 (dependsOn)
- [ ] 영향받는 파일 식별
구현 계획:
1. [파일명]: [변경사항]
2. [파일명]: [변경사항]
3. 테스트: [테스트 시나리오]
### Step 2: 구현 (20-30분)
**TDD 방식 권장:**
1. 실패하는 테스트 먼저 작성
2. 테스트를 통과하는 최소 코드 작성
3. 리팩토링
**코드 품질 기준:**
- 명확한 변수/함수명
- 적절한 주석
- 에러 처리
- 타입 안전성 (TypeScript/PropTypes)
- 접근성 (a11y)
// 나쁜 예
function f(x) {
return x.map(i => i.n);
}
// 좋은 예
/**
* 사용자 목록에서 이름만 추출
* @param {Array<User>} users - 사용자 객체 배열
* @returns {Array<string>} 사용자 이름 배열
*/
function extractUserNames(users) {
return users.map(user => user.name);
}
### Step 3: E2E 테스트 (10-15분)
// tests/e2e/feature-[ID].test.js
const puppeteer = require('puppeteer');
describe(`Feature #[ID]: [제목]`, () => {
let browser, page;
beforeAll(async () => {
browser = await puppeteer.launch({
headless: process.env.CI === 'true',
slowMo: 50
});
page = await browser.newPage();
await page.setViewport({ width: 1280, height: 720 });
});
afterAll(async () => {
await browser.close();
});
test('[Acceptance Criteria 1]', async () => {
// 1. 설정
await page.goto('http://localhost:3000');
// 2. 실행
await page.click('#some-button');
// 3. 검증
const result = await page.$eval('#result', el => el.textContent);
expect(result).toBe('Expected Value');
// 4. 스크린샷 증거
await page.screenshot({
path: `verification/feature-[ID]-scenario-1.png`,
fullPage: true
});
});
// 모든 acceptance criteria에 대한 테스트...
});
### Step 4: 검증 (5분)
# 전체 테스트 스위트 실행
npm test
# 변경된 파일 확인
git status
# 코드 품질
npm run lint
### Step 5: 커밋 (5분)
git add .
git commit -m "feat: implement [기능명] (#[ID])
[구현 내용 상세 설명]
Changes:
- Created/Modified: [파일 목록]
- Added E2E tests: [테스트 파일]
- Test coverage: [커버리지]%
Testing:
- All acceptance criteria verified
- Screenshots: verification/feature-[ID]-*.png
- Manual testing: [수동 테스트 내용]
Performance:
- Page load: [시간]ms
- API response: [시간]ms
Next Feature: #[다음ID] - [다음 기능명]
"
# feature_list.json 업데이트
node << 'EOF'
const fs = require('fs');
const data = JSON.parse(fs.readFileSync('feature_list.json'));
const feature = data.features.find(f => f.id === [현재ID]);
feature.passes = true;
feature.completed_at = new Date().toISOString();
fs.writeFileSync('feature_list.json', JSON.stringify(data, null, 2));
console.log(`✓ Feature #[현재ID] marked as complete`);
EOF
git add feature_list.json
git commit --amend --no-edit
### Step 6: 진행 로그 (2분)
echo "
=== Session $(date '+%Y-%m-%d %H:%M:%S') ===
Completed: Feature #[ID] - [제목]
Duration: ~[시간]분
Status: ✓ All tests passing
Screenshots: verification/feature-[ID]-*.png
Summary:
[구현 내용 요약]
Issues encountered:
[발생한 문제와 해결책]
Next: Feature #[다음ID] - [다음 기능명]
Priority: [우선순위]
Estimated: [예상시간]분
========================================
" >> claude-progress.txt
## 엣지 케이스 처리
### 테스트 실패 시
1. 헤드리스 모드 끄고 재시도
2. 단계별 스크린샷으로 디버깅
3. 콘솔 로그 확인
4. 타임아웃 증가
5. 필요시 이전 커밋으로 롤백
### 세션 시간 부족 시
1. 중간 체크포인트 커밋:
git add .
git commit -m "wip: partial implementation of feature #[ID]
Progress:
- [완료한 부분]
Remaining:
- [남은 작업]
Note: 다음 세션에서 계속"
2. claude-progress.txt에 상세 메모
### 복잡한 기능 발견 시
1. feature_list.json에서 기능 분할:
// Feature #15를 #15a, #15b, #15c로 분할
{
"id": "15a",
"title": "[원제목] - Part 1: [세부사항]",
"description": "...",
"steps": ["..."],
"dependsOn": [/* 이전 의존성 */],
"passes": false
},
{
"id": "15b",
"title": "[원제목] - Part 2: [세부사항]",
"dependsOn": ["15a"],
...
}
## 세션 종료 전 체크리스트
- [ ] 모든 테스트 통과
- [ ] 스크린샷 저장 완료
- [ ] feature_list.json 업데이트
- [ ] Git 커밋 완료
- [ ] claude-progress.txt 업데이트
- [ ] 다음 기능 식별 완료
- [ ] 변경사항 리뷰 (git diff)
## 품질 기준
이 중 하나라도 미달이면 passes:true 금지:
- 모든 acceptance criteria 충족
- E2E 테스트 100% 통과
- 스크린샷 증거 존재
- 코드 리뷰 통과 (자체 검토)
- 성능 기준 충족
- 접근성 기준 충족
- 보안 체크 통과
맺음말
장기 실행 AI 에이전트 시스템은 단순히 AI를 오래 실행하는 것이 아니라, 구조화된 하네스를 통해 세션 간 연속성을 보장하는 것입니다. 이 가이드에서 제시한 패턴들은 Anthropic의 실전 경험에서 검증된 것이며, 며칠에서 몇 주에 걸친 복잡한 프로젝트도 안정적으로 완수할 수 있게 해줍니다.
핵심은 프롬프트가 아니라 시스템입니다. Git, JSON, 테스트라는 3가지 기둥 위에 에이전트가 작동하면, 컨텍스트 윈도우의 한계를 극복하고 진정한 자율성을 달성할 수 있습니다.
이 가이드가 여러분의 장기 실행 AI 에이전트 프로젝트 성공에 도움이 되기를 바랍니다.
문서 버전: 1.0.0
최종 업데이트: 2024-01-15
작성자: Claude (Anthropic)
라이선스: MIT
부록 D: FAQ (자주 묻는 질문)
Q1: 왜 한 세션에 한 기능만 구현해야 하나요?
A: 여러 가지 이유가 있습니다:
컨텍스트 윈도우 관리: 한 기능에 집중하면 관련 코드와 테스트만 로드하여 컨텍스트를 효율적으로 사용할 수 있습니다.
명확한 인계: 다음 세션의 에이전트가 정확히 무엇이 완료되었는지 알 수 있습니다.
롤백 용이성: 문제 발생 시 한 기능만 되돌리면 됩니다.
테스트 격리: 각 기능이 독립적으로 테스트되어 버그 추적이 쉽습니다.
단, 매우 작은 기능들(5-10분 소요)이 여러 개 있다면 관련된 기능들을 묶어서 처리할 수 있습니다.
Q2: feature_list.json의 적정 기능 개수는?
A: Anthropic의 권장사항은 200개 이상입니다. 이는 다음과 같은 이유에서입니다:
- 각 기능이 충분히 작고 구체적이어야 함 (30-60분 소요)
- 세밀한 진행 상황 추적 가능
- 테스트 커버리지 향상
그러나 프로젝트 규모에 따라 조정 가능합니다:
- 소규모 프로젝트: 50-100개
- 중규모 프로젝트: 100-200개
- 대규모 프로젝트: 200-500개
중요한 것은 개수가 아니라 각 기능의 크기와 명확성입니다.
Q3: 테스트가 계속 실패하면 어떻게 하나요?
A: 단계적 디버깅 접근:
- 헤드리스 모드 끄기
1 2 3 4
const browser = await puppeteer.launch({ headless: false, // 브라우저를 볼 수 있게 slowMo: 250 // 동작을 천천히 });
- 단계별 스크린샷
1 2
await page.screenshot({ path: 'debug-step-1.png' }); // 각 단계마다 스크린샷
- 콘솔 로그 캡처
1
page.on('console', msg => console.log('PAGE:', msg.text()));
- 대기 시간 증가
1
await page.waitForSelector('#element', { timeout: 30000 });
- 최후의 수단: 기능 분할
- 복잡한 기능을 더 작은 단위로 쪼개기
Q4: 이전 세션이 버그를 남겼을 때는?
A: 세션 시작 루틴에서 기존 테스트를 실행하는 이유가 바로 이것입니다:
1
2
3
4
5
6
7
8
9
10
11
12
# 세션 시작 시
npm test # 기존 테스트 실행
# 실패한 테스트 발견 시
git log --oneline -20 # 최근 커밋 확인
git show HEAD # 마지막 변경사항 확인
git diff HEAD~1 # 이전 버전과 비교
# 필요시 롤백
git revert HEAD
# 또는
git reset --hard HEAD~1
버그 수정 후:
1
2
3
4
5
6
7
git commit -m "fix: resolve regression in feature #X
Previous session introduced bug in [기능].
Root cause: [원인]
Solution: [해결책]
Tests: All passing now"
Q5: 세션이 중간에 끊기면?
A: 이것이 바로 하네스 시스템의 진가가 드러나는 순간입니다:
Git이 메모리 역할: 마지막 커밋까지의 작업은 안전하게 보존됩니다.
다음 세션 시작 시:
1
2
3
4
5
git status # 커밋되지 않은 변경사항 확인
# WIP(Work In Progress) 커밋이 있다면
git log -1 # 마지막 커밋 메시지 읽기
# "wip: partial implementation..." 메시지에서 진행 상황 파악
- 예방책: 주기적 체크포인트
1 2 3 4
# 20분마다 자동 체크포인트 if time_elapsed > 20 * 60: subprocess.run(['git', 'add', '.']) subprocess.run(['git', 'commit', '-m', f'checkpoint: {timestamp}'])
Q6: 여러 에이전트를 동시에 실행할 수 있나요?
A: 가능하지만 주의가 필요합니다:
안전한 병렬 실행 조건:
- 기능 간 의존성이 없음
- 서로 다른 파일을 수정함
- 각자 독립된 작업 디렉토리 사용
구현 예시:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 3개 기능을 병렬 처리
async def parallel_execution():
features = [
feature_15, # 프론트엔드
feature_16, # 백엔드 API
feature_17 # 문서화
]
# 각 에이전트에 별도 디렉토리 할당
tasks = [
run_agent(f, f'./workspace/agent-{f.id}')
for f in features
]
results = await asyncio.gather(*tasks)
주의사항:
- feature_list.json 동시 수정 방지 (락 메커니즘 필요)
- Git 충돌 가능성
- 비용 증가 (여러 API 호출)
대부분의 경우 순차 실행이 더 안전하고 효율적입니다.
Q7: 프라이빗 API 키나 시크릿은 어떻게 관리하나요?
A: 절대 코드에 하드코딩하지 마세요:
1
2
3
4
5
// ❌ 나쁜 예
const API_KEY = "sk-1234567890abcdef";
// ✅ 좋은 예
const API_KEY = process.env.API_KEY;
권장 방법:
- .env 파일 사용
1 2 3
# .env (Git에 커밋하지 않음) API_KEY=sk-1234567890abcdef DATABASE_URL=postgresql://user:pass@localhost/db
- .gitignore에 추가
1 2 3 4 5
.env .env.local *.key *.pem secrets/
- .env.example 제공
1 2 3
# .env.example (Git에 커밋) API_KEY=your_api_key_here DATABASE_URL=your_database_url_here
- 에이전트에게 명시적 지시
1
2
3
4
5
6
7
8
9
10
11
12
## 보안 규칙
절대 금지:
- API 키를 코드에 하드코딩
- .env 파일을 Git에 커밋
- 로그에 시크릿 출력
필수:
- 환경 변수 사용
- .gitignore에 시크릿 파일 추가
- .env.example 제공
Q8: 데이터베이스 마이그레이션은 어떻게 관리하나요?
A: 데이터베이스 스키마도 Git으로 버전 관리:
1
2
3
4
5
migrations/
├── 001_initial_schema.sql
├── 002_add_users_table.sql
├── 003_add_email_column.sql
└── 004_create_sessions_table.sql
마이그레이션 스크립트:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// tools/migrate.js
const fs = require('fs');
const path = require('path');
const { Pool } = require('pg');
async function runMigrations() {
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
// 마이그레이션 테이블 생성
await pool.query(`
CREATE TABLE IF NOT EXISTS migrations (
id SERIAL PRIMARY KEY,
filename VARCHAR(255) UNIQUE,
applied_at TIMESTAMP DEFAULT NOW()
)
`);
// 실행된 마이그레이션 조회
const { rows } = await pool.query(
'SELECT filename FROM migrations'
);
const applied = new Set(rows.map(r => r.filename));
// 미실행 마이그레이션 찾기
const migrationDir = path.join(__dirname, '../migrations');
const files = fs.readdirSync(migrationDir)
.filter(f => f.endsWith('.sql'))
.sort();
for (const file of files) {
if (applied.has(file)) continue;
console.log(`Running migration: ${file}`);
const sql = fs.readFileSync(
path.join(migrationDir, file),
'utf8'
);
await pool.query('BEGIN');
try {
await pool.query(sql);
await pool.query(
'INSERT INTO migrations (filename) VALUES ($1)',
[file]
);
await pool.query('COMMIT');
console.log(`✓ ${file} applied`);
} catch (error) {
await pool.query('ROLLBACK');
console.error(`✗ ${file} failed:`, error.message);
throw error;
}
}
await pool.end();
}
runMigrations().catch(console.error);
feature_list.json에 포함:
1
2
3
4
5
6
7
8
9
{
"id": 25,
"title": "데이터베이스 마이그레이션: 사용자 테이블",
"steps": [
"migrations/002_add_users_table.sql 생성",
"마이그레이션 스크립트 실행",
"테이블 생성 확인"
]
}
Q9: 에이전트가 무한 루프에 빠지면?
A: 방지 메커니즘 구현:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# tools/safety_monitor.py
import time
from collections import defaultdict
class SafetyMonitor:
def __init__(self):
self.action_history = []
self.action_counts = defaultdict(int)
self.start_time = time.time()
def record_action(self, action):
"""액션 기록"""
self.action_history.append({
'action': action,
'timestamp': time.time()
})
self.action_counts[action] += 1
def detect_loop(self, threshold=5):
"""무한 루프 감지"""
# 같은 액션이 5번 이상 반복
for action, count in self.action_counts.items():
if count >= threshold:
return True, f"Action '{action}' repeated {count} times"
# 최근 10개 액션이 모두 동일
if len(self.action_history) >= 10:
recent = self.action_history[-10:]
if len(set(a['action'] for a in recent)) == 1:
return True, "Same action repeated 10 times in a row"
return False, None
def check_timeout(self, max_minutes=60):
"""타임아웃 확인"""
elapsed = (time.time() - self.start_time) / 60
if elapsed > max_minutes:
return True, f"Session exceeded {max_minutes} minutes"
return False, None
def should_stop(self):
"""중단해야 하는지 확인"""
loop_detected, loop_msg = self.detect_loop()
if loop_detected:
print(f"⚠️ 무한 루프 감지: {loop_msg}")
return True
timeout, timeout_msg = self.check_timeout()
if timeout:
print(f"⚠️ 타임아웃: {timeout_msg}")
return True
return False
# 사용 예시
monitor = SafetyMonitor()
while not monitor.should_stop():
action = agent.get_next_action()
monitor.record_action(action)
agent.execute(action)
Q10: 프로젝트가 완료되었는지 어떻게 확인하나요?
A: 다단계 검증:
1. feature_list.json 확인
1
2
3
4
5
6
7
8
9
10
11
12
13
node << 'EOF'
const data = require('./feature_list.json');
const incomplete = data.features.filter(f => !f.passes);
if (incomplete.length === 0) {
console.log('✅ 모든 기능 완료!');
} else {
console.log(`❌ ${incomplete.length}개 기능 미완료:`);
incomplete.forEach(f => {
console.log(` - #${f.id}: ${f.title}`);
});
}
EOF
2. 전체 테스트 스위트
1
npm test -- --coverage
3. 최종 검증 스크립트
1
bash tools/final-validation.sh
4. 수동 검수
- 모든 주요 사용자 시나리오 테스트
- 다양한 브라우저에서 확인
- 모바일 반응형 확인
- 성능 벤치마크
- 보안 체크리스트
5. 프로덕션 빌드
1
2
npm run build
npm run preview # 빌드된 버전 테스트
부록 E: 실전 사례 연구
사례 1: E-커머스 웹사이트 (3일 프로젝트)
프로젝트 개요:
- 목표: 완전한 기능의 온라인 쇼핑몰
- 기간: 3일 (72시간)
- 기능 수: 250개
- 최종 결과: 248개 완료 (99.2%)
타임라인:
Day 1 (8시간)
- 0-2시간: Initializer Agent (환경 설정)
- 2-8시간: 12개 기능 완료
- 기본 레이아웃
- 네비게이션
- 홈페이지
- 제품 목록 페이지
Day 2 (10시간)
- 28개 기능 완료
- 사용자 인증
- 제품 상세 페이지
- 장바구니
- 검색 기능
Day 3 (12시간)
- 208개 기능 완료 (가속화)
- 결제 시스템
- 주문 관리
- 리뷰 시스템
- 관리자 대시보드
- 이메일 알림
- 성능 최적화
주요 학습:
- 초반이 느림: 첫날은 구조를 잡는 시간. 정상입니다.
- 가속 효과: 구조가 잡히면 속도가 급격히 증가
- 재사용 패턴: 비슷한 기능(CRUD)은 복사-수정으로 빠르게 처리
- 테스트 자동화: E2E 테스트 템플릿 만들어두니 반복 작업 감소
성공 요인:
- 명확한 feature_list.json (250개 상세 기능)
- 일관된 커밋 패턴
- 철저한 테스트 (스크린샷 500장+)
사례 2: 데이터 분석 대시보드 (5일 프로젝트)
프로젝트 개요:
- 목표: 실시간 데이터 시각화 대시보드
- 기간: 5일
- 기능 수: 180개
- 도전: 복잡한 데이터 처리 로직
특이사항:
이 프로젝트는 반복적 실패를 경험했습니다:
첫 번째 시도 (실패)
- 문제: feature_list.json이 너무 추상적
1 2 3 4 5
{ "id": 1, "title": "데이터 처리 시스템", // 너무 광범위! "description": "모든 데이터 처리" }
- 결과: 에이전트가 범위를 정하지 못하고 방황
두 번째 시도 (부분 성공)
- 개선: 기능을 더 작게 쪼갬
- 문제: 의존성 관리 실패
- Feature #50이 #30에 의존하는데 먼저 구현됨
- 결과: 40% 지점에서 리팩토링 필요
세 번째 시도 (성공)
- 핵심 변경:
- 명확한 의존성 그래프
- 우선순위 기반 정렬
- 각 기능 30분 이내로 제한
최종 feature_list.json 구조:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
"id": 15,
"title": "CSV 파싱 - 기본 구조",
"estimatedMinutes": 25,
"dependsOn": [],
"passes": false
},
{
"id": 16,
"title": "CSV 파싱 - 타입 변환",
"estimatedMinutes": 30,
"dependsOn": [15],
"passes": false
},
{
"id": 17,
"title": "차트 컴포넌트 - 라인 그래프",
"estimatedMinutes": 35,
"dependsOn": [16],
"passes": false
}
학습:
- 초기 설계가 중요 (Initializer Agent에 시간 투자)
- 의존성 명시가 필수
- 실패도 학습 과정
사례 3: API 통합 서비스 (연구용, 2주)
프로젝트 개요:
- 목표: 10개 외부 API 통합
- 기간: 2주
- 기능 수: 420개
- 특징: 많은 외부 의존성
도전 과제:
- API 키 관리
- 해결: .env 파일 + 시크릿 관리 시스템
- Git 훅으로 실수 방지
- Rate Limiting
- 해결: 캐싱 시스템 구현
- 테스트 시 Mock API 사용
- 외부 서비스 다운타임
- 해결: 재시도 로직 + 폴백 메커니즘
1 2 3 4 5 6 7 8 9 10
async function callAPIWithRetry(url, maxRetries = 3) { for (let i = 0; i < maxRetries; i++) { try { return await fetch(url); } catch (error) { if (i === maxRetries - 1) throw error; await sleep(1000 * (i + 1)); // 지수 백오프 } } }
- 해결: 재시도 로직 + 폴백 메커니즘
성공 전략:
- 각 API를 별도 기능으로 분리
- Mock 데이터로 오프라인 개발 가능
- 통합 테스트에 실제 API 사용
부록 F: 도구 및 스크립트 모음
완전 자동화 스크립트
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#!/bin/bash
# auto-agent.sh - 완전 자동화된 에이전트 실행
set -e
PROJECT_DIR="$1"
SESSION_LIMIT="${2:-10}" # 최대 10세션
if [ -z "$PROJECT_DIR" ]; then
echo "사용법: $0 <프로젝트_디렉토리> [세션_제한]"
exit 1
fi
cd "$PROJECT_DIR"
echo "🤖 자율 에이전트 시작"
echo "프로젝트: $PROJECT_DIR"
echo "최대 세션: $SESSION_LIMIT"
echo ""
# 초기화 확인
if [ ! -f "feature_list.json" ]; then
echo "📋 Initializer Agent 실행..."
python3 autonomous_agent.py --mode initialize
fi
# 메인 루프
for session in $(seq 1 $SESSION_LIMIT); do
echo ""
echo "========================================"
echo "세션 #$session 시작"
echo "========================================"
# 남은 작업 확인
REMAINING=$(cat feature_list.json | grep '"passes": false' | wc -l)
if [ $REMAINING -eq 0 ]; then
echo "🎉 모든 기능 완료!"
break
fi
echo "남은 기능: $REMAINING"
# Coding Agent 실행
python3 autonomous_agent.py --mode code
# 검증
if npm test > /dev/null 2>&1; then
echo "✅ 테스트 통과"
else
echo "⚠️ 테스트 실패 - 중단"
break
fi
# 진행 상황 출력
COMPLETED=$(cat feature_list.json | grep '"passes": true' | wc -l)
TOTAL=$(cat feature_list.json | grep '"id"' | wc -l)
PERCENTAGE=$(echo "scale=1; $COMPLETED * 100 / $TOTAL" | bc)
echo "진행률: $COMPLETED/$TOTAL ($PERCENTAGE%)"
# 쿨다운 (Rate limiting 방지)
sleep 2
done
echo ""
echo "========================================"
echo "자동 실행 완료"
echo "========================================"
# 최종 보고서 생성
node tools/progress-tracker.js
Git 훅 설정
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
#!/bin/bash
# setup-git-hooks.sh - Git 훅 자동 설정
HOOKS_DIR=".git/hooks"
# pre-commit 훅
cat > "$HOOKS_DIR/pre-commit" << 'EOF'
#!/bin/bash
echo "🔍 사전 커밋 검증..."
# 1. 금지된 파일 확인
FORBIDDEN_FILES=(".env" "*.key" "*.pem" "secrets/*")
for pattern in "${FORBIDDEN_FILES[@]}"; do
if git diff --cached --name-only | grep -q "$pattern"; then
echo "❌ 금지된 파일 커밋 시도: $pattern"
exit 1
fi
done
# 2. feature_list.json 검증
if git diff --cached --name-only | grep -q "feature_list.json"; then
python3 << 'PYTHON'
import json
import sys
try:
with open('feature_list.json') as f:
data = json.load(f)
# passes:true인데 커밋이 없는 경우 확인
import subprocess
git_log = subprocess.check_output(['git', 'log', '--all']).decode()
for feature in data['features']:
if feature.get('passes') and f"#{feature['id']}" not in git_log:
print(f"❌ Feature #{feature['id']} passes:true but no commit found")
sys.exit(1)
print("✅ feature_list.json 검증 통과")
except Exception as e:
print(f"❌ 검증 실패: {e}")
sys.exit(1)
PYTHON
if [ $? -ne 0 ]; then
exit 1
fi
fi
# 3. 린트 검사 (있는 경우)
if command -v npm &> /dev/null && [ -f "package.json" ]; then
if npm run lint --if-present 2>&1 | grep -q "error"; then
echo "❌ 린트 에러 발견"
exit 1
fi
fi
echo "✅ 사전 커밋 검증 통과"
EOF
chmod +x "$HOOKS_DIR/pre-commit"
# commit-msg 훅
cat > "$HOOKS_DIR/commit-msg" << 'EOF'
#!/bin/bash
COMMIT_MSG_FILE=$1
COMMIT_MSG=$(cat "$COMMIT_MSG_FILE")
# 최소 길이 확인
if [ ${#COMMIT_MSG} -lt 20 ]; then
echo "❌ 커밋 메시지가 너무 짧습니다 (최소 20자)"
exit 1
fi
# 패턴 확인 (feat:, fix:, docs: 등)
if ! echo "$COMMIT_MSG" | grep -qE "^(feat|fix|docs|style|refactor|test|chore):"; then
echo "⚠️ 권장 형식: feat|fix|docs|style|refactor|test|chore: 메시지"
fi
echo "✅ 커밋 메시지 검증 통과"
EOF
chmod +x "$HOOKS_DIR/commit-msg"
echo "✅ Git 훅 설정 완료"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
👉 컨텍스트 윈도우 한계로 “기억 상실” 😵
Anthropic의 해법: Initializer + Coding Agent 하네스
며칠짜리 웹앱 빌드도 안정적으로 가능
AIAgents #Claude
2/7
대표 실패 패턴 ❌
• 한 번에 다 끝내려다(one-shot) 망함
• 중간에 “완료!” 선언(early victory)
해결 👉 작업을 작은 기능 단위로 쪼개고
JSON 체크리스트로 범위 강제
3/7
Initializer Agent 역할 🧠
• 첫 세션에서 feature_list.json 생성
• 각 기능에 description / steps / passes:false
이 JSON이 에이전트의 To-Do 리스트
구조화 덕분에 임의 수정도 방지됨
4/7
운영 방식 💻
• Git repo 초기화
• claude-progress.txt에 세션 로그 기록
• https://init.sh로 dev 서버 자동 실행
Coding Agent는 한 세션에 한 기능만 구현
→ 세션 종료 시 완전한 핸드오버
5/7
세션 시작 루틴 🔄
현재 디렉토리 확인
진행 로그 + git log 읽기
JSON에서 다음 미완료 기능 선택
dev 서버 + 기본 E2E 테스트 “어제 뭐 했지?” 문제 완전 해결
6/7
품질 관리 핵심 ⚠️
• 구현 후 반드시 Git 커밋 (상세 메시지)
• 테스트 통과 전엔 passes:true 금지
• Puppeteer로 실제 사용자 시나리오 테스트
→ 사람 엔지니어처럼 클린 핸드오버
7/7
결론 🧩
장기 AI 에이전트의 핵심은
👉 프롬프트가 아니라 하네스
(Git = 메모리, JSON = 구조, 테스트 = 검증)
웹·연구·자동화 전부 재사용 가능