Skip to content

Commit 73869b0

Browse files
jylee2033sounmindclaude
committed
feat: optimize synchronize event to re-analyze only changed files (#7)
Use Compare API (before...after SHA) to identify files changed in the latest push, so only those files get pattern-analyzed and their bot comments deleted/recreated. Falls back to full analysis if Compare API fails. Co-Authored-By: sounmind <37020415+sounmind@users.noreply.github.com> Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent d5fa7a4 commit 73869b0

File tree

2 files changed

+74
-12
lines changed

2 files changed

+74
-12
lines changed

handlers/tag-patterns.js

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ const MAX_FILE_SIZE = 20000; // 20K 문자 제한 (OpenAI 토큰 안전장치)
2323
* @param {object} prData - PR 객체 (draft, labels 포함)
2424
* @param {string} appToken - GitHub App installation token
2525
* @param {string} openaiApiKey
26+
* @param {string[]|null} [changedFilenames=null] - synchronize 시 변경된 파일명 목록 (null이면 전체 분석)
2627
*/
2728
export async function tagPatterns(
2829
repoOwner,
@@ -31,7 +32,8 @@ export async function tagPatterns(
3132
headSha,
3233
prData,
3334
appToken,
34-
openaiApiKey
35+
openaiApiKey,
36+
changedFilenames = null
3537
) {
3638
// 2-1. Skip 조건
3739
if (prData.draft === true) {
@@ -58,22 +60,34 @@ export async function tagPatterns(
5860
}
5961

6062
const allFiles = await filesResponse.json();
61-
const solutionFiles = allFiles.filter(
63+
let solutionFiles = allFiles.filter(
6264
(f) =>
6365
(f.status === "added" || f.status === "modified") &&
6466
SOLUTION_PATH_REGEX.test(f.filename)
6567
);
6668

69+
// changedFilenames가 제공되면 해당 파일만 대상으로 좁힘 (synchronize 최적화)
70+
if (changedFilenames !== null) {
71+
const changedSet = new Set(changedFilenames);
72+
solutionFiles = solutionFiles.filter((f) => changedSet.has(f.filename));
73+
console.log(
74+
`[tagPatterns] PR #${prNumber}: narrowed to ${solutionFiles.length} changed solution files`
75+
);
76+
}
77+
6778
console.log(
68-
`[tagPatterns] PR #${prNumber}: ${allFiles.length} files, ${solutionFiles.length} solution files`
79+
`[tagPatterns] PR #${prNumber}: ${allFiles.length} total files, ${solutionFiles.length} solution files to analyze`
6980
);
7081

7182
if (solutionFiles.length === 0) {
7283
return { skipped: "no-solution-files" };
7384
}
7485

75-
// 2-3. 기존 Bot 패턴 태그 코멘트 삭제
76-
await deletePreviousPatternComments(repoOwner, repoName, prNumber, appToken);
86+
// 2-3. 기존 Bot 패턴 태그 코멘트 삭제 (변경 파일만)
87+
const targetFilenames = solutionFiles.map((f) => f.filename);
88+
await deletePreviousPatternComments(
89+
repoOwner, repoName, prNumber, appToken, targetFilenames
90+
);
7791

7892
// 2-4. 파일별 OpenAI 분석 + 코멘트 작성 (각 파일 try/catch 래핑)
7993
const results = [];
@@ -101,13 +115,16 @@ export async function tagPatterns(
101115
}
102116

103117
/**
104-
* 기존 Bot 패턴 태그 코멘트 삭제 (다른 사용자 코멘트는 절대 건드리지 않음)
118+
* 기존 Bot 패턴 태그 코멘트 삭제 (대상 파일만, 다른 사용자 코멘트는 절대 건드리지 않음)
119+
*
120+
* @param {string[]} targetFilenames - 삭제 대상 파일명 목록
105121
*/
106122
async function deletePreviousPatternComments(
107123
repoOwner,
108124
repoName,
109125
prNumber,
110-
appToken
126+
appToken,
127+
targetFilenames
111128
) {
112129
const response = await fetch(
113130
`https://api.github.com/repos/${repoOwner}/${repoName}/pulls/${prNumber}/comments?per_page=100`,
@@ -122,8 +139,12 @@ async function deletePreviousPatternComments(
122139
}
123140

124141
const comments = await response.json();
142+
const targetSet = new Set(targetFilenames);
125143
const botPatternComments = comments.filter(
126-
(c) => c.user?.type === "Bot" && c.body?.includes(COMMENT_MARKER)
144+
(c) =>
145+
c.user?.type === "Bot" &&
146+
c.body?.includes(COMMENT_MARKER) &&
147+
targetSet.has(c.path)
127148
);
128149

129150
for (const comment of botPatternComments) {
@@ -149,7 +170,7 @@ async function deletePreviousPatternComments(
149170
}
150171

151172
console.log(
152-
`[tagPatterns] Deleted ${botPatternComments.length} previous pattern comments`
173+
`[tagPatterns] Deleted ${botPatternComments.length} previous pattern comments for ${targetFilenames.length} files`
153174
);
154175
}
155176

handlers/webhooks.js

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -204,10 +204,35 @@ async function handleProjectsV2ItemEvent(payload, env) {
204204
});
205205
}
206206

207+
/**
208+
* Compare API로 두 커밋 사이에 변경된 파일명 목록 조회
209+
*
210+
* @param {string} repoOwner
211+
* @param {string} repoName
212+
* @param {string} baseSha - 이전 커밋 SHA
213+
* @param {string} headSha - 새 커밋 SHA
214+
* @param {string} appToken
215+
* @returns {Promise<string[]|null>} 변경된 파일명 배열 (실패 시 null → 전체 분석 fallback)
216+
*/
217+
async function getChangedFilenames(repoOwner, repoName, baseSha, headSha, appToken) {
218+
const response = await fetch(
219+
`https://api.github.com/repos/${repoOwner}/${repoName}/compare/${baseSha}...${headSha}`,
220+
{ headers: getGitHubHeaders(appToken) }
221+
);
222+
223+
if (!response.ok) {
224+
console.error(`[getChangedFilenames] Compare API failed: ${response.status}`);
225+
return null;
226+
}
227+
228+
const data = await response.json();
229+
return (data.files || []).map((f) => f.filename);
230+
}
231+
207232
/**
208233
* Pull Request 이벤트 처리
209-
* - opened/reopened: Week 설정 체크 + 알고리즘 패턴 태깅
210-
* - synchronize: 알고리즘 패턴 태깅만 (Week 체크 스킵 - 이미 설정됐을 가능성 높음)
234+
* - opened/reopened: Week 설정 체크 + 알고리즘 패턴 태깅 (전체 파일)
235+
* - synchronize: 알고리즘 패턴 태깅만 (변경된 파일만, Week 체크 스킵)
211236
*/
212237
async function handlePullRequestEvent(payload, env) {
213238
const action = payload.action;
@@ -256,14 +281,30 @@ async function handlePullRequestEvent(payload, env) {
256281
// 알고리즘 패턴 태깅 (OPENAI_API_KEY 있을 때만)
257282
if (env.OPENAI_API_KEY) {
258283
try {
284+
// synchronize일 때만 변경 파일 목록 추출 (최적화: #7)
285+
let changedFilenames = null;
286+
if (action === "synchronize" && payload.before && payload.after) {
287+
changedFilenames = await getChangedFilenames(
288+
repoOwner,
289+
repoName,
290+
payload.before,
291+
payload.after,
292+
appToken
293+
);
294+
console.log(
295+
`[handlePullRequestEvent] synchronize: ${changedFilenames?.length ?? "fallback(all)"} files changed between ${payload.before.slice(0, 7)}...${payload.after.slice(0, 7)}`
296+
);
297+
}
298+
259299
await tagPatterns(
260300
repoOwner,
261301
repoName,
262302
prNumber,
263303
pr.head.sha,
264304
pr,
265305
appToken,
266-
env.OPENAI_API_KEY
306+
env.OPENAI_API_KEY,
307+
changedFilenames
267308
);
268309
} catch (error) {
269310
console.error(`[handlePullRequestEvent] tagPatterns failed: ${error.message}`);

0 commit comments

Comments
 (0)