@@ -11,12 +11,178 @@ on:
 jobs:
   code_review:
     runs-on: ubuntu-latest
-    name: ChatGPT Code Review
+    name: Gemini Code Review
     steps:
-      - uses: anc95/ChatGPT-CodeReview@main
+      - uses: actions/checkout@v4
+
+      - name: Setup Node.js
+        uses: actions/setup-node@v4
+        with:
+          node-version: "20"
+
+      - name: Install dependencies
+        run: npm install @google/generative-ai parse-diff
+
+      - name: Review Changed Files
+        uses: actions/github-script@v7
         env:
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
-          # optional
-          LANGUAGE: Korean
-          MODEL: gpt-4o-mini-2024-07-18 # https://platform.openai.com/docs/models
+          GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
+          GEMINI_MODEL: ${{ secrets.GEMINI_MODEL }}
+          MAX_CONCURRENT_REVIEWS: 3 # 동시에 처리할 파일 수
+          MAX_RETRIES: 3
+          RETRY_DELAY: 1000
+
+        with:
+          script: |
+            const { GoogleGenerativeAI } = require("@google/generative-ai");
+            const parseDiff = require('parse-diff');
+            // core는 이미 github-script에서 제공됨
+
+            // 재시도 로직을 포함한 API 호출 함수
+            async function withRetry(fn, retries = process.env.MAX_RETRIES) {
+              for (let i = 0; i < retries; i++) {
+                try {
+                  return await fn();
+
+                } catch (error) {
+                  if (error.message.includes('rate limit') && i < retries - 1) {
+                    const delay = process.env.RETRY_DELAY * Math.pow(2, i);
+                    console.log(`Rate limit hit, waiting ${delay}ms before retry ${i + 1}`);
+
+                    await new Promise(resolve => setTimeout(resolve, delay));
+                    continue;
+                  }
+
+                  throw error;
+                }
+              }
+            }
+
+            // Gemini AI 클라이언트 초기화
+            const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY);
+            const model = genAI.getGenerativeModel({ model: process.env.GEMINI_MODEL });
+
+            /**
+             * 코드 변경사항을 검토하고 피드백을 생성
+             * 
+             * @param content 리뷰할 코드 내용
+             * @param filename 파일명
+             * @returns 리뷰 코멘트
+             */
+            async function reviewCode(content, filename) {
+              try {
+                // 파일명 sanitize
+                const sanitizedFilename = filename.replace(/[^a-zA-Z0-9.-_\/]/g, '_');
+
+                const prompt = `당신은 시니어 개발자입니다. 아래 ${sanitizedFilename} 파일의 코드를 검토하고 다음 사항들을 한국어로 리뷰해주세요:
+                1. 코드의 품질과 가독성
+                2. 잠재적인 버그나 문제점
+                3. 성능 개선 포인트
+                4. 보안 관련 이슈
+                5. 개선을 위한 구체적인 제안
+
+                코드:
+                ${content}`;
+
+                const result = await withRetry(() => model.generateContent(prompt));
+                const response = await result.response;
+
+                if (!response.text()) {
+                  throw new Error('Gemini API returned empty response');
+                }
+
+                console.log(`Successfully reviewed ${filename}`);
+                return response.text();
+
+              } catch (error) {
+                console.error(`Error reviewing ${filename}:`, error);
+                return `⚠️ 코드 리뷰 중 오류가 발생했습니다: ${error.message}
+                       다시 시도하시거나 관리자에게 문의해주세요.`;
+              }
+            }
+
+            /**
+             * PR의 각 파일을 처리하고 리뷰 코멘트 작성
+             * @param file PR에서 변경된 파일 정보
+             * @param pr PR 정보
+             */
+            async function processFile(file, pr) {
+              if (file.status === 'removed') {
+                console.log(`Skipping removed file: ${file.filename}`);
+                return;
+              }
+
+              try {
+                if (!file.patch) {
+                  console.warn(`No patch found for ${file.filename}, skipping.`);
+                  return;
+                }
+
+                const diff = parseDiff(file.patch)[0];
+                if (!diff || !diff.chunks) {
+                  console.log(`No valid diff found for ${file.filename}`);
+                  return;
+                }
+
+                // 모든 변경사항을 하나로 합치기 (추가된 부분만 필터링)
+                const changes = diff.chunks
+                  .flatMap(chunk => chunk.changes)
+                  .filter(change => change.type === 'add');
+
+                if (changes.length === 0) {
+                  console.log(`No added changes found in ${file.filename}`);
+                  return;
+                }
+
+                // 변경사항을 하나의 문자열로 합치기
+                const content = changes.map(change => change.content).join('\n');
+                const review = await reviewCode(content, file.filename);
+
+                // PR에 리뷰 코멘트 작성 (파일명을 헤더로 추가)
+                return github.rest.issues.createComment({
+                  owner: context.repo.owner,
+                  repo: context.repo.repo,
+                  issue_number: context.issue.number,
+                  body: `## ${file.filename} 리뷰\n\n${review}`
+                });
+
+              } catch (error) {
+                console.error(`Error processing ${file.filename}:`, error);
+                throw error;
+              }
+            }
+
+            try {
+              // PR 정보 조회
+              const { data: pr } = await github.rest.pulls.get({
+                owner: context.repo.owner,
+                repo: context.repo.repo,
+                pull_number: context.issue.number,
+              });
+
+              // 변경된 파일 목록 조회
+              const { data: files } = await github.rest.pulls.listFiles({
+                owner: context.repo.owner,
+                repo: context.repo.repo,
+                pull_number: context.issue.number,
+              });
+
+              console.log(`Starting review of ${files.length} files...`);
+
+              // 모든 파일 처리 완료를 기다림
+              const promises = [];
+              const batchSize = parseInt(process.env.MAX_CONCURRENT_REVIEWS);
+            
+              for (let i = 0; i < files.length; i += batchSize) {
+                const batch = files.slice(i, i + batchSize);
+                const batchPromises = batch.map(file => processFile(file, pr));
+                promises.push(...batchPromises);
+              }
+
+              await Promise.all(promises);
+              console.log('All reviews completed successfully');
+
+            } catch (error) {
+              console.error('Workflow failed:', error);
+              core.setFailed(`Workflow failed: ${error.message}`);
+            }

리뷰: 이 코드는 GitHub Actions를 이용하여 Google Gemini API를 통해 Pull Request에 대한 코드 리뷰를 자동화하는 작업입니다.  하지만 몇 가지 개선이 필요합니다.

**1. 코드의 품질과 가독성:**

* **가독성 개선:**  `reviewCode` 함수 내부의 JSON 구조가 중첩되어 가독성이 떨어집니다.  더 명확한 변수명을 사용하고, JSON 생성 부분을 여러 줄로 나누어 가독성을 높여야 합니다.  예를 들어, `contents`, `parts` 등의 변수명은 컨텍스트를 명확히 하도록 수정할 수 있습니다.
* **오류 처리 부족:**  `fetch` 호출과 `response.json()`에서 발생할 수 있는 네트워크 에러나 JSON 파싱 에러에 대한 처리가 전혀 없습니다.  `try...catch` 블록을 사용하여 에러를 처리하고, 에러 로그를 남기거나 적절한 fallback 처리를 해야 합니다.
* **하드코딩된 API 엔드포인트:**  `https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent` 와 같은 API 엔드포인트가 하드코딩되어 있습니다.  환경 변수로 관리하거나, 더 유연한 방법을 사용하여 추후 변경에 대비해야 합니다.
* **비효율적인 패치 파일 처리:**  모든 파일의 `patch` 전체를 Gemini API에 전달합니다. 파일이 매우 크다면 API 요청 제한에 걸릴 수 있습니다.  작은 단위로 나누어 전달하거나, 파일 크기를 제한하는 로직이 필요합니다.
* **마지막 줄 번호:**  `line: file.patch.split('\n').length` 는 파일의 마지막 줄을 지정합니다.  실제 변경 사항이 있는 줄을 특정하는 것이 더 정확합니다.  diff를 파싱하여 변경된 줄을 찾는 로직을 추가해야 합니다.


**2. 잠재적인 버그나 문제점:**

* **API 키 노출:**  `GEMINI_API_KEY` 는 GitHub Secrets로 관리되지만, 코드 자체에 직접적으로 사용되므로, 코드베이스가 유출될 경우 API 키가 노출될 위험이 있습니다.  API 키를 직접 사용하지 않고, 적절한 클라이언트 라이브러리를 사용하는 것이 좋습니다.  Google Cloud Client Library for Node.js를 사용하는 것을 고려해 보세요.
* **Gemini API의 응답 처리:**  `result.candidates[0].content.parts[0].text` 와 같이 Gemini API의 응답 구조에 직접 접근하고 있습니다.  API 응답 구조가 변경되면 코드가 동작하지 않을 수 있습니다.  더 안전하고 유연한 방식으로 응답을 처리해야 합니다. (예: 응답의 유효성 검사)
* **대량의 Pull Request:** 매우 큰 Pull Request의 경우,  API 호출 횟수가 많아지고, GitHub Actions 실행 시간 제한에 걸릴 수 있습니다.
* **비동기 처리 누락:**  `for` 루프 내부에서 비동기 함수 `reviewCode`를 호출하지만,  결과를 기다리지 않고 순차적으로 다음 파일을 처리합니다.  `Promise.all`을 사용하여 모든 리뷰가 완료될 때까지 기다리는 것이 좋습니다.


**3. 성능 개선 포인트:**

* **병렬 처리:**  `Promise.all` 을 사용하여 여러 파일의 코드 리뷰를 병렬로 처리하여 전체 처리 시간을 단축할 수 있습니다.
* **캐싱:**  이전에 리뷰한 코드는 캐싱하여 중복된 API 호출을 줄일 수 있습니다.
* **API 요청 최적화:**  한 번에 여러 파일을 처리하거나, 더 작은 단위로 파일을 분할하여 API 요청 횟수를 줄일 수 있습니다.


**4. 보안 관련 이슈:**

* **API 키 관리:** 앞서 언급했듯이, API 키를 환경 변수로 관리하고,  가능하면  Google Cloud Client Library를 사용하여  API 키를 직접 노출하지 않도록 해야 합니다.
* **입력 검증:**  Gemini API에 전달하는 코드에 대한 입력 검증이 필요합니다. 악성 코드가 포함된 경우 API를 악용할 수 있습니다.


**5. 개선을 위한 구체적인 제안:**

* **Google Cloud Client Library 사용:** Google Cloud Client Library for Node.js를 사용하여 Gemini API를 더 안전하고 효율적으로 호출합니다.
* **오류 처리 및 로깅 강화:**  `try...catch` 블록을 사용하여 에러를 처리하고, 자세한 에러 로그를 남겨 문제 해결을 용이하게 합니다.
* **비동기 처리 개선:**  `Promise.all`을 사용하여 병렬 처리를 구현합니다.
* **입력 검증 추가:**  Gemini API에 전달하는 코드를 검증하여 보안 취약성을 방지합니다.
* **응답 처리 개선:**  Gemini API의 응답 구조를 고려하여 더 안정적인 응답 처리 로직을 구현합니다.  schema validation을 고려해볼 수 있습니다.
* **코드 분할:**  `reviewCode` 함수를 더 작고 특정 기능을 담당하는 여러 함수로 분할하여 가독성과 유지보수성을 높입니다.
* **Diff 파싱:**  `file.patch` 대신 diff를 파싱하여 실제 변경된 줄을 찾아 정확한 위치에 리뷰 코멘트를 남깁니다.  `diff` 라이브러리를 사용하는 것을 고려해보세요.
* **Rate Limiting:**  Gemini API의 rate limit을 고려하여 요청 빈도를 조절하는 메커니즘을 추가해야 합니다.


요약하자면, 이 코드는 기능적으로는 동작하지만, 보안, 오류 처리, 성능 측면에서 개선이 필요합니다.  위에서 제시된 제안을 바탕으로 코드를 개선하면 더 안전하고 효율적인 코드 리뷰 시스템을 구축할 수 있습니다.  특히 Google Cloud Client Library 사용은 필수적으로 고려해야 할 부분입니다.