edsger 0.41.1 → 0.41.2

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.
Files changed (103) hide show
  1. package/.claude/settings.local.json +23 -3
  2. package/.env.local +12 -0
  3. package/dist/api/features/__tests__/regression-prevention.test.d.ts +5 -0
  4. package/dist/api/features/__tests__/regression-prevention.test.js +338 -0
  5. package/dist/api/features/__tests__/status-updater.integration.test.d.ts +5 -0
  6. package/dist/api/features/__tests__/status-updater.integration.test.js +497 -0
  7. package/dist/commands/workflow/pipeline-runner.d.ts +17 -0
  8. package/dist/commands/workflow/pipeline-runner.js +393 -0
  9. package/dist/commands/workflow/runner.d.ts +26 -0
  10. package/dist/commands/workflow/runner.js +119 -0
  11. package/dist/commands/workflow/workflow-runner.d.ts +26 -0
  12. package/dist/commands/workflow/workflow-runner.js +119 -0
  13. package/dist/index.js +0 -0
  14. package/dist/phases/code-implementation/analyzer-helpers.d.ts +28 -0
  15. package/dist/phases/code-implementation/analyzer-helpers.js +177 -0
  16. package/dist/phases/code-implementation/analyzer.d.ts +32 -0
  17. package/dist/phases/code-implementation/analyzer.js +629 -0
  18. package/dist/phases/code-implementation/context-fetcher.d.ts +17 -0
  19. package/dist/phases/code-implementation/context-fetcher.js +86 -0
  20. package/dist/phases/code-implementation/mcp-server.d.ts +1 -0
  21. package/dist/phases/code-implementation/mcp-server.js +93 -0
  22. package/dist/phases/code-implementation/prompts-improvement.d.ts +5 -0
  23. package/dist/phases/code-implementation/prompts-improvement.js +108 -0
  24. package/dist/phases/code-implementation-verification/verifier.d.ts +31 -0
  25. package/dist/phases/code-implementation-verification/verifier.js +196 -0
  26. package/dist/phases/code-refine/analyzer.d.ts +41 -0
  27. package/dist/phases/code-refine/analyzer.js +561 -0
  28. package/dist/phases/code-refine/context-fetcher.d.ts +94 -0
  29. package/dist/phases/code-refine/context-fetcher.js +423 -0
  30. package/dist/phases/code-refine-verification/analysis/llm-analyzer.d.ts +22 -0
  31. package/dist/phases/code-refine-verification/analysis/llm-analyzer.js +134 -0
  32. package/dist/phases/code-refine-verification/verifier.d.ts +47 -0
  33. package/dist/phases/code-refine-verification/verifier.js +597 -0
  34. package/dist/phases/code-review/analyzer.d.ts +29 -0
  35. package/dist/phases/code-review/analyzer.js +363 -0
  36. package/dist/phases/code-review/context-fetcher.d.ts +92 -0
  37. package/dist/phases/code-review/context-fetcher.js +296 -0
  38. package/dist/phases/feature-analysis/analyzer-helpers.d.ts +10 -0
  39. package/dist/phases/feature-analysis/analyzer-helpers.js +47 -0
  40. package/dist/phases/feature-analysis/analyzer.d.ts +11 -0
  41. package/dist/phases/feature-analysis/analyzer.js +208 -0
  42. package/dist/phases/feature-analysis/context-fetcher.d.ts +26 -0
  43. package/dist/phases/feature-analysis/context-fetcher.js +134 -0
  44. package/dist/phases/feature-analysis/http-fallback.d.ts +20 -0
  45. package/dist/phases/feature-analysis/http-fallback.js +95 -0
  46. package/dist/phases/feature-analysis/mcp-server.d.ts +1 -0
  47. package/dist/phases/feature-analysis/mcp-server.js +144 -0
  48. package/dist/phases/feature-analysis/prompts-improvement.d.ts +8 -0
  49. package/dist/phases/feature-analysis/prompts-improvement.js +109 -0
  50. package/dist/phases/feature-analysis-verification/verifier.d.ts +37 -0
  51. package/dist/phases/feature-analysis-verification/verifier.js +147 -0
  52. package/dist/phases/pr-execution/file-assigner.js +20 -12
  53. package/dist/phases/technical-design/analyzer-helpers.d.ts +25 -0
  54. package/dist/phases/technical-design/analyzer-helpers.js +39 -0
  55. package/dist/phases/technical-design/analyzer.d.ts +21 -0
  56. package/dist/phases/technical-design/analyzer.js +461 -0
  57. package/dist/phases/technical-design/context-fetcher.d.ts +12 -0
  58. package/dist/phases/technical-design/context-fetcher.js +39 -0
  59. package/dist/phases/technical-design/http-fallback.d.ts +17 -0
  60. package/dist/phases/technical-design/http-fallback.js +151 -0
  61. package/dist/phases/technical-design/mcp-server.d.ts +1 -0
  62. package/dist/phases/technical-design/mcp-server.js +157 -0
  63. package/dist/phases/technical-design/prompts-improvement.d.ts +5 -0
  64. package/dist/phases/technical-design/prompts-improvement.js +93 -0
  65. package/dist/phases/technical-design-verification/verifier.d.ts +53 -0
  66. package/dist/phases/technical-design-verification/verifier.js +170 -0
  67. package/dist/services/feature-branches.d.ts +77 -0
  68. package/dist/services/feature-branches.js +205 -0
  69. package/dist/workflow-runner/config/phase-configs.d.ts +5 -0
  70. package/dist/workflow-runner/config/phase-configs.js +120 -0
  71. package/dist/workflow-runner/core/feature-filter.d.ts +16 -0
  72. package/dist/workflow-runner/core/feature-filter.js +46 -0
  73. package/dist/workflow-runner/core/index.d.ts +8 -0
  74. package/dist/workflow-runner/core/index.js +12 -0
  75. package/dist/workflow-runner/core/pipeline-evaluator.d.ts +24 -0
  76. package/dist/workflow-runner/core/pipeline-evaluator.js +32 -0
  77. package/dist/workflow-runner/core/state-manager.d.ts +24 -0
  78. package/dist/workflow-runner/core/state-manager.js +42 -0
  79. package/dist/workflow-runner/core/workflow-logger.d.ts +20 -0
  80. package/dist/workflow-runner/core/workflow-logger.js +65 -0
  81. package/dist/workflow-runner/executors/phase-executor.d.ts +8 -0
  82. package/dist/workflow-runner/executors/phase-executor.js +248 -0
  83. package/dist/workflow-runner/feature-workflow-runner.d.ts +26 -0
  84. package/dist/workflow-runner/feature-workflow-runner.js +119 -0
  85. package/dist/workflow-runner/index.d.ts +2 -0
  86. package/dist/workflow-runner/index.js +2 -0
  87. package/dist/workflow-runner/pipeline-runner.d.ts +17 -0
  88. package/dist/workflow-runner/pipeline-runner.js +393 -0
  89. package/dist/workflow-runner/workflow-processor.d.ts +54 -0
  90. package/dist/workflow-runner/workflow-processor.js +170 -0
  91. package/package.json +1 -1
  92. package/dist/services/lifecycle-agent/__tests__/phase-criteria.test.d.ts +0 -4
  93. package/dist/services/lifecycle-agent/__tests__/phase-criteria.test.js +0 -133
  94. package/dist/services/lifecycle-agent/__tests__/transition-rules.test.d.ts +0 -4
  95. package/dist/services/lifecycle-agent/__tests__/transition-rules.test.js +0 -336
  96. package/dist/services/lifecycle-agent/index.d.ts +0 -24
  97. package/dist/services/lifecycle-agent/index.js +0 -25
  98. package/dist/services/lifecycle-agent/phase-criteria.d.ts +0 -57
  99. package/dist/services/lifecycle-agent/phase-criteria.js +0 -335
  100. package/dist/services/lifecycle-agent/transition-rules.d.ts +0 -60
  101. package/dist/services/lifecycle-agent/transition-rules.js +0 -184
  102. package/dist/services/lifecycle-agent/types.d.ts +0 -190
  103. package/dist/services/lifecycle-agent/types.js +0 -12
@@ -0,0 +1,423 @@
1
+ /**
2
+ * Context fetcher for code refine phase
3
+ * Fetches GitHub PR review comments and reviews using GraphQL API
4
+ */
5
+ import { Octokit } from '@octokit/rest';
6
+ import { getFeature } from '../../api/features/get-feature.js';
7
+ /**
8
+ * Extract owner, repo, and PR number from GitHub PR URL
9
+ */
10
+ export function parsePullRequestUrl(pullRequestUrl) {
11
+ const match = pullRequestUrl.match(/github\.com\/([^\/]+)\/([^\/]+)\/pull\/(\d+)/);
12
+ if (!match) {
13
+ return null;
14
+ }
15
+ return {
16
+ owner: match[1],
17
+ repo: match[2],
18
+ prNumber: parseInt(match[3], 10),
19
+ };
20
+ }
21
+ /**
22
+ * Fetch PR reviews that request changes
23
+ */
24
+ export async function fetchPRReviews(octokit, owner, repo, prNumber, verbose) {
25
+ if (verbose) {
26
+ console.log(`📋 Fetching PR reviews for ${owner}/${repo}#${prNumber}...`);
27
+ }
28
+ const { data: reviews } = await octokit.pulls.listReviews({
29
+ owner,
30
+ repo,
31
+ pull_number: prNumber,
32
+ });
33
+ // Filter for reviews that request changes
34
+ const requestChangesReviews = reviews.filter((review) => review.state === 'CHANGES_REQUESTED');
35
+ if (verbose) {
36
+ console.log(`✅ Found ${requestChangesReviews.length} reviews requesting changes`);
37
+ }
38
+ return requestChangesReviews;
39
+ }
40
+ /**
41
+ * Fetch PR review comments
42
+ */
43
+ export async function fetchPRReviewComments(octokit, owner, repo, prNumber, verbose) {
44
+ if (verbose) {
45
+ console.log(`💬 Fetching PR review comments for ${owner}/${repo}#${prNumber}...`);
46
+ }
47
+ const { data: comments } = await octokit.pulls.listReviewComments({
48
+ owner,
49
+ repo,
50
+ pull_number: prNumber,
51
+ });
52
+ if (verbose) {
53
+ console.log(`✅ Found ${comments.length} review comments`);
54
+ }
55
+ return comments;
56
+ }
57
+ /**
58
+ * Fetch PR data using GraphQL API including review threads and reviews
59
+ */
60
+ export async function fetchPRDataGraphQL(octokit, owner, repo, prNumber, verbose) {
61
+ if (verbose) {
62
+ console.log(`🔍 Fetching PR data via GraphQL for ${owner}/${repo}#${prNumber}...`);
63
+ }
64
+ const query = `
65
+ query($owner: String!, $repo: String!, $prNumber: Int!) {
66
+ repository(owner: $owner, name: $repo) {
67
+ pullRequest(number: $prNumber) {
68
+ reviews(first: 100) {
69
+ nodes {
70
+ id
71
+ databaseId
72
+ author {
73
+ login
74
+ }
75
+ body
76
+ state
77
+ submittedAt
78
+ }
79
+ }
80
+ reviewThreads(first: 100) {
81
+ nodes {
82
+ id
83
+ isResolved
84
+ isOutdated
85
+ line
86
+ originalLine
87
+ startLine
88
+ originalStartLine
89
+ diffSide
90
+ comments(first: 100) {
91
+ nodes {
92
+ id
93
+ databaseId
94
+ body
95
+ path
96
+ originalPosition
97
+ diffHunk
98
+ createdAt
99
+ url
100
+ author {
101
+ login
102
+ }
103
+ }
104
+ }
105
+ }
106
+ }
107
+ }
108
+ }
109
+ }
110
+ `;
111
+ const result = await octokit.graphql(query, {
112
+ owner,
113
+ repo,
114
+ prNumber,
115
+ });
116
+ const pullRequest = result.repository.pullRequest;
117
+ // Transform GraphQL reviews to our interface
118
+ const reviews = (pullRequest.reviews.nodes || []).map((review) => ({
119
+ id: review.databaseId,
120
+ user: {
121
+ login: review.author?.login || 'unknown',
122
+ },
123
+ body: review.body,
124
+ state: review.state,
125
+ submitted_at: review.submittedAt,
126
+ }));
127
+ // Transform GraphQL review threads to our interface
128
+ const reviewThreads = (pullRequest.reviewThreads.nodes || []).map((thread) => ({
129
+ id: thread.id,
130
+ isResolved: thread.isResolved,
131
+ isOutdated: thread.isOutdated,
132
+ line: thread.line,
133
+ originalLine: thread.originalLine,
134
+ startLine: thread.startLine,
135
+ originalStartLine: thread.originalStartLine,
136
+ diffSide: thread.diffSide,
137
+ comments: (thread.comments.nodes || []).map((comment) => ({
138
+ id: comment.databaseId,
139
+ body: comment.body,
140
+ path: comment.path,
141
+ line: thread.line, // Use thread's line for consistency
142
+ user: {
143
+ login: comment.author?.login || 'unknown',
144
+ },
145
+ created_at: comment.createdAt,
146
+ position: comment.originalPosition,
147
+ original_position: comment.originalPosition,
148
+ diff_hunk: comment.diffHunk,
149
+ url: comment.url,
150
+ })),
151
+ }));
152
+ // Filter for reviews requesting changes
153
+ const changesRequestedReviews = reviews.filter((review) => review.state === 'CHANGES_REQUESTED');
154
+ // Filter for unresolved threads
155
+ const unresolvedThreads = reviewThreads.filter((thread) => !thread.isResolved);
156
+ if (verbose) {
157
+ console.log(`✅ Found ${changesRequestedReviews.length} reviews requesting changes`);
158
+ console.log(`✅ Found ${unresolvedThreads.length} unresolved review threads`);
159
+ }
160
+ return {
161
+ reviewThreads: unresolvedThreads,
162
+ reviews: changesRequestedReviews,
163
+ };
164
+ }
165
+ /**
166
+ * Fetch user stories via MCP
167
+ */
168
+ export async function fetchUserStories(featureId, verbose) {
169
+ try {
170
+ if (verbose) {
171
+ console.log(`📖 Fetching user stories for ${featureId}...`);
172
+ }
173
+ const mcpServerUrl = process.env.MCP_SERVER_URL;
174
+ const mcpToken = process.env.MCP_TOKEN;
175
+ const response = await fetch(`${mcpServerUrl}/mcp`, {
176
+ method: 'POST',
177
+ headers: {
178
+ 'Content-Type': 'application/json',
179
+ Authorization: `Bearer ${mcpToken}`,
180
+ },
181
+ body: JSON.stringify({
182
+ jsonrpc: '2.0',
183
+ id: 1,
184
+ method: 'user_stories/list',
185
+ params: {
186
+ feature_id: featureId,
187
+ },
188
+ }),
189
+ });
190
+ if (!response.ok) {
191
+ if (verbose) {
192
+ console.log(`⚠️ Could not fetch user stories: ${response.status}`);
193
+ }
194
+ return [];
195
+ }
196
+ const data = await response.json();
197
+ if (data.error || !data.result) {
198
+ if (verbose) {
199
+ console.log(`⚠️ User stories not available`);
200
+ }
201
+ return [];
202
+ }
203
+ return data.result.user_stories || [];
204
+ }
205
+ catch (error) {
206
+ if (verbose) {
207
+ console.log(`⚠️ Error fetching user stories: ${error}`);
208
+ }
209
+ return [];
210
+ }
211
+ }
212
+ /**
213
+ * Fetch test cases via MCP
214
+ */
215
+ export async function fetchTestCases(featureId, verbose) {
216
+ try {
217
+ if (verbose) {
218
+ console.log(`🧪 Fetching test cases for ${featureId}...`);
219
+ }
220
+ const mcpServerUrl = process.env.MCP_SERVER_URL;
221
+ const mcpToken = process.env.MCP_TOKEN;
222
+ const response = await fetch(`${mcpServerUrl}/mcp`, {
223
+ method: 'POST',
224
+ headers: {
225
+ 'Content-Type': 'application/json',
226
+ Authorization: `Bearer ${mcpToken}`,
227
+ },
228
+ body: JSON.stringify({
229
+ jsonrpc: '2.0',
230
+ id: 1,
231
+ method: 'test_cases/list',
232
+ params: {
233
+ feature_id: featureId,
234
+ },
235
+ }),
236
+ });
237
+ if (!response.ok) {
238
+ if (verbose) {
239
+ console.log(`⚠️ Could not fetch test cases: ${response.status}`);
240
+ }
241
+ return [];
242
+ }
243
+ const data = await response.json();
244
+ if (data.error || !data.result) {
245
+ if (verbose) {
246
+ console.log(`⚠️ Test cases not available`);
247
+ }
248
+ return [];
249
+ }
250
+ return data.result.test_cases || [];
251
+ }
252
+ catch (error) {
253
+ if (verbose) {
254
+ console.log(`⚠️ Error fetching test cases: ${error}`);
255
+ }
256
+ return [];
257
+ }
258
+ }
259
+ /**
260
+ * Fetch complete code refine context
261
+ */
262
+ export async function fetchCodeRefineContext(featureId, githubToken, verbose) {
263
+ // Fetch feature info using shared API
264
+ const feature = await getFeature(featureId, verbose);
265
+ if (!feature.pull_request_url) {
266
+ throw new Error(`Feature ${featureId} does not have a pull request URL. Cannot perform code refine.`);
267
+ }
268
+ // Parse PR URL
269
+ const prInfo = parsePullRequestUrl(feature.pull_request_url);
270
+ if (!prInfo) {
271
+ throw new Error(`Invalid pull request URL: ${feature.pull_request_url}. Expected format: https://github.com/owner/repo/pull/123`);
272
+ }
273
+ const { owner, repo, prNumber } = prInfo;
274
+ // Initialize Octokit with GitHub token
275
+ const octokit = new Octokit({
276
+ auth: githubToken,
277
+ });
278
+ // Fetch PR data using GraphQL API and additional context
279
+ const [prData, userStories, testCases] = await Promise.all([
280
+ fetchPRDataGraphQL(octokit, owner, repo, prNumber, verbose),
281
+ fetchUserStories(featureId, verbose),
282
+ fetchTestCases(featureId, verbose),
283
+ ]);
284
+ // Extract review comments from unresolved threads
285
+ const reviewComments = prData.reviewThreads.flatMap((thread) => thread.comments);
286
+ if (verbose) {
287
+ console.log(`📊 Summary: ${prData.reviews.length} reviews requesting changes, ${prData.reviewThreads.length} unresolved threads, ${reviewComments.length} total comments`);
288
+ }
289
+ return {
290
+ featureId,
291
+ featureName: feature.name,
292
+ featureDescription: feature.description ?? null,
293
+ pullRequestUrl: feature.pull_request_url,
294
+ pullRequestNumber: prNumber,
295
+ owner,
296
+ repo,
297
+ reviews: prData.reviews,
298
+ reviewComments,
299
+ reviewThreads: prData.reviewThreads,
300
+ technicalDesign: feature.technical_design,
301
+ userStories,
302
+ testCases,
303
+ };
304
+ }
305
+ /**
306
+ * Format code refine context for prompt
307
+ */
308
+ export function formatContextForPrompt(context) {
309
+ const sections = [];
310
+ // Feature information
311
+ sections.push(`# Feature Information`);
312
+ sections.push(`**Feature ID**: ${context.featureId}`);
313
+ sections.push(`**Feature Name**: ${context.featureName}`);
314
+ if (context.featureDescription) {
315
+ sections.push(`**Description**: ${context.featureDescription}`);
316
+ }
317
+ sections.push(`**Pull Request**: ${context.pullRequestUrl} (#${context.pullRequestNumber})`);
318
+ sections.push('');
319
+ // Reviews requesting changes
320
+ if (context.reviews.length > 0) {
321
+ sections.push(`## Code Reviews Requesting Changes`);
322
+ sections.push('');
323
+ context.reviews.forEach((review, idx) => {
324
+ sections.push(`### Review ${idx + 1} by @${review.user.login}`);
325
+ sections.push(`**Submitted**: ${review.submitted_at}`);
326
+ if (review.body) {
327
+ sections.push(`**Feedback**:`);
328
+ sections.push(review.body);
329
+ }
330
+ sections.push('');
331
+ });
332
+ }
333
+ // Review threads (unresolved conversations)
334
+ if (context.reviewThreads && context.reviewThreads.length > 0) {
335
+ sections.push(`## Unresolved Review Threads`);
336
+ sections.push(`*These conversations must be resolved before the PR can be merged.*`);
337
+ sections.push('');
338
+ context.reviewThreads.forEach((thread, idx) => {
339
+ sections.push(`### Thread ${idx + 1} ${thread.isOutdated ? '(Outdated)' : ''}`);
340
+ // Show thread location
341
+ if (thread.line !== null) {
342
+ sections.push(`**Line**: ${thread.line}`);
343
+ }
344
+ if (thread.startLine !== null && thread.startLine !== thread.line) {
345
+ sections.push(`**Line Range**: ${thread.startLine} - ${thread.line || thread.originalLine}`);
346
+ }
347
+ sections.push(`**Status**: ❌ Unresolved`);
348
+ sections.push('');
349
+ // Show all comments in this thread
350
+ thread.comments.forEach((comment, commentIdx) => {
351
+ sections.push(`#### ${commentIdx === 0 ? 'Original Comment' : `Reply ${commentIdx}`} by @${comment.user.login}`);
352
+ if (comment.path) {
353
+ sections.push(`**File**: ${comment.path}`);
354
+ }
355
+ if (comment.url) {
356
+ sections.push(`**URL**: ${comment.url}`);
357
+ }
358
+ if (commentIdx === 0 && comment.diff_hunk) {
359
+ sections.push(`**Context**:`);
360
+ sections.push('```diff');
361
+ sections.push(comment.diff_hunk);
362
+ sections.push('```');
363
+ }
364
+ sections.push(`**Comment**:`);
365
+ sections.push(comment.body);
366
+ sections.push('');
367
+ });
368
+ });
369
+ }
370
+ else if (context.reviewComments.length > 0) {
371
+ // Fallback to old format if no thread data available
372
+ sections.push(`## Review Comments`);
373
+ sections.push('');
374
+ context.reviewComments.forEach((comment, idx) => {
375
+ sections.push(`### Comment ${idx + 1} by @${comment.user.login}`);
376
+ if (comment.path) {
377
+ sections.push(`**File**: ${comment.path}`);
378
+ }
379
+ if (comment.line !== null) {
380
+ sections.push(`**Line**: ${comment.line}`);
381
+ }
382
+ if (comment.diff_hunk) {
383
+ sections.push(`**Context**:`);
384
+ sections.push('```diff');
385
+ sections.push(comment.diff_hunk);
386
+ sections.push('```');
387
+ }
388
+ sections.push(`**Comment**:`);
389
+ sections.push(comment.body);
390
+ sections.push('');
391
+ });
392
+ }
393
+ // Technical design
394
+ if (context.technicalDesign) {
395
+ sections.push(`## Technical Design`);
396
+ sections.push('');
397
+ sections.push(context.technicalDesign);
398
+ sections.push('');
399
+ }
400
+ // User stories
401
+ if (context.userStories && context.userStories.length > 0) {
402
+ sections.push(`## User Stories`);
403
+ sections.push('');
404
+ context.userStories.forEach((story) => {
405
+ sections.push(`### ${story.title}`);
406
+ sections.push(story.description || '');
407
+ sections.push('');
408
+ });
409
+ }
410
+ // Test cases
411
+ if (context.testCases && context.testCases.length > 0) {
412
+ sections.push(`## Test Cases`);
413
+ sections.push('');
414
+ context.testCases.forEach((testCase) => {
415
+ sections.push(`### ${testCase.title}`);
416
+ if (testCase.description) {
417
+ sections.push(testCase.description);
418
+ }
419
+ sections.push('');
420
+ });
421
+ }
422
+ return sections.join('\n');
423
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * LLM-based analysis for code review comment verification
3
+ * Uses Claude to intelligently determine if feedback has been addressed
4
+ */
5
+ import { ReviewThread, PRFileChange } from '../types.js';
6
+ import { EdsgerConfig } from '../../../types/index.js';
7
+ export interface ThreadAnalysisResult {
8
+ isAddressed: boolean;
9
+ reason: string;
10
+ }
11
+ /**
12
+ * Analyze whether a review thread has been addressed by examining code changes
13
+ * Uses LLM to intelligently determine if the feedback was addressed
14
+ */
15
+ export declare function analyzeThreadWithLLM(thread: ReviewThread, fileChange: PRFileChange | undefined, config: EdsgerConfig, verbose?: boolean): Promise<ThreadAnalysisResult>;
16
+ /**
17
+ * Analyze all review threads in parallel using LLM
18
+ */
19
+ export declare function analyzeAllThreads(threads: ReviewThread[], fileChanges: PRFileChange[], config: EdsgerConfig, verbose?: boolean): Promise<Array<{
20
+ thread: ReviewThread;
21
+ analysis: ThreadAnalysisResult;
22
+ }>>;
@@ -0,0 +1,134 @@
1
+ /**
2
+ * LLM-based analysis for code review comment verification
3
+ * Uses Claude to intelligently determine if feedback has been addressed
4
+ */
5
+ import { query } from '@anthropic-ai/claude-code';
6
+ import { logInfo, logError } from '../../../utils/logger.js';
7
+ import { createThreadAnalysisPrompt } from '../prompts.js';
8
+ /**
9
+ * Analyze whether a review thread has been addressed by examining code changes
10
+ * Uses LLM to intelligently determine if the feedback was addressed
11
+ */
12
+ export async function analyzeThreadWithLLM(thread, fileChange, config, verbose) {
13
+ const firstComment = thread.comments.nodes[0];
14
+ if (!firstComment) {
15
+ return {
16
+ isAddressed: false,
17
+ reason: 'Comment thread exists but has no comments',
18
+ };
19
+ }
20
+ // If file was not changed at all, feedback definitely not addressed
21
+ if (!fileChange || !fileChange.patch) {
22
+ return {
23
+ isAddressed: false,
24
+ reason: `File ${firstComment.path} has not been modified in this PR`,
25
+ };
26
+ }
27
+ // If file was deleted, thread should be resolved as obsolete
28
+ if (fileChange.status === 'removed') {
29
+ return {
30
+ isAddressed: true,
31
+ reason: 'File has been removed',
32
+ };
33
+ }
34
+ try {
35
+ if (verbose) {
36
+ logInfo(`🤖 Using LLM to analyze if comment in ${firstComment.path}:${firstComment.line} has been addressed...`);
37
+ }
38
+ const analysisPrompt = createThreadAnalysisPrompt(thread, fileChange, firstComment);
39
+ let lastResponse = '';
40
+ let analysisResult = null;
41
+ function* userMessage() {
42
+ yield {
43
+ type: 'user',
44
+ message: { role: 'user', content: analysisPrompt },
45
+ };
46
+ }
47
+ for await (const message of query({
48
+ prompt: userMessage(),
49
+ options: {
50
+ model: config.claude.model || 'sonnet',
51
+ maxTurns: 10,
52
+ permissionMode: 'bypassPermissions',
53
+ },
54
+ })) {
55
+ if (message.type === 'assistant' && message.message?.content) {
56
+ for (const content of message.message.content) {
57
+ if (content.type === 'text') {
58
+ lastResponse += content.text + '\n';
59
+ }
60
+ }
61
+ }
62
+ if (message.type === 'result') {
63
+ if (message.subtype === 'success') {
64
+ const responseText = message.result || lastResponse;
65
+ // Try to extract JSON from response
66
+ const jsonMatch = responseText.match(/```json\s*\n([\s\S]*?)\n\s*```/);
67
+ if (jsonMatch) {
68
+ analysisResult = JSON.parse(jsonMatch[1]);
69
+ }
70
+ else {
71
+ // Try to parse directly
72
+ try {
73
+ analysisResult = JSON.parse(responseText);
74
+ }
75
+ catch {
76
+ // Fallback: look for isAddressed boolean in text
77
+ const isAddressedMatch = /isAddressed["']?\s*:\s*(true|false)/i.exec(responseText);
78
+ if (isAddressedMatch) {
79
+ analysisResult = {
80
+ isAddressed: isAddressedMatch[1].toLowerCase() === 'true',
81
+ reason: responseText.split('\n').find((line) => line.trim()) ||
82
+ 'Analysis completed',
83
+ };
84
+ }
85
+ }
86
+ }
87
+ }
88
+ }
89
+ }
90
+ if (analysisResult) {
91
+ if (verbose) {
92
+ logInfo(` ${analysisResult.isAddressed ? '✅' : '❌'} ${analysisResult.reason}`);
93
+ }
94
+ return analysisResult;
95
+ }
96
+ // Fallback if LLM analysis failed
97
+ return {
98
+ isAddressed: false,
99
+ reason: 'Unable to analyze - please review manually',
100
+ };
101
+ }
102
+ catch (error) {
103
+ if (verbose) {
104
+ logError(`LLM analysis failed: ${error}`);
105
+ }
106
+ return {
107
+ isAddressed: false,
108
+ reason: 'Analysis failed - please review manually',
109
+ };
110
+ }
111
+ }
112
+ /**
113
+ * Analyze all review threads in parallel using LLM
114
+ */
115
+ export async function analyzeAllThreads(threads, fileChanges, config, verbose) {
116
+ if (verbose && threads.length > 0) {
117
+ logInfo(`🔍 Analyzing ${threads.length} unresolved threads with LLM...`);
118
+ }
119
+ const results = await Promise.all(threads.map(async (thread) => {
120
+ const firstComment = thread.comments.nodes[0];
121
+ const fileChange = fileChanges.find((fc) => fc.filename === firstComment?.path);
122
+ const analysis = await analyzeThreadWithLLM(thread, fileChange, config, verbose);
123
+ return {
124
+ thread,
125
+ analysis,
126
+ };
127
+ }));
128
+ if (verbose) {
129
+ const addressedCount = results.filter((r) => r.analysis.isAddressed).length;
130
+ const unresolvedCount = results.length - addressedCount;
131
+ logInfo(`📊 LLM Analysis: ${addressedCount} threads addressed, ${unresolvedCount} still need attention`);
132
+ }
133
+ return results;
134
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Code Refine Verification
3
+ * Verifies that all PR review comments have been addressed and resolves them
4
+ * Uses GitHub GraphQL API to accurately detect unresolved review threads
5
+ */
6
+ import { EdsgerConfig } from '../../types/index.js';
7
+ export interface CodeRefineVerificationOptions {
8
+ featureId: string;
9
+ githubToken: string;
10
+ config: EdsgerConfig;
11
+ verbose?: boolean;
12
+ }
13
+ export interface CodeRefineVerificationData {
14
+ featureId: string;
15
+ totalReviews: number;
16
+ unresolvedReviews: number;
17
+ totalComments: number;
18
+ resolvedComments: number;
19
+ unresolvedComments: number;
20
+ commentsMarkedResolved?: number;
21
+ suggestions?: string[];
22
+ unresolvedReviewDetails?: Array<{
23
+ reviewId: number;
24
+ author: string;
25
+ state: string;
26
+ body: string | null;
27
+ submittedAt: string | null;
28
+ }>;
29
+ unresolvedCommentDetails?: Array<{
30
+ commentId: string;
31
+ author: string;
32
+ file: string;
33
+ line: number | null;
34
+ body: string;
35
+ failureReason: string;
36
+ url: string;
37
+ }>;
38
+ }
39
+ export interface CodeRefineVerificationResult {
40
+ status: 'success' | 'error';
41
+ message: string;
42
+ data: CodeRefineVerificationData;
43
+ }
44
+ /**
45
+ * Verify and resolve PR review comments using GraphQL API and LLM analysis
46
+ */
47
+ export declare function verifyAndResolveComments(options: CodeRefineVerificationOptions): Promise<CodeRefineVerificationResult>;