edsger 0.4.11 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Code Review Analyzer
3
+ * Reviews GitHub PR code and creates review comments with REQUEST_CHANGES
4
+ */
5
+ import { EdsgerConfig } from '../../types/index.js';
6
+ export interface CodeReviewOptions {
7
+ featureId: string;
8
+ mcpServerUrl: string;
9
+ mcpToken: string;
10
+ githubToken: string;
11
+ verbose?: boolean;
12
+ }
13
+ export interface ReviewComment {
14
+ path: string;
15
+ line: number;
16
+ side: 'LEFT' | 'RIGHT';
17
+ body: string;
18
+ }
19
+ export interface CodeReviewResult {
20
+ featureId: string;
21
+ status: 'success' | 'error';
22
+ message: string;
23
+ reviewId?: number;
24
+ reviewUrl?: string;
25
+ commentsCount?: number;
26
+ summary?: string;
27
+ }
28
+ /**
29
+ * Main code review function
30
+ */
31
+ export declare const reviewPullRequest: (options: CodeReviewOptions, config: EdsgerConfig) => Promise<CodeReviewResult>;
32
+ export declare function checkCodeReviewRequirements(): Promise<boolean>;
@@ -0,0 +1,369 @@
1
+ /**
2
+ * Code Review Analyzer
3
+ * Reviews GitHub PR code and creates review comments with REQUEST_CHANGES
4
+ */
5
+ import { query } from '@anthropic-ai/claude-code';
6
+ import { logInfo, logError } from '../../utils/logger.js';
7
+ import { Octokit } from '@octokit/rest';
8
+ import { fetchCodeReviewContext, formatContextForPrompt, } from './context-fetcher.js';
9
+ import { getFeedbacksForPhase, formatFeedbacksForContext, } from '../../services/feedbacks.js';
10
+ function userMessage(content) {
11
+ return {
12
+ type: 'user',
13
+ message: { role: 'user', content: content },
14
+ };
15
+ }
16
+ async function* prompt(reviewPrompt) {
17
+ yield userMessage(reviewPrompt);
18
+ await new Promise((res) => setTimeout(res, 10000));
19
+ }
20
+ /**
21
+ * Main code review function
22
+ */
23
+ export const reviewPullRequest = async (options, config) => {
24
+ const { featureId, mcpServerUrl, mcpToken, githubToken, verbose } = options;
25
+ if (verbose) {
26
+ logInfo(`Starting code review for feature ID: ${featureId}`);
27
+ logInfo(`Using MCP server: ${mcpServerUrl}`);
28
+ }
29
+ try {
30
+ // Fetch code review context (PR data, files, commits)
31
+ if (verbose) {
32
+ logInfo('Fetching code review context from GitHub PR...');
33
+ }
34
+ const context = await fetchCodeReviewContext(mcpServerUrl, mcpToken, featureId, githubToken, verbose);
35
+ // Check if there are any files to review
36
+ if (context.files.length === 0) {
37
+ if (verbose) {
38
+ logInfo('✅ No files to review in the PR.');
39
+ }
40
+ return {
41
+ featureId,
42
+ status: 'success',
43
+ message: 'No files to review',
44
+ summary: 'No changed files found in the PR',
45
+ };
46
+ }
47
+ if (verbose) {
48
+ logInfo(`📋 Found ${context.files.length} files to review across ${context.commits.length} commits`);
49
+ }
50
+ // Fetch additional feedbacks for code-review phase
51
+ let feedbacksInfo;
52
+ try {
53
+ const feedbacksContext = await getFeedbacksForPhase({ featureId, mcpServerUrl, mcpToken, verbose }, 'code_review');
54
+ if (feedbacksContext.feedbacks.length > 0) {
55
+ feedbacksInfo = formatFeedbacksForContext(feedbacksContext);
56
+ if (verbose) {
57
+ logInfo(`Added ${feedbacksContext.feedbacks.length} human feedbacks to code review context`);
58
+ }
59
+ }
60
+ }
61
+ catch (error) {
62
+ if (verbose) {
63
+ logInfo(`Note: Could not fetch feedbacks (${error instanceof Error ? error.message : String(error)})`);
64
+ }
65
+ }
66
+ // Create prompt for code review
67
+ const systemPrompt = createSystemPrompt(config);
68
+ const reviewPrompt = createCodeReviewPrompt(featureId, context, feedbacksInfo);
69
+ let lastAssistantResponse = '';
70
+ let structuredReviewResult = null;
71
+ if (verbose) {
72
+ logInfo('Starting Claude analysis for code review...');
73
+ }
74
+ // Use Claude to analyze the code (not using Claude Code SDK for actual implementation)
75
+ // This is a simplified approach - just analyze the code
76
+ for await (const message of query({
77
+ prompt: prompt(reviewPrompt),
78
+ options: {
79
+ appendSystemPrompt: systemPrompt,
80
+ model: config.claude.model || 'sonnet',
81
+ maxTurns: 50, // Limited turns since this is just analysis
82
+ permissionMode: 'bypassPermissions',
83
+ },
84
+ })) {
85
+ if (verbose) {
86
+ logInfo(`Received message type: ${message.type}`);
87
+ }
88
+ // Stream the code review process
89
+ if (message.type === 'assistant' && message.message?.content) {
90
+ for (const content of message.message.content) {
91
+ if (content.type === 'text') {
92
+ lastAssistantResponse += content.text + '\n';
93
+ if (verbose) {
94
+ console.log(`\n🔍 ${content.text}`);
95
+ }
96
+ }
97
+ }
98
+ }
99
+ else if (message.type === 'result') {
100
+ if (message.subtype === 'success') {
101
+ logInfo('\n🛠️ Code review analysis completed, parsing results...');
102
+ try {
103
+ const responseText = message.result || lastAssistantResponse;
104
+ let jsonResult = null;
105
+ const jsonBlockMatch = responseText.match(/```json\s*\n([\s\S]*?)\n\s*```/);
106
+ if (jsonBlockMatch) {
107
+ jsonResult = JSON.parse(jsonBlockMatch[1]);
108
+ }
109
+ else {
110
+ jsonResult = JSON.parse(responseText);
111
+ }
112
+ if (jsonResult && jsonResult.review_result) {
113
+ structuredReviewResult = jsonResult.review_result;
114
+ }
115
+ else {
116
+ throw new Error('Invalid JSON structure');
117
+ }
118
+ }
119
+ catch (error) {
120
+ logError(`Failed to parse structured review result: ${error}`);
121
+ structuredReviewResult = parseCodeReviewResponse(message.result || lastAssistantResponse);
122
+ }
123
+ }
124
+ else {
125
+ logError(`\n⚠️ Code review incomplete: ${message.subtype}`);
126
+ if (message.subtype === 'error_max_turns') {
127
+ logError('💡 Try simplifying the review scope');
128
+ }
129
+ if (lastAssistantResponse) {
130
+ try {
131
+ const responseText = lastAssistantResponse;
132
+ let jsonResult = null;
133
+ const jsonBlockMatch = responseText.match(/```json\s*\n([\s\S]*?)\n\s*```/);
134
+ if (jsonBlockMatch) {
135
+ jsonResult = JSON.parse(jsonBlockMatch[1]);
136
+ if (jsonResult && jsonResult.review_result) {
137
+ structuredReviewResult = jsonResult.review_result;
138
+ }
139
+ }
140
+ else {
141
+ structuredReviewResult = parseCodeReviewResponse(lastAssistantResponse);
142
+ }
143
+ }
144
+ catch (error) {
145
+ logError(`Failed to parse assistant response: ${error}`);
146
+ }
147
+ }
148
+ }
149
+ }
150
+ }
151
+ // Create GitHub review with comments
152
+ if (structuredReviewResult) {
153
+ const { summary, comments, overall_assessment } = structuredReviewResult;
154
+ // Initialize Octokit
155
+ const octokit = new Octokit({ auth: githubToken });
156
+ if (!comments || comments.length === 0) {
157
+ if (verbose) {
158
+ logInfo('✅ No issues found. PR looks good!');
159
+ }
160
+ // Create an approval review if no issues found
161
+ const review = await octokit.pulls.createReview({
162
+ owner: context.owner,
163
+ repo: context.repo,
164
+ pull_number: context.pullRequestNumber,
165
+ event: 'APPROVE',
166
+ body: summary ||
167
+ overall_assessment ||
168
+ 'Code review completed. No issues found.',
169
+ });
170
+ return {
171
+ featureId,
172
+ status: 'success',
173
+ message: 'Code review completed - no issues found',
174
+ reviewId: review.data.id,
175
+ reviewUrl: review.data.html_url,
176
+ commentsCount: 0,
177
+ summary: summary || 'No issues found',
178
+ };
179
+ }
180
+ if (verbose) {
181
+ logInfo(`Creating GitHub review with ${comments.length} comments...`);
182
+ }
183
+ // Create review with inline comments
184
+ const reviewComments = comments.map((comment) => ({
185
+ path: comment.file || comment.path,
186
+ line: comment.line,
187
+ body: comment.comment || comment.body,
188
+ }));
189
+ const review = await octokit.pulls.createReview({
190
+ owner: context.owner,
191
+ repo: context.repo,
192
+ pull_number: context.pullRequestNumber,
193
+ event: 'COMMENT',
194
+ body: summary ||
195
+ overall_assessment ||
196
+ 'Please address the following review comments.',
197
+ comments: reviewComments,
198
+ });
199
+ if (verbose) {
200
+ logInfo(`✅ GitHub review created: ${review.data.html_url}`);
201
+ logInfo(`Review ID: ${review.data.id}`);
202
+ logInfo(`Comments posted: ${comments.length}`);
203
+ }
204
+ return {
205
+ featureId,
206
+ status: 'success',
207
+ message: 'Code review completed and posted to GitHub',
208
+ reviewId: review.data.id,
209
+ reviewUrl: review.data.html_url,
210
+ commentsCount: comments.length,
211
+ summary: summary || 'Code review completed',
212
+ };
213
+ }
214
+ else {
215
+ return {
216
+ featureId,
217
+ status: 'error',
218
+ message: 'Code review analysis failed or incomplete',
219
+ };
220
+ }
221
+ }
222
+ catch (error) {
223
+ const errorMessage = error instanceof Error ? error.message : String(error);
224
+ logError(`Code review failed: ${errorMessage}`);
225
+ return {
226
+ featureId,
227
+ status: 'error',
228
+ message: `Code review failed: ${errorMessage}`,
229
+ };
230
+ }
231
+ };
232
+ function createSystemPrompt(_config) {
233
+ return `You are an expert code reviewer specializing in thorough, constructive code reviews. Your goal is to analyze pull request code and identify issues, potential bugs, code quality concerns, and areas for improvement.
234
+
235
+ **Your Role**: Review pull request code and provide detailed, actionable feedback.
236
+
237
+ **Review Focus Areas**:
238
+ 1. **Code Quality**: Clean code principles, readability, maintainability
239
+ 2. **Best Practices**: Language-specific conventions, design patterns
240
+ 3. **Potential Bugs**: Logic errors, edge cases, error handling
241
+ 4. **Security**: Security vulnerabilities, input validation
242
+ 5. **Performance**: Performance issues, optimization opportunities
243
+ 6. **Testing**: Test coverage, test quality
244
+ 7. **Documentation**: Code comments, documentation completeness
245
+ 8. **Architecture**: Design decisions, coupling, cohesion
246
+
247
+ **Important Guidelines**:
248
+ - Be thorough but focus on significant issues
249
+ - Provide constructive, actionable feedback
250
+ - Suggest specific improvements
251
+ - Consider the technical design and requirements
252
+ - Be respectful and professional
253
+ - Focus on issues that truly matter, not nitpicks
254
+ - If code looks good overall, provide positive feedback
255
+
256
+ **CRITICAL - Result Format**:
257
+ You MUST end your response with a JSON object containing the review results in this EXACT format:
258
+
259
+ \`\`\`json
260
+ {
261
+ "review_result": {
262
+ "feature_id": "FEATURE_ID_PLACEHOLDER",
263
+ "summary": "Overall review summary and assessment",
264
+ "overall_assessment": "APPROVE | REQUEST_CHANGES | COMMENT",
265
+ "comments": [
266
+ {
267
+ "file": "path/to/file.ts",
268
+ "line": 42,
269
+ "comment": "Detailed comment about the issue and suggested fix"
270
+ }
271
+ ],
272
+ "issues_found": {
273
+ "critical": 0,
274
+ "major": 0,
275
+ "minor": 0
276
+ }
277
+ }
278
+ }
279
+ \`\`\`
280
+
281
+ **Comment Guidelines**:
282
+ - Each comment should reference a specific file and line number
283
+ - Be specific about what the issue is
284
+ - Provide a suggested fix or improvement
285
+ - Use a constructive, helpful tone
286
+
287
+ **Assessment Options**:
288
+ - **APPROVE**: Code looks good, no significant issues
289
+ - **REQUEST_CHANGES**: Issues found that should be addressed
290
+ - **COMMENT**: Minor suggestions or questions, not blocking
291
+
292
+ Focus on providing valuable, actionable code review feedback.`;
293
+ }
294
+ function createCodeReviewPrompt(featureId, context, feedbacksInfo) {
295
+ let contextInfo = formatContextForPrompt(context);
296
+ if (feedbacksInfo) {
297
+ contextInfo = contextInfo + '\n\n' + feedbacksInfo;
298
+ }
299
+ return `Review the pull request code for feature: ${featureId}
300
+
301
+ ${contextInfo}
302
+
303
+ ## Code Review Instructions
304
+
305
+ Follow this systematic approach:
306
+
307
+ 1. **Understand the Context**: Review the feature description, technical design, user stories, and test cases to understand what the code should accomplish.
308
+
309
+ 2. **Analyze Each File**: For each changed file:
310
+ - Review the code changes in the diff
311
+ - Look for code quality issues
312
+ - Check for potential bugs or logic errors
313
+ - Verify error handling
314
+ - Consider security implications
315
+ - Evaluate performance considerations
316
+ - Check test coverage
317
+
318
+ 3. **Check Against Requirements**:
319
+ - Does the code implement the technical design correctly?
320
+ - Are all user stories addressed?
321
+ - Are test cases covered?
322
+
323
+ 4. **Identify Issues**: Categorize issues by severity:
324
+ - **Critical**: Security vulnerabilities, data loss risks, major bugs
325
+ - **Major**: Logic errors, incorrect implementations, missing error handling
326
+ - **Minor**: Code quality improvements, style issues, optimization suggestions
327
+
328
+ 5. **Provide Actionable Feedback**: For each issue:
329
+ - Reference the specific file and line number
330
+ - Explain what the issue is
331
+ - Suggest how to fix it
332
+ - Be constructive and helpful
333
+
334
+ 6. **Generate Review Result**: Create a structured JSON response with:
335
+ - Overall summary
336
+ - Assessment (APPROVE/REQUEST_CHANGES/COMMENT)
337
+ - List of comments with file, line, and comment text
338
+ - Count of issues by severity
339
+
340
+ ## Important Notes
341
+ - Be thorough but focus on meaningful issues
342
+ - Provide specific, actionable suggestions
343
+ - Consider the context and requirements
344
+ - Be professional and constructive
345
+ - If code is good, say so!
346
+
347
+ Begin by analyzing the changed files and identifying any issues or improvements.`;
348
+ }
349
+ function parseCodeReviewResponse(response) {
350
+ const summaryMatch = response.match(/## Review Summary\n([\s\S]*?)(?=\n##|\n\n|$)/);
351
+ const summary = summaryMatch
352
+ ? summaryMatch[1].trim()
353
+ : 'Code review completed';
354
+ return {
355
+ summary,
356
+ comments: [],
357
+ overall_assessment: 'COMMENT',
358
+ issues_found: {
359
+ critical: 0,
360
+ major: 0,
361
+ minor: 0,
362
+ },
363
+ };
364
+ }
365
+ export function checkCodeReviewRequirements() {
366
+ // Dependencies are declared in package.json and will be installed automatically
367
+ // No need to check for them here
368
+ return Promise.resolve(true);
369
+ }
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Context fetcher for code review phase
3
+ * Fetches GitHub PR data including files, diffs, and commits for review
4
+ */
5
+ import { Octokit } from '@octokit/rest';
6
+ export interface PRFile {
7
+ filename: string;
8
+ status: string;
9
+ additions: number;
10
+ deletions: number;
11
+ changes: number;
12
+ patch?: string;
13
+ blob_url: string;
14
+ }
15
+ export interface PRCommit {
16
+ sha: string;
17
+ commit: {
18
+ message: string;
19
+ author: {
20
+ name: string;
21
+ date: string;
22
+ };
23
+ };
24
+ }
25
+ export interface PRData {
26
+ number: number;
27
+ title: string;
28
+ body: string | null;
29
+ state: string;
30
+ head: {
31
+ ref: string;
32
+ sha: string;
33
+ };
34
+ base: {
35
+ ref: string;
36
+ sha: string;
37
+ };
38
+ user: {
39
+ login: string;
40
+ };
41
+ }
42
+ export interface CodeReviewContext {
43
+ featureId: string;
44
+ featureName: string;
45
+ featureDescription: string | null;
46
+ pullRequestUrl: string;
47
+ pullRequestNumber: number;
48
+ owner: string;
49
+ repo: string;
50
+ prData: PRData;
51
+ files: PRFile[];
52
+ commits: PRCommit[];
53
+ technicalDesign?: string;
54
+ userStories?: any[];
55
+ testCases?: any[];
56
+ }
57
+ /**
58
+ * Extract owner, repo, and PR number from GitHub PR URL
59
+ */
60
+ export declare function parsePullRequestUrl(pullRequestUrl: string): {
61
+ owner: string;
62
+ repo: string;
63
+ prNumber: number;
64
+ } | null;
65
+ /**
66
+ * Fetch PR details
67
+ */
68
+ export declare function fetchPRDetails(octokit: Octokit, owner: string, repo: string, prNumber: number, verbose?: boolean): Promise<PRData>;
69
+ /**
70
+ * Fetch PR files (changed files with diffs)
71
+ */
72
+ export declare function fetchPRFiles(octokit: Octokit, owner: string, repo: string, prNumber: number, verbose?: boolean): Promise<PRFile[]>;
73
+ /**
74
+ * Fetch PR commits
75
+ */
76
+ export declare function fetchPRCommits(octokit: Octokit, owner: string, repo: string, prNumber: number, verbose?: boolean): Promise<PRCommit[]>;
77
+ /**
78
+ * Fetch user stories via MCP
79
+ */
80
+ export declare function fetchUserStories(mcpServerUrl: string, mcpToken: string, featureId: string, verbose?: boolean): Promise<any[]>;
81
+ /**
82
+ * Fetch test cases via MCP
83
+ */
84
+ export declare function fetchTestCases(mcpServerUrl: string, mcpToken: string, featureId: string, verbose?: boolean): Promise<any[]>;
85
+ /**
86
+ * Fetch complete code review context
87
+ */
88
+ export declare function fetchCodeReviewContext(mcpServerUrl: string, mcpToken: string, featureId: string, githubToken: string, verbose?: boolean): Promise<CodeReviewContext>;
89
+ /**
90
+ * Format code review context for prompt
91
+ */
92
+ export declare function formatContextForPrompt(context: CodeReviewContext): string;
@@ -0,0 +1,292 @@
1
+ /**
2
+ * Context fetcher for code review phase
3
+ * Fetches GitHub PR data including files, diffs, and commits for review
4
+ */
5
+ import { Octokit } from '@octokit/rest';
6
+ import { getFeature } from '../../api/features/get-feature.js';
7
+ /**
8
+ * Extract owner, repo, and PR number from GitHub PR URL
9
+ */
10
+ export function parsePullRequestUrl(pullRequestUrl) {
11
+ const match = pullRequestUrl.match(/github\.com\/([^\/]+)\/([^\/]+)\/pull\/(\d+)/);
12
+ if (!match) {
13
+ return null;
14
+ }
15
+ return {
16
+ owner: match[1],
17
+ repo: match[2],
18
+ prNumber: parseInt(match[3], 10),
19
+ };
20
+ }
21
+ /**
22
+ * Fetch PR details
23
+ */
24
+ export async function fetchPRDetails(octokit, owner, repo, prNumber, verbose) {
25
+ if (verbose) {
26
+ console.log(`📋 Fetching PR details for ${owner}/${repo}#${prNumber}...`);
27
+ }
28
+ const { data } = await octokit.pulls.get({
29
+ owner,
30
+ repo,
31
+ pull_number: prNumber,
32
+ });
33
+ if (verbose) {
34
+ console.log(`✅ Fetched PR: ${data.title}`);
35
+ }
36
+ return data;
37
+ }
38
+ /**
39
+ * Fetch PR files (changed files with diffs)
40
+ */
41
+ export async function fetchPRFiles(octokit, owner, repo, prNumber, verbose) {
42
+ if (verbose) {
43
+ console.log(`📂 Fetching PR files for ${owner}/${repo}#${prNumber}...`);
44
+ }
45
+ const { data: files } = await octokit.pulls.listFiles({
46
+ owner,
47
+ repo,
48
+ pull_number: prNumber,
49
+ per_page: 100,
50
+ });
51
+ if (verbose) {
52
+ console.log(`✅ Found ${files.length} changed files`);
53
+ }
54
+ return files;
55
+ }
56
+ /**
57
+ * Fetch PR commits
58
+ */
59
+ export async function fetchPRCommits(octokit, owner, repo, prNumber, verbose) {
60
+ if (verbose) {
61
+ console.log(`💾 Fetching PR commits for ${owner}/${repo}#${prNumber}...`);
62
+ }
63
+ const { data: commits } = await octokit.pulls.listCommits({
64
+ owner,
65
+ repo,
66
+ pull_number: prNumber,
67
+ per_page: 100,
68
+ });
69
+ if (verbose) {
70
+ console.log(`✅ Found ${commits.length} commits`);
71
+ }
72
+ return commits;
73
+ }
74
+ /**
75
+ * Fetch user stories via MCP
76
+ */
77
+ export async function fetchUserStories(mcpServerUrl, mcpToken, featureId, verbose) {
78
+ try {
79
+ if (verbose) {
80
+ console.log(`📖 Fetching user stories for ${featureId}...`);
81
+ }
82
+ const response = await fetch(`${mcpServerUrl}/mcp`, {
83
+ method: 'POST',
84
+ headers: {
85
+ 'Content-Type': 'application/json',
86
+ Authorization: `Bearer ${mcpToken}`,
87
+ },
88
+ body: JSON.stringify({
89
+ jsonrpc: '2.0',
90
+ id: 1,
91
+ method: 'user_stories/list',
92
+ params: {
93
+ feature_id: featureId,
94
+ },
95
+ }),
96
+ });
97
+ if (!response.ok) {
98
+ if (verbose) {
99
+ console.log(`⚠️ Could not fetch user stories: ${response.status}`);
100
+ }
101
+ return [];
102
+ }
103
+ const data = await response.json();
104
+ if (data.error || !data.result) {
105
+ if (verbose) {
106
+ console.log(`⚠️ User stories not available`);
107
+ }
108
+ return [];
109
+ }
110
+ return data.result.user_stories || [];
111
+ }
112
+ catch (error) {
113
+ if (verbose) {
114
+ console.log(`⚠️ Error fetching user stories: ${error}`);
115
+ }
116
+ return [];
117
+ }
118
+ }
119
+ /**
120
+ * Fetch test cases via MCP
121
+ */
122
+ export async function fetchTestCases(mcpServerUrl, mcpToken, featureId, verbose) {
123
+ try {
124
+ if (verbose) {
125
+ console.log(`🧪 Fetching test cases for ${featureId}...`);
126
+ }
127
+ const response = await fetch(`${mcpServerUrl}/mcp`, {
128
+ method: 'POST',
129
+ headers: {
130
+ 'Content-Type': 'application/json',
131
+ Authorization: `Bearer ${mcpToken}`,
132
+ },
133
+ body: JSON.stringify({
134
+ jsonrpc: '2.0',
135
+ id: 1,
136
+ method: 'test_cases/list',
137
+ params: {
138
+ feature_id: featureId,
139
+ },
140
+ }),
141
+ });
142
+ if (!response.ok) {
143
+ if (verbose) {
144
+ console.log(`⚠️ Could not fetch test cases: ${response.status}`);
145
+ }
146
+ return [];
147
+ }
148
+ const data = await response.json();
149
+ if (data.error || !data.result) {
150
+ if (verbose) {
151
+ console.log(`⚠️ Test cases not available`);
152
+ }
153
+ return [];
154
+ }
155
+ return data.result.test_cases || [];
156
+ }
157
+ catch (error) {
158
+ if (verbose) {
159
+ console.log(`⚠️ Error fetching test cases: ${error}`);
160
+ }
161
+ return [];
162
+ }
163
+ }
164
+ /**
165
+ * Fetch complete code review context
166
+ */
167
+ export async function fetchCodeReviewContext(mcpServerUrl, mcpToken, featureId, githubToken, verbose) {
168
+ // Fetch feature info using shared API
169
+ const feature = await getFeature(mcpServerUrl, mcpToken, featureId, verbose);
170
+ if (!feature.pull_request_url) {
171
+ throw new Error(`Feature ${featureId} does not have a pull request URL. Cannot perform code review.`);
172
+ }
173
+ // Parse PR URL
174
+ const prInfo = parsePullRequestUrl(feature.pull_request_url);
175
+ if (!prInfo) {
176
+ throw new Error(`Invalid pull request URL: ${feature.pull_request_url}. Expected format: https://github.com/owner/repo/pull/123`);
177
+ }
178
+ const { owner, repo, prNumber } = prInfo;
179
+ // Initialize Octokit with GitHub token
180
+ const octokit = new Octokit({
181
+ auth: githubToken,
182
+ });
183
+ // Fetch PR data, files, commits, and additional context in parallel
184
+ const [prData, files, commits, userStories, testCases] = await Promise.all([
185
+ fetchPRDetails(octokit, owner, repo, prNumber, verbose),
186
+ fetchPRFiles(octokit, owner, repo, prNumber, verbose),
187
+ fetchPRCommits(octokit, owner, repo, prNumber, verbose),
188
+ fetchUserStories(mcpServerUrl, mcpToken, featureId, verbose),
189
+ fetchTestCases(mcpServerUrl, mcpToken, featureId, verbose),
190
+ ]);
191
+ if (verbose) {
192
+ console.log(`📊 Summary: ${files.length} files changed across ${commits.length} commits`);
193
+ }
194
+ return {
195
+ featureId,
196
+ featureName: feature.name,
197
+ featureDescription: feature.description ?? null,
198
+ pullRequestUrl: feature.pull_request_url,
199
+ pullRequestNumber: prNumber,
200
+ owner,
201
+ repo,
202
+ prData,
203
+ files,
204
+ commits,
205
+ technicalDesign: feature.technical_design,
206
+ userStories,
207
+ testCases,
208
+ };
209
+ }
210
+ /**
211
+ * Format code review context for prompt
212
+ */
213
+ export function formatContextForPrompt(context) {
214
+ const sections = [];
215
+ // Feature information
216
+ sections.push(`# Feature Information`);
217
+ sections.push(`**Feature ID**: ${context.featureId}`);
218
+ sections.push(`**Feature Name**: ${context.featureName}`);
219
+ if (context.featureDescription) {
220
+ sections.push(`**Description**: ${context.featureDescription}`);
221
+ }
222
+ sections.push(`**Pull Request**: ${context.pullRequestUrl} (#${context.pullRequestNumber})`);
223
+ sections.push('');
224
+ // PR details
225
+ sections.push(`## Pull Request Details`);
226
+ sections.push(`**Title**: ${context.prData.title}`);
227
+ sections.push(`**Author**: @${context.prData.user.login}`);
228
+ sections.push(`**Base Branch**: ${context.prData.base.ref}`);
229
+ sections.push(`**Head Branch**: ${context.prData.head.ref}`);
230
+ if (context.prData.body) {
231
+ sections.push(`**Description**:`);
232
+ sections.push(context.prData.body);
233
+ }
234
+ sections.push('');
235
+ // Commits
236
+ if (context.commits.length > 0) {
237
+ sections.push(`## Commits (${context.commits.length})`);
238
+ sections.push('');
239
+ context.commits.forEach((commit) => {
240
+ sections.push(`- **${commit.sha.substring(0, 7)}**: ${commit.commit.message}`);
241
+ sections.push(` by ${commit.commit.author.name} on ${commit.commit.author.date}`);
242
+ });
243
+ sections.push('');
244
+ }
245
+ // Changed files
246
+ if (context.files.length > 0) {
247
+ sections.push(`## Changed Files (${context.files.length})`);
248
+ sections.push('');
249
+ context.files.forEach((file) => {
250
+ sections.push(`### ${file.filename}`);
251
+ sections.push(`**Status**: ${file.status}`);
252
+ sections.push(`**Changes**: +${file.additions} -${file.deletions}`);
253
+ if (file.patch) {
254
+ sections.push(`**Diff**:`);
255
+ sections.push('```diff');
256
+ sections.push(file.patch);
257
+ sections.push('```');
258
+ }
259
+ sections.push('');
260
+ });
261
+ }
262
+ // Technical design
263
+ if (context.technicalDesign) {
264
+ sections.push(`## Technical Design`);
265
+ sections.push('');
266
+ sections.push(context.technicalDesign);
267
+ sections.push('');
268
+ }
269
+ // User stories
270
+ if (context.userStories && context.userStories.length > 0) {
271
+ sections.push(`## User Stories`);
272
+ sections.push('');
273
+ context.userStories.forEach((story) => {
274
+ sections.push(`### ${story.title}`);
275
+ sections.push(story.description || '');
276
+ sections.push('');
277
+ });
278
+ }
279
+ // Test cases
280
+ if (context.testCases && context.testCases.length > 0) {
281
+ sections.push(`## Test Cases`);
282
+ sections.push('');
283
+ context.testCases.forEach((testCase) => {
284
+ sections.push(`### ${testCase.title}`);
285
+ if (testCase.description) {
286
+ sections.push(testCase.description);
287
+ }
288
+ sections.push('');
289
+ });
290
+ }
291
+ return sections.join('\n');
292
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Code Review Module
3
+ * Reviews pull request code and creates GitHub review comments
4
+ * Part of the automated development workflow
5
+ */
6
+ export { reviewPullRequest, checkCodeReviewRequirements, type CodeReviewOptions, type CodeReviewResult, type ReviewComment, } from './analyzer.js';
7
+ export { fetchCodeReviewContext, formatContextForPrompt, parsePullRequestUrl, type CodeReviewContext, type PRFile, type PRCommit, type PRData, } from './context-fetcher.js';
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Code Review Module
3
+ * Reviews pull request code and creates GitHub review comments
4
+ * Part of the automated development workflow
5
+ */
6
+ export { reviewPullRequest, checkCodeReviewRequirements, } from './analyzer.js';
7
+ export { fetchCodeReviewContext, formatContextForPrompt, parsePullRequestUrl, } from './context-fetcher.js';
@@ -5,7 +5,7 @@
5
5
  export interface LogPhaseEventParams {
6
6
  featureId: string;
7
7
  eventType: 'phase_started' | 'phase_completed' | 'phase_failed';
8
- phase: 'feature_analysis' | 'technical_design' | 'code_implementation' | 'functional_testing' | 'code_refine' | 'code_refine_verification';
8
+ phase: 'feature_analysis' | 'technical_design' | 'code_implementation' | 'functional_testing' | 'code_refine' | 'code_refine_verification' | 'code_review';
9
9
  result?: 'success' | 'error' | 'warning' | 'info';
10
10
  metadata?: Record<string, any>;
11
11
  errorMessage?: string;
@@ -19,7 +19,7 @@ export interface LogChecklistEventParams {
19
19
  }
20
20
  export interface LogVerificationEventParams {
21
21
  featureId: string;
22
- phase: 'feature_analysis' | 'technical_design' | 'code_implementation' | 'functional_testing' | 'code_refine' | 'code_refine_verification';
22
+ phase: 'feature_analysis' | 'technical_design' | 'code_implementation' | 'functional_testing' | 'code_refine' | 'code_refine_verification' | 'code_review';
23
23
  iteration: number;
24
24
  result: 'success' | 'error';
25
25
  verificationData: {
@@ -3,7 +3,7 @@
3
3
  */
4
4
  import { EdsgerConfig } from './index.js';
5
5
  import { ChecklistPhaseContext } from '../services/checklist.js';
6
- export type ExecutionMode = 'full_pipeline' | 'only_feature_analysis' | 'only_technical_design' | 'only_code_implementation' | 'only_functional_testing' | 'only_code_refine' | 'from_feature_analysis' | 'from_technical_design' | 'from_code_implementation' | 'from_functional_testing';
6
+ export type ExecutionMode = 'full_pipeline' | 'only_feature_analysis' | 'only_technical_design' | 'only_code_implementation' | 'only_functional_testing' | 'only_code_refine' | 'only_code_review' | 'from_feature_analysis' | 'from_technical_design' | 'from_code_implementation' | 'from_functional_testing' | 'from_code_review';
7
7
  export interface PipelinePhaseOptions {
8
8
  readonly featureId: string;
9
9
  readonly mcpServerUrl: string;
@@ -7,6 +7,7 @@ import { implementFeatureCode, checkCodeImplementationRequirements, } from '../.
7
7
  import { runFunctionalTesting, checkFunctionalTestingRequirements, } from '../../phases/functional-testing/analyzer.js';
8
8
  import { refineCodeFromPRFeedback, checkCodeRefineRequirements, } from '../../phases/code-refine/analyzer.js';
9
9
  import { verifyAndResolveComments, checkCodeRefineVerificationRequirements, } from '../../phases/code-refine-verification/verifier.js';
10
+ import { reviewPullRequest, checkCodeReviewRequirements, } from '../../phases/code-review/analyzer.js';
10
11
  /**
11
12
  * Wrapper for code-refine phase to inject GitHub token
12
13
  */
@@ -46,6 +47,25 @@ const executeCodeRefineVerification = async (options, config) => {
46
47
  verbose: options.verbose,
47
48
  });
48
49
  };
50
+ /**
51
+ * Wrapper for code-review phase to inject GitHub token
52
+ */
53
+ const executeCodeReview = async (options, config) => {
54
+ const githubToken = process.env.GITHUB_TOKEN || process.env.GITHUB_ACCESS_TOKEN;
55
+ if (!githubToken) {
56
+ return {
57
+ status: 'error',
58
+ message: 'GitHub token not found. Set GITHUB_TOKEN or GITHUB_ACCESS_TOKEN environment variable.',
59
+ };
60
+ }
61
+ return reviewPullRequest({
62
+ featureId: options.featureId,
63
+ mcpServerUrl: options.mcpServerUrl,
64
+ mcpToken: options.mcpToken,
65
+ githubToken,
66
+ verbose: options.verbose,
67
+ }, config);
68
+ };
49
69
  // Pipeline phase configurations
50
70
  export const phaseConfigs = [
51
71
  {
@@ -84,4 +104,10 @@ export const phaseConfigs = [
84
104
  execute: executeCodeRefineVerification,
85
105
  requirementsError: 'Code refine verification requirements not met. Install with: npm install @octokit/rest',
86
106
  },
107
+ {
108
+ name: 'code-review',
109
+ checkRequirements: checkCodeReviewRequirements,
110
+ execute: executeCodeReview,
111
+ requirementsError: 'Code review requirements not met. Install with: npm install @anthropic-ai/claude-code @octokit/rest',
112
+ },
87
113
  ];
@@ -4,5 +4,5 @@
4
4
  import { EdsgerConfig } from '../../types/index.js';
5
5
  import { PipelinePhaseOptions, PipelineResult, PhaseConfig } from '../../types/pipeline.js';
6
6
  export declare const createPhaseRunner: (phaseConfig: PhaseConfig) => (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>;
7
- declare const runFeatureAnalysisPhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>, runTechnicalDesignPhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>, runCodeImplementationPhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>, runFunctionalTestingPhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>, runCodeRefinePhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>, runCodeRefineVerificationPhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>;
8
- export { runFeatureAnalysisPhase, runTechnicalDesignPhase, runCodeImplementationPhase, runFunctionalTestingPhase, runCodeRefinePhase, runCodeRefineVerificationPhase, };
7
+ declare const runFeatureAnalysisPhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>, runTechnicalDesignPhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>, runCodeImplementationPhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>, runFunctionalTestingPhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>, runCodeRefinePhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>, runCodeRefineVerificationPhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>, runCodeReviewPhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>;
8
+ export { runFeatureAnalysisPhase, runTechnicalDesignPhase, runCodeImplementationPhase, runFunctionalTestingPhase, runCodeRefinePhase, runCodeRefineVerificationPhase, runCodeReviewPhase, };
@@ -243,6 +243,6 @@ export const createPhaseRunner = (phaseConfig) => async (options, config) => {
243
243
  }
244
244
  };
245
245
  // Create phase runners using the configuration
246
- const [runFeatureAnalysisPhase, runTechnicalDesignPhase, runCodeImplementationPhase, runFunctionalTestingPhase, runCodeRefinePhase, runCodeRefineVerificationPhase,] = phaseConfigs.map(createPhaseRunner);
246
+ const [runFeatureAnalysisPhase, runTechnicalDesignPhase, runCodeImplementationPhase, runFunctionalTestingPhase, runCodeRefinePhase, runCodeRefineVerificationPhase, runCodeReviewPhase,] = phaseConfigs.map(createPhaseRunner);
247
247
  // Export individual phase runners for granular control
248
- export { runFeatureAnalysisPhase, runTechnicalDesignPhase, runCodeImplementationPhase, runFunctionalTestingPhase, runCodeRefinePhase, runCodeRefineVerificationPhase, };
248
+ export { runFeatureAnalysisPhase, runTechnicalDesignPhase, runCodeImplementationPhase, runFunctionalTestingPhase, runCodeRefinePhase, runCodeRefineVerificationPhase, runCodeReviewPhase, };
@@ -83,10 +83,12 @@ export function getAvailableExecutionModes() {
83
83
  'only_code_implementation',
84
84
  'only_functional_testing',
85
85
  'only_code_refine',
86
+ 'only_code_review',
86
87
  'from_feature_analysis',
87
88
  'from_technical_design',
88
89
  'from_code_implementation',
89
90
  'from_functional_testing',
91
+ 'from_code_review',
90
92
  ];
91
93
  }
92
94
  /**
@@ -106,10 +108,12 @@ export function getExecutionModeDescription(mode) {
106
108
  only_code_implementation: 'Execute only: code implementation',
107
109
  only_functional_testing: 'Execute only: functional testing',
108
110
  only_code_refine: 'Execute only: code refine (address PR review feedback and verify resolution)',
111
+ only_code_review: 'Execute only: code review (review PR code and create review comments)',
109
112
  from_feature_analysis: 'Execute from feature analysis to end: analysis → design → implementation → testing',
110
113
  from_technical_design: 'Execute from technical design to end: design → implementation → testing',
111
114
  from_code_implementation: 'Execute from code implementation to end: implementation → testing',
112
115
  from_functional_testing: 'Execute from functional testing to end: testing',
116
+ from_code_review: 'Execute from code review to end: code-review → code-refine → code-refine-verification',
113
117
  };
114
118
  return descriptions[mode] || 'Unknown execution mode';
115
119
  }
@@ -4,7 +4,7 @@
4
4
  * Uses functional programming principles
5
5
  */
6
6
  import { updateFeatureStatusForPhase } from '../api/features/index.js';
7
- import { runFeatureAnalysisPhase, runTechnicalDesignPhase, runCodeImplementationPhase, runFunctionalTestingPhase, } from './executors/phase-executor.js';
7
+ import { runFeatureAnalysisPhase, runTechnicalDesignPhase, runCodeImplementationPhase, runFunctionalTestingPhase, runCodeReviewPhase, } from './executors/phase-executor.js';
8
8
  import { logPipelineStart, logPhaseResult, logPipelineComplete, shouldContinuePipeline, } from '../utils/pipeline-logger.js';
9
9
  import { handleTestFailuresWithRetry } from '../phases/functional-testing/test-retry-handler.js';
10
10
  import { handleCodeRefineWithRetry } from '../phases/code-refine/retry-handler.js';
@@ -53,6 +53,11 @@ export const runPipelineByMode = async (options, config, executionMode) => {
53
53
  // Code Refine
54
54
  case 'only_code_refine':
55
55
  return await runOnlyCodeRefine(options, config);
56
+ // Code Review
57
+ case 'only_code_review':
58
+ return await runOnlyCodeReview(options, config);
59
+ case 'from_code_review':
60
+ return await runFromCodeReview(options, config);
56
61
  default:
57
62
  throw new Error(`Unsupported execution mode: ${executionMode}`);
58
63
  }
@@ -283,3 +288,37 @@ const runOnlyCodeRefine = async (options, config) => {
283
288
  });
284
289
  return logPipelineComplete(results, verbose);
285
290
  };
291
+ /**
292
+ * Run only code review phase (review PR and create review comments)
293
+ */
294
+ const runOnlyCodeReview = async (options, config) => {
295
+ const { featureId, verbose } = options;
296
+ logPipelineStart(featureId, verbose);
297
+ const results = [];
298
+ // Code Review - analyze PR and create review comments
299
+ const reviewResult = await runCodeReviewPhase(options, config);
300
+ results.push(logPhaseResult(reviewResult, verbose));
301
+ return logPipelineComplete(results, verbose);
302
+ };
303
+ /**
304
+ * Run from code review to end (code-review → code-refine → code-refine-verification)
305
+ */
306
+ const runFromCodeReview = async (options, config) => {
307
+ const { featureId, verbose } = options;
308
+ logPipelineStart(featureId, verbose);
309
+ const results = [];
310
+ // 1. Code Review - analyze PR and create review comments
311
+ const reviewResult = await runCodeReviewPhase(options, config);
312
+ results.push(logPhaseResult(reviewResult, verbose));
313
+ if (!shouldContinuePipeline(results)) {
314
+ return logPipelineComplete(results, verbose);
315
+ }
316
+ // 2. Code Refine with automatic retry for verification failures
317
+ await handleCodeRefineWithRetry({
318
+ options,
319
+ config,
320
+ results,
321
+ verbose,
322
+ });
323
+ return logPipelineComplete(results, verbose);
324
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "edsger",
3
- "version": "0.4.11",
3
+ "version": "0.5.1",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "bin": {