edsger 0.2.13 → 0.3.0

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,25 @@
1
+ /**
2
+ * Code Refine Analyzer
3
+ * Processes GitHub PR review feedback and refines code accordingly
4
+ */
5
+ import { EdsgerConfig } from '../../types/index.js';
6
+ export interface CodeRefineOptions {
7
+ featureId: string;
8
+ mcpServerUrl: string;
9
+ mcpToken: string;
10
+ githubToken: string;
11
+ verbose?: boolean;
12
+ }
13
+ export interface CodeRefineResult {
14
+ featureId: string;
15
+ status: 'success' | 'error';
16
+ message: string;
17
+ summary?: string;
18
+ filesModified?: string[];
19
+ commitsCreated?: number;
20
+ }
21
+ /**
22
+ * Main code refine function
23
+ */
24
+ export declare const refineCodeFromPRFeedback: (options: CodeRefineOptions, config: EdsgerConfig) => Promise<CodeRefineResult>;
25
+ export declare function checkCodeRefineRequirements(): Promise<boolean>;
@@ -0,0 +1,425 @@
1
+ /**
2
+ * Code Refine Analyzer
3
+ * Processes GitHub PR review feedback and refines code accordingly
4
+ */
5
+ import { query } from '@anthropic-ai/claude-code';
6
+ import { logInfo, logError } from '../../utils/logger.js';
7
+ import { execSync } from 'child_process';
8
+ import { fetchCodeRefineContext, 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(refinePrompt) {
17
+ yield userMessage(refinePrompt);
18
+ await new Promise((res) => setTimeout(res, 10000));
19
+ }
20
+ /**
21
+ * Get current Git branch name
22
+ */
23
+ const getCurrentBranch = () => {
24
+ try {
25
+ return execSync('git branch --show-current', { encoding: 'utf-8' }).trim();
26
+ }
27
+ catch (error) {
28
+ throw new Error(`Failed to get current branch: ${error instanceof Error ? error.message : String(error)}`);
29
+ }
30
+ };
31
+ /**
32
+ * Switch to a specific Git branch
33
+ */
34
+ const switchToBranch = (branch, verbose) => {
35
+ try {
36
+ if (verbose) {
37
+ logInfo(`🔄 Switching to branch ${branch}...`);
38
+ }
39
+ execSync(`git checkout ${branch}`, { encoding: 'utf-8' });
40
+ if (verbose) {
41
+ logInfo(`✅ Switched to ${branch} branch`);
42
+ }
43
+ }
44
+ catch (error) {
45
+ throw new Error(`Failed to switch to ${branch} branch: ${error instanceof Error ? error.message : String(error)}`);
46
+ }
47
+ };
48
+ /**
49
+ * Pull latest changes from remote
50
+ */
51
+ const pullLatestChanges = (verbose) => {
52
+ try {
53
+ if (verbose) {
54
+ logInfo(`📥 Pulling latest changes from remote...`);
55
+ }
56
+ execSync('git pull origin $(git branch --show-current) --rebase', {
57
+ encoding: 'utf-8',
58
+ });
59
+ if (verbose) {
60
+ logInfo(`✅ Successfully pulled latest changes`);
61
+ }
62
+ }
63
+ catch (error) {
64
+ throw new Error(`Failed to pull latest changes: ${error instanceof Error ? error.message : String(error)}`);
65
+ }
66
+ };
67
+ /**
68
+ * Push changes to remote branch
69
+ */
70
+ const pushChanges = (verbose) => {
71
+ try {
72
+ if (verbose) {
73
+ logInfo(`📤 Pushing changes to remote...`);
74
+ }
75
+ execSync('git push origin $(git branch --show-current)', {
76
+ encoding: 'utf-8',
77
+ });
78
+ if (verbose) {
79
+ logInfo(`✅ Successfully pushed changes`);
80
+ }
81
+ }
82
+ catch (error) {
83
+ throw new Error(`Failed to push changes: ${error instanceof Error ? error.message : String(error)}`);
84
+ }
85
+ };
86
+ /**
87
+ * Main code refine function
88
+ */
89
+ export const refineCodeFromPRFeedback = async (options, config) => {
90
+ const { featureId, mcpServerUrl, mcpToken, githubToken, verbose } = options;
91
+ if (verbose) {
92
+ logInfo(`Starting code refine for feature ID: ${featureId}`);
93
+ logInfo(`Using MCP server: ${mcpServerUrl}`);
94
+ }
95
+ try {
96
+ // Fetch code refine context (PR reviews and comments)
97
+ if (verbose) {
98
+ logInfo('Fetching code refine context from GitHub PR...');
99
+ }
100
+ const context = await fetchCodeRefineContext(mcpServerUrl, mcpToken, featureId, githubToken, verbose);
101
+ // Check if there are any reviews or comments to address
102
+ if (context.reviews.length === 0 && context.reviewComments.length === 0) {
103
+ if (verbose) {
104
+ logInfo('✅ No review comments or change requests found. Nothing to refine.');
105
+ }
106
+ return {
107
+ featureId,
108
+ status: 'success',
109
+ message: 'No review feedback to address',
110
+ summary: 'No change requests or review comments found on the PR',
111
+ };
112
+ }
113
+ if (verbose) {
114
+ logInfo(`📋 Found ${context.reviews.length} reviews and ${context.reviewComments.length} comments to address`);
115
+ }
116
+ // Fetch additional feedbacks for code-refine phase
117
+ let feedbacksInfo;
118
+ try {
119
+ const feedbacksContext = await getFeedbacksForPhase({ featureId, mcpServerUrl, mcpToken, verbose }, 'code_refine');
120
+ if (feedbacksContext.feedbacks.length > 0) {
121
+ feedbacksInfo = formatFeedbacksForContext(feedbacksContext);
122
+ if (verbose) {
123
+ logInfo(`Added ${feedbacksContext.feedbacks.length} human feedbacks to code refine context`);
124
+ }
125
+ }
126
+ }
127
+ catch (error) {
128
+ if (verbose) {
129
+ logInfo(`Note: Could not fetch feedbacks (${error instanceof Error ? error.message : String(error)})`);
130
+ }
131
+ }
132
+ // Get current branch and switch to dev/feature-id if needed
133
+ const currentBranch = getCurrentBranch();
134
+ const targetBranch = `dev/${featureId}`;
135
+ if (currentBranch !== targetBranch) {
136
+ if (verbose) {
137
+ logInfo(`Current branch is ${currentBranch}, switching to ${targetBranch}...`);
138
+ }
139
+ switchToBranch(targetBranch, verbose);
140
+ }
141
+ // Pull latest changes
142
+ pullLatestChanges(verbose);
143
+ // Create prompt for code refine
144
+ const systemPrompt = createSystemPrompt(config);
145
+ const refinePrompt = createCodeRefinePrompt(featureId, context, feedbacksInfo);
146
+ let lastAssistantResponse = '';
147
+ let structuredRefineResult = null;
148
+ if (verbose) {
149
+ logInfo('Starting Claude Code query for code refine...');
150
+ }
151
+ // Use Claude Code SDK to refine the code
152
+ for await (const message of query({
153
+ prompt: prompt(refinePrompt),
154
+ options: {
155
+ appendSystemPrompt: systemPrompt,
156
+ model: config.claude.model || 'sonnet',
157
+ maxTurns: 2000,
158
+ permissionMode: 'bypassPermissions',
159
+ },
160
+ })) {
161
+ if (verbose) {
162
+ logInfo(`Received message type: ${message.type}`);
163
+ }
164
+ // Stream the code refine process
165
+ if (message.type === 'assistant' && message.message?.content) {
166
+ for (const content of message.message.content) {
167
+ if (content.type === 'text') {
168
+ lastAssistantResponse += content.text + '\n';
169
+ if (verbose) {
170
+ console.log(`\n🔧 ${content.text}`);
171
+ }
172
+ }
173
+ else if (content.type === 'tool_use') {
174
+ if (verbose) {
175
+ console.log(`\n🛠️ ${content.name}: ${content.input.description || 'Running...'}`);
176
+ }
177
+ }
178
+ }
179
+ }
180
+ else if (message.type === 'result') {
181
+ if (message.subtype === 'success') {
182
+ logInfo('\n🛠️ Code refine completed, parsing results...');
183
+ try {
184
+ const responseText = message.result || lastAssistantResponse;
185
+ let jsonResult = null;
186
+ const jsonBlockMatch = responseText.match(/```json\s*\n([\s\S]*?)\n\s*```/);
187
+ if (jsonBlockMatch) {
188
+ jsonResult = JSON.parse(jsonBlockMatch[1]);
189
+ }
190
+ else {
191
+ jsonResult = JSON.parse(responseText);
192
+ }
193
+ if (jsonResult && jsonResult.refine_result) {
194
+ structuredRefineResult = jsonResult.refine_result;
195
+ }
196
+ else {
197
+ throw new Error('Invalid JSON structure');
198
+ }
199
+ }
200
+ catch (error) {
201
+ logError(`Failed to parse structured refine result: ${error}`);
202
+ structuredRefineResult = parseCodeRefineResponse(message.result || lastAssistantResponse);
203
+ }
204
+ }
205
+ else {
206
+ logError(`\n⚠️ Code refine incomplete: ${message.subtype}`);
207
+ if (message.subtype === 'error_max_turns') {
208
+ logError('💡 Try simplifying the changes or reducing scope');
209
+ }
210
+ if (lastAssistantResponse) {
211
+ try {
212
+ const responseText = lastAssistantResponse;
213
+ let jsonResult = null;
214
+ const jsonBlockMatch = responseText.match(/```json\s*\n([\s\S]*?)\n\s*```/);
215
+ if (jsonBlockMatch) {
216
+ jsonResult = JSON.parse(jsonBlockMatch[1]);
217
+ if (jsonResult && jsonResult.refine_result) {
218
+ structuredRefineResult = jsonResult.refine_result;
219
+ }
220
+ }
221
+ else {
222
+ structuredRefineResult = parseCodeRefineResponse(lastAssistantResponse);
223
+ }
224
+ }
225
+ catch (error) {
226
+ logError(`Failed to parse assistant response: ${error}`);
227
+ }
228
+ }
229
+ }
230
+ }
231
+ }
232
+ // Push changes to remote
233
+ if (structuredRefineResult) {
234
+ if (verbose) {
235
+ logInfo('📤 Pushing refined code to remote...');
236
+ }
237
+ try {
238
+ pushChanges(verbose);
239
+ const { summary, files_modified, commits_created } = structuredRefineResult;
240
+ if (verbose) {
241
+ logInfo(`Code refine completed for feature: ${featureId}`);
242
+ if (files_modified?.length > 0) {
243
+ logInfo(`Files modified: ${files_modified.join(', ')}`);
244
+ }
245
+ if (commits_created) {
246
+ logInfo(`Commits created: ${commits_created}`);
247
+ }
248
+ }
249
+ return {
250
+ featureId,
251
+ status: 'success',
252
+ message: 'Code successfully refined based on PR feedback',
253
+ summary: summary || 'Code refined based on PR review feedback',
254
+ filesModified: files_modified || [],
255
+ commitsCreated: commits_created || 1,
256
+ };
257
+ }
258
+ catch (pushError) {
259
+ logError(`Failed to push changes: ${pushError}`);
260
+ return {
261
+ featureId,
262
+ status: 'error',
263
+ message: `Code refined but failed to push: ${pushError}`,
264
+ };
265
+ }
266
+ }
267
+ else {
268
+ return {
269
+ featureId,
270
+ status: 'error',
271
+ message: 'Code refine failed or incomplete',
272
+ };
273
+ }
274
+ }
275
+ catch (error) {
276
+ const errorMessage = error instanceof Error ? error.message : String(error);
277
+ logError(`Code refine failed: ${errorMessage}`);
278
+ return {
279
+ featureId,
280
+ status: 'error',
281
+ message: `Code refine failed: ${errorMessage}`,
282
+ };
283
+ }
284
+ };
285
+ function createSystemPrompt(_config) {
286
+ return `You are an expert software engineer specializing in addressing code review feedback. Your goal is to carefully analyze review comments and refine the code to address all concerns raised by human reviewers.
287
+
288
+ **Your Role**: Refine code based on PR review feedback while maintaining code quality and functionality.
289
+
290
+ **Available Tools**:
291
+ - Bash: Run commands, tests, and git operations
292
+ - Glob: Find files and understand project structure
293
+ - Read: Examine existing code and files
294
+ - Edit: Modify existing files to address feedback
295
+ - Write: Create new files if necessary
296
+ - TodoWrite: Track code refine tasks (use proactively)
297
+
298
+ **Code Refine Process**:
299
+ 1. **Analyze Feedback**: Carefully study all review comments and change requests
300
+ 2. **Understand Context**: Review the code, technical design, and related files
301
+ 3. **Plan Changes**: Create a systematic plan to address each review comment
302
+ 4. **Refine Code**: Make targeted, thoughtful changes that address the feedback
303
+ 5. **Test Changes**: Ensure your changes work and don't break functionality
304
+ 6. **Commit Changes**: Create clear, descriptive commits for your refinements
305
+ 7. **Document**: Explain what changes were made and why
306
+
307
+ **Important Guidelines**:
308
+ - Address ALL review comments and change requests
309
+ - Maintain code quality and follow best practices
310
+ - Preserve existing functionality
311
+ - Write clear commit messages that reference the feedback
312
+ - Test your changes thoroughly
313
+ - Be respectful of the reviewer's time and expertise
314
+
315
+ **CRITICAL - Result Format**:
316
+ You MUST end your response with a JSON object containing the code refine results in this EXACT format:
317
+
318
+ \`\`\`json
319
+ {
320
+ "refine_result": {
321
+ "feature_id": "FEATURE_ID_PLACEHOLDER",
322
+ "summary": "Brief description of changes made",
323
+ "files_modified": ["file1.ts", "file2.tsx"],
324
+ "commits_created": 1,
325
+ "feedback_addressed": [
326
+ {
327
+ "comment_id": "Comment reference or ID",
328
+ "issue": "Description of the reviewer's concern",
329
+ "resolution": "Description of how it was addressed",
330
+ "files": ["path/to/file.ts"]
331
+ }
332
+ ]
333
+ }
334
+ }
335
+ \`\`\`
336
+
337
+ Focus on systematic code refinement based on the provided PR review feedback.`;
338
+ }
339
+ function createCodeRefinePrompt(featureId, context, feedbacksInfo) {
340
+ let contextInfo = formatContextForPrompt(context);
341
+ if (feedbacksInfo) {
342
+ contextInfo = contextInfo + '\n\n' + feedbacksInfo;
343
+ }
344
+ return `Refine code based on PR review feedback for feature: ${featureId}
345
+
346
+ ${contextInfo}
347
+
348
+ ## Code Refine Instructions
349
+
350
+ Follow this systematic approach:
351
+
352
+ 1. **Analyze All Feedback**: Study all review comments and change requests above:
353
+ - What specific concerns were raised?
354
+ - Which files and code sections need changes?
355
+ - What are the underlying issues?
356
+ - Are there any patterns in the feedback?
357
+
358
+ 2. **Examine Current Code**: Use Read and Glob tools to:
359
+ - Understand the current implementation
360
+ - Identify the code sections mentioned in reviews
361
+ - Review related files and dependencies
362
+ - Understand the broader context
363
+
364
+ 3. **Plan Your Changes**: Create a systematic plan:
365
+ - Address each review comment one by one
366
+ - Prioritize critical feedback
367
+ - Consider how changes relate to each other
368
+ - Ensure changes align with technical design
369
+
370
+ 4. **Refine the Code**: Make targeted improvements:
371
+ - Address each reviewer's concern thoughtfully
372
+ - Follow code quality best practices
373
+ - Maintain consistency with existing patterns
374
+ - Make code more maintainable and readable
375
+
376
+ 5. **Test Your Changes**: Verify everything works:
377
+ - Run relevant tests
378
+ - Check that functionality still works
379
+ - Ensure no regressions were introduced
380
+
381
+ 6. **Commit Your Changes**: Create clear commits:
382
+ - Write descriptive commit messages
383
+ - Reference the feedback being addressed
384
+ - Group related changes logically
385
+
386
+ ## Important Notes
387
+ - Be thorough - address ALL review comments
388
+ - Be respectful - reviewers are trying to help improve the code
389
+ - Be systematic - don't rush through the feedback
390
+ - Test your changes - ensure nothing breaks
391
+ - Communicate clearly in commit messages
392
+
393
+ You are currently on the \`dev/${featureId}\` branch. Make your changes, commit them, and they will be pushed to the remote repository.
394
+
395
+ Begin by analyzing all the review feedback and creating a plan to address each comment.`;
396
+ }
397
+ function parseCodeRefineResponse(response) {
398
+ const summaryMatch = response.match(/## Refine Summary\n([\s\S]*?)(?=\n##|\n\n|$)/);
399
+ const summary = summaryMatch
400
+ ? summaryMatch[1].trim()
401
+ : 'Code refined based on PR feedback';
402
+ const filesMatch = response.match(/## Files Modified\n([\s\S]*?)(?=\n##|\n\n|$)/);
403
+ let files_modified = [];
404
+ if (filesMatch) {
405
+ files_modified = filesMatch[1]
406
+ .split('\n')
407
+ .filter((line) => line.trim().startsWith('-'))
408
+ .map((line) => line.replace(/^-\s*/, '').trim());
409
+ }
410
+ return {
411
+ summary,
412
+ files_modified,
413
+ commits_created: 1,
414
+ feedback_addressed: [],
415
+ };
416
+ }
417
+ export function checkCodeRefineRequirements() {
418
+ try {
419
+ require.resolve('@anthropic-ai/claude-code');
420
+ return Promise.resolve(true);
421
+ }
422
+ catch {
423
+ return Promise.resolve(false);
424
+ }
425
+ }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Context fetcher for code refine phase
3
+ * Fetches GitHub PR review comments that request changes
4
+ */
5
+ import { Octokit } from '@octokit/rest';
6
+ export interface PRReviewComment {
7
+ id: number;
8
+ body: string;
9
+ path: string | null;
10
+ line: number | null;
11
+ user: {
12
+ login: string;
13
+ };
14
+ created_at: string;
15
+ position: number | null;
16
+ original_position: number | null;
17
+ diff_hunk: string | null;
18
+ in_reply_to_id?: number;
19
+ }
20
+ export interface PRReview {
21
+ id: number;
22
+ user: {
23
+ login: string;
24
+ };
25
+ body: string | null;
26
+ state: string;
27
+ submitted_at: string | null;
28
+ }
29
+ export interface CodeRefineContext {
30
+ featureId: string;
31
+ featureName: string;
32
+ featureDescription: string | null;
33
+ pullRequestUrl: string;
34
+ pullRequestNumber: number;
35
+ owner: string;
36
+ repo: string;
37
+ reviews: PRReview[];
38
+ reviewComments: PRReviewComment[];
39
+ technicalDesign?: string;
40
+ userStories?: any[];
41
+ testCases?: any[];
42
+ }
43
+ /**
44
+ * Extract owner, repo, and PR number from GitHub PR URL
45
+ */
46
+ export declare function parsePullRequestUrl(pullRequestUrl: string): {
47
+ owner: string;
48
+ repo: string;
49
+ prNumber: number;
50
+ } | null;
51
+ /**
52
+ * Fetch PR reviews that request changes
53
+ */
54
+ export declare function fetchPRReviews(octokit: Octokit, owner: string, repo: string, prNumber: number, verbose?: boolean): Promise<PRReview[]>;
55
+ /**
56
+ * Fetch PR review comments
57
+ */
58
+ export declare function fetchPRReviewComments(octokit: Octokit, owner: string, repo: string, prNumber: number, verbose?: boolean): Promise<PRReviewComment[]>;
59
+ /**
60
+ * Fetch technical design via MCP
61
+ */
62
+ export declare function fetchTechnicalDesign(mcpServerUrl: string, mcpToken: string, featureId: string, verbose?: boolean): Promise<string | undefined>;
63
+ /**
64
+ * Fetch user stories via MCP
65
+ */
66
+ export declare function fetchUserStories(mcpServerUrl: string, mcpToken: string, featureId: string, verbose?: boolean): Promise<any[]>;
67
+ /**
68
+ * Fetch test cases via MCP
69
+ */
70
+ export declare function fetchTestCases(mcpServerUrl: string, mcpToken: string, featureId: string, verbose?: boolean): Promise<any[]>;
71
+ /**
72
+ * Fetch complete code refine context
73
+ */
74
+ export declare function fetchCodeRefineContext(mcpServerUrl: string, mcpToken: string, featureId: string, githubToken: string, verbose?: boolean): Promise<CodeRefineContext>;
75
+ /**
76
+ * Format code refine context for prompt
77
+ */
78
+ export declare function formatContextForPrompt(context: CodeRefineContext): string;