edsger 0.3.2 → 0.4.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.
- package/dist/phases/code-refine/context-fetcher.d.ts +0 -4
- package/dist/phases/code-refine/context-fetcher.js +4 -50
- package/dist/phases/code-refine/retry-handler.d.ts +17 -0
- package/dist/phases/code-refine/retry-handler.js +109 -0
- package/dist/phases/code-refine-verification/verifier.d.ts +23 -3
- package/dist/phases/code-refine-verification/verifier.js +146 -35
- package/dist/workflow-runner/executors/phase-executor.js +25 -4
- package/dist/workflow-runner/pipeline-runner.js +10 -10
- package/package.json +1 -1
|
@@ -56,10 +56,6 @@ export declare function fetchPRReviews(octokit: Octokit, owner: string, repo: st
|
|
|
56
56
|
* Fetch PR review comments
|
|
57
57
|
*/
|
|
58
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
59
|
/**
|
|
64
60
|
* Fetch user stories via MCP
|
|
65
61
|
*/
|
|
@@ -54,51 +54,6 @@ export async function fetchPRReviewComments(octokit, owner, repo, prNumber, verb
|
|
|
54
54
|
}
|
|
55
55
|
return comments;
|
|
56
56
|
}
|
|
57
|
-
/**
|
|
58
|
-
* Fetch technical design via MCP
|
|
59
|
-
*/
|
|
60
|
-
export async function fetchTechnicalDesign(mcpServerUrl, mcpToken, featureId, verbose) {
|
|
61
|
-
try {
|
|
62
|
-
if (verbose) {
|
|
63
|
-
console.log(`📐 Fetching technical design for ${featureId}...`);
|
|
64
|
-
}
|
|
65
|
-
const response = await fetch(`${mcpServerUrl}/mcp`, {
|
|
66
|
-
method: 'POST',
|
|
67
|
-
headers: {
|
|
68
|
-
'Content-Type': 'application/json',
|
|
69
|
-
Authorization: `Bearer ${mcpToken}`,
|
|
70
|
-
},
|
|
71
|
-
body: JSON.stringify({
|
|
72
|
-
jsonrpc: '2.0',
|
|
73
|
-
id: 1,
|
|
74
|
-
method: 'technical-design/get',
|
|
75
|
-
params: {
|
|
76
|
-
feature_id: featureId,
|
|
77
|
-
},
|
|
78
|
-
}),
|
|
79
|
-
});
|
|
80
|
-
if (!response.ok) {
|
|
81
|
-
if (verbose) {
|
|
82
|
-
console.log(`⚠️ Could not fetch technical design: ${response.status}`);
|
|
83
|
-
}
|
|
84
|
-
return undefined;
|
|
85
|
-
}
|
|
86
|
-
const data = await response.json();
|
|
87
|
-
if (data.error || !data.result) {
|
|
88
|
-
if (verbose) {
|
|
89
|
-
console.log(`⚠️ Technical design not available`);
|
|
90
|
-
}
|
|
91
|
-
return undefined;
|
|
92
|
-
}
|
|
93
|
-
return data.result.technical_design;
|
|
94
|
-
}
|
|
95
|
-
catch (error) {
|
|
96
|
-
if (verbose) {
|
|
97
|
-
console.log(`⚠️ Error fetching technical design: ${error}`);
|
|
98
|
-
}
|
|
99
|
-
return undefined;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
57
|
/**
|
|
103
58
|
* Fetch user stories via MCP
|
|
104
59
|
*/
|
|
@@ -116,7 +71,7 @@ export async function fetchUserStories(mcpServerUrl, mcpToken, featureId, verbos
|
|
|
116
71
|
body: JSON.stringify({
|
|
117
72
|
jsonrpc: '2.0',
|
|
118
73
|
id: 1,
|
|
119
|
-
method: '
|
|
74
|
+
method: 'user_stories/list',
|
|
120
75
|
params: {
|
|
121
76
|
feature_id: featureId,
|
|
122
77
|
},
|
|
@@ -161,7 +116,7 @@ export async function fetchTestCases(mcpServerUrl, mcpToken, featureId, verbose)
|
|
|
161
116
|
body: JSON.stringify({
|
|
162
117
|
jsonrpc: '2.0',
|
|
163
118
|
id: 1,
|
|
164
|
-
method: '
|
|
119
|
+
method: 'test_cases/list',
|
|
165
120
|
params: {
|
|
166
121
|
feature_id: featureId,
|
|
167
122
|
},
|
|
@@ -209,10 +164,9 @@ export async function fetchCodeRefineContext(mcpServerUrl, mcpToken, featureId,
|
|
|
209
164
|
auth: githubToken,
|
|
210
165
|
});
|
|
211
166
|
// Fetch PR reviews and comments
|
|
212
|
-
const [reviews, reviewComments,
|
|
167
|
+
const [reviews, reviewComments, userStories, testCases] = await Promise.all([
|
|
213
168
|
fetchPRReviews(octokit, owner, repo, prNumber, verbose),
|
|
214
169
|
fetchPRReviewComments(octokit, owner, repo, prNumber, verbose),
|
|
215
|
-
fetchTechnicalDesign(mcpServerUrl, mcpToken, featureId, verbose),
|
|
216
170
|
fetchUserStories(mcpServerUrl, mcpToken, featureId, verbose),
|
|
217
171
|
fetchTestCases(mcpServerUrl, mcpToken, featureId, verbose),
|
|
218
172
|
]);
|
|
@@ -226,7 +180,7 @@ export async function fetchCodeRefineContext(mcpServerUrl, mcpToken, featureId,
|
|
|
226
180
|
repo,
|
|
227
181
|
reviews,
|
|
228
182
|
reviewComments,
|
|
229
|
-
technicalDesign,
|
|
183
|
+
technicalDesign: feature.technical_design,
|
|
230
184
|
userStories,
|
|
231
185
|
testCases,
|
|
232
186
|
};
|
|
@@ -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,109 @@
|
|
|
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
|
+
do {
|
|
17
|
+
// 1. Run code refine phase
|
|
18
|
+
const codeRefineResult = await runCodeRefinePhase(options, config);
|
|
19
|
+
results.push(logPhaseResult(codeRefineResult, verbose));
|
|
20
|
+
// If code refine failed, stop immediately
|
|
21
|
+
if (codeRefineResult.status === 'error') {
|
|
22
|
+
if (verbose) {
|
|
23
|
+
console.log(`❌ Code refine failed: ${codeRefineResult.message}. Cannot proceed to verification.`);
|
|
24
|
+
}
|
|
25
|
+
return codeRefineResult;
|
|
26
|
+
}
|
|
27
|
+
// 2. Run code refine verification
|
|
28
|
+
verificationResult = await runCodeRefineVerificationPhase(options, config);
|
|
29
|
+
results.push(logPhaseResult(verificationResult, verbose));
|
|
30
|
+
// If verification passed, we're done
|
|
31
|
+
if (verificationResult.status === 'success') {
|
|
32
|
+
if (verbose) {
|
|
33
|
+
console.log(`✅ Code refine verification passed. All PR comments have been addressed.`);
|
|
34
|
+
}
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
// Verification failed - check if we should retry
|
|
38
|
+
if (refineAttempt < MAX_REFINE_ATTEMPTS) {
|
|
39
|
+
refineAttempt++;
|
|
40
|
+
if (verbose) {
|
|
41
|
+
console.log(`⚠️ Code refine verification failed. Retrying... (Attempt ${refineAttempt}/${MAX_REFINE_ATTEMPTS})`);
|
|
42
|
+
}
|
|
43
|
+
// Extract verification failure details
|
|
44
|
+
const verificationData = verificationResult.data;
|
|
45
|
+
const unresolvedComments = verificationData?.unresolvedComments ||
|
|
46
|
+
verificationData?.unresolved_comments ||
|
|
47
|
+
0;
|
|
48
|
+
const suggestions = verificationData?.suggestions || [];
|
|
49
|
+
const unresolvedDetails = verificationData?.unresolvedCommentDetails ||
|
|
50
|
+
verificationData?.unresolved_comment_details ||
|
|
51
|
+
[];
|
|
52
|
+
// Log audit event for verification failure with suggestions
|
|
53
|
+
await logFeaturePhaseEvent(options.mcpServerUrl, options.mcpToken, {
|
|
54
|
+
featureId: options.featureId,
|
|
55
|
+
eventType: 'phase_failed',
|
|
56
|
+
phase: 'code_refine_verification',
|
|
57
|
+
result: 'error',
|
|
58
|
+
metadata: {
|
|
59
|
+
attempt: refineAttempt,
|
|
60
|
+
unresolved_comments: unresolvedComments,
|
|
61
|
+
suggestions: suggestions,
|
|
62
|
+
unresolved_comment_details: unresolvedDetails,
|
|
63
|
+
will_retry: true,
|
|
64
|
+
},
|
|
65
|
+
errorMessage: verificationResult.message,
|
|
66
|
+
}, verbose);
|
|
67
|
+
if (verbose && suggestions.length > 0) {
|
|
68
|
+
console.log(`\n💡 Suggestions for next iteration:`);
|
|
69
|
+
suggestions.forEach((suggestion) => {
|
|
70
|
+
console.log(` ${suggestion}`);
|
|
71
|
+
});
|
|
72
|
+
console.log('');
|
|
73
|
+
}
|
|
74
|
+
// Remove the failed verification result - we'll add the new one after retry
|
|
75
|
+
results.pop();
|
|
76
|
+
// Update status to indicate we're retrying code refine
|
|
77
|
+
await updateFeatureStatusForPhase({ mcpServerUrl: options.mcpServerUrl, mcpToken: options.mcpToken }, options.featureId, 'code-refine', verbose);
|
|
78
|
+
// Also remove the code refine result so it can be retried
|
|
79
|
+
results.pop();
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
// Max attempts reached
|
|
83
|
+
if (verbose) {
|
|
84
|
+
console.log(`⚠️ Maximum refine attempts (${MAX_REFINE_ATTEMPTS}) reached. Verification still failing.`);
|
|
85
|
+
}
|
|
86
|
+
// Log final failure with all details
|
|
87
|
+
const verificationData = verificationResult.data;
|
|
88
|
+
const suggestions = verificationData?.suggestions || [];
|
|
89
|
+
const unresolvedDetails = verificationData?.unresolvedCommentDetails ||
|
|
90
|
+
verificationData?.unresolved_comment_details ||
|
|
91
|
+
[];
|
|
92
|
+
await logFeaturePhaseEvent(options.mcpServerUrl, options.mcpToken, {
|
|
93
|
+
featureId: options.featureId,
|
|
94
|
+
eventType: 'phase_failed',
|
|
95
|
+
phase: 'code_refine_verification',
|
|
96
|
+
result: 'error',
|
|
97
|
+
metadata: {
|
|
98
|
+
attempt: refineAttempt,
|
|
99
|
+
max_attempts_reached: true,
|
|
100
|
+
suggestions: suggestions,
|
|
101
|
+
unresolved_comment_details: unresolvedDetails,
|
|
102
|
+
},
|
|
103
|
+
errorMessage: `Max attempts reached: ${verificationResult.message}`,
|
|
104
|
+
}, verbose);
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
} while (refineAttempt <= MAX_REFINE_ATTEMPTS);
|
|
108
|
+
return verificationResult;
|
|
109
|
+
}
|
|
@@ -9,14 +9,34 @@ export interface CodeRefineVerificationOptions {
|
|
|
9
9
|
githubToken: string;
|
|
10
10
|
verbose?: boolean;
|
|
11
11
|
}
|
|
12
|
-
export interface
|
|
12
|
+
export interface CodeRefineVerificationData {
|
|
13
13
|
featureId: string;
|
|
14
|
-
|
|
15
|
-
|
|
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,55 @@ 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
|
|
112
|
-
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
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
|
}
|
|
141
|
+
// Check review comments resolution status
|
|
129
142
|
const commentStatus = await Promise.all(reviewComments.map(async (comment) => {
|
|
130
143
|
const isResolved = await isCommentThreadResolved(octokit, owner, repo, prNumber, comment.id, verbose);
|
|
131
144
|
return { comment, isResolved };
|
|
132
145
|
}));
|
|
133
146
|
const resolvedComments = commentStatus.filter((c) => c.isResolved);
|
|
134
147
|
const unresolvedComments = commentStatus.filter((c) => !c.isResolved);
|
|
148
|
+
// Check reviews - they need to be dismissed or re-reviewed
|
|
149
|
+
// We consider reviews as "unresolved" if they are still in CHANGES_REQUESTED state
|
|
150
|
+
const unresolvedReviews = reviews.filter((review) => review.state === 'CHANGES_REQUESTED');
|
|
135
151
|
if (verbose) {
|
|
136
|
-
logInfo(`📊
|
|
152
|
+
logInfo(`📊 Comments: ${resolvedComments.length} resolved, ${unresolvedComments.length} unresolved`);
|
|
153
|
+
if (reviews.length > 0) {
|
|
154
|
+
logInfo(`📊 Reviews: ${reviews.length - unresolvedReviews.length} addressed, ${unresolvedReviews.length} still requesting changes`);
|
|
155
|
+
}
|
|
137
156
|
}
|
|
138
|
-
// If all comments are resolved
|
|
139
|
-
if (unresolvedComments.length === 0) {
|
|
157
|
+
// If all comments are resolved AND no reviews requesting changes, success
|
|
158
|
+
if (unresolvedComments.length === 0 && unresolvedReviews.length === 0) {
|
|
140
159
|
if (verbose) {
|
|
141
160
|
logInfo('✅ All comments have been addressed! Marking them as resolved...');
|
|
142
161
|
}
|
|
@@ -150,34 +169,118 @@ export async function verifyAndResolveComments(options) {
|
|
|
150
169
|
if (verbose) {
|
|
151
170
|
logInfo(`✅ Marked ${markedCount} comments as resolved on GitHub`);
|
|
152
171
|
}
|
|
172
|
+
const successMessage = reviews.length > 0
|
|
173
|
+
? 'All reviews and review comments have been addressed and resolved'
|
|
174
|
+
: 'All review comments have been addressed and resolved';
|
|
153
175
|
return {
|
|
154
|
-
featureId,
|
|
155
176
|
status: 'success',
|
|
156
|
-
message:
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
177
|
+
message: successMessage,
|
|
178
|
+
data: {
|
|
179
|
+
featureId,
|
|
180
|
+
totalReviews: reviews.length,
|
|
181
|
+
unresolvedReviews: 0,
|
|
182
|
+
totalComments: reviewComments.length,
|
|
183
|
+
resolvedComments: resolvedComments.length,
|
|
184
|
+
unresolvedComments: 0,
|
|
185
|
+
commentsMarkedResolved: markedCount,
|
|
186
|
+
},
|
|
161
187
|
};
|
|
162
188
|
}
|
|
163
189
|
else {
|
|
164
190
|
if (verbose) {
|
|
165
|
-
|
|
191
|
+
if (unresolvedReviews.length > 0) {
|
|
192
|
+
logInfo(`⚠️ ${unresolvedReviews.length} reviews still requesting changes`);
|
|
193
|
+
unresolvedReviews.forEach((review) => {
|
|
194
|
+
logInfo(` - Review ${review.id} by @${review.user.login}`);
|
|
195
|
+
if (review.body) {
|
|
196
|
+
logInfo(` ${review.body.substring(0, 100)}...`);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
if (unresolvedComments.length > 0) {
|
|
201
|
+
logInfo(`⚠️ ${unresolvedComments.length} comments still need to be addressed`);
|
|
202
|
+
unresolvedComments.forEach(({ comment }) => {
|
|
203
|
+
logInfo(` - Comment ${comment.id} by @${comment.user.login}`);
|
|
204
|
+
if (comment.path) {
|
|
205
|
+
logInfo(` File: ${comment.path}:${comment.line || '?'}`);
|
|
206
|
+
}
|
|
207
|
+
logInfo(` ${comment.body.substring(0, 100)}...`);
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
// Build suggestions for code-refine phase
|
|
212
|
+
const suggestions = [];
|
|
213
|
+
// Add reviews information
|
|
214
|
+
if (unresolvedReviews.length > 0) {
|
|
215
|
+
suggestions.push(`Found ${unresolvedReviews.length} reviews still requesting changes:`);
|
|
216
|
+
unresolvedReviews.forEach((review) => {
|
|
217
|
+
suggestions.push(`- Review by @${review.user.login}${review.body ? `: ${review.body}` : ''}`);
|
|
218
|
+
});
|
|
219
|
+
suggestions.push('');
|
|
220
|
+
}
|
|
221
|
+
// Add comments information
|
|
222
|
+
if (unresolvedComments.length > 0) {
|
|
223
|
+
suggestions.push(`Found ${unresolvedComments.length} unresolved PR review comments:`);
|
|
166
224
|
unresolvedComments.forEach(({ comment }) => {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
}
|
|
171
|
-
logInfo(` ${comment.body.substring(0, 100)}...`);
|
|
225
|
+
const location = comment.path
|
|
226
|
+
? `${comment.path}${comment.line ? `:${comment.line}` : ''}`
|
|
227
|
+
: 'General comment';
|
|
228
|
+
suggestions.push(`- [${location}] ${comment.user.login}: ${comment.body}`);
|
|
172
229
|
});
|
|
230
|
+
suggestions.push('');
|
|
231
|
+
}
|
|
232
|
+
suggestions.push('Suggestions for next code-refine iteration:');
|
|
233
|
+
if (unresolvedReviews.length > 0) {
|
|
234
|
+
suggestions.push('1. Address the overall concerns raised in the reviews requesting changes');
|
|
235
|
+
suggestions.push('2. Make significant improvements to satisfy the reviewers');
|
|
236
|
+
}
|
|
237
|
+
if (unresolvedComments.length > 0) {
|
|
238
|
+
suggestions.push(`${unresolvedReviews.length > 0 ? '3' : '1'}. Carefully read each unresolved comment above`);
|
|
239
|
+
suggestions.push(`${unresolvedReviews.length > 0 ? '4' : '2'}. Locate the specific file and line mentioned in each comment`);
|
|
240
|
+
suggestions.push(`${unresolvedReviews.length > 0 ? '5' : '3'}. Make the requested changes to address the reviewer feedback`);
|
|
241
|
+
}
|
|
242
|
+
suggestions.push(`${unresolvedReviews.length > 0 || unresolvedComments.length > 0 ? (unresolvedReviews.length > 0 ? (unresolvedComments.length > 0 ? '6' : '3') : '4') : '1'}. Ensure your changes fully resolve the concerns raised`);
|
|
243
|
+
suggestions.push(`${unresolvedReviews.length > 0 || unresolvedComments.length > 0 ? (unresolvedReviews.length > 0 ? (unresolvedComments.length > 0 ? '7' : '4') : '5') : '2'}. Test your changes before committing`);
|
|
244
|
+
// Build detailed unresolved info
|
|
245
|
+
const unresolvedCommentDetails = unresolvedComments.map(({ comment }) => ({
|
|
246
|
+
commentId: comment.id,
|
|
247
|
+
author: comment.user.login,
|
|
248
|
+
file: comment.path || 'unknown',
|
|
249
|
+
line: comment.line,
|
|
250
|
+
body: comment.body,
|
|
251
|
+
}));
|
|
252
|
+
const unresolvedReviewDetails = unresolvedReviews.map((review) => ({
|
|
253
|
+
reviewId: review.id,
|
|
254
|
+
author: review.user.login,
|
|
255
|
+
state: review.state,
|
|
256
|
+
body: review.body,
|
|
257
|
+
submittedAt: review.submitted_at,
|
|
258
|
+
}));
|
|
259
|
+
const totalUnresolved = unresolvedReviews.length + unresolvedComments.length;
|
|
260
|
+
let errorMessage = '';
|
|
261
|
+
if (unresolvedReviews.length > 0 && unresolvedComments.length > 0) {
|
|
262
|
+
errorMessage = `${unresolvedReviews.length} reviews and ${unresolvedComments.length} comments still need to be addressed`;
|
|
263
|
+
}
|
|
264
|
+
else if (unresolvedReviews.length > 0) {
|
|
265
|
+
errorMessage = `${unresolvedReviews.length} reviews still requesting changes`;
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
errorMessage = `${unresolvedComments.length} review comments still need to be addressed`;
|
|
173
269
|
}
|
|
174
270
|
return {
|
|
175
|
-
featureId,
|
|
176
271
|
status: 'error',
|
|
177
|
-
message:
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
272
|
+
message: errorMessage,
|
|
273
|
+
data: {
|
|
274
|
+
featureId,
|
|
275
|
+
totalReviews: reviews.length,
|
|
276
|
+
unresolvedReviews: unresolvedReviews.length,
|
|
277
|
+
totalComments: reviewComments.length,
|
|
278
|
+
resolvedComments: resolvedComments.length,
|
|
279
|
+
unresolvedComments: unresolvedComments.length,
|
|
280
|
+
suggestions,
|
|
281
|
+
unresolvedReviewDetails,
|
|
282
|
+
unresolvedCommentDetails,
|
|
283
|
+
},
|
|
181
284
|
};
|
|
182
285
|
}
|
|
183
286
|
}
|
|
@@ -185,12 +288,20 @@ export async function verifyAndResolveComments(options) {
|
|
|
185
288
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
186
289
|
logError(`Code refine verification failed: ${errorMessage}`);
|
|
187
290
|
return {
|
|
188
|
-
featureId,
|
|
189
291
|
status: 'error',
|
|
190
292
|
message: `Verification failed: ${errorMessage}`,
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
293
|
+
data: {
|
|
294
|
+
featureId,
|
|
295
|
+
totalReviews: 0,
|
|
296
|
+
unresolvedReviews: 0,
|
|
297
|
+
totalComments: 0,
|
|
298
|
+
resolvedComments: 0,
|
|
299
|
+
unresolvedComments: 0,
|
|
300
|
+
suggestions: [
|
|
301
|
+
`Verification failed with error: ${errorMessage}`,
|
|
302
|
+
'Please check the error message and try again',
|
|
303
|
+
],
|
|
304
|
+
},
|
|
194
305
|
};
|
|
195
306
|
}
|
|
196
307
|
}
|
|
@@ -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
|
-
|
|
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,
|
|
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
|
-
//
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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
|
};
|