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.
- package/.claude/settings.local.json +23 -3
- package/.env.local +12 -0
- package/dist/api/features/__tests__/regression-prevention.test.d.ts +5 -0
- package/dist/api/features/__tests__/regression-prevention.test.js +338 -0
- package/dist/api/features/__tests__/status-updater.integration.test.d.ts +5 -0
- package/dist/api/features/__tests__/status-updater.integration.test.js +497 -0
- package/dist/commands/workflow/pipeline-runner.d.ts +17 -0
- package/dist/commands/workflow/pipeline-runner.js +393 -0
- package/dist/commands/workflow/runner.d.ts +26 -0
- package/dist/commands/workflow/runner.js +119 -0
- package/dist/commands/workflow/workflow-runner.d.ts +26 -0
- package/dist/commands/workflow/workflow-runner.js +119 -0
- package/dist/index.js +0 -0
- package/dist/phases/code-implementation/analyzer-helpers.d.ts +28 -0
- package/dist/phases/code-implementation/analyzer-helpers.js +177 -0
- package/dist/phases/code-implementation/analyzer.d.ts +32 -0
- package/dist/phases/code-implementation/analyzer.js +629 -0
- package/dist/phases/code-implementation/context-fetcher.d.ts +17 -0
- package/dist/phases/code-implementation/context-fetcher.js +86 -0
- package/dist/phases/code-implementation/mcp-server.d.ts +1 -0
- package/dist/phases/code-implementation/mcp-server.js +93 -0
- package/dist/phases/code-implementation/prompts-improvement.d.ts +5 -0
- package/dist/phases/code-implementation/prompts-improvement.js +108 -0
- package/dist/phases/code-implementation-verification/verifier.d.ts +31 -0
- package/dist/phases/code-implementation-verification/verifier.js +196 -0
- package/dist/phases/code-refine/analyzer.d.ts +41 -0
- package/dist/phases/code-refine/analyzer.js +561 -0
- package/dist/phases/code-refine/context-fetcher.d.ts +94 -0
- package/dist/phases/code-refine/context-fetcher.js +423 -0
- package/dist/phases/code-refine-verification/analysis/llm-analyzer.d.ts +22 -0
- package/dist/phases/code-refine-verification/analysis/llm-analyzer.js +134 -0
- package/dist/phases/code-refine-verification/verifier.d.ts +47 -0
- package/dist/phases/code-refine-verification/verifier.js +597 -0
- package/dist/phases/code-review/analyzer.d.ts +29 -0
- package/dist/phases/code-review/analyzer.js +363 -0
- package/dist/phases/code-review/context-fetcher.d.ts +92 -0
- package/dist/phases/code-review/context-fetcher.js +296 -0
- package/dist/phases/feature-analysis/analyzer-helpers.d.ts +10 -0
- package/dist/phases/feature-analysis/analyzer-helpers.js +47 -0
- package/dist/phases/feature-analysis/analyzer.d.ts +11 -0
- package/dist/phases/feature-analysis/analyzer.js +208 -0
- package/dist/phases/feature-analysis/context-fetcher.d.ts +26 -0
- package/dist/phases/feature-analysis/context-fetcher.js +134 -0
- package/dist/phases/feature-analysis/http-fallback.d.ts +20 -0
- package/dist/phases/feature-analysis/http-fallback.js +95 -0
- package/dist/phases/feature-analysis/mcp-server.d.ts +1 -0
- package/dist/phases/feature-analysis/mcp-server.js +144 -0
- package/dist/phases/feature-analysis/prompts-improvement.d.ts +8 -0
- package/dist/phases/feature-analysis/prompts-improvement.js +109 -0
- package/dist/phases/feature-analysis-verification/verifier.d.ts +37 -0
- package/dist/phases/feature-analysis-verification/verifier.js +147 -0
- package/dist/phases/pr-execution/file-assigner.js +20 -12
- package/dist/phases/technical-design/analyzer-helpers.d.ts +25 -0
- package/dist/phases/technical-design/analyzer-helpers.js +39 -0
- package/dist/phases/technical-design/analyzer.d.ts +21 -0
- package/dist/phases/technical-design/analyzer.js +461 -0
- package/dist/phases/technical-design/context-fetcher.d.ts +12 -0
- package/dist/phases/technical-design/context-fetcher.js +39 -0
- package/dist/phases/technical-design/http-fallback.d.ts +17 -0
- package/dist/phases/technical-design/http-fallback.js +151 -0
- package/dist/phases/technical-design/mcp-server.d.ts +1 -0
- package/dist/phases/technical-design/mcp-server.js +157 -0
- package/dist/phases/technical-design/prompts-improvement.d.ts +5 -0
- package/dist/phases/technical-design/prompts-improvement.js +93 -0
- package/dist/phases/technical-design-verification/verifier.d.ts +53 -0
- package/dist/phases/technical-design-verification/verifier.js +170 -0
- package/dist/services/feature-branches.d.ts +77 -0
- package/dist/services/feature-branches.js +205 -0
- package/dist/workflow-runner/config/phase-configs.d.ts +5 -0
- package/dist/workflow-runner/config/phase-configs.js +120 -0
- package/dist/workflow-runner/core/feature-filter.d.ts +16 -0
- package/dist/workflow-runner/core/feature-filter.js +46 -0
- package/dist/workflow-runner/core/index.d.ts +8 -0
- package/dist/workflow-runner/core/index.js +12 -0
- package/dist/workflow-runner/core/pipeline-evaluator.d.ts +24 -0
- package/dist/workflow-runner/core/pipeline-evaluator.js +32 -0
- package/dist/workflow-runner/core/state-manager.d.ts +24 -0
- package/dist/workflow-runner/core/state-manager.js +42 -0
- package/dist/workflow-runner/core/workflow-logger.d.ts +20 -0
- package/dist/workflow-runner/core/workflow-logger.js +65 -0
- package/dist/workflow-runner/executors/phase-executor.d.ts +8 -0
- package/dist/workflow-runner/executors/phase-executor.js +248 -0
- package/dist/workflow-runner/feature-workflow-runner.d.ts +26 -0
- package/dist/workflow-runner/feature-workflow-runner.js +119 -0
- package/dist/workflow-runner/index.d.ts +2 -0
- package/dist/workflow-runner/index.js +2 -0
- package/dist/workflow-runner/pipeline-runner.d.ts +17 -0
- package/dist/workflow-runner/pipeline-runner.js +393 -0
- package/dist/workflow-runner/workflow-processor.d.ts +54 -0
- package/dist/workflow-runner/workflow-processor.js +170 -0
- package/package.json +1 -1
- package/dist/services/lifecycle-agent/__tests__/phase-criteria.test.d.ts +0 -4
- package/dist/services/lifecycle-agent/__tests__/phase-criteria.test.js +0 -133
- package/dist/services/lifecycle-agent/__tests__/transition-rules.test.d.ts +0 -4
- package/dist/services/lifecycle-agent/__tests__/transition-rules.test.js +0 -336
- package/dist/services/lifecycle-agent/index.d.ts +0 -24
- package/dist/services/lifecycle-agent/index.js +0 -25
- package/dist/services/lifecycle-agent/phase-criteria.d.ts +0 -57
- package/dist/services/lifecycle-agent/phase-criteria.js +0 -335
- package/dist/services/lifecycle-agent/transition-rules.d.ts +0 -60
- package/dist/services/lifecycle-agent/transition-rules.js +0 -184
- package/dist/services/lifecycle-agent/types.d.ts +0 -190
- 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>;
|