장기 실행 AI 에이전트 개발 가이드 - k82022603/k82022603.github.io GitHub Wiki
장기 실행 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은 이러한 문제를 해결하기 위해 이중 에이전트 패턴을 개발했습니다.
- Initializer Agent: 첫 세션에서 환경을 설정하고 작업 범위를 명확히 정의
- Coding Agent: 이후 세션에서 점진적으로 기능을 구현하고 명확한 인계 자료 생성
이 패턴의 핵심은 작업을 작은 단위로 쪼개고, 구조화된 체크리스트를 통해 범위를 강제하는 것입니다.
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 파일입니다. 각 기능은 다음과 같은 구조를 가집니다.
{
"features": [
{
"id": 1,
"title": "사용자 로그인 구현",
"description": "이메일과 비밀번호를 사용한 기본 로그인 기능",
"steps": [
"로그인 폼 UI 생성",
"백엔드 인증 API 구현",
"세션 관리 추가",
"에러 핸들링 구현"
],
"passes": false,
"priority": "high"
}
]
}2. claude-progress.txt 각 세션의 작업 내용을 기록하는 로그 파일입니다. 다음 세션의 에이전트가 이 파일을 읽어 이전 작업 내용을 파악합니다.
=== 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는 프로젝트의 첫 세션에서 실행되며, 이후 모든 Coding Agent가 효과적으로 작업할 수 있는 환경을 구축합니다.
1. 사양 분석
# app_spec.txt 파일을 읽고 요구사항 파악
cat app_spec.txt2. feature_list.json 생성 애플리케이션 사양을 바탕으로 200개 이상의 상세한 테스트 케이스를 작성합니다. 이 과정은 10-20분 정도 소요될 수 있으며, 에이전트가 멈춘 것처럼 보일 수 있지만 정상적인 동작입니다.
{
"features": [
{
"id": 1,
"title": "홈페이지 레이아웃",
"description": "반응형 홈페이지 레이아웃 구현",
"steps": ["헤더 생성", "네비게이션 바", "푸터 구현"],
"passes": false
},
// ... 199개 더
]
}3. 프로젝트 구조 설정
# 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. 보안 설정
// .claude_settings.json
{
"allowed_commands": [
"npm", "node", "git", "cat", "ls",
"mkdir", "cp", "mv", "rm"
],
"blocked_patterns": [
"rm -rf /",
"sudo",
"chmod 777"
]
}Coding Agent는 매 세션마다 단 하나의 기능만 구현합니다. 이는 작업을 관리 가능한 크기로 유지하고, 각 세션이 명확한 결과물을 남기도록 보장합니다.
1. 컨텍스트 수집
# 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 -l2. 기능 선택
feature_list.json에서 "passes": false인 최우선 순위 기능을 선택합니다.
3. 개발 서버 실행
# init.sh 스크립트로 자동 실행
./init.sh
# 또는 수동으로
npm install
npm run dev4. 기능 구현 선택한 기능의 모든 단계를 완료합니다.
// 예: 로그인 기능 구현
// 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를 사용하여 실제 사용자 시나리오를 테스트합니다.
// 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', '[email protected]');
// 비밀번호 입력
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 커밋
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로 변경합니다.
{
"id": 1,
"title": "사용자 로그인 구현",
"passes": true, // ← 테스트 통과 후에만 변경
"completed_at": "2024-01-15T14:45:00Z"
}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는 일관된 루틴을 따릅니다. 이는 "어제 뭐 했지?" 문제를 완전히 해결합니다.
#!/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 "=== 루틴 완료 ==="1. Rule-Based 검증 코드가 기본 규칙을 준수하는지 자동으로 확인합니다.
// 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를 테스트하고 스크린샷을 저장합니다.
// 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를 사용하여 추가 검증을 수행할 수 있습니다.
# 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": true/false,
"issues": ["문제1", "문제2"],
"recommendations": ["권장사항1"]
}}
"""
async for message in query(prompt=prompt):
if hasattr(message, 'result'):
return json.loads(message.result)명확한 커밋 메시지는 미래 세션의 에이전트에게 중요한 컨텍스트를 제공합니다.
feat: implement [기능명] (#[기능번호])
[구현 세부사항]
- 변경사항 1
- 변경사항 2
- 변경사항 3
[테스트]
- 테스트 시나리오 1: 통과
- 테스트 시나리오 2: 통과
- E2E 테스트: verification/screenshots/
[업데이트]
- feature_list.json: #[번호] passes: true로 변경
[다음 단계]
Feature #[다음번호] - [다음 기능명]
# Node.js 환경
npm install -g @anthropic-ai/claude-code
# Python 환경
pip install claude-agent-sdk
# 인증 설정
export ANTHROPIC_API_KEY="your-api-key"# 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
# 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
# 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+ 테스트가 모두 통과하는 프로덕션 품질의 애플리케이션좋은 예시:
{
"id": 5,
"title": "로그인 폼 유효성 검사",
"description": "이메일 형식과 비밀번호 길이 검증",
"steps": [
"이메일 정규식 검증 구현",
"비밀번호 8자 이상 확인",
"에러 메시지 표시"
],
"acceptance_criteria": [
"잘못된 이메일 형식 입력 시 에러 표시",
"8자 미만 비밀번호 입력 시 에러 표시",
"올바른 입력 시 에러 없음"
]
}나쁜 예시:
{
"id": 5,
"title": "사용자 관리 시스템",
"description": "전체 사용자 관리 기능"
// 너무 광범위함!
}// 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 };// 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🎉 모든 기능이 완료되었습니다!');
}# 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// 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;여러 전문화된 에이전트를 동시에 실행할 수 있습니다.
# 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']} 완료")# 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)// 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('우선순위 조정 완료');증상: 매 세션마다 같은 작업을 반복하거나 이전에 완료한 기능을 다시 시도합니다.
해결책:
- claude-progress.txt가 제대로 업데이트되고 있는지 확인
- 세션 시작 루틴이 실행되는지 확인
- Git 커밋 메시지가 충분히 상세한지 검토
# 진단 스크립트
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증상: E2E 테스트가 계속 실패하거나 타임아웃이 발생합니다.
해결책:
- 개발 서버가 제대로 실행 중인지 확인
- 테스트 대기 시간을 충분히 설정
- 헤드리스 모드를 끄고 실제 브라우저에서 확인
// 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', '[email protected]');
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();증상: 테스트 없이 passes: true로 변경하거나 기능을 임의로 추가/삭제합니다.
해결책:
- 시스템 프롬프트에 명확한 제약사항 추가
- Git 훅으로 무단 수정 감지
- 검증 스크립트 자동 실행
#!/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 "✓ 사전 커밋 검증 완료"증상: 긴 작업 중 세션이 만료됩니다.
해결책:
- 작업을 더 작은 단위로 쪼개기
- 주기적으로 중간 커밋 생성
- 체크포인트 시스템 구현
# 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분 세션 가정
}독립적인 기능들을 동시에 처리하여 전체 완료 시간을 단축할 수 있습니다.
# 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']}")반복적인 작업을 캐시하여 속도를 향상시킵니다.
// 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;모든 테스트를 매번 실행하는 대신 변경된 부분만 테스트합니다.
// 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;// 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;# 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)// 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');# 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#!/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# .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
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 # 프로젝트 문서
{
"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"
}
}{
"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"]
}
}# 애플리케이션 사양
## 프로젝트 개요
프로젝트명: [프로젝트 이름]
목적: [프로젝트의 목적과 목표]
대상 사용자: [타겟 사용자]
## 기술 스택
- 프론트엔드: 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건
세션 시작
│
├─> 세션 시작 루틴 실행
│ ├─> 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 업데이트
│
└─> 세션 종료
├─> 다음 기능 남음? → 새 세션 시작
└─> 모든 기능 완료? → 최종 검증
# 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 "[email protected]"
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
첫 번째 기능: [상세 설명]
# 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
A: 여러 가지 이유가 있습니다:
-
컨텍스트 윈도우 관리: 한 기능에 집중하면 관련 코드와 테스트만 로드하여 컨텍스트를 효율적으로 사용할 수 있습니다.
-
명확한 인계: 다음 세션의 에이전트가 정확히 무엇이 완료되었는지 알 수 있습니다.
-
롤백 용이성: 문제 발생 시 한 기능만 되돌리면 됩니다.
-
테스트 격리: 각 기능이 독립적으로 테스트되어 버그 추적이 쉽습니다.
단, 매우 작은 기능들(5-10분 소요)이 여러 개 있다면 관련된 기능들을 묶어서 처리할 수 있습니다.
A: Anthropic의 권장사항은 200개 이상입니다. 이는 다음과 같은 이유에서입니다:
- 각 기능이 충분히 작고 구체적이어야 함 (30-60분 소요)
- 세밀한 진행 상황 추적 가능
- 테스트 커버리지 향상
그러나 프로젝트 규모에 따라 조정 가능합니다:
- 소규모 프로젝트: 50-100개
- 중규모 프로젝트: 100-200개
- 대규모 프로젝트: 200-500개
중요한 것은 개수가 아니라 각 기능의 크기와 명확성입니다.
A: 단계적 디버깅 접근:
- 헤드리스 모드 끄기
const browser = await puppeteer.launch({
headless: false, // 브라우저를 볼 수 있게
slowMo: 250 // 동작을 천천히
});- 단계별 스크린샷
await page.screenshot({ path: 'debug-step-1.png' });
// 각 단계마다 스크린샷- 콘솔 로그 캡처
page.on('console', msg => console.log('PAGE:', msg.text()));- 대기 시간 증가
await page.waitForSelector('#element', { timeout: 30000 });- 최후의 수단: 기능 분할
- 복잡한 기능을 더 작은 단위로 쪼개기
A: 세션 시작 루틴에서 기존 테스트를 실행하는 이유가 바로 이것입니다:
# 세션 시작 시
npm test # 기존 테스트 실행
# 실패한 테스트 발견 시
git log --oneline -20 # 최근 커밋 확인
git show HEAD # 마지막 변경사항 확인
git diff HEAD~1 # 이전 버전과 비교
# 필요시 롤백
git revert HEAD
# 또는
git reset --hard HEAD~1버그 수정 후:
git commit -m "fix: resolve regression in feature #X
Previous session introduced bug in [기능].
Root cause: [원인]
Solution: [해결책]
Tests: All passing now"A: 이것이 바로 하네스 시스템의 진가가 드러나는 순간입니다:
-
Git이 메모리 역할: 마지막 커밋까지의 작업은 안전하게 보존됩니다.
-
다음 세션 시작 시:
git status # 커밋되지 않은 변경사항 확인
# WIP(Work In Progress) 커밋이 있다면
git log -1 # 마지막 커밋 메시지 읽기
# "wip: partial implementation..." 메시지에서 진행 상황 파악- 예방책: 주기적 체크포인트
# 20분마다 자동 체크포인트
if time_elapsed > 20 * 60:
subprocess.run(['git', 'add', '.'])
subprocess.run(['git', 'commit', '-m', f'checkpoint: {timestamp}'])A: 가능하지만 주의가 필요합니다:
안전한 병렬 실행 조건:
- 기능 간 의존성이 없음
- 서로 다른 파일을 수정함
- 각자 독립된 작업 디렉토리 사용
구현 예시:
# 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 호출)
대부분의 경우 순차 실행이 더 안전하고 효율적입니다.
A: 절대 코드에 하드코딩하지 마세요:
// ❌ 나쁜 예
const API_KEY = "sk-1234567890abcdef";
// ✅ 좋은 예
const API_KEY = process.env.API_KEY;권장 방법:
- .env 파일 사용
# .env (Git에 커밋하지 않음)
API_KEY=sk-1234567890abcdef
DATABASE_URL=postgresql://user:pass@localhost/db- .gitignore에 추가
.env
.env.local
*.key
*.pem
secrets/
- .env.example 제공
# .env.example (Git에 커밋)
API_KEY=your_api_key_here
DATABASE_URL=your_database_url_here- 에이전트에게 명시적 지시
## 보안 규칙
절대 금지:
- API 키를 코드에 하드코딩
- .env 파일을 Git에 커밋
- 로그에 시크릿 출력
필수:
- 환경 변수 사용
- .gitignore에 시크릿 파일 추가
- .env.example 제공
A: 데이터베이스 스키마도 Git으로 버전 관리:
migrations/
├── 001_initial_schema.sql
├── 002_add_users_table.sql
├── 003_add_email_column.sql
└── 004_create_sessions_table.sql
마이그레이션 스크립트:
// 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에 포함:
{
"id": 25,
"title": "데이터베이스 마이그레이션: 사용자 테이블",
"steps": [
"migrations/002_add_users_table.sql 생성",
"마이그레이션 스크립트 실행",
"테이블 생성 확인"
]
}A: 방지 메커니즘 구현:
# 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)A: 다단계 검증:
1. feature_list.json 확인
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}`);
});
}
EOF2. 전체 테스트 스위트
npm test -- --coverage3. 최종 검증 스크립트
bash tools/final-validation.sh4. 수동 검수
- 모든 주요 사용자 시나리오 테스트
- 다양한 브라우저에서 확인
- 모바일 반응형 확인
- 성능 벤치마크
- 보안 체크리스트
5. 프로덕션 빌드
npm run build
npm run preview # 빌드된 버전 테스트프로젝트 개요:
- 목표: 완전한 기능의 온라인 쇼핑몰
- 기간: 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장+)
프로젝트 개요:
- 목표: 실시간 데이터 시각화 대시보드
- 기간: 5일
- 기능 수: 180개
- 도전: 복잡한 데이터 처리 로직
특이사항:
이 프로젝트는 반복적 실패를 경험했습니다:
첫 번째 시도 (실패)
- 문제: feature_list.json이 너무 추상적
{ "id": 1, "title": "데이터 처리 시스템", // 너무 광범위! "description": "모든 데이터 처리" } - 결과: 에이전트가 범위를 정하지 못하고 방황
두 번째 시도 (부분 성공)
- 개선: 기능을 더 작게 쪼갬
- 문제: 의존성 관리 실패
- Feature #50이 #30에 의존하는데 먼저 구현됨
- 결과: 40% 지점에서 리팩토링 필요
세 번째 시도 (성공)
- 핵심 변경:
- 명확한 의존성 그래프
- 우선순위 기반 정렬
- 각 기능 30분 이내로 제한
최종 feature_list.json 구조:
{
"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에 시간 투자)
- 의존성 명시가 필수
- 실패도 학습 과정
프로젝트 개요:
- 목표: 10개 외부 API 통합
- 기간: 2주
- 기능 수: 420개
- 특징: 많은 외부 의존성
도전 과제:
-
API 키 관리
- 해결: .env 파일 + 시크릿 관리 시스템
- Git 훅으로 실수 방지
-
Rate Limiting
- 해결: 캐싱 시스템 구현
- 테스트 시 Mock API 사용
-
외부 서비스 다운타임
- 해결: 재시도 로직 + 폴백 메커니즘
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 사용
#!/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#!/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 훅 설정 완료"👉 컨텍스트 윈도우 한계로 “기억 상실” 😵
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 = 구조, 테스트 = 검증)
웹·연구·자동화 전부 재사용 가능