Skip to content

Commit 4aafbb9

Browse files
committed
wip
1 parent 0f8f6a5 commit 4aafbb9

File tree

7 files changed

+167
-71
lines changed

7 files changed

+167
-71
lines changed

src/github/process-review.ts

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { getConfig } from "../config.js";
22
import { reviewDiff } from "../review/reviewer.js";
33
import { GitHubClient } from "./client.js";
44
import { CHECK_STATUS, CHECK_CONCLUSION, PRDetails, GitHubPullRequestEvent } from "./types.js";
5+
import { startReviewSession, endReviewSession } from "../review/comment-collector.js";
56

67
export async function processReview(
78
jobId: string,
@@ -61,24 +62,61 @@ export async function processReview(
6162

6263
const prDetailsContent = `Repository: ${payload.repository.full_name}, PR Number: ${prDetails.pr_number}, Commit SHA: ${prDetails.commit_sha}, PR URL: ${prDetails.pr_url}`;
6364

65+
console.log(`Starting review session for job ${jobId}`);
66+
const sessionId = `session-${jobId}`;
67+
startReviewSession(sessionId);
68+
6469
console.log(`Calling reviewDiff() for job ${jobId}`);
65-
const reviewResult = await reviewDiff(diffContent, prDetailsContent, installationId);
70+
await reviewDiff(diffContent, prDetailsContent, installationId, sessionId);
6671
console.log(`Review completed for job ${jobId}`);
6772

68-
// Update check run with success
73+
// Collect all comments from the review session
74+
const collector = endReviewSession(sessionId);
75+
console.log(`Collected ${collector.getInlineComments().length} inline comments and ${collector.getGeneralComments().length} general comments`);
76+
77+
// Post aggregated review if there are any comments
78+
if (collector.hasComments()) {
79+
console.log('📋 Posting aggregated PR review...');
80+
const reviewSummary = collector.getReviewSummary();
81+
const inlineComments = collector.getInlineComments();
82+
83+
await githubClient.createPRReview(
84+
owner,
85+
repo,
86+
prNumber,
87+
reviewSummary,
88+
'COMMENT',
89+
inlineComments.map(comment => ({
90+
path: comment.path,
91+
line: comment.line,
92+
body: comment.body
93+
}))
94+
);
95+
console.log('✅ PR review posted successfully');
96+
}
97+
98+
// Update check run with success (simplified since review details are now in PR review)
6999
await githubClient.updateCheckRun(owner, repo, checkRun.id, {
70100
status: CHECK_STATUS.COMPLETED,
71101
conclusion: CHECK_CONCLUSION.SUCCESS,
72102
output: {
73103
title: 'Code Review Completed',
74-
summary: 'Code review has been completed successfully.',
75-
text: reviewResult.result || 'Review completed successfully.',
104+
summary: 'Code review has been completed successfully. See PR conversation for details.',
76105
},
77106
details_url: prUrl,
78107
});
79108

80109
} catch (error) {
81110
console.error(`Review job ${jobId} failed with exception:`, error);
111+
112+
// End review session if it was started
113+
try {
114+
const sessionId = `session-${jobId}`;
115+
endReviewSession(sessionId);
116+
} catch {
117+
console.log('No active review session to end');
118+
}
119+
82120
// Post FAILED check run for exceptions
83121
await postFailureCheckRun(githubClient, payload, error instanceof Error ? error.message : String(error));
84122
}

src/mcp/server.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -149,10 +149,7 @@ class GitHubMCPServer {
149149
case 'leave_general_comment': {
150150
console.log(`🗨️ Executing leave_general_comment...`);
151151
const validatedArgs = validateLeaveGeneralCommentArgs(args);
152-
const result = await leaveGeneralComment(
153-
validatedArgs,
154-
this.config
155-
);
152+
const result = await leaveGeneralComment(validatedArgs);
156153
console.log(`✅ leave_general_comment completed in ${Date.now() - startTime}ms`);
157154
return {
158155
content: [
@@ -167,10 +164,7 @@ class GitHubMCPServer {
167164
case 'leave_inline_comment': {
168165
console.log(`📝 Executing leave_inline_comment...`);
169166
const validatedArgs = validateLeaveInlineCommentArgs(args);
170-
const result = await leaveInlineComment(
171-
validatedArgs,
172-
this.config
173-
);
167+
const result = await leaveInlineComment(validatedArgs);
174168
console.log(`✅ leave_inline_comment completed in ${Date.now() - startTime}ms`);
175169
return {
176170
content: [

src/mcp/tools/leave_comment.ts

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { GitHubClient } from '../../github/client.js';
2-
import { Config } from '../../config.js';
1+
import { getCurrentCollector } from '../../review/comment-collector.js';
32

43
export interface LeaveGeneralCommentArgs {
54
message: string;
@@ -9,21 +8,27 @@ export interface LeaveGeneralCommentArgs {
98
}
109

1110
export async function leaveGeneralComment(
12-
args: LeaveGeneralCommentArgs,
13-
config: Config
14-
): Promise<{ success: boolean; comment_id?: number; error?: string }> {
11+
args: LeaveGeneralCommentArgs
12+
): Promise<{ success: boolean; error?: string }> {
1513
try {
16-
const { message, owner, repo, pr_number } = args;
14+
const { message } = args;
1715

18-
const githubClient = GitHubClient.fromEnv(config);
19-
const response = await githubClient.createPRComment(owner, repo, pr_number, message);
16+
console.log('📝 Collecting general comment for later review');
17+
18+
// Get session ID from environment (passed by the review process)
19+
const sessionId = process.env.REVIEW_SESSION_ID;
20+
if (!sessionId) {
21+
throw new Error('No REVIEW_SESSION_ID found in environment. Review session not properly initialized.');
22+
}
23+
24+
const collector = getCurrentCollector(sessionId);
25+
collector.addGeneralComment(message);
2026

2127
return {
22-
success: true,
23-
comment_id: response.id
28+
success: true
2429
};
2530
} catch (error) {
26-
console.error('Failed to leave general comment:', error);
31+
console.error('Failed to collect general comment:', error);
2732
return {
2833
success: false,
2934
error: error instanceof Error ? error.message : 'Unknown error'

src/mcp/tools/leave_inline_comment.ts

Lines changed: 14 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { GitHubClient } from '../../github/client.js';
2-
import { Config } from '../../config.js';
1+
import { getCurrentCollector } from '../../review/comment-collector.js';
32

43
export interface LeaveInlineCommentArgs {
54
message: string;
@@ -12,57 +11,27 @@ export interface LeaveInlineCommentArgs {
1211
}
1312

1413
export async function leaveInlineComment(
15-
args: LeaveInlineCommentArgs,
16-
config: Config
17-
): Promise<{ success: boolean; review_id?: number; error?: string }> {
14+
args: LeaveInlineCommentArgs
15+
): Promise<{ success: boolean; error?: string }> {
1816
try {
19-
const { message, owner, repo, pr_number, path, line, commit_sha } = args;
17+
const { message, path, line } = args;
2018

21-
const githubClient = GitHubClient.fromEnv(config);
22-
23-
console.log('🎯 Creating inline comment via PR review:', { path, line });
24-
25-
// Get commit SHA if not provided
26-
let actualCommitSha = commit_sha;
19+
console.log('📝 Collecting inline comment for later review:', { path, line });
2720

28-
if (!actualCommitSha) {
29-
console.log('🔍 Fetching commit SHA from PR...');
30-
try {
31-
const prInfo = await githubClient.getPRInfo(owner, repo, pr_number);
32-
actualCommitSha = prInfo.head.sha;
33-
console.log('✅ Using commit SHA:', actualCommitSha?.substring(0, 8));
34-
} catch (error) {
35-
console.log('⚠️ Could not fetch PR info for commit SHA:', error);
36-
throw new Error('Cannot create inline comment without valid commit SHA');
37-
}
38-
}
39-
40-
if (!actualCommitSha) {
41-
throw new Error('Missing required commit SHA for inline comment');
21+
// Get session ID from environment (passed by the review process)
22+
const sessionId = process.env.REVIEW_SESSION_ID;
23+
if (!sessionId) {
24+
throw new Error('No REVIEW_SESSION_ID found in environment. Review session not properly initialized.');
4225
}
43-
44-
console.log('📍 Creating PR review with inline comment at:', { path, line });
45-
46-
// Create a review with inline comment
47-
const response = await githubClient.createPRReview(
48-
owner,
49-
repo,
50-
pr_number,
51-
'', // Empty body to avoid duplication
52-
'COMMENT',
53-
[{
54-
path,
55-
line,
56-
body: message
57-
}]
58-
);
26+
27+
const collector = getCurrentCollector(sessionId);
28+
collector.addInlineComment(path, line, message);
5929

6030
return {
61-
success: true,
62-
review_id: response.id
31+
success: true
6332
};
6433
} catch (error) {
65-
console.error('Failed to leave inline comment:', error);
34+
console.error('Failed to collect inline comment:', error);
6635
return {
6736
success: false,
6837
error: error instanceof Error ? error.message : 'Unknown error'

src/review/comment-collector.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// Comment collection system for aggregating MCP tool comments into a single PR review
2+
3+
export interface InlineComment {
4+
path: string;
5+
line: number;
6+
body: string;
7+
}
8+
9+
export interface GeneralComment {
10+
body: string;
11+
}
12+
13+
export class ReviewCommentCollector {
14+
private inlineComments: InlineComment[] = [];
15+
private generalComments: GeneralComment[] = [];
16+
17+
addInlineComment(path: string, line: number, body: string): void {
18+
this.inlineComments.push({ path, line, body });
19+
}
20+
21+
addGeneralComment(body: string): void {
22+
this.generalComments.push({ body });
23+
}
24+
25+
getInlineComments(): InlineComment[] {
26+
return [...this.inlineComments];
27+
}
28+
29+
getGeneralComments(): GeneralComment[] {
30+
return [...this.generalComments];
31+
}
32+
33+
getReviewSummary(): string {
34+
const generalText = this.generalComments.map(c => c.body).join('\n\n');
35+
36+
if (!generalText) {
37+
return 'Code review completed.';
38+
}
39+
40+
return generalText;
41+
}
42+
43+
hasComments(): boolean {
44+
return this.inlineComments.length > 0 || this.generalComments.length > 0;
45+
}
46+
47+
clear(): void {
48+
this.inlineComments = [];
49+
this.generalComments = [];
50+
}
51+
}
52+
53+
// Global collectors keyed by session ID for concurrent reviews
54+
const activeCollectors = new Map<string, ReviewCommentCollector>();
55+
56+
export function startReviewSession(sessionId: string): ReviewCommentCollector {
57+
const collector = new ReviewCommentCollector();
58+
activeCollectors.set(sessionId, collector);
59+
console.log(`Started review session: ${sessionId}`);
60+
return collector;
61+
}
62+
63+
export function getCurrentCollector(sessionId: string): ReviewCommentCollector {
64+
const collector = activeCollectors.get(sessionId);
65+
if (!collector) {
66+
throw new Error(`No active review session found for ID: ${sessionId}. Call startReviewSession() first.`);
67+
}
68+
return collector;
69+
}
70+
71+
export function endReviewSession(sessionId: string): ReviewCommentCollector {
72+
const collector = activeCollectors.get(sessionId);
73+
if (!collector) {
74+
throw new Error(`No active review session found for ID: ${sessionId}.`);
75+
}
76+
activeCollectors.delete(sessionId);
77+
console.log(`Ended review session: ${sessionId}`);
78+
return collector;
79+
}

src/review/reviewer.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { Config, getConfig } from "../config.js";
66
import { newThread, execute } from "../amp.js";
77

88

9-
export const reviewDiff = async (diffContent: string, mrDetailsContent: string, installationId: number) => {
9+
export const reviewDiff = async (diffContent: string, mrDetailsContent: string, installationId: number, sessionId?: string) => {
1010

1111
// Get config
1212
const config: Config = getConfig();
@@ -45,14 +45,15 @@ export const reviewDiff = async (diffContent: string, mrDetailsContent: string,
4545
// Write settings to file with installation ID
4646
const settings = { ...ampConfig.settings };
4747

48-
// Ensure GitHub MCP server environment exists and set installation ID
48+
// Ensure GitHub MCP server environment exists and set installation ID and session ID
4949
settings['amp.mcpServers'] = {
5050
...settings['amp.mcpServers'],
5151
github: {
5252
...settings['amp.mcpServers']?.github,
5353
env: {
5454
...settings['amp.mcpServers']?.github?.env,
5555
GITHUB_INSTALLATION_ID: installationId.toString(),
56+
...(sessionId && { REVIEW_SESSION_ID: sessionId }),
5657
}
5758
}
5859
};

src/routes/github.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const github = new Hono();
1212
const config: Config = getConfig();
1313

1414
// PR actions that trigger code reviews
15-
const REVIEW_TRIGGER_ACTIONS = ['opened', 'reopened', 'ready_for_review'];
15+
const REVIEW_TRIGGER_ACTIONS = ['opened', 'reopened', 'ready_for_review', 'review_requested'];
1616

1717
// We'll need to receive the reviewQueue from the main server
1818
let reviewQueue: ReviewJobQueue | null = null;
@@ -58,6 +58,16 @@ async function handlePullRequestEvent(payload: GitHubPullRequestEvent) {
5858
return { message: 'Action ignored' };
5959
}
6060

61+
// For review_requested actions, only respond if our bot was requested
62+
if (action === 'review_requested') {
63+
const requestedReviewer = (payload as any).requested_reviewer;
64+
if (!requestedReviewer || requestedReviewer.type !== 'Bot') {
65+
return { message: 'Review not requested for this bot' };
66+
}
67+
// You could add additional bot name checking here if needed
68+
console.log('Re-review requested for bot:', requestedReviewer.login);
69+
}
70+
6171
console.log(`Processing PR ${payload.pull_request.number} action: ${action}`);
6272

6373
// Extract installation ID directly from webhook payload

0 commit comments

Comments
 (0)