@@ -8,112 +8,131 @@ jobs:
88 ai-review :
99 runs-on : ubuntu-latest
1010 if : github.actor != 'dependabot[bot]'
11+ permissions :
12+ contents : read
13+ pull-requests : write
14+ issues : write
1115
1216 steps :
1317 - name : Checkout code
1418 uses : actions/checkout@v4
1519 with :
1620 fetch-depth : 0
1721
18- - name : Get changed files
19- id : changed-files
20- run : |
21- # Get the list of changed files
22- CHANGED_FILES=$(git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.sha }} | grep -E '\.(kt|kts|java|sql|yml|yaml|properties|md)$' | head -20)
23- echo "files<<CHANGEDFILES" >> $GITHUB_OUTPUT
24- echo "$CHANGED_FILES" >> $GITHUB_OUTPUT
25- echo "CHANGEDFILES" >> $GITHUB_OUTPUT
26-
27- - name : Read changed files content
28- id : file-content
29- run : |
30- CONTENT=""
31- if [ -n "${{ steps.changed-files.outputs.files }}" ]; then
32- while IFS= read -r file; do
33- if [ -f "$file" ] && [ -s "$file" ]; then
34- echo "Processing: $file"
35- CONTENT="${CONTENT}
36-
37- --- File: $file ---
38- "
39- FILE_CONTENT=$(head -c 8000 " $file")
40- CONTENT="${CONTENT}${FILE_CONTENT}"
41- fi
42- done <<< "${{ steps.changed-files.outputs.files }}"
43- fi
44-
45- echo "content<<FILECONTENT" >> $GITHUB_OUTPUT
46- echo "$CONTENT" >> $GITHUB_OUTPUT
47- echo "FILECONTENT" >> $GITHUB_OUTPUT
48-
4922 - name : AI Code Review
50- id : ai-review
51- run : |
52- if [ -z "${{ steps.file-content.outputs.content }}" ]; then
53- echo "No relevant files changed"
54- echo "review=No Kotlin/configuration files were changed in this PR." >> $GITHUB_OUTPUT
55- exit 0
56- fi
57-
58- REVIEW=$(curl -s -X POST "https://openrouter.ai/api/v1/chat/completions" \
59- -H "Authorization: Bearer ${{ secrets.OPENROUTER_API_KEY }}" \
60- -H "Content-Type: application/json" \
61- -d '{
62- "model": "x-ai/grok-4-fast:free",
63- "messages": [
64- {
65- "role": "system",
66- "content": "코드 리뷰어입니다. 다음 규칙만 검토하세요:\n\n## 검토 항목\n1. **글로벌 익셉션 처리**: @ControllerAdvice 사용, 표준 에러 응답\n2. **ApiResponse 사용**: 모든 API는 ApiResponse<T>로 감싸서 응답\n3. **Kotlin 최적화**: data class, null safety, when 표현식, 확장함수 등\n4. **ktlint 규칙**: 포맷팅, 네이밍 컨벤션\n\n## 응답 형식\n### ✅ 준수사항\n- [잘 지켜진 부분]\n\n### ❌ 위반사항 \n- [파일:라인] 문제점과 수정방법\n\n**점수**: X/10"
67- },
68- {
69- "role": "user",
70- "content": "다음 PR의 변경사항을 리뷰해주세요:\n\nPR 제목: ${{ github.event.pull_request.title }}\nPR 설명: ${{ github.event.pull_request.body }}\n\n변경된 파일들:\n${{ steps.file-content.outputs.content }}"
71- }
72- ],
73- "temperature": 0.3
74- }')
75-
76- # Extract the review content
77- REVIEW_CONTENT=$(echo "$REVIEW" | jq -r '.choices[0].message.content // "리뷰 생성에 실패했습니다."')
78-
79- echo "review<<EOF" >> $GITHUB_OUTPUT
80- echo "$REVIEW_CONTENT" >> $GITHUB_OUTPUT
81- echo "EOF" >> $GITHUB_OUTPUT
82-
83- - name : Post review comment
8423 uses : actions/github-script@v7
8524 with :
8625 script : |
87- const review = ` ${{ steps.ai-review.outputs.review }}` ;
26+ const OPENROUTER_API_KEY = ' ${{ secrets.OPENROUTER_API_KEY }}' ;
8827
89- // Find existing review comment
90- const comments = await github.rest.issues.listComments({
28+ const diff = await github.rest.repos.compareCommits({
9129 owner: context.repo.owner,
9230 repo: context.repo.repo,
93- issue_number: context.issue.number,
31+ base: context.payload.pull_request.base.sha,
32+ head: context.payload.pull_request.head.sha
9433 });
9534
96- const botComment = comments.data.find(comment =>
97- comment.user.login === 'github-actions[bot]' &&
98- comment.body.includes('🤖 AI 코드 리뷰')
35+ const filesToReview = diff.data.files.filter(file =>
36+ file.patch &&
37+ !file.filename.includes('test/') &&
38+ !file.filename.includes('build/') &&
39+ (file.filename.endsWith('.kt') ||
40+ file.filename.endsWith('.kts') ||
41+ file.filename.endsWith('.java') ||
42+ file.filename.endsWith('.yml') ||
43+ file.filename.endsWith('.yaml') ||
44+ file.filename.endsWith('.md'))
9945 );
10046
101- const commentBody = "## 🤖 AI 코드 리뷰\n\n" + review + "\n\n---\n<details>\n<summary>💡 리뷰 정보</summary>\n\n- 모델: Grok-4-fast\n- 리뷰 시간: " + new Date().toLocaleString('ko-KR', {timeZone: 'Asia/Seoul'}) + "\n- PR: #${{ github.event.number }}\n\n</details>";
47+ if (filesToReview.length === 0) {
48+ console.log('No files to review');
49+ return;
50+ }
10251
103- if (botComment) {
104- // Update existing comment
105- await github.rest.issues.updateComment({
106- owner: context.repo.owner,
107- repo: context.repo.repo,
108- comment_id: botComment.id,
109- body: commentBody
110- });
111- } else {
112- // Create new comment
113- await github.rest.issues.createComment({
114- owner: context.repo.owner,
115- repo: context.repo.repo,
116- issue_number: context.issue.number,
117- body: commentBody
52+ console.log(`Found ${filesToReview.length} files to review`);
53+
54+ let overallReview = `## 🤖 AI 코드 리뷰\n\n`;
55+
56+ let allChanges = '';
57+ const filesSummary = [];
58+
59+ for (const file of filesToReview) {
60+ filesSummary.push(`- ${file.filename} (+${file.additions}/-${file.deletions})`);
61+ allChanges += `\n### ${file.filename}\n`;
62+ allChanges += `\`\`\`diff\n${file.patch}\n\`\`\`\n`;
63+ }
64+
65+ try {
66+ const response = await fetch('https://openrouter.ai/api/v1/chat/completions', {
67+ method: 'POST',
68+ headers: {
69+ 'Authorization': `Bearer ${OPENROUTER_API_KEY}`,
70+ 'Content-Type': 'application/json'
71+ },
72+ body: JSON.stringify({
73+ model: 'x-ai/grok-4-fast:free',
74+ messages: [{
75+ role: 'system',
76+ content: `코드 리뷰어입니다. 다음 규칙만 검토하세요:
77+
78+ # # 검토 항목
79+ 1. **글로벌 익셉션 처리** : @ControllerAdvice 사용, 표준 에러 응답
80+ 2. **ApiResponse 사용** : 모든 API는 ApiResponse<T>로 감싸서 응답
81+ 3. **Kotlin 최적화** : data class, null safety, when 표현식, 확장함수 등
82+ 4. **ktlint 규칙** : 포맷팅, 네이밍 컨벤션
83+
84+ # # 응답 형식
85+ # ## ✅ 준수사항
86+ - [잘 지켜진 부분]
87+
88+ # ## ❌ 위반사항
89+ - [파일:라인] 문제점과 수정방법
90+
91+ **점수**: X/10`
92+ }, {
93+ role : ' user' ,
94+ content : ` 다음 PR의 변경사항을 리뷰해주세요:
95+
96+ PR 제목: ${{ github.event.pull_request.title }}
97+ PR 설명: ${{ github.event.pull_request.body }}
98+
99+ 변경된 파일:
100+ ${filesSummary.join('\n ')}
101+
102+ 전체 변경사항:
103+ ${allChanges}`
104+ }],
105+ temperature : 0.3
106+ })
118107 });
119- }
108+
109+ if (response.ok) {
110+ const result = await response.json();
111+ overallReview += result.choices[0].message.content + '\n\n';
112+ console.log('✅ API review completed successfully!');
113+ } else {
114+ const errorText = await response.text();
115+ console.error('API request failed:', response.status, errorText);
116+ overallReview += `> ⚠️ 리뷰 실패 : ${response.status} - ${errorText}\n\n`;
117+ }
118+ } catch (error) {
119+ console.error('Error during review:', error);
120+ overallReview += `> ⚠️ 리뷰 실패 : ${error.message}\n\n`;
121+ }
122+
123+ overallReview += `---\n`;
124+ overallReview += `<details>\n<summary>💡 리뷰 정보</summary>\n\n`;
125+ overallReview += `- 모델 : Grok-4-fast\n`;
126+ overallReview += `- 리뷰 시간 : ${new Date().toLocaleString('ko-KR', {timeZone: 'Asia/Seoul'})}\n`;
127+ overallReview += `- PR : # ${{ github.event.number }}\n\n`;
128+ overallReview += `</details>`;
129+
130+ await github.rest.pulls.createReview({
131+ owner : context.repo.owner,
132+ repo : context.repo.repo,
133+ pull_number : context.payload.pull_request.number,
134+ body : overallReview,
135+ event : ' COMMENT'
136+ });
137+
138+ console.log(`✅ Review completed for ${filesToReview.length} files!`);
0 commit comments