edsger 0.4.2 → 0.4.3

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.
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Context fetcher for code refine phase
3
- * Fetches GitHub PR review comments that request changes
3
+ * Fetches GitHub PR review comments and reviews using GraphQL API
4
4
  */
5
5
  import { Octokit } from '@octokit/rest';
6
6
  export interface PRReviewComment {
@@ -16,6 +16,7 @@ export interface PRReviewComment {
16
16
  original_position: number | null;
17
17
  diff_hunk: string | null;
18
18
  in_reply_to_id?: number;
19
+ url?: string;
19
20
  }
20
21
  export interface PRReview {
21
22
  id: number;
@@ -26,6 +27,17 @@ export interface PRReview {
26
27
  state: string;
27
28
  submitted_at: string | null;
28
29
  }
30
+ export interface PRReviewThread {
31
+ id: string;
32
+ isResolved: boolean;
33
+ isOutdated: boolean;
34
+ line: number | null;
35
+ originalLine: number | null;
36
+ startLine: number | null;
37
+ originalStartLine: number | null;
38
+ diffSide: string;
39
+ comments: PRReviewComment[];
40
+ }
29
41
  export interface CodeRefineContext {
30
42
  featureId: string;
31
43
  featureName: string;
@@ -36,6 +48,7 @@ export interface CodeRefineContext {
36
48
  repo: string;
37
49
  reviews: PRReview[];
38
50
  reviewComments: PRReviewComment[];
51
+ reviewThreads?: PRReviewThread[];
39
52
  technicalDesign?: string;
40
53
  userStories?: any[];
41
54
  testCases?: any[];
@@ -56,6 +69,13 @@ export declare function fetchPRReviews(octokit: Octokit, owner: string, repo: st
56
69
  * Fetch PR review comments
57
70
  */
58
71
  export declare function fetchPRReviewComments(octokit: Octokit, owner: string, repo: string, prNumber: number, verbose?: boolean): Promise<PRReviewComment[]>;
72
+ /**
73
+ * Fetch PR data using GraphQL API including review threads and reviews
74
+ */
75
+ export declare function fetchPRDataGraphQL(octokit: Octokit, owner: string, repo: string, prNumber: number, verbose?: boolean): Promise<{
76
+ reviewThreads: PRReviewThread[];
77
+ reviews: PRReview[];
78
+ }>;
59
79
  /**
60
80
  * Fetch user stories via MCP
61
81
  */
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Context fetcher for code refine phase
3
- * Fetches GitHub PR review comments that request changes
3
+ * Fetches GitHub PR review comments and reviews using GraphQL API
4
4
  */
5
5
  import { Octokit } from '@octokit/rest';
6
6
  import { getFeature } from '../../api/features/get-feature.js';
@@ -54,6 +54,114 @@ export async function fetchPRReviewComments(octokit, owner, repo, prNumber, verb
54
54
  }
55
55
  return comments;
56
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
+ }
57
165
  /**
58
166
  * Fetch user stories via MCP
59
167
  */
@@ -163,13 +271,17 @@ export async function fetchCodeRefineContext(mcpServerUrl, mcpToken, featureId,
163
271
  const octokit = new Octokit({
164
272
  auth: githubToken,
165
273
  });
166
- // Fetch PR reviews and comments
167
- const [reviews, reviewComments, userStories, testCases] = await Promise.all([
168
- fetchPRReviews(octokit, owner, repo, prNumber, verbose),
169
- fetchPRReviewComments(octokit, owner, repo, prNumber, verbose),
274
+ // Fetch PR data using GraphQL API and additional context
275
+ const [prData, userStories, testCases] = await Promise.all([
276
+ fetchPRDataGraphQL(octokit, owner, repo, prNumber, verbose),
170
277
  fetchUserStories(mcpServerUrl, mcpToken, featureId, verbose),
171
278
  fetchTestCases(mcpServerUrl, mcpToken, featureId, verbose),
172
279
  ]);
280
+ // Extract review comments from unresolved threads
281
+ const reviewComments = prData.reviewThreads.flatMap((thread) => thread.comments);
282
+ if (verbose) {
283
+ console.log(`📊 Summary: ${prData.reviews.length} reviews requesting changes, ${prData.reviewThreads.length} unresolved threads, ${reviewComments.length} total comments`);
284
+ }
173
285
  return {
174
286
  featureId,
175
287
  featureName: feature.name,
@@ -178,8 +290,9 @@ export async function fetchCodeRefineContext(mcpServerUrl, mcpToken, featureId,
178
290
  pullRequestNumber: prNumber,
179
291
  owner,
180
292
  repo,
181
- reviews,
293
+ reviews: prData.reviews,
182
294
  reviewComments,
295
+ reviewThreads: prData.reviewThreads,
183
296
  technicalDesign: feature.technical_design,
184
297
  userStories,
185
298
  testCases,
@@ -213,8 +326,45 @@ export function formatContextForPrompt(context) {
213
326
  sections.push('');
214
327
  });
215
328
  }
216
- // Review comments
217
- if (context.reviewComments.length > 0) {
329
+ // Review threads (unresolved conversations)
330
+ if (context.reviewThreads && context.reviewThreads.length > 0) {
331
+ sections.push(`## Unresolved Review Threads`);
332
+ sections.push(`*These conversations must be resolved before the PR can be merged.*`);
333
+ sections.push('');
334
+ context.reviewThreads.forEach((thread, idx) => {
335
+ sections.push(`### Thread ${idx + 1} ${thread.isOutdated ? '(Outdated)' : ''}`);
336
+ // Show thread location
337
+ if (thread.line !== null) {
338
+ sections.push(`**Line**: ${thread.line}`);
339
+ }
340
+ if (thread.startLine !== null && thread.startLine !== thread.line) {
341
+ sections.push(`**Line Range**: ${thread.startLine} - ${thread.line || thread.originalLine}`);
342
+ }
343
+ sections.push(`**Status**: ❌ Unresolved`);
344
+ sections.push('');
345
+ // Show all comments in this thread
346
+ thread.comments.forEach((comment, commentIdx) => {
347
+ sections.push(`#### ${commentIdx === 0 ? 'Original Comment' : `Reply ${commentIdx}`} by @${comment.user.login}`);
348
+ if (comment.path) {
349
+ sections.push(`**File**: ${comment.path}`);
350
+ }
351
+ if (comment.url) {
352
+ sections.push(`**URL**: ${comment.url}`);
353
+ }
354
+ if (commentIdx === 0 && comment.diff_hunk) {
355
+ sections.push(`**Context**:`);
356
+ sections.push('```diff');
357
+ sections.push(comment.diff_hunk);
358
+ sections.push('```');
359
+ }
360
+ sections.push(`**Comment**:`);
361
+ sections.push(comment.body);
362
+ sections.push('');
363
+ });
364
+ });
365
+ }
366
+ else if (context.reviewComments.length > 0) {
367
+ // Fallback to old format if no thread data available
218
368
  sections.push(`## Review Comments`);
219
369
  sections.push('');
220
370
  context.reviewComments.forEach((comment, idx) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "edsger",
3
- "version": "0.4.2",
3
+ "version": "0.4.3",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "bin": {