edsger 0.3.3 → 0.4.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.
@@ -9,6 +9,23 @@ export interface CodeRefineOptions {
9
9
  mcpToken: string;
10
10
  githubToken: string;
11
11
  verbose?: boolean;
12
+ verificationFailureContext?: {
13
+ attempt: number;
14
+ suggestions: string[];
15
+ unresolvedCommentDetails: Array<{
16
+ commentId: number;
17
+ author: string;
18
+ file: string;
19
+ line: number | null;
20
+ body: string;
21
+ }>;
22
+ unresolvedReviewDetails?: Array<{
23
+ reviewId: number;
24
+ author: string;
25
+ state: string;
26
+ body: string | null;
27
+ }>;
28
+ };
12
29
  }
13
30
  export interface CodeRefineResult {
14
31
  featureId: string;
@@ -142,7 +142,7 @@ export const refineCodeFromPRFeedback = async (options, config) => {
142
142
  pullLatestChanges(verbose);
143
143
  // Create prompt for code refine
144
144
  const systemPrompt = createSystemPrompt(config);
145
- const refinePrompt = createCodeRefinePrompt(featureId, context, feedbacksInfo);
145
+ const refinePrompt = createCodeRefinePrompt(featureId, context, feedbacksInfo, options.verificationFailureContext);
146
146
  let lastAssistantResponse = '';
147
147
  let structuredRefineResult = null;
148
148
  if (verbose) {
@@ -336,20 +336,60 @@ You MUST end your response with a JSON object containing the code refine results
336
336
 
337
337
  Focus on systematic code refinement based on the provided PR review feedback.`;
338
338
  }
339
- function createCodeRefinePrompt(featureId, context, feedbacksInfo) {
339
+ function createCodeRefinePrompt(featureId, context, feedbacksInfo, verificationFailureContext) {
340
340
  let contextInfo = formatContextForPrompt(context);
341
341
  if (feedbacksInfo) {
342
342
  contextInfo = contextInfo + '\n\n' + feedbacksInfo;
343
343
  }
344
- return `Refine code based on PR review feedback for feature: ${featureId}
344
+ // Add verification failure context if this is a retry
345
+ let verificationSection = '';
346
+ if (verificationFailureContext) {
347
+ verificationSection = `
348
+
349
+ ## ⚠️ PREVIOUS ATTEMPT FAILED VERIFICATION (Attempt ${verificationFailureContext.attempt})
350
+
351
+ Your previous code refine attempt did NOT successfully address all the review feedback.
352
+ The verification phase found the following unresolved issues:
353
+
354
+ ### Verification Failure Summary
355
+ ${verificationFailureContext.suggestions.map((s) => `- ${s}`).join('\n')}
345
356
 
357
+ ### Unresolved Comments Still Needing Attention
358
+ ${verificationFailureContext.unresolvedCommentDetails
359
+ .map((detail, idx) => `${idx + 1}. **${detail.file}${detail.line ? `:${detail.line}` : ''}** by @${detail.author}
360
+ ${detail.body}`)
361
+ .join('\n\n')}
362
+
363
+ ${verificationFailureContext.unresolvedReviewDetails &&
364
+ verificationFailureContext.unresolvedReviewDetails.length > 0
365
+ ? `
366
+ ### Unresolved Reviews Still Requesting Changes
367
+ ${verificationFailureContext.unresolvedReviewDetails
368
+ .map((review, idx) => `${idx + 1}. Review by @${review.author} (${review.state})
369
+ ${review.body || 'No review body provided'}`)
370
+ .join('\n\n')}
371
+ `
372
+ : ''}
373
+
374
+ **CRITICAL**: You MUST address all the above issues in this iteration. Pay special attention to:
375
+ 1. Read each unresolved comment carefully and understand what change is being requested
376
+ 2. Locate the exact file and line mentioned
377
+ 3. Make the specific changes requested by the reviewer
378
+ 4. Test your changes to ensure they work correctly
379
+ 5. Do not skip or overlook any comments - address ALL of them
380
+ `;
381
+ }
382
+ return `Refine code based on PR review feedback for feature: ${featureId}
383
+ ${verificationFailureContext ? '\n🔄 **This is a RETRY after verification failure**\n' : ''}
346
384
  ${contextInfo}
385
+ ${verificationSection}
347
386
 
348
387
  ## Code Refine Instructions
349
388
 
350
389
  Follow this systematic approach:
351
390
 
352
391
  1. **Analyze All Feedback**: Study all review comments and change requests above:
392
+ ${verificationFailureContext ? ' - **FOCUS ON THE UNRESOLVED COMMENTS listed in the verification failure section**' : ''}
353
393
  - What specific concerns were raised?
354
394
  - Which files and code sections need changes?
355
395
  - What are the underlying issues?
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Code refine retry handler for pipeline execution
3
+ * Handles verification failures and retries code refine with suggestions
4
+ */
5
+ import { EdsgerConfig } from '../../types/index.js';
6
+ import { PipelinePhaseOptions, PipelineResult } from '../../types/pipeline.js';
7
+ export declare const MAX_REFINE_ATTEMPTS = 10;
8
+ export interface CodeRefineRetryOptions {
9
+ options: PipelinePhaseOptions;
10
+ config: EdsgerConfig;
11
+ results: PipelineResult[];
12
+ verbose?: boolean;
13
+ }
14
+ /**
15
+ * Handle code refine verification failures with automatic retry
16
+ */
17
+ export declare function handleCodeRefineWithRetry({ options, config, results, verbose, }: CodeRefineRetryOptions): Promise<PipelineResult>;
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Code refine retry handler for pipeline execution
3
+ * Handles verification failures and retries code refine with suggestions
4
+ */
5
+ import { logPhaseResult } from '../../utils/pipeline-logger.js';
6
+ import { runCodeRefinePhase, runCodeRefineVerificationPhase, } from '../../workflow-runner/executors/phase-executor.js';
7
+ import { updateFeatureStatusForPhase } from '../../api/features/index.js';
8
+ import { logFeaturePhaseEvent } from '../../services/audit-logs.js';
9
+ export const MAX_REFINE_ATTEMPTS = 10;
10
+ /**
11
+ * Handle code refine verification failures with automatic retry
12
+ */
13
+ export async function handleCodeRefineWithRetry({ options, config, results, verbose, }) {
14
+ let verificationResult;
15
+ let refineAttempt = 0;
16
+ let verificationFailureContext;
17
+ do {
18
+ // 1. Run code refine phase (with verification context if this is a retry)
19
+ const enhancedOptions = verificationFailureContext
20
+ ? { ...options, verificationFailureContext }
21
+ : options;
22
+ const codeRefineResult = await runCodeRefinePhase(enhancedOptions, config);
23
+ results.push(logPhaseResult(codeRefineResult, verbose));
24
+ // If code refine failed, stop immediately
25
+ if (codeRefineResult.status === 'error') {
26
+ if (verbose) {
27
+ console.log(`❌ Code refine failed: ${codeRefineResult.message}. Cannot proceed to verification.`);
28
+ }
29
+ return codeRefineResult;
30
+ }
31
+ // 2. Run code refine verification
32
+ verificationResult = await runCodeRefineVerificationPhase(options, config);
33
+ results.push(logPhaseResult(verificationResult, verbose));
34
+ // If verification passed, we're done
35
+ if (verificationResult.status === 'success') {
36
+ if (verbose) {
37
+ console.log(`✅ Code refine verification passed. All PR comments have been addressed.`);
38
+ }
39
+ break;
40
+ }
41
+ // Verification failed - check if we should retry
42
+ if (refineAttempt < MAX_REFINE_ATTEMPTS) {
43
+ refineAttempt++;
44
+ if (verbose) {
45
+ console.log(`⚠️ Code refine verification failed. Retrying... (Attempt ${refineAttempt}/${MAX_REFINE_ATTEMPTS})`);
46
+ }
47
+ // Extract verification failure details
48
+ const verificationData = verificationResult.data;
49
+ const unresolvedComments = verificationData?.unresolvedComments ||
50
+ verificationData?.unresolved_comments ||
51
+ 0;
52
+ const suggestions = verificationData?.suggestions || [];
53
+ const unresolvedCommentDetails = verificationData?.unresolvedCommentDetails ||
54
+ verificationData?.unresolved_comment_details ||
55
+ [];
56
+ const unresolvedReviewDetails = verificationData?.unresolvedReviewDetails ||
57
+ verificationData?.unresolved_review_details;
58
+ // Prepare verification failure context for next code-refine attempt
59
+ verificationFailureContext = {
60
+ attempt: refineAttempt + 1, // Next attempt number
61
+ suggestions,
62
+ unresolvedCommentDetails,
63
+ unresolvedReviewDetails,
64
+ };
65
+ // Log audit event for verification failure with suggestions
66
+ await logFeaturePhaseEvent(options.mcpServerUrl, options.mcpToken, {
67
+ featureId: options.featureId,
68
+ eventType: 'phase_failed',
69
+ phase: 'code_refine_verification',
70
+ result: 'error',
71
+ metadata: {
72
+ attempt: refineAttempt,
73
+ unresolved_comments: unresolvedComments,
74
+ suggestions: suggestions,
75
+ unresolved_comment_details: unresolvedCommentDetails,
76
+ unresolved_review_details: unresolvedReviewDetails,
77
+ will_retry: true,
78
+ },
79
+ errorMessage: verificationResult.message,
80
+ }, verbose);
81
+ if (verbose && suggestions.length > 0) {
82
+ console.log(`\n💡 Suggestions for next iteration:`);
83
+ suggestions.forEach((suggestion) => {
84
+ console.log(` ${suggestion}`);
85
+ });
86
+ console.log('');
87
+ }
88
+ // Remove the failed verification result - we'll add the new one after retry
89
+ results.pop();
90
+ // Update status to indicate we're retrying code refine
91
+ await updateFeatureStatusForPhase({ mcpServerUrl: options.mcpServerUrl, mcpToken: options.mcpToken }, options.featureId, 'code-refine', verbose);
92
+ // Also remove the code refine result so it can be retried
93
+ results.pop();
94
+ }
95
+ else {
96
+ // Max attempts reached
97
+ if (verbose) {
98
+ console.log(`⚠️ Maximum refine attempts (${MAX_REFINE_ATTEMPTS}) reached. Verification still failing.`);
99
+ }
100
+ // Log final failure with all details
101
+ const verificationData = verificationResult.data;
102
+ const suggestions = verificationData?.suggestions || [];
103
+ const unresolvedDetails = verificationData?.unresolvedCommentDetails ||
104
+ verificationData?.unresolved_comment_details ||
105
+ [];
106
+ await logFeaturePhaseEvent(options.mcpServerUrl, options.mcpToken, {
107
+ featureId: options.featureId,
108
+ eventType: 'phase_failed',
109
+ phase: 'code_refine_verification',
110
+ result: 'error',
111
+ metadata: {
112
+ attempt: refineAttempt,
113
+ max_attempts_reached: true,
114
+ suggestions: suggestions,
115
+ unresolved_comment_details: unresolvedDetails,
116
+ },
117
+ errorMessage: `Max attempts reached: ${verificationResult.message}`,
118
+ }, verbose);
119
+ break;
120
+ }
121
+ } while (refineAttempt <= MAX_REFINE_ATTEMPTS);
122
+ return verificationResult;
123
+ }
@@ -9,14 +9,34 @@ export interface CodeRefineVerificationOptions {
9
9
  githubToken: string;
10
10
  verbose?: boolean;
11
11
  }
12
- export interface CodeRefineVerificationResult {
12
+ export interface CodeRefineVerificationData {
13
13
  featureId: string;
14
- status: 'success' | 'error';
15
- message: string;
14
+ totalReviews: number;
15
+ unresolvedReviews: number;
16
16
  totalComments: number;
17
17
  resolvedComments: number;
18
18
  unresolvedComments: number;
19
19
  commentsMarkedResolved?: number;
20
+ suggestions?: string[];
21
+ unresolvedReviewDetails?: Array<{
22
+ reviewId: number;
23
+ author: string;
24
+ state: string;
25
+ body: string | null;
26
+ submittedAt: string | null;
27
+ }>;
28
+ unresolvedCommentDetails?: Array<{
29
+ commentId: number;
30
+ author: string;
31
+ file: string;
32
+ line: number | null;
33
+ body: string;
34
+ }>;
35
+ }
36
+ export interface CodeRefineVerificationResult {
37
+ status: 'success' | 'error';
38
+ message: string;
39
+ data: CodeRefineVerificationData;
20
40
  }
21
41
  /**
22
42
  * Verify and resolve PR review comments
@@ -4,7 +4,7 @@
4
4
  */
5
5
  import { Octokit } from '@octokit/rest';
6
6
  import { logInfo, logError } from '../../utils/logger.js';
7
- import { parsePullRequestUrl, fetchPRReviewComments, } from '../code-refine/context-fetcher.js';
7
+ import { parsePullRequestUrl, fetchPRReviews, fetchPRReviewComments, } from '../code-refine/context-fetcher.js';
8
8
  import { getFeature } from '../../api/features/get-feature.js';
9
9
  /**
10
10
  * Check if a review comment thread is resolved
@@ -107,36 +107,65 @@ export async function verifyAndResolveComments(options) {
107
107
  const octokit = new Octokit({
108
108
  auth: githubToken,
109
109
  });
110
- // Fetch all review comments
111
- const reviewComments = await fetchPRReviewComments(octokit, owner, repo, prNumber, verbose);
112
- if (reviewComments.length === 0) {
110
+ // Fetch all reviews requesting changes and review comments
111
+ const [reviews, reviewComments] = await Promise.all([
112
+ fetchPRReviews(octokit, owner, repo, prNumber, verbose),
113
+ fetchPRReviewComments(octokit, owner, repo, prNumber, verbose),
114
+ ]);
115
+ if (reviews.length === 0 && reviewComments.length === 0) {
113
116
  if (verbose) {
114
- logInfo('✅ No review comments found. Verification complete.');
117
+ logInfo('✅ No reviews or review comments found. Verification complete.');
115
118
  }
116
119
  return {
117
- featureId,
118
120
  status: 'success',
119
- message: 'No review comments to verify',
120
- totalComments: 0,
121
- resolvedComments: 0,
122
- unresolvedComments: 0,
121
+ message: 'No reviews or review comments to verify',
122
+ data: {
123
+ featureId,
124
+ totalReviews: 0,
125
+ unresolvedReviews: 0,
126
+ totalComments: 0,
127
+ resolvedComments: 0,
128
+ unresolvedComments: 0,
129
+ },
123
130
  };
124
131
  }
125
132
  // Check which comments are resolved
126
133
  if (verbose) {
127
- logInfo(`🔍 Checking ${reviewComments.length} review comments...`);
134
+ if (reviews.length > 0) {
135
+ logInfo(`🔍 Checking ${reviews.length} reviews requesting changes...`);
136
+ }
137
+ if (reviewComments.length > 0) {
138
+ logInfo(`🔍 Checking ${reviewComments.length} review comments...`);
139
+ }
128
140
  }
129
- const commentStatus = await Promise.all(reviewComments.map(async (comment) => {
141
+ // Filter out already resolved comments (position is null but original_position exists)
142
+ // This is a heuristic - when code is modified at comment location, GitHub sets position to null
143
+ const unresolvedCommentsCandidates = reviewComments.filter((comment) =>
144
+ // Include comments where position is still set (not yet addressed)
145
+ // OR where both position and original_position are null (general comments)
146
+ comment.position !== null ||
147
+ (comment.position === null && comment.original_position === null));
148
+ if (verbose && unresolvedCommentsCandidates.length < reviewComments.length) {
149
+ logInfo(`📊 Filtered out ${reviewComments.length - unresolvedCommentsCandidates.length} likely resolved comments (position changed from original)`);
150
+ }
151
+ // Check review comments resolution status for the candidates
152
+ const commentStatus = await Promise.all(unresolvedCommentsCandidates.map(async (comment) => {
130
153
  const isResolved = await isCommentThreadResolved(octokit, owner, repo, prNumber, comment.id, verbose);
131
154
  return { comment, isResolved };
132
155
  }));
133
156
  const resolvedComments = commentStatus.filter((c) => c.isResolved);
134
157
  const unresolvedComments = commentStatus.filter((c) => !c.isResolved);
158
+ // Check reviews - they need to be dismissed or re-reviewed
159
+ // We consider reviews as "unresolved" if they are still in CHANGES_REQUESTED state
160
+ const unresolvedReviews = reviews.filter((review) => review.state === 'CHANGES_REQUESTED');
135
161
  if (verbose) {
136
- logInfo(`📊 Status: ${resolvedComments.length} resolved, ${unresolvedComments.length} unresolved`);
162
+ logInfo(`📊 Comments: ${resolvedComments.length} resolved, ${unresolvedComments.length} unresolved`);
163
+ if (reviews.length > 0) {
164
+ logInfo(`📊 Reviews: ${reviews.length - unresolvedReviews.length} addressed, ${unresolvedReviews.length} still requesting changes`);
165
+ }
137
166
  }
138
- // If all comments are resolved, mark them as resolved on GitHub
139
- if (unresolvedComments.length === 0) {
167
+ // If all comments are resolved AND no reviews requesting changes, success
168
+ if (unresolvedComments.length === 0 && unresolvedReviews.length === 0) {
140
169
  if (verbose) {
141
170
  logInfo('✅ All comments have been addressed! Marking them as resolved...');
142
171
  }
@@ -150,34 +179,95 @@ export async function verifyAndResolveComments(options) {
150
179
  if (verbose) {
151
180
  logInfo(`✅ Marked ${markedCount} comments as resolved on GitHub`);
152
181
  }
182
+ const successMessage = reviews.length > 0
183
+ ? 'All reviews and review comments have been addressed and resolved'
184
+ : 'All review comments have been addressed and resolved';
153
185
  return {
154
- featureId,
155
186
  status: 'success',
156
- message: 'All review comments have been addressed and resolved',
157
- totalComments: reviewComments.length,
158
- resolvedComments: resolvedComments.length,
159
- unresolvedComments: 0,
160
- commentsMarkedResolved: markedCount,
187
+ message: successMessage,
188
+ data: {
189
+ featureId,
190
+ totalReviews: reviews.length,
191
+ unresolvedReviews: 0,
192
+ totalComments: reviewComments.length,
193
+ resolvedComments: resolvedComments.length,
194
+ unresolvedComments: 0,
195
+ commentsMarkedResolved: markedCount,
196
+ },
161
197
  };
162
198
  }
163
199
  else {
164
200
  if (verbose) {
165
- logInfo(`⚠️ ${unresolvedComments.length} comments still need to be addressed`);
166
- unresolvedComments.forEach(({ comment }) => {
167
- logInfo(` - Comment ${comment.id} by @${comment.user.login}`);
168
- if (comment.path) {
169
- logInfo(` File: ${comment.path}:${comment.line || '?'}`);
170
- }
171
- logInfo(` ${comment.body.substring(0, 100)}...`);
172
- });
201
+ if (unresolvedReviews.length > 0) {
202
+ logInfo(`⚠️ ${unresolvedReviews.length} reviews still requesting changes`);
203
+ unresolvedReviews.forEach((review) => {
204
+ logInfo(` - Review ${review.id} by @${review.user.login}`);
205
+ if (review.body) {
206
+ logInfo(` ${review.body.substring(0, 100)}...`);
207
+ }
208
+ });
209
+ }
210
+ if (unresolvedComments.length > 0) {
211
+ logInfo(`⚠️ ${unresolvedComments.length} comments still need to be addressed`);
212
+ unresolvedComments.forEach(({ comment }) => {
213
+ logInfo(` - Comment ${comment.id} by @${comment.user.login}`);
214
+ if (comment.path) {
215
+ logInfo(` File: ${comment.path}:${comment.line || '?'}`);
216
+ }
217
+ logInfo(` ${comment.body.substring(0, 100)}...`);
218
+ });
219
+ }
220
+ }
221
+ // Build concise suggestions for code-refine phase (similar to technical-design)
222
+ const suggestions = [];
223
+ suggestions.push('Verification failed - address the following issues:');
224
+ if (unresolvedReviews.length > 0) {
225
+ suggestions.push(`1. ${unresolvedReviews.length} review(s) still requesting changes - address the overall concerns raised by reviewers`);
226
+ }
227
+ if (unresolvedComments.length > 0) {
228
+ suggestions.push(`${unresolvedReviews.length > 0 ? '2' : '1'}. ${unresolvedComments.length} unresolved comment(s) - carefully address each specific feedback point`);
229
+ }
230
+ suggestions.push(`${unresolvedReviews.length > 0 || unresolvedComments.length > 0 ? (unresolvedReviews.length > 0 && unresolvedComments.length > 0 ? '3' : '2') : '2'}. Review the unresolved_comment_details in metadata for specific locations and feedback`);
231
+ suggestions.push(`${unresolvedReviews.length > 0 || unresolvedComments.length > 0 ? (unresolvedReviews.length > 0 && unresolvedComments.length > 0 ? '4' : '3') : '3'}. Test your changes thoroughly before committing`);
232
+ // Build detailed unresolved info
233
+ const unresolvedCommentDetails = unresolvedComments.map(({ comment }) => ({
234
+ commentId: comment.id,
235
+ author: comment.user.login,
236
+ file: comment.path || 'unknown',
237
+ line: comment.line,
238
+ body: comment.body,
239
+ }));
240
+ const unresolvedReviewDetails = unresolvedReviews.map((review) => ({
241
+ reviewId: review.id,
242
+ author: review.user.login,
243
+ state: review.state,
244
+ body: review.body,
245
+ submittedAt: review.submitted_at,
246
+ }));
247
+ let errorMessage = '';
248
+ if (unresolvedReviews.length > 0 && unresolvedComments.length > 0) {
249
+ errorMessage = `${unresolvedReviews.length} reviews and ${unresolvedComments.length} comments still need to be addressed`;
250
+ }
251
+ else if (unresolvedReviews.length > 0) {
252
+ errorMessage = `${unresolvedReviews.length} reviews still requesting changes`;
253
+ }
254
+ else {
255
+ errorMessage = `${unresolvedComments.length} review comments still need to be addressed`;
173
256
  }
174
257
  return {
175
- featureId,
176
258
  status: 'error',
177
- message: `${unresolvedComments.length} review comments still need to be addressed`,
178
- totalComments: reviewComments.length,
179
- resolvedComments: resolvedComments.length,
180
- unresolvedComments: unresolvedComments.length,
259
+ message: errorMessage,
260
+ data: {
261
+ featureId,
262
+ totalReviews: reviews.length,
263
+ unresolvedReviews: unresolvedReviews.length,
264
+ totalComments: reviewComments.length,
265
+ resolvedComments: resolvedComments.length,
266
+ unresolvedComments: unresolvedComments.length,
267
+ suggestions,
268
+ unresolvedReviewDetails,
269
+ unresolvedCommentDetails,
270
+ },
181
271
  };
182
272
  }
183
273
  }
@@ -185,12 +275,20 @@ export async function verifyAndResolveComments(options) {
185
275
  const errorMessage = error instanceof Error ? error.message : String(error);
186
276
  logError(`Code refine verification failed: ${errorMessage}`);
187
277
  return {
188
- featureId,
189
278
  status: 'error',
190
279
  message: `Verification failed: ${errorMessage}`,
191
- totalComments: 0,
192
- resolvedComments: 0,
193
- unresolvedComments: 0,
280
+ data: {
281
+ featureId,
282
+ totalReviews: 0,
283
+ unresolvedReviews: 0,
284
+ totalComments: 0,
285
+ resolvedComments: 0,
286
+ unresolvedComments: 0,
287
+ suggestions: [
288
+ `Verification failed with error: ${errorMessage}`,
289
+ 'Please check the error message and try again',
290
+ ],
291
+ },
194
292
  };
195
293
  }
196
294
  }
@@ -173,15 +173,36 @@ export const createPhaseRunner = (phaseConfig) => async (options, config) => {
173
173
  }, verbose);
174
174
  }
175
175
  else {
176
+ // Extract additional failure details from result data
177
+ const failureMetadata = {
178
+ duration_ms: phaseDuration,
179
+ };
180
+ // For verification phases, include suggestions and details
181
+ if (name.includes('verification') &&
182
+ result.data &&
183
+ typeof result.data === 'object') {
184
+ const data = result.data;
185
+ if (data.suggestions) {
186
+ failureMetadata.suggestions = data.suggestions;
187
+ }
188
+ if (data.unresolvedCommentDetails ||
189
+ data.unresolved_comment_details) {
190
+ failureMetadata.unresolved_comment_details =
191
+ data.unresolvedCommentDetails || data.unresolved_comment_details;
192
+ }
193
+ if (data.unresolvedComments !== undefined ||
194
+ data.unresolved_comments !== undefined) {
195
+ failureMetadata.unresolved_comments =
196
+ data.unresolvedComments ?? data.unresolved_comments;
197
+ }
198
+ }
176
199
  await logFeaturePhaseEvent(mcpServerUrl, mcpToken, {
177
200
  featureId,
178
201
  eventType: 'phase_failed',
179
202
  phase: name.replace(/-/g, '_'),
180
203
  result: 'error',
181
- metadata: {
182
- duration_ms: phaseDuration,
183
- },
184
- errorMessage: result.summary || 'Phase execution failed',
204
+ metadata: failureMetadata,
205
+ errorMessage: result.summary || result.message || 'Phase execution failed',
185
206
  }, verbose);
186
207
  }
187
208
  return {
@@ -4,9 +4,10 @@
4
4
  * Uses functional programming principles
5
5
  */
6
6
  import { updateFeatureStatusForPhase } from '../api/features/index.js';
7
- import { runFeatureAnalysisPhase, runTechnicalDesignPhase, runCodeImplementationPhase, runFunctionalTestingPhase, runCodeRefinePhase, runCodeRefineVerificationPhase, } from './executors/phase-executor.js';
7
+ import { runFeatureAnalysisPhase, runTechnicalDesignPhase, runCodeImplementationPhase, runFunctionalTestingPhase, } 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
+ import { handleCodeRefineWithRetry } from '../phases/code-refine/retry-handler.js';
10
11
  import { handlePullRequestCreation } from '../phases/pull-request/handler.js';
11
12
  /**
12
13
  * Run pipeline based on execution mode
@@ -267,19 +268,18 @@ const runFromFunctionalTesting = async (options, config) => {
267
268
  };
268
269
  /**
269
270
  * Run only code refine phase (refine code based on PR feedback)
271
+ * Includes automatic retry loop for verification failures
270
272
  */
271
273
  const runOnlyCodeRefine = async (options, config) => {
272
274
  const { featureId, verbose } = options;
273
275
  logPipelineStart(featureId, verbose);
274
276
  const results = [];
275
- // 1. Code Refine - Address PR review feedback
276
- const codeRefineResult = await runCodeRefinePhase(options, config);
277
- results.push(logPhaseResult(codeRefineResult, verbose));
278
- if (!shouldContinuePipeline(results)) {
279
- return logPipelineComplete(results, verbose);
280
- }
281
- // 2. Code Refine Verification - Verify all comments are addressed
282
- const verificationResult = await runCodeRefineVerificationPhase(options, config);
283
- results.push(logPhaseResult(verificationResult, verbose));
277
+ // Code Refine with automatic retry for verification failures
278
+ await handleCodeRefineWithRetry({
279
+ options,
280
+ config,
281
+ results,
282
+ verbose,
283
+ });
284
284
  return logPipelineComplete(results, verbose);
285
285
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "edsger",
3
- "version": "0.3.3",
3
+ "version": "0.4.1",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "bin": {