-
Notifications
You must be signed in to change notification settings - Fork 2
풀이 추가/수정 커밋시 기존 분석은 유지하고 추가/수정 파일에 대해서만 분석하도록 변경 #50
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,10 +5,10 @@ | |
| * 파일별 review comment로 남긴다. 복잡도 분석은 패턴 분석 루프와 병렬로 | ||
| * OpenAI 1콜에서 모든 파일을 한 번에 처리하고, 그 결과를 파일별 댓글 | ||
| * 본문에 한 섹션 더 붙이는 형태로 묻어간다. | ||
| * 재실행 시 기존 패턴 댓글과 답글은 보존하고, 변경된 파일에 새 분석 댓글을 추가한다. | ||
| * | ||
| * 주의: 솔루션 파일이 12개를 넘으면 subrequest 한도(50)에 가까워진다. | ||
| * 기존 패턴 태깅 자체도 13파일 이상에서 한도를 넘는 cliff 가 있으니 | ||
| * 복잡도 합본은 그 cliff 를 1파일분(13→12) 당기는 정도다. | ||
| * 주의: Workers Free 플랜의 외부 subrequest 한도는 invocation당 50회이므로 준수해야 한다. | ||
| * @see https://developers.cloudflare.com/workers/platform/limits/#subrequests | ||
| */ | ||
|
|
||
| import { getGitHubHeaders } from "../utils/github.js"; | ||
|
|
@@ -95,23 +95,17 @@ export async function tagPatterns( | |
| return { skipped: "no-solution-files" }; | ||
| } | ||
|
|
||
| // 2-3. 기존 Bot 패턴 태그 코멘트 삭제 (변경 파일만) | ||
| const targetFilenames = solutionFiles.map((f) => f.filename); | ||
| await deletePreviousPatternComments( | ||
| repoOwner, repoName, prNumber, appToken, targetFilenames | ||
| ); | ||
|
|
||
| // 2-4. 모든 파일 raw 다운로드 (한 번만, 복잡도 분석과 공유) | ||
| // 2-3. 모든 파일 raw 다운로드 (한 번만, 복잡도 분석과 공유) | ||
| const fileEntries = await downloadFileEntries(solutionFiles); | ||
|
|
||
| // 2-5. 복잡도 분석은 1콜이므로 패턴 루프와 병렬 진행. 실패해도 패턴 댓글은 작성. | ||
| // 2-4. 복잡도 분석은 1콜이므로 패턴 루프와 병렬 진행. 실패해도 패턴 댓글은 작성. | ||
| const complexityPromise = callComplexityAnalysis(fileEntries, openaiApiKey) | ||
| .catch((err) => { | ||
| console.error(`[tagPatterns] complexity analysis failed: ${err.message}`); | ||
| return []; | ||
| }); | ||
|
|
||
| // 2-6. 파일별 OpenAI 분석 + 코멘트 작성 (각 파일 try/catch 래핑) | ||
| // 2-5. 파일별 OpenAI 분석 + 코멘트 작성 (각 파일 try/catch 래핑) | ||
| const results = []; | ||
| for (const fe of fileEntries) { | ||
| try { | ||
|
|
@@ -134,74 +128,14 @@ export async function tagPatterns( | |
| } | ||
| } | ||
|
|
||
| // 2-7. 마이그레이션: 구버전이 남긴 단독 복잡도 issue comment 가 있으면 삭제 | ||
| // 2-6. 마이그레이션: 구버전이 남긴 단독 복잡도 issue comment 가 있으면 삭제 | ||
| await deleteLegacyComplexityIssueComment( | ||
| repoOwner, repoName, prNumber, appToken | ||
| ); | ||
|
|
||
| return { tagged: results.filter((r) => !r.error).length, results }; | ||
| } | ||
|
|
||
| /** | ||
| * 기존 Bot 패턴 태그 코멘트 삭제 (대상 파일만, 다른 사용자 코멘트는 절대 건드리지 않음) | ||
| * | ||
| * @param {string[]} targetFilenames - 삭제 대상 파일명 목록 | ||
| */ | ||
| async function deletePreviousPatternComments( | ||
| repoOwner, | ||
| repoName, | ||
| prNumber, | ||
| appToken, | ||
| targetFilenames | ||
| ) { | ||
| const response = await fetch( | ||
| `https://api.github.com/repos/${repoOwner}/${repoName}/pulls/${prNumber}/comments?per_page=100`, | ||
| { headers: getGitHubHeaders(appToken) } | ||
| ); | ||
|
|
||
| if (!response.ok) { | ||
| console.error( | ||
| `[tagPatterns] Failed to fetch review comments: ${response.status}` | ||
| ); | ||
| return; | ||
| } | ||
|
|
||
| const comments = await response.json(); | ||
| const targetSet = new Set(targetFilenames); | ||
| const botPatternComments = comments.filter( | ||
| (c) => | ||
| c.user?.type === "Bot" && | ||
| c.body?.includes(COMMENT_MARKER) && | ||
| targetSet.has(c.path) | ||
| ); | ||
|
|
||
| for (const comment of botPatternComments) { | ||
| try { | ||
| const deleteResponse = await fetch( | ||
| `https://api.github.com/repos/${repoOwner}/${repoName}/pulls/comments/${comment.id}`, | ||
| { | ||
| method: "DELETE", | ||
| headers: getGitHubHeaders(appToken), | ||
| } | ||
| ); | ||
|
|
||
| if (!deleteResponse.ok) { | ||
| console.error( | ||
| `[tagPatterns] Failed to delete comment ${comment.id}: ${deleteResponse.status}` | ||
| ); | ||
| } | ||
| } catch (error) { | ||
| console.error( | ||
| `[tagPatterns] Error deleting comment ${comment.id}: ${error.message}` | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| console.log( | ||
| `[tagPatterns] Deleted ${botPatternComments.length} previous pattern comments for ${targetFilenames.length} files` | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * 단일 파일 분석 + 코멘트 작성 | ||
| * | ||
|
|
@@ -233,6 +167,8 @@ async function tagSingleFile( | |
| let body = `${COMMENT_MARKER} | ||
| ### 🏷️ 알고리즘 패턴 분석 | ||
|
|
||
| ${renderAnalyzedSource(file.filename, fileContent)} | ||
|
|
||
| - **패턴**: ${patternsText} | ||
| - **설명**: ${analysis.description || "(설명 없음)"}`; | ||
|
|
||
|
|
@@ -273,6 +209,20 @@ async function tagSingleFile( | |
| return { patterns: analysis.patterns }; | ||
| } | ||
|
|
||
| function renderAnalyzedSource(filename, content) { | ||
| const language = filename.includes(".") ? filename.split(".").pop() : ""; | ||
| const truncated = content.length >= MAX_FILE_SIZE ? "\n... (이하 생략)" : ""; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
|
||
| return `<details> | ||
| <summary>${filename}</summary> | ||
|
|
||
| \`\`\`${language} | ||
| ${content}${truncated} | ||
| \`\`\` | ||
|
|
||
| </details>`; | ||
|
Comment on lines
+216
to
+223
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 풀이 파일의 주석이나 docstring에 |
||
| } | ||
|
|
||
| /** | ||
| * 솔루션 파일들의 raw 내용을 한 번에 다운로드한다. | ||
| * 패턴 분석 + 복잡도 분석이 같은 fileEntries 를 공유한다. | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -235,6 +235,28 @@ async function getChangedFilenames(repoOwner, repoName, baseSha, headSha, appTok | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return (data.files || []).map((f) => f.filename); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async function resolveChangedFilenames(payload, repoOwner, repoName, appToken) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (payload.action !== "synchronize" || !payload.before || !payload.after) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let changedFilenames = null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| changedFilenames = await getChangedFilenames( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| repoOwner, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| repoName, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| payload.before, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| payload.after, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| appToken | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.error(`[resolveChangedFilenames] failed: ${error.message}`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return changedFilenames; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+243
to
+257
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이렇게 살짝 다르게 작성하는 패턴에 대해서는 어떻게 생각하세요? 실패하면 null이 반환되는 부분이 더 쉽게 읽힐 수 있거든요.
Suggested change
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 저도 이 방식이 더 직관적으로 보이네요! |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+238
to
+258
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 일반 push는 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Pull Request 이벤트 처리 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * - opened/reopened: Week 설정 체크 + 알고리즘 패턴 태깅 (전체 파일) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -288,6 +310,13 @@ async function handlePullRequestEvent(payload, env, ctx) { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (env.OPENAI_API_KEY && env.INTERNAL_SECRET && env.WORKER_URL) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const baseUrl = env.WORKER_URL; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const changedFilenames = await resolveChangedFilenames( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| payload, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| repoOwner, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| repoName, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| appToken | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const dispatchHeaders = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Content-Type": "application/json", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "X-Internal-Secret": env.INTERNAL_SECRET, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -304,6 +333,7 @@ async function handlePullRequestEvent(payload, env, ctx) { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ...commonPayload, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| headSha: pr.head.sha, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| prData: pr, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| changedFilenames, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }).catch((err) => | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.error(`[dispatch] tagPatterns failed: ${err.message}`) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -329,22 +359,14 @@ async function handlePullRequestEvent(payload, env, ctx) { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // INTERNAL_SECRET/WORKER_URL 미설정 시 기존 방식으로 폴백 (동일 invocation에서 순차 실행) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.warn("[handlePullRequestEvent] INTERNAL_SECRET or WORKER_URL not set, running handlers in-process"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // synchronize일 때만 변경 파일 목록 추출 (최적화: #7) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let changedFilenames = null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (action === "synchronize" && payload.before && payload.after) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| changedFilenames = await getChangedFilenames( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| repoOwner, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| repoName, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| payload.before, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| payload.after, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| appToken | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.log( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| `[handlePullRequestEvent] synchronize: ${changedFilenames?.length ?? "fallback(all)"} files changed between ${payload.before.slice(0, 7)}...${payload.after.slice(0, 7)}` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const changedFilenames = await resolveChangedFilenames( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| payload, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| repoOwner, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| repoName, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| appToken | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await tagPatterns( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| repoOwner, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| repoName, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
원본 소스코드를 보여주는 아이디어 좋네요!
코드 변경 이후의 히스토리 저장 측면에서도 유용하다고 생각합니다.
다만 여기서 더 나아가 diff를 보여준다던지 하는 건 없을까요? 어디서 어떻게 변경됐는지도 알 수 있으면 좋겠다 생각했기 떄문이에요.
그러면 깃허브에서 자동으로 제공하는 diff 링크 같은 게 있을까도 궁금합니다.
아래 코멘트에서 언급된 rebase로 인한 force push 업데이트에서도 깃허브에서 변경사항을 보여주는 링크가 제공되기에 해결할 수 있는 방법 중 하나가 되지 않을까 생각해봤습니다.