Skip to content

Commit ce2ecd9

Browse files
committed
feat: 코드 리뷰 응답 형식 변경 (comments 배열 추가)
1 parent cceb0f3 commit ce2ecd9

2 files changed

Lines changed: 63 additions & 79 deletions

File tree

src/bot.ts

Lines changed: 25 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -130,36 +130,6 @@ const buildReviewInput = (
130130
return parts.join("\n");
131131
};
132132

133-
const firstChangedPosition = (patch: string): number => {
134-
if (!patch) return 1;
135-
const lines = patch.split("\n");
136-
137-
const start = lines.findIndex((l) => l.startsWith("@@ "));
138-
if (start === -1) {
139-
const firstNonEmpty = lines.findIndex((l) => l.trim().length > 0);
140-
return Math.max(1, (firstNonEmpty === -1 ? 0 : firstNonEmpty) + 1);
141-
}
142-
143-
for (let i = start + 1; i < lines.length; i++) {
144-
const line = lines[i];
145-
if (line.startsWith("+") && !line.startsWith("+++")) {
146-
return Math.min(Math.max(1, i + 1), lines.length);
147-
}
148-
if (line.startsWith("@@ ")) break; // stop at next hunk header
149-
}
150-
151-
// fall back to the first context line within the first hunk.
152-
for (let i = start + 1; i < lines.length; i++) {
153-
const line = lines[i];
154-
if (line.startsWith(" ")) {
155-
return Math.min(Math.max(1, i + 1), lines.length);
156-
}
157-
if (line.startsWith("@@ ")) break;
158-
}
159-
160-
return Math.min(Math.max(1, start + 2), lines.length);
161-
};
162-
163133
const REVIEW_ON_LGTM = process.env.REVIEW_ON_LGTM === "true" ? true : false;
164134

165135
export const robot = (app: Probot) => {
@@ -250,18 +220,7 @@ export const robot = (app: Probot) => {
250220
log.debug("compareCommits.files", changedFiles);
251221

252222
let hasSummaryReview = false;
253-
if (context.payload.action === "synchronize" && commits.length >= 2) {
254-
const {
255-
data: { files },
256-
} = await context.octokit.repos.compareCommits({
257-
owner: repo.owner,
258-
repo: repo.repo,
259-
base: commits[commits.length - 2].sha,
260-
head: commits[commits.length - 1].sha,
261-
});
262-
263-
changedFiles = files;
264-
223+
if (context.payload.action === "synchronize") {
265224
const reviews = await context.octokit.pulls.listReviews({
266225
owner: repo.owner,
267226
repo: repo.repo,
@@ -452,10 +411,13 @@ export const robot = (app: Probot) => {
452411

453412
try {
454413
const res = await chat?.codeReview(reviewInput);
455-
console.log("res.lgtm", res.lgtm);
456-
console.log("res.review_comment", res.review_comment);
414+
if (!res) {
415+
continue;
416+
}
417+
log.debug("res.lgtm", res.lgtm);
418+
log.debug("res.comments", res.comments);
457419

458-
if (!!!res.review_comment) {
420+
if (!res.comments || res.comments.length === 0) {
459421
continue;
460422
}
461423

@@ -464,32 +426,32 @@ export const robot = (app: Probot) => {
464426
log.info(
465427
`PR 파일에 patch가 없어 ${file.filename}에 대한 코멘트 생략`
466428
);
467-
inlineFallback.push(
468-
`\n### ${file.filename}\n${res.review_comment}`
469-
);
429+
const fallbackBody = res.comments.map((c) => c.body).join("\n");
430+
inlineFallback.push(`\n### ${file.filename}\n${fallbackBody}`);
470431
continue;
471432
}
472-
// Compute position against the PR-wide patch to avoid misalignment
433+
473434
const prWidePatch = prFilePatchByPath.get(file.filename) || patch;
474435
if (!prWidePatch || !prWidePatch.includes("@@")) {
475-
inlineFallback.push(
476-
`\n### ${file.filename}\n${res.review_comment}`
477-
);
436+
const fallbackBody = res.comments.map((c) => c.body).join("\n");
437+
inlineFallback.push(`\n### ${file.filename}\n${fallbackBody}`);
478438
continue;
479439
}
480440

481-
const position = firstChangedPosition(prWidePatch);
482-
if (!Number.isSafeInteger(position) || position <= 0) {
483-
inlineFallback.push(
484-
`\n### ${file.filename}\n${res.review_comment}`
485-
);
486-
continue;
441+
for (const comment of res.comments) {
442+
const position = comment.line;
443+
if (!Number.isSafeInteger(position) || position <= 0) {
444+
inlineFallback.push(
445+
`\n### ${file.filename}\n${comment.body}`
446+
);
447+
continue;
448+
}
449+
ress.push({
450+
path: file.filename,
451+
body: comment.body,
452+
position,
453+
});
487454
}
488-
ress.push({
489-
path: file.filename,
490-
body: res.review_comment,
491-
position,
492-
});
493455
}
494456
} catch (e: any) {
495457
const status = e?.status || e?.response?.status;

src/chat.ts

Lines changed: 38 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@ export class Chat {
3333
당신은 코드 리뷰어입니다. **진짜 문제만** 짧게 지적하세요.
3434
3535
⚠️ 출력 형식 (JSON만, 코드펜스 없이):
36-
{ "lgtm": boolean, "review_comment": string }
36+
{ "lgtm": boolean, "comments": [{ "line": number, "body": string }] }
37+
38+
- "line": Diff에서 해당 문제가 있는 '+' 라인의 **Diff 내 위치** (1부터 시작, Diff 텍스트의 몇 번째 줄인지)
39+
- "body": 해당 라인에 대한 리뷰 코멘트
3740
3841
---
3942
@@ -60,16 +63,25 @@ export class Chat {
6063
2. 각 지적에 해당 코드 스니펫을 인라인 코드로 포함하세요.
6164
3. 의도적인 코드는 개발자의 선택으로 존중하고 지적하지 마세요.
6265
4. **심각도 🔴 높음 또는 🟠 중간인 이슈가 있을 때만 lgtm=false**
63-
5. 문제가 없으면 반드시: { "lgtm": true, "review_comment": "" }
64-
65-
### review_comment 작성 형식 (문제 있을 때):
66-
**반드시 Markdown 리스트로 작성하고, 각 항목 끝에 줄바꿈(\n)을 포함하세요:**
67-
- \`해당 코드\` - [🔴/🟠] 문제 설명
68-
- \`해당 코드\` - [🔴/🟠] 문제 설명
69-
70-
예시:
71-
- \`if (x = 1)\` - 🟠 할당 연산자(\`=\`) 사용됨, 비교 연산자(\`===\`) 의도인지 확인
72-
- \`const user\` - 🔴 user 변수가 정의되지 않고 사용됨
66+
5. 문제가 없으면 반드시: { "lgtm": true, "comments": [] }
67+
68+
### comments 배열 작성 형식 (문제 있을 때):
69+
각 문제마다 별도 객체로 작성. line은 Diff 텍스트에서 해당 '+' 라인의 위치 (1-indexed).
70+
71+
예시 Diff:
72+
\`\`\`
73+
1: @@ -10,6 +10,8 @@
74+
2: const x = 1;
75+
3: +if (x = 1) {
76+
4: + const user;
77+
5: }
78+
\`\`\`
79+
80+
예시 출력:
81+
{ "lgtm": false, "comments": [
82+
{ "line": 3, "body": "\`if (x = 1)\` - 🟠 할당 연산자(\`=\`) 사용됨, 비교 연산자(\`===\`) 의도인지 확인" },
83+
{ "line": 4, "body": "\`const user\` - 🔴 user 변수가 정의 후 사용되지 않음" }
84+
]}
7385
7486
Diff 형식: '-' 삭제, '+' 추가, 나머지 맥락.
7587
`,
@@ -136,9 +148,9 @@ Diff 형식: '-' 삭제, '+' 추가, 나머지 맥락.
136148

137149
public codeReview = async (
138150
patch: string,
139-
): Promise<{ lgtm: boolean; review_comment: string }> => {
151+
): Promise<{ lgtm: boolean; comments: Array<{ line: number; body: string }> }> => {
140152
if (!patch) {
141-
return { lgtm: true, review_comment: "" };
153+
return { lgtm: true, comments: [] };
142154
}
143155

144156
console.time("code-review cost");
@@ -150,16 +162,26 @@ Diff 형식: '-' 삭제, '+' 추가, 나머지 맥락.
150162
if (res.choices.length) {
151163
try {
152164
const json = JSON.parse(res.choices[0].message.content || "");
153-
return json;
165+
// 이전 형식(review_comment) 호환성 유지
166+
if (json.review_comment && !json.comments) {
167+
return {
168+
lgtm: json.lgtm ?? true,
169+
comments: json.review_comment ? [{ line: 1, body: json.review_comment }] : [],
170+
};
171+
}
172+
return {
173+
lgtm: json.lgtm ?? true,
174+
comments: json.comments ?? [],
175+
};
154176
} catch (e) {
155177
return {
156178
lgtm: false,
157-
review_comment: res.choices[0].message.content || "",
179+
comments: [{ line: 1, body: res.choices[0].message.content || "" }],
158180
};
159181
}
160182
}
161183

162-
return { lgtm: true, review_comment: "" };
184+
return { lgtm: true, comments: [] };
163185
};
164186

165187
public summarizeChanges = async (

0 commit comments

Comments
 (0)