edsger 0.19.9 → 0.19.10

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.
@@ -16,6 +16,7 @@ import { logPhaseResult } from '../../utils/pipeline-logger.js';
16
16
  import { runFeatureAnalysisPhase, runTechnicalDesignPhase, runBranchPlanningPhase, runCodeImplementationPhase, runFunctionalTestingPhase, runCodeRefinePhase, runCodeReviewPhase, } from './executors/phase-executor.js';
17
17
  /**
18
18
  * Map workflow phase names (underscore format) to phase runner functions
19
+ * Note: code_refine includes built-in verification loop (similar to technical_design)
19
20
  */
20
21
  const PHASE_RUNNERS = {
21
22
  feature_analysis: runFeatureAnalysisPhase,
@@ -1,8 +1,11 @@
1
1
  /**
2
2
  * Code Refine Analyzer
3
3
  * Processes GitHub PR review feedback and refines code accordingly
4
+ * Includes built-in verification loop that resolves PR comments and dismisses reviews
4
5
  */
5
6
  import { EdsgerConfig } from '../../types/index.js';
7
+ import { type CodeRefineVerificationResult } from '../code-refine-verification/index.js';
8
+ export declare const MAX_REFINE_ITERATIONS = 10;
6
9
  export interface CodeRefineOptions {
7
10
  featureId: string;
8
11
  githubToken: string;
@@ -34,8 +37,12 @@ export interface CodeRefineResult {
34
37
  summary?: string;
35
38
  filesModified?: string[];
36
39
  commitsCreated?: number;
40
+ iterations?: number;
41
+ verificationResult?: CodeRefineVerificationResult;
37
42
  }
38
43
  /**
39
- * Main code refine function
44
+ * Main code refine function with built-in verification loop
45
+ * Similar to technical-design, this includes an iterative improvement cycle:
46
+ * refine → verification → improve → re-refine (if needed)
40
47
  */
41
48
  export declare const refineCodeFromPRFeedback: (options: CodeRefineOptions, config: EdsgerConfig) => Promise<CodeRefineResult>;
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Code Refine Analyzer
3
3
  * Processes GitHub PR review feedback and refines code accordingly
4
+ * Includes built-in verification loop that resolves PR comments and dismisses reviews
4
5
  */
5
6
  import { query } from '@anthropic-ai/claude-agent-sdk';
6
7
  import { logInfo, logError } from '../../utils/logger.js';
@@ -12,6 +13,9 @@ import { preparePhaseGitEnvironment, prepareCustomBranchGitEnvironment, hasUncom
12
13
  import { getFeature } from '../../api/features/get-feature.js';
13
14
  import { getReadyForReviewBranch, } from '../../services/branches.js';
14
15
  import { parsePullRequestUrl } from './context.js';
16
+ import { verifyAndResolveComments, } from '../code-refine-verification/index.js';
17
+ // Maximum number of refine + verification iterations
18
+ export const MAX_REFINE_ITERATIONS = 10;
15
19
  function userMessage(content) {
16
20
  return {
17
21
  type: 'user',
@@ -58,7 +62,9 @@ const pushChanges = (verbose) => {
58
62
  }
59
63
  };
60
64
  /**
61
- * Main code refine function
65
+ * Main code refine function with built-in verification loop
66
+ * Similar to technical-design, this includes an iterative improvement cycle:
67
+ * refine → verification → improve → re-refine (if needed)
62
68
  */
63
69
  export const refineCodeFromPRFeedback = async (options, config) => {
64
70
  const { featureId, githubToken, verbose } = options;
@@ -129,13 +135,14 @@ export const refineCodeFromPRFeedback = async (options, config) => {
129
135
  ? prepareCustomBranchGitEnvironment(branchName, 'main', verbose)
130
136
  : preparePhaseGitEnvironment(featureId, 'main', verbose);
131
137
  try {
132
- // Fetch code refine context (PR reviews and comments)
138
+ // Fetch initial code refine context (PR reviews and comments)
133
139
  if (verbose) {
134
140
  logInfo('Fetching code refine context from GitHub PR...');
135
141
  }
136
- const context = await fetchCodeRefineContext(featureId, githubToken, verbose, pullRequestUrl || undefined);
142
+ const initialContext = await fetchCodeRefineContext(featureId, githubToken, verbose, pullRequestUrl || undefined);
137
143
  // Check if there are any reviews or comments to address
138
- if (context.reviews.length === 0 && context.reviewComments.length === 0) {
144
+ if (initialContext.reviews.length === 0 &&
145
+ initialContext.reviewComments.length === 0) {
139
146
  if (verbose) {
140
147
  logInfo('✅ No review comments or change requests found. Nothing to refine.');
141
148
  }
@@ -144,129 +151,43 @@ export const refineCodeFromPRFeedback = async (options, config) => {
144
151
  status: 'success',
145
152
  message: 'No review feedback to address',
146
153
  summary: 'No change requests or review comments found on the PR',
154
+ iterations: 0,
147
155
  };
148
156
  }
149
157
  if (verbose) {
150
- logInfo(`📋 Found ${context.reviews.length} reviews and ${context.reviewComments.length} comments to address`);
158
+ logInfo(`📋 Found ${initialContext.reviews.length} reviews and ${initialContext.reviewComments.length} comments to address`);
151
159
  }
152
- // Fetch additional feedbacks for code-refine phase
153
- // For multi-branch features, filter by the current branch to get branch-specific feedbacks
154
- let feedbacksInfo;
155
- try {
156
- const feedbacksContext = await getFeedbacksForPhase({ featureId, verbose }, 'code_refine', currentBranch?.id // Pass branch_id if we have a current branch
157
- );
158
- if (feedbacksContext.feedbacks.length > 0) {
159
- feedbacksInfo = await formatFeedbacksForContext(feedbacksContext);
160
- if (verbose) {
161
- logInfo(`Added ${feedbacksContext.feedbacks.length} human feedbacks to code refine context${currentBranch ? ` (including branch-specific for "${currentBranch.name}")` : ''}`);
162
- }
163
- }
164
- }
165
- catch (error) {
160
+ // Iterative refine verification loop
161
+ let currentIteration = 0;
162
+ let verificationFailureContext;
163
+ let lastRefineResult = null;
164
+ let lastVerificationResult = null;
165
+ while (currentIteration < MAX_REFINE_ITERATIONS) {
166
+ currentIteration++;
166
167
  if (verbose) {
167
- logInfo(`Note: Could not fetch feedbacks (${error instanceof Error ? error.message : String(error)})`);
168
- }
169
- }
170
- // Feature branch switching is handled by preparePhaseGitEnvironment above
171
- // Create prompt for code refine
172
- const systemPrompt = createSystemPrompt();
173
- const refinePrompt = createCodeRefinePrompt(featureId, context, feedbacksInfo, options.verificationFailureContext);
174
- let lastAssistantResponse = '';
175
- let structuredRefineResult = null;
176
- if (verbose) {
177
- logInfo('Starting Claude Code query for code refine...');
178
- }
179
- // Use Claude Code SDK to refine the code
180
- for await (const message of query({
181
- prompt: prompt(refinePrompt),
182
- options: {
183
- systemPrompt: {
184
- type: 'preset',
185
- preset: 'claude_code',
186
- append: systemPrompt,
187
- },
188
- model: config.claude.model || 'sonnet',
189
- maxTurns: 2000,
190
- permissionMode: 'bypassPermissions',
191
- },
192
- })) {
193
- if (verbose) {
194
- logInfo(`Received message type: ${message.type}`);
195
- }
196
- // Stream the code refine process
197
- if (message.type === 'assistant' && message.message?.content) {
198
- for (const content of message.message.content) {
199
- if (content.type === 'text') {
200
- lastAssistantResponse += content.text + '\n';
201
- if (verbose) {
202
- console.log(`\n🔧 ${content.text}`);
203
- }
204
- }
205
- else if (content.type === 'tool_use') {
206
- if (verbose) {
207
- console.log(`\n🛠️ ${content.name}: ${content.input.description || 'Running...'}`);
208
- }
209
- }
210
- }
211
- }
212
- else if (message.type === 'result') {
213
- if (message.subtype === 'success') {
214
- logInfo('\n🛠️ Code refine completed, parsing results...');
215
- try {
216
- const responseText = message.result || lastAssistantResponse;
217
- let jsonResult = null;
218
- const jsonBlockMatch = responseText.match(/```json\s*\n([\s\S]*?)\n\s*```/);
219
- if (jsonBlockMatch) {
220
- jsonResult = JSON.parse(jsonBlockMatch[1]);
221
- }
222
- else {
223
- jsonResult = JSON.parse(responseText);
224
- }
225
- if (jsonResult && jsonResult.refine_result) {
226
- structuredRefineResult = jsonResult.refine_result;
227
- }
228
- else {
229
- throw new Error('Invalid JSON structure');
230
- }
231
- }
232
- catch (error) {
233
- logError(`Failed to parse structured refine result: ${error}`);
234
- structuredRefineResult = parseCodeRefineResponse(message.result || lastAssistantResponse);
235
- }
168
+ if (currentIteration === 1) {
169
+ logInfo(`\n🔄 Starting refine iteration ${currentIteration}/${MAX_REFINE_ITERATIONS}`);
236
170
  }
237
171
  else {
238
- logError(`\n⚠️ Code refine incomplete: ${message.subtype}`);
239
- if (message.subtype === 'error_max_turns') {
240
- logError('💡 Try simplifying the changes or reducing scope');
241
- }
242
- if (lastAssistantResponse) {
243
- try {
244
- const responseText = lastAssistantResponse;
245
- let jsonResult = null;
246
- const jsonBlockMatch = responseText.match(/```json\s*\n([\s\S]*?)\n\s*```/);
247
- if (jsonBlockMatch) {
248
- jsonResult = JSON.parse(jsonBlockMatch[1]);
249
- if (jsonResult && jsonResult.refine_result) {
250
- structuredRefineResult = jsonResult.refine_result;
251
- }
252
- }
253
- else {
254
- structuredRefineResult = parseCodeRefineResponse(lastAssistantResponse);
255
- }
256
- }
257
- catch (error) {
258
- logError(`Failed to parse assistant response: ${error}`);
259
- }
260
- }
172
+ logInfo(`\n🔄 Retry iteration ${currentIteration}/${MAX_REFINE_ITERATIONS}: Improving based on verification feedback...`);
261
173
  }
262
174
  }
263
- }
264
- // Push changes to remote
265
- if (structuredRefineResult) {
266
- // Verify all changes are committed before pushing
175
+ // Execute refine for this iteration
176
+ const refineResult = await executeRefineIteration(featureId, githubToken, config, pullRequestUrl || undefined, currentBranch, verificationFailureContext, verbose);
177
+ if (refineResult.status === 'error') {
178
+ // Refine failed - return error
179
+ return {
180
+ featureId,
181
+ status: 'error',
182
+ message: refineResult.message || 'Code refine failed',
183
+ iterations: currentIteration,
184
+ };
185
+ }
186
+ lastRefineResult = refineResult;
187
+ // Push changes before verification
267
188
  if (hasUncommittedChanges()) {
268
189
  const uncommittedFiles = getUncommittedFiles();
269
- const errorMsg = `Code refine completed but there are uncommitted changes. All changes must be committed before verification.
190
+ const errorMsg = `Code refine completed but there are uncommitted changes.
270
191
 
271
192
  Uncommitted files:
272
193
  ${uncommittedFiles.join('\n')}
@@ -277,6 +198,7 @@ Please ensure Claude Code commits all changes before completing the refine phase
277
198
  featureId,
278
199
  status: 'error',
279
200
  message: errorMsg,
201
+ iterations: currentIteration,
280
202
  };
281
203
  }
282
204
  if (verbose) {
@@ -284,24 +206,6 @@ Please ensure Claude Code commits all changes before completing the refine phase
284
206
  }
285
207
  try {
286
208
  pushChanges(verbose);
287
- const { summary, files_modified, commits_created } = structuredRefineResult;
288
- if (verbose) {
289
- logInfo(`Code refine completed for feature: ${featureId}`);
290
- if (files_modified?.length > 0) {
291
- logInfo(`Files modified: ${files_modified.join(', ')}`);
292
- }
293
- if (commits_created) {
294
- logInfo(`Commits created: ${commits_created}`);
295
- }
296
- }
297
- return {
298
- featureId,
299
- status: 'success',
300
- message: 'Code successfully refined based on PR feedback',
301
- summary: summary || 'Code refined based on PR review feedback',
302
- filesModified: files_modified || [],
303
- commitsCreated: commits_created || 1,
304
- };
305
209
  }
306
210
  catch (pushError) {
307
211
  logError(`Failed to push changes: ${pushError}`);
@@ -309,16 +213,77 @@ Please ensure Claude Code commits all changes before completing the refine phase
309
213
  featureId,
310
214
  status: 'error',
311
215
  message: `Code refined but failed to push: ${pushError}`,
216
+ iterations: currentIteration,
312
217
  };
313
218
  }
314
- }
315
- else {
316
- return {
219
+ // Run verification
220
+ if (verbose) {
221
+ logInfo('\n🔍 Running verification to check if all comments are addressed...');
222
+ }
223
+ const verificationResult = await verifyAndResolveComments({
317
224
  featureId,
318
- status: 'error',
319
- message: 'Code refine failed or incomplete',
320
- };
225
+ githubToken,
226
+ config,
227
+ verbose,
228
+ });
229
+ lastVerificationResult = verificationResult;
230
+ // If verification passed, we're done!
231
+ if (verificationResult.status === 'success') {
232
+ if (verbose) {
233
+ logInfo('✅ Verification passed! All PR comments have been addressed and resolved.');
234
+ }
235
+ return {
236
+ featureId,
237
+ status: 'success',
238
+ message: verificationResult.message,
239
+ summary: lastRefineResult.summary || 'Code refined based on PR review feedback',
240
+ filesModified: lastRefineResult.files_modified || [],
241
+ commitsCreated: lastRefineResult.commits_created || 1,
242
+ iterations: currentIteration,
243
+ verificationResult,
244
+ };
245
+ }
246
+ // Verification failed - prepare context for retry
247
+ if (currentIteration < MAX_REFINE_ITERATIONS) {
248
+ const verificationData = verificationResult.data;
249
+ const suggestions = verificationData.suggestions || [];
250
+ const unresolvedCommentDetails = verificationData.unresolvedCommentDetails || [];
251
+ const unresolvedReviewDetails = verificationData.unresolvedReviewDetails;
252
+ verificationFailureContext = {
253
+ attempt: currentIteration + 1,
254
+ suggestions,
255
+ unresolvedCommentDetails,
256
+ unresolvedReviewDetails,
257
+ };
258
+ if (verbose) {
259
+ logInfo(`\n⚠️ Verification failed: ${verificationResult.message}`);
260
+ if (suggestions.length > 0) {
261
+ logInfo(`💡 Suggestions for next iteration:`);
262
+ suggestions.forEach((suggestion) => {
263
+ logInfo(` ${suggestion}`);
264
+ });
265
+ }
266
+ logInfo(`\n🔄 Will retry... (${MAX_REFINE_ITERATIONS - currentIteration} attempts remaining)`);
267
+ }
268
+ }
269
+ else {
270
+ // Max iterations reached
271
+ if (verbose) {
272
+ logInfo(`\n⚠️ Maximum iterations (${MAX_REFINE_ITERATIONS}) reached. Verification still failing.`);
273
+ }
274
+ }
321
275
  }
276
+ // Return last verification result (which failed)
277
+ return {
278
+ featureId,
279
+ status: 'error',
280
+ message: lastVerificationResult?.message || 'Verification failed after max iterations',
281
+ summary: lastRefineResult?.summary,
282
+ filesModified: lastRefineResult?.files_modified || [],
283
+ commitsCreated: lastRefineResult?.commits_created || 1,
284
+ iterations: currentIteration,
285
+ verificationResult: lastVerificationResult || undefined,
286
+ };
322
287
  }
323
288
  catch (error) {
324
289
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -334,6 +299,136 @@ Please ensure Claude Code commits all changes before completing the refine phase
334
299
  cleanupGit();
335
300
  }
336
301
  };
302
+ /**
303
+ * Execute a single refine iteration
304
+ */
305
+ async function executeRefineIteration(featureId, githubToken, config, pullRequestUrl, currentBranch, verificationFailureContext, verbose) {
306
+ // Fetch code refine context (PR reviews and comments)
307
+ const context = await fetchCodeRefineContext(featureId, githubToken, verbose, pullRequestUrl);
308
+ // Fetch additional feedbacks for code-refine phase
309
+ let feedbacksInfo;
310
+ try {
311
+ const feedbacksContext = await getFeedbacksForPhase({ featureId, verbose }, 'code_refine', currentBranch?.id);
312
+ if (feedbacksContext.feedbacks.length > 0) {
313
+ feedbacksInfo = await formatFeedbacksForContext(feedbacksContext);
314
+ if (verbose) {
315
+ logInfo(`Added ${feedbacksContext.feedbacks.length} human feedbacks to code refine context${currentBranch ? ` (including branch-specific for "${currentBranch.name}")` : ''}`);
316
+ }
317
+ }
318
+ }
319
+ catch (error) {
320
+ if (verbose) {
321
+ logInfo(`Note: Could not fetch feedbacks (${error instanceof Error ? error.message : String(error)})`);
322
+ }
323
+ }
324
+ // Create prompt for code refine
325
+ const systemPrompt = createSystemPrompt();
326
+ const refinePrompt = createCodeRefinePrompt(featureId, context, feedbacksInfo, verificationFailureContext);
327
+ let lastAssistantResponse = '';
328
+ let structuredRefineResult = null;
329
+ if (verbose) {
330
+ logInfo('Starting Claude Code query for code refine...');
331
+ }
332
+ // Use Claude Code SDK to refine the code
333
+ for await (const message of query({
334
+ prompt: prompt(refinePrompt),
335
+ options: {
336
+ systemPrompt: {
337
+ type: 'preset',
338
+ preset: 'claude_code',
339
+ append: systemPrompt,
340
+ },
341
+ model: config.claude.model || 'sonnet',
342
+ maxTurns: 2000,
343
+ permissionMode: 'bypassPermissions',
344
+ },
345
+ })) {
346
+ if (verbose) {
347
+ logInfo(`Received message type: ${message.type}`);
348
+ }
349
+ // Stream the code refine process
350
+ if (message.type === 'assistant' && message.message?.content) {
351
+ for (const content of message.message.content) {
352
+ if (content.type === 'text') {
353
+ lastAssistantResponse += content.text + '\n';
354
+ if (verbose) {
355
+ console.log(`\n🔧 ${content.text}`);
356
+ }
357
+ }
358
+ else if (content.type === 'tool_use') {
359
+ if (verbose) {
360
+ console.log(`\n🛠️ ${content.name}: ${content.input.description || 'Running...'}`);
361
+ }
362
+ }
363
+ }
364
+ }
365
+ else if (message.type === 'result') {
366
+ if (message.subtype === 'success') {
367
+ logInfo('\n🛠️ Code refine completed, parsing results...');
368
+ try {
369
+ const responseText = message.result || lastAssistantResponse;
370
+ let jsonResult = null;
371
+ const jsonBlockMatch = responseText.match(/```json\s*\n([\s\S]*?)\n\s*```/);
372
+ if (jsonBlockMatch) {
373
+ jsonResult = JSON.parse(jsonBlockMatch[1]);
374
+ }
375
+ else {
376
+ jsonResult = JSON.parse(responseText);
377
+ }
378
+ if (jsonResult && jsonResult.refine_result) {
379
+ structuredRefineResult = jsonResult.refine_result;
380
+ }
381
+ else {
382
+ throw new Error('Invalid JSON structure');
383
+ }
384
+ }
385
+ catch (error) {
386
+ logError(`Failed to parse structured refine result: ${error}`);
387
+ structuredRefineResult = parseCodeRefineResponse(message.result || lastAssistantResponse);
388
+ }
389
+ }
390
+ else {
391
+ logError(`\n⚠️ Code refine incomplete: ${message.subtype}`);
392
+ if (message.subtype === 'error_max_turns') {
393
+ logError('💡 Try simplifying the changes or reducing scope');
394
+ }
395
+ if (lastAssistantResponse) {
396
+ try {
397
+ const responseText = lastAssistantResponse;
398
+ let jsonResult = null;
399
+ const jsonBlockMatch = responseText.match(/```json\s*\n([\s\S]*?)\n\s*```/);
400
+ if (jsonBlockMatch) {
401
+ jsonResult = JSON.parse(jsonBlockMatch[1]);
402
+ if (jsonResult && jsonResult.refine_result) {
403
+ structuredRefineResult = jsonResult.refine_result;
404
+ }
405
+ }
406
+ else {
407
+ structuredRefineResult = parseCodeRefineResponse(lastAssistantResponse);
408
+ }
409
+ }
410
+ catch (error) {
411
+ logError(`Failed to parse assistant response: ${error}`);
412
+ }
413
+ }
414
+ }
415
+ }
416
+ }
417
+ if (structuredRefineResult) {
418
+ return {
419
+ status: 'success',
420
+ summary: structuredRefineResult.summary,
421
+ files_modified: structuredRefineResult.files_modified,
422
+ commits_created: structuredRefineResult.commits_created,
423
+ };
424
+ }
425
+ else {
426
+ return {
427
+ status: 'error',
428
+ message: 'Code refine failed or incomplete',
429
+ };
430
+ }
431
+ }
337
432
  function parseCodeRefineResponse(response) {
338
433
  const summaryMatch = response.match(/## Refine Summary\n([\s\S]*?)(?=\n##|\n\n|$)/);
339
434
  const summary = summaryMatch
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "edsger",
3
- "version": "0.19.9",
3
+ "version": "0.19.10",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "edsger": "dist/index.js"