Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
8f655d5
과제 시작
im-binary Oct 27, 2025
d9826f7
feat: agent 관련 설정 md 파일 추가
im-binary Oct 27, 2025
38d07c6
feat: 가벼운 기능으로 에이전트 구현 확인하기
im-binary Oct 27, 2025
7ccd1c7
feat: cli 도구 만들기
im-binary Oct 27, 2025
8e13863
refactor: path 설정
im-binary Oct 27, 2025
da5ab29
chore: .gitignore env 추가
im-binary Oct 27, 2025
0f786af
chore: .env.example 제거
im-binary Oct 27, 2025
ed9cdda
feat: @google/generative-ai, dotenv 패키지 설치
im-binary Oct 27, 2025
c649d3f
feat: 변경된 워크플로우 확인
im-binary Oct 28, 2025
81b92ed
chore
im-binary Oct 28, 2025
8a9e36d
chore: 불필요 코드 제거
im-binary Oct 28, 2025
bf55669
refactor: 변화한 워크플로우대로 cli 도움말 수정
im-binary Oct 28, 2025
47f7ed9
refactor: 프롬프트 수정
im-binary Oct 28, 2025
043d844
revert: path 설정 되돌리기
im-binary Oct 28, 2025
149a3d0
chore: 불필요 문서 제거
im-binary Oct 28, 2025
22a06ce
chore: 불필요 문서 제거
im-binary Oct 28, 2025
11ef909
revert
im-binary Oct 28, 2025
a8cbb5a
chore: 프롬프트에 이모지 다 없애기
im-binary Oct 28, 2025
b82ddfe
fix: 프롬프트에서 **, * 제거 및 최대 사용 토큰 값 수정
im-binary Oct 29, 2025
8072217
fix: test-designer에게 철학과 테스트 환경 자세하게 알려주기
im-binary Oct 29, 2025
ab6ed5b
fix: llm이 쓸데없는 장식 사용해서 응답하지 않도록 한 내용 살짝 풀어주기
im-binary Oct 29, 2025
cebc41a
fix: 이미 작성된 테스트 케이스를 중복으로 작성하지 않도록 프롬프트 수정
im-binary Oct 29, 2025
d8a0875
feat-1: (🔴 RED) 반복 일정 생성 기능 구현
im-binary Oct 29, 2025
58d79a9
feat-1: (🟢 GREEN) 반복 일정 생성 기능 구현
im-binary Oct 29, 2025
1a47ef7
feat-1: (🔵 REFACTOR) 반복 일정 생성 기능 구현
im-binary Oct 29, 2025
a5392bc
fix: 컴포넌트 렌더링 테스트 시 지켜야 할 규칙 추가
im-binary Oct 29, 2025
44746ee
feat-2: (🔴 RED) 반복 일정 표시 기능 구현
im-binary Oct 29, 2025
5e74c53
feat-2: (🟢 GREEN) 반복 일정 표시 기능 구현
im-binary Oct 29, 2025
54ef986
feat-2: (🔵 REFACTOR) 반복 일정 표시 기능
im-binary Oct 29, 2025
5094711
fix: 시간 조작 테스트, 비동기 상태 업데이트 관련 프롬프트 수정
im-binary Oct 29, 2025
ecb89ee
feat-3: (🔴 RED) 반복 일정 수정 기능 구현
im-binary Oct 29, 2025
5ee5046
feat-3: (🟢 GREEN) 반복 일정 수정 기능 구현
im-binary Oct 29, 2025
6903ebc
feat-3: (🔵 REFACTOR) 반복 일정 수정 기능 구현
im-binary Oct 29, 2025
fdcabdb
feat-4: (🔴 RED) 반복 일정 삭제 기능 구현
im-binary Oct 30, 2025
6969fa2
feat-4: (🟢 GREEN) 반복 일정 삭제 기능 구현
im-binary Oct 30, 2025
2a76518
feat-4: (🔵 REFACTOR) 반복 일정 삭제 기능 구현
im-binary Oct 30, 2025
2183d50
chore: 폴더 구조 수정
im-binary Oct 30, 2025
22099cf
fix: lint error
im-binary Oct 30, 2025
5660e13
chore
im-binary Oct 30, 2025
a6e1f36
fix: 누락된 테스트 작성
im-binary Oct 30, 2025
5d29153
fix: 누락된 테스트 작성
im-binary Oct 30, 2025
e515602
chore: 테스트 설명문 수정
im-binary Oct 30, 2025
6fe7346
docs: report 내용 작성
im-binary Oct 31, 2025
41ee3d7
refactor: hydration 경고 수정
im-binary Oct 31, 2025
276ba7d
fix: ci timeout 해결
im-binary Oct 31, 2025
d670b27
docs: report 내용 추가
im-binary Oct 31, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@
.vscode
node_modules
.coverage

.env
137 changes: 137 additions & 0 deletions agents/cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
#!/usr/bin/env node
/**
* Agent Orchestrator CLI (Interactive TDD Mode)
*
* 커맨드라인에서 대화형 TDD 워크플로우를 실행하는 CLI 도구
*/

import * as readline from 'readline';

import { runInteractiveWorkflow } from './orchestrator';

/**
* CLI 실행
*/
async function main() {
const args = process.argv.slice(2);

if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
printHelp();
process.exit(0);
}

if (args.includes('--version') || args.includes('-v')) {
printVersion();
process.exit(0);
}

// 요구사항 추출
const requirementIndex = args.indexOf('--requirement') + 1 || args.indexOf('-r') + 1;

if (!requirementIndex || !args[requirementIndex]) {
console.error('❌ 오류: 요구사항을 입력해주세요.');
console.error('예시: pnpm agent:run -r "일정 제목에 접두사 추가"');
process.exit(1);
}

const requirement = args[requirementIndex];

try {
console.log('\n🎯 대화형 TDD 모드로 시작합니다...\n');
const result = await runInteractiveWorkflow(requirement);

// 종료 코드 설정
process.exit(result.status === 'success' ? 0 : 1);
} catch (error) {
console.error('💥 워크플로우 실행 중 오류 발생:', error);
process.exit(1);
}
}

/**
* 사용자 입력 대기
*/
export async function waitForUserConfirmation(message: string): Promise<boolean> {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});

return new Promise((resolve) => {
rl.question(`\n${message} (yes/no): `, (answer) => {
rl.close();
const confirmed = answer.toLowerCase() === 'yes' || answer.toLowerCase() === 'y';
resolve(confirmed);
});
});
}

/**
* 도움말 출력
*/
function printHelp() {
console.log(`
🤖 AI Orchestration System (TDD Mode)

사용법:
pnpm agent:run [options]

옵션:
-r, --requirement <text> 개발할 기능 요구사항
-h, --help 도움말 표시
-v, --version 버전 표시

예시:
pnpm agent:run -r "일정 제목에 추가되는 접두사 제거"

🎯 실제 TDD 워크플로우 (통합 방식):

Step 1: [Gemini] 기능 명세서 작성
→ 실행: pnpm agent:run -r "요구사항"
→ 확인: agents/output/ 폴더의 명세서 파일

Step 2: [Gemini] 테스트 케이스 설계
→ agents/output/ 폴더의 테스트 설계 파일 확인

Step 3: [Copilot] TDD RED 단계 - 실패하는 테스트 작성
→ Copilot에게 명세서와 테스트 설계를 첨부하여 요청
→ 요청 예시: "# TDD RED 단계: 테스트 코드 작성
(명세서 내용 첨부)
실패하는 테스트 코드를 작성해주세요"
→ 확인: 테스트가 실패하는지 확인 (pnpm test)

Step 4: [Copilot] TDD GREEN 단계 - 최소 구현
→ 요청: "# TDD GREEN 단계: 최소 구현 요청
(명세서 및 테스트 코드 내용 포함)
테스트를 통과하는 최소한의 코드를 작성해주세요"
→ 확인: 모든 테스트가 통과하는지 확인 (pnpm test)

Step 5: [Copilot] TDD REFACTOR 단계 - 코드 개선
→ 요청: "# TDD REFACTOR 단계: 코드 개선 요청
(명세서 포함)
테스트를 유지하면서 코드를 리팩토링해주세요"
→ 확인: 리팩토링 후에도 모든 테스트 통과 확인
→ 완료! ✅

💡 팁:
- 각 단계마다 Copilot과 대화하면서 진행하세요
- 명세서를 항상 첨부하여 컨텍스트를 유지하세요
- 테스트 실행 결과를 확인하며 진행하세요
- 필요시 추가 리팩토링이나 개선을 요청하세요

`);
}

/**
* 버전 출력
*/
function printVersion() {
console.log('Agent Orchestrator v1.0.0');
}

// CLI 실행 (ES 모듈 방식)
// import.meta.url을 사용하여 현재 파일이 직접 실행되었는지 확인
const isMainModule = import.meta.url === `file://${process.argv[1]}`;
if (isMainModule) {
main();
}
136 changes: 136 additions & 0 deletions agents/llmClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { GoogleGenerativeAI } from '@google/generative-ai';

export class LLMClient {
private geminiClient: any;
private temperature: number;
private maxTokens: number;

constructor(config: any = {}) {
this.temperature = config.temperature || 0.7;
this.maxTokens = config.maxTokens || 30000;

if (!config.geminiApiKey) {
throw new Error('GOOGLE_AI_KEY가 설정되지 않았습니다.');
}

const genAI = new GoogleGenerativeAI(config.geminiApiKey);
this.geminiClient = genAI.getGenerativeModel({
model: 'gemini-2.5-flash', // 최신 무료 모델 시도
generationConfig: {
temperature: this.temperature,
maxOutputTokens: this.maxTokens,
},
});
console.log('✅ Gemini 클라이언트 초기화 완료 (gemini-2.5-flash)\n');
}

async generate(prompt: string): Promise<string> {
console.log('🤖 Gemini 호출 중...');
console.log(`📊 프롬프트 크기: ${prompt.length} 문자`);

try {
// 타임아웃 설정 (5분)
const timeoutMs = 5 * 60 * 1000;
const timeoutPromise = new Promise<never>((_, reject) => {
setTimeout(() => reject(new Error(`Gemini API 타임아웃 (${timeoutMs}ms)`)), timeoutMs);
});

const generatePromise = (async () => {
const result = await this.geminiClient.generateContent(prompt);
const response = await result.response;
return response.text();
})();

const text = await Promise.race([generatePromise, timeoutPromise]);
console.log(`✅ Gemini 응답 완료 (${text.length} 문자)`);
return text;
} catch (error: any) {
console.error('❌ Gemini API 오류:', error.message);
throw error;
}
}

/**
* Markdown 형식으로 응답을 받아 반환
* JSON보다 안정적이고 LLM이 더 잘 생성함
*/
async generateMarkdown(prompt: string): Promise<string> {
const instruction = `
CRITICAL OUTPUT RULES - MUST FOLLOW:
1. 간결한 Markdown 형식으로 응답하세요
2. 제목은 ## 또는 ### 만 사용
3. 목록은 - 또는 1. 만 사용
4. ABSOLUTELY FORBIDDEN:출력에서 절대로 별표(*)나 이중 별표(**)를 사용하지 마세요. 즉, 볼드, 이탤릭, 마크다운 스타일링은 절대 금지입니다.
5. ABSOLUTELY FORBIDDEN: 이모지 절대 사용 금지
6. 일반 텍스트만 사용하고, 강조가 필요하면 "중요:", "핵심:" 등의 접두어 사용
7. 코드 블록은 필요시에만 사용 (백틱 3개)
8. 핵심 정보만 포함하고 반복 설명 제거

VIOLATION EXAMPLES (DO NOT USE):
- **텍스트** (볼드)
- *텍스트* (이탤릭)
- _텍스트_ (언더스코어 강조)
- 😀 (이모지)
`;

const fullPrompt = instruction + prompt;

// 재시도 로직 (최대 3번)
const maxRetries = 3;
let lastError: Error | null = null;

for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
console.log(`\n🔄 시도 ${attempt}/${maxRetries}...`);
const response = await this.generate(fullPrompt);

if (!response || response.trim().length === 0) {
throw new Error('빈 응답 수신');
}

// // 강제로 볼드/이탤릭 제거 (보험 처리)
// let cleaned = response.trim();
// // 볼드 제거: **텍스트** -> 텍스트
// cleaned = cleaned.replace(/\*\*([^*]+)\*\*/g, '$1');
// // 이탤릭 제거: *텍스트* -> 텍스트 (단, 목록 기호는 유지)
// cleaned = cleaned.replace(/(?<!^|\n)(?<![\s-])\*([^*\n]+)\*/g, '$1');
// // 언더스코어 강조 제거: _텍스트_ -> 텍스트
// cleaned = cleaned.replace(/_([^_]+)_/g, '$1');
// // 이모지 제거 (간단한 유니코드 범위)
// cleaned = cleaned.replace(
// /[\u{1F600}-\u{1F64F}\u{1F300}-\u{1F5FF}\u{1F680}-\u{1F6FF}\u{1F1E0}-\u{1F1FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}]/gu,
// ''
// );

// return cleaned;
return response.trim();
} catch (error: any) {
lastError = error;
console.error(`⚠️ 시도 ${attempt} 실패:`, error.message);

if (attempt < maxRetries) {
const waitTime = attempt * 5000; // 5초, 10초, 15초
console.log(`⏳ ${waitTime / 1000}초 후 재시도...`);
await new Promise((resolve) => setTimeout(resolve, waitTime));
}
}
}

throw new Error(`Gemini API ${maxRetries}번 시도 후 실패: ${lastError?.message}`);
}

getProvider() {
return 'gemini';
}
}

export function createLLMClient(config?: any): LLMClient {
console.log({ 'createLLMClient config': config });

return new LLMClient({
geminiApiKey: process.env.GOOGLE_AI_KEY,
temperature: parseFloat(process.env.LLM_TEMPERATURE || '0.7'),
maxTokens: parseInt(process.env.LLM_MAX_TOKENS || '30000'),
});
}
Loading
Loading