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,296 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context fetcher for code review phase
|
|
3
|
+
* Fetches GitHub PR data including files, diffs, and commits for review
|
|
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 details
|
|
23
|
+
*/
|
|
24
|
+
export async function fetchPRDetails(octokit, owner, repo, prNumber, verbose) {
|
|
25
|
+
if (verbose) {
|
|
26
|
+
console.log(`📋 Fetching PR details for ${owner}/${repo}#${prNumber}...`);
|
|
27
|
+
}
|
|
28
|
+
const { data } = await octokit.pulls.get({
|
|
29
|
+
owner,
|
|
30
|
+
repo,
|
|
31
|
+
pull_number: prNumber,
|
|
32
|
+
});
|
|
33
|
+
if (verbose) {
|
|
34
|
+
console.log(`✅ Fetched PR: ${data.title}`);
|
|
35
|
+
}
|
|
36
|
+
return data;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Fetch PR files (changed files with diffs)
|
|
40
|
+
*/
|
|
41
|
+
export async function fetchPRFiles(octokit, owner, repo, prNumber, verbose) {
|
|
42
|
+
if (verbose) {
|
|
43
|
+
console.log(`📂 Fetching PR files for ${owner}/${repo}#${prNumber}...`);
|
|
44
|
+
}
|
|
45
|
+
const { data: files } = await octokit.pulls.listFiles({
|
|
46
|
+
owner,
|
|
47
|
+
repo,
|
|
48
|
+
pull_number: prNumber,
|
|
49
|
+
per_page: 100,
|
|
50
|
+
});
|
|
51
|
+
if (verbose) {
|
|
52
|
+
console.log(`✅ Found ${files.length} changed files`);
|
|
53
|
+
}
|
|
54
|
+
return files;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Fetch PR commits
|
|
58
|
+
*/
|
|
59
|
+
export async function fetchPRCommits(octokit, owner, repo, prNumber, verbose) {
|
|
60
|
+
if (verbose) {
|
|
61
|
+
console.log(`💾 Fetching PR commits for ${owner}/${repo}#${prNumber}...`);
|
|
62
|
+
}
|
|
63
|
+
const { data: commits } = await octokit.pulls.listCommits({
|
|
64
|
+
owner,
|
|
65
|
+
repo,
|
|
66
|
+
pull_number: prNumber,
|
|
67
|
+
per_page: 100,
|
|
68
|
+
});
|
|
69
|
+
if (verbose) {
|
|
70
|
+
console.log(`✅ Found ${commits.length} commits`);
|
|
71
|
+
}
|
|
72
|
+
return commits;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Fetch user stories via MCP
|
|
76
|
+
*/
|
|
77
|
+
export async function fetchUserStories(featureId, verbose) {
|
|
78
|
+
try {
|
|
79
|
+
if (verbose) {
|
|
80
|
+
console.log(`📖 Fetching user stories for ${featureId}...`);
|
|
81
|
+
}
|
|
82
|
+
const mcpServerUrl = process.env.MCP_SERVER_URL;
|
|
83
|
+
const mcpToken = process.env.MCP_TOKEN;
|
|
84
|
+
const response = await fetch(`${mcpServerUrl}/mcp`, {
|
|
85
|
+
method: 'POST',
|
|
86
|
+
headers: {
|
|
87
|
+
'Content-Type': 'application/json',
|
|
88
|
+
Authorization: `Bearer ${mcpToken}`,
|
|
89
|
+
},
|
|
90
|
+
body: JSON.stringify({
|
|
91
|
+
jsonrpc: '2.0',
|
|
92
|
+
id: 1,
|
|
93
|
+
method: 'user_stories/list',
|
|
94
|
+
params: {
|
|
95
|
+
feature_id: featureId,
|
|
96
|
+
},
|
|
97
|
+
}),
|
|
98
|
+
});
|
|
99
|
+
if (!response.ok) {
|
|
100
|
+
if (verbose) {
|
|
101
|
+
console.log(`⚠️ Could not fetch user stories: ${response.status}`);
|
|
102
|
+
}
|
|
103
|
+
return [];
|
|
104
|
+
}
|
|
105
|
+
const data = await response.json();
|
|
106
|
+
if (data.error || !data.result) {
|
|
107
|
+
if (verbose) {
|
|
108
|
+
console.log(`⚠️ User stories not available`);
|
|
109
|
+
}
|
|
110
|
+
return [];
|
|
111
|
+
}
|
|
112
|
+
return data.result.user_stories || [];
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
if (verbose) {
|
|
116
|
+
console.log(`⚠️ Error fetching user stories: ${error}`);
|
|
117
|
+
}
|
|
118
|
+
return [];
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Fetch test cases via MCP
|
|
123
|
+
*/
|
|
124
|
+
export async function fetchTestCases(featureId, verbose) {
|
|
125
|
+
try {
|
|
126
|
+
if (verbose) {
|
|
127
|
+
console.log(`🧪 Fetching test cases for ${featureId}...`);
|
|
128
|
+
}
|
|
129
|
+
const mcpServerUrl = process.env.MCP_SERVER_URL;
|
|
130
|
+
const mcpToken = process.env.MCP_TOKEN;
|
|
131
|
+
const response = await fetch(`${mcpServerUrl}/mcp`, {
|
|
132
|
+
method: 'POST',
|
|
133
|
+
headers: {
|
|
134
|
+
'Content-Type': 'application/json',
|
|
135
|
+
Authorization: `Bearer ${mcpToken}`,
|
|
136
|
+
},
|
|
137
|
+
body: JSON.stringify({
|
|
138
|
+
jsonrpc: '2.0',
|
|
139
|
+
id: 1,
|
|
140
|
+
method: 'test_cases/list',
|
|
141
|
+
params: {
|
|
142
|
+
feature_id: featureId,
|
|
143
|
+
},
|
|
144
|
+
}),
|
|
145
|
+
});
|
|
146
|
+
if (!response.ok) {
|
|
147
|
+
if (verbose) {
|
|
148
|
+
console.log(`⚠️ Could not fetch test cases: ${response.status}`);
|
|
149
|
+
}
|
|
150
|
+
return [];
|
|
151
|
+
}
|
|
152
|
+
const data = await response.json();
|
|
153
|
+
if (data.error || !data.result) {
|
|
154
|
+
if (verbose) {
|
|
155
|
+
console.log(`⚠️ Test cases not available`);
|
|
156
|
+
}
|
|
157
|
+
return [];
|
|
158
|
+
}
|
|
159
|
+
return data.result.test_cases || [];
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
if (verbose) {
|
|
163
|
+
console.log(`⚠️ Error fetching test cases: ${error}`);
|
|
164
|
+
}
|
|
165
|
+
return [];
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Fetch complete code review context
|
|
170
|
+
*/
|
|
171
|
+
export async function fetchCodeReviewContext(featureId, githubToken, verbose) {
|
|
172
|
+
// Fetch feature info using shared API
|
|
173
|
+
const feature = await getFeature(featureId, verbose);
|
|
174
|
+
if (!feature.pull_request_url) {
|
|
175
|
+
throw new Error(`Feature ${featureId} does not have a pull request URL. Cannot perform code review.`);
|
|
176
|
+
}
|
|
177
|
+
// Parse PR URL
|
|
178
|
+
const prInfo = parsePullRequestUrl(feature.pull_request_url);
|
|
179
|
+
if (!prInfo) {
|
|
180
|
+
throw new Error(`Invalid pull request URL: ${feature.pull_request_url}. Expected format: https://github.com/owner/repo/pull/123`);
|
|
181
|
+
}
|
|
182
|
+
const { owner, repo, prNumber } = prInfo;
|
|
183
|
+
// Initialize Octokit with GitHub token
|
|
184
|
+
const octokit = new Octokit({
|
|
185
|
+
auth: githubToken,
|
|
186
|
+
});
|
|
187
|
+
// Fetch PR data, files, commits, and additional context in parallel
|
|
188
|
+
const [prData, files, commits, userStories, testCases] = await Promise.all([
|
|
189
|
+
fetchPRDetails(octokit, owner, repo, prNumber, verbose),
|
|
190
|
+
fetchPRFiles(octokit, owner, repo, prNumber, verbose),
|
|
191
|
+
fetchPRCommits(octokit, owner, repo, prNumber, verbose),
|
|
192
|
+
fetchUserStories(featureId, verbose),
|
|
193
|
+
fetchTestCases(featureId, verbose),
|
|
194
|
+
]);
|
|
195
|
+
if (verbose) {
|
|
196
|
+
console.log(`📊 Summary: ${files.length} files changed across ${commits.length} commits`);
|
|
197
|
+
}
|
|
198
|
+
return {
|
|
199
|
+
featureId,
|
|
200
|
+
featureName: feature.name,
|
|
201
|
+
featureDescription: feature.description ?? null,
|
|
202
|
+
pullRequestUrl: feature.pull_request_url,
|
|
203
|
+
pullRequestNumber: prNumber,
|
|
204
|
+
owner,
|
|
205
|
+
repo,
|
|
206
|
+
prData,
|
|
207
|
+
files,
|
|
208
|
+
commits,
|
|
209
|
+
technicalDesign: feature.technical_design,
|
|
210
|
+
userStories,
|
|
211
|
+
testCases,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Format code review context for prompt
|
|
216
|
+
*/
|
|
217
|
+
export function formatContextForPrompt(context) {
|
|
218
|
+
const sections = [];
|
|
219
|
+
// Feature information
|
|
220
|
+
sections.push(`# Feature Information`);
|
|
221
|
+
sections.push(`**Feature ID**: ${context.featureId}`);
|
|
222
|
+
sections.push(`**Feature Name**: ${context.featureName}`);
|
|
223
|
+
if (context.featureDescription) {
|
|
224
|
+
sections.push(`**Description**: ${context.featureDescription}`);
|
|
225
|
+
}
|
|
226
|
+
sections.push(`**Pull Request**: ${context.pullRequestUrl} (#${context.pullRequestNumber})`);
|
|
227
|
+
sections.push('');
|
|
228
|
+
// PR details
|
|
229
|
+
sections.push(`## Pull Request Details`);
|
|
230
|
+
sections.push(`**Title**: ${context.prData.title}`);
|
|
231
|
+
sections.push(`**Author**: @${context.prData.user.login}`);
|
|
232
|
+
sections.push(`**Base Branch**: ${context.prData.base.ref}`);
|
|
233
|
+
sections.push(`**Head Branch**: ${context.prData.head.ref}`);
|
|
234
|
+
if (context.prData.body) {
|
|
235
|
+
sections.push(`**Description**:`);
|
|
236
|
+
sections.push(context.prData.body);
|
|
237
|
+
}
|
|
238
|
+
sections.push('');
|
|
239
|
+
// Commits
|
|
240
|
+
if (context.commits.length > 0) {
|
|
241
|
+
sections.push(`## Commits (${context.commits.length})`);
|
|
242
|
+
sections.push('');
|
|
243
|
+
context.commits.forEach((commit) => {
|
|
244
|
+
sections.push(`- **${commit.sha.substring(0, 7)}**: ${commit.commit.message}`);
|
|
245
|
+
sections.push(` by ${commit.commit.author.name} on ${commit.commit.author.date}`);
|
|
246
|
+
});
|
|
247
|
+
sections.push('');
|
|
248
|
+
}
|
|
249
|
+
// Changed files
|
|
250
|
+
if (context.files.length > 0) {
|
|
251
|
+
sections.push(`## Changed Files (${context.files.length})`);
|
|
252
|
+
sections.push('');
|
|
253
|
+
context.files.forEach((file) => {
|
|
254
|
+
sections.push(`### ${file.filename}`);
|
|
255
|
+
sections.push(`**Status**: ${file.status}`);
|
|
256
|
+
sections.push(`**Changes**: +${file.additions} -${file.deletions}`);
|
|
257
|
+
if (file.patch) {
|
|
258
|
+
sections.push(`**Diff**:`);
|
|
259
|
+
sections.push('```diff');
|
|
260
|
+
sections.push(file.patch);
|
|
261
|
+
sections.push('```');
|
|
262
|
+
}
|
|
263
|
+
sections.push('');
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
// Technical design
|
|
267
|
+
if (context.technicalDesign) {
|
|
268
|
+
sections.push(`## Technical Design`);
|
|
269
|
+
sections.push('');
|
|
270
|
+
sections.push(context.technicalDesign);
|
|
271
|
+
sections.push('');
|
|
272
|
+
}
|
|
273
|
+
// User stories
|
|
274
|
+
if (context.userStories && context.userStories.length > 0) {
|
|
275
|
+
sections.push(`## User Stories`);
|
|
276
|
+
sections.push('');
|
|
277
|
+
context.userStories.forEach((story) => {
|
|
278
|
+
sections.push(`### ${story.title}`);
|
|
279
|
+
sections.push(story.description || '');
|
|
280
|
+
sections.push('');
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
// Test cases
|
|
284
|
+
if (context.testCases && context.testCases.length > 0) {
|
|
285
|
+
sections.push(`## Test Cases`);
|
|
286
|
+
sections.push('');
|
|
287
|
+
context.testCases.forEach((testCase) => {
|
|
288
|
+
sections.push(`### ${testCase.title}`);
|
|
289
|
+
if (testCase.description) {
|
|
290
|
+
sections.push(testCase.description);
|
|
291
|
+
}
|
|
292
|
+
sections.push('');
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
return sections.join('\n');
|
|
296
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helper functions for feature analysis
|
|
3
|
+
* Extracted from analyzer.ts to improve modularity and testability
|
|
4
|
+
*/
|
|
5
|
+
import { FeatureAnalysisResult } from '../../types/index.js';
|
|
6
|
+
import { FeatureAnalysisContext } from './context.js';
|
|
7
|
+
/**
|
|
8
|
+
* Build the final analysis result object
|
|
9
|
+
*/
|
|
10
|
+
export declare function buildAnalysisResult(featureId: string, context: FeatureAnalysisContext, structuredAnalysisResult: any, currentIteration: number): FeatureAnalysisResult;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build the final analysis result object
|
|
3
|
+
*/
|
|
4
|
+
export function buildAnalysisResult(featureId, context, structuredAnalysisResult, currentIteration) {
|
|
5
|
+
const { created_user_stories, created_test_cases, checklist_results, checklist_item_results, } = structuredAnalysisResult;
|
|
6
|
+
return {
|
|
7
|
+
featureId,
|
|
8
|
+
productInfo: context.product,
|
|
9
|
+
featureInfo: context.feature,
|
|
10
|
+
existingUserStories: context.existing_user_stories.map((story) => ({
|
|
11
|
+
...story,
|
|
12
|
+
status: story.status,
|
|
13
|
+
created_at: story.created_at || new Date().toISOString(),
|
|
14
|
+
updated_at: story.updated_at || new Date().toISOString(),
|
|
15
|
+
})),
|
|
16
|
+
existingTestCases: context.existing_test_cases.map((testCase) => ({
|
|
17
|
+
...testCase,
|
|
18
|
+
created_at: testCase.created_at || new Date().toISOString(),
|
|
19
|
+
updated_at: testCase.updated_at || new Date().toISOString(),
|
|
20
|
+
})),
|
|
21
|
+
createdUserStories: (created_user_stories || []).map((story) => ({
|
|
22
|
+
id: '',
|
|
23
|
+
title: story.title,
|
|
24
|
+
description: story.description,
|
|
25
|
+
status: story.status || 'draft',
|
|
26
|
+
created_at: new Date().toISOString(),
|
|
27
|
+
updated_at: new Date().toISOString(),
|
|
28
|
+
})),
|
|
29
|
+
createdTestCases: (created_test_cases || []).map((testCase) => ({
|
|
30
|
+
id: '',
|
|
31
|
+
name: testCase.name,
|
|
32
|
+
description: testCase.description,
|
|
33
|
+
is_critical: testCase.is_critical || false,
|
|
34
|
+
created_at: new Date().toISOString(),
|
|
35
|
+
updated_at: new Date().toISOString(),
|
|
36
|
+
})),
|
|
37
|
+
summary: structuredAnalysisResult.summary ||
|
|
38
|
+
`Analysis completed${currentIteration > 1 ? ` after ${currentIteration} iterations` : ''}`,
|
|
39
|
+
status: structuredAnalysisResult.status === 'success' ? 'success' : 'error',
|
|
40
|
+
data: {
|
|
41
|
+
checklist_results,
|
|
42
|
+
checklist_item_results,
|
|
43
|
+
verification_result: structuredAnalysisResult.verification_result,
|
|
44
|
+
iterations: currentIteration,
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { EdsgerConfig, FeatureAnalysisResult } from '../../types/index.js';
|
|
2
|
+
import { ChecklistPhaseContext } from '../../services/checklist.js';
|
|
3
|
+
export interface FeatureAnalysisOptions {
|
|
4
|
+
featureId: string;
|
|
5
|
+
mcpServerUrl: string;
|
|
6
|
+
mcpToken: string;
|
|
7
|
+
verbose?: boolean;
|
|
8
|
+
maxVerificationIterations?: number;
|
|
9
|
+
}
|
|
10
|
+
export declare const analyzeFeatureWithMCP: (options: FeatureAnalysisOptions, config: EdsgerConfig, checklistContext?: ChecklistPhaseContext | null) => Promise<FeatureAnalysisResult>;
|
|
11
|
+
export declare const checkFeatureAnalysisRequirements: () => Promise<boolean>;
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { logInfo, logError } from '../../utils/logger.js';
|
|
2
|
+
import { fetchFeatureAnalysisContext, } from './context-fetcher.js';
|
|
3
|
+
import { formatFeatureAnalysisContext } from '../../utils/formatters.js';
|
|
4
|
+
import { createFeatureAnalysisSystemPrompt, createFeatureAnalysisPromptWithContext, } from './prompts.js';
|
|
5
|
+
import { formatChecklistsForContext, } from '../../services/checklist.js';
|
|
6
|
+
import { getFeedbacksForPhase, formatFeedbacksForContext, } from '../../services/feedbacks.js';
|
|
7
|
+
import { executeAnalysisQuery, performVerificationCycle, saveAnalysisArtifactsAsDraft, updateArtifactsToReady, deleteArtifacts, buildAnalysisResult, buildVerificationFailureResult, buildNoResultsError, } from './analyzer-helpers.js';
|
|
8
|
+
import { logFeaturePhaseEvent } from '../../services/audit-logs.js';
|
|
9
|
+
export const analyzeFeatureWithMCP = async (options, config, checklistContext) => {
|
|
10
|
+
const { featureId, mcpServerUrl, mcpToken, verbose } = options;
|
|
11
|
+
if (verbose) {
|
|
12
|
+
logInfo(`Starting feature analysis for feature ID: ${featureId}`);
|
|
13
|
+
logInfo(`Using MCP server: ${mcpServerUrl}`);
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
// Fetch and prepare context
|
|
17
|
+
const context = await prepareAnalysisContext(mcpServerUrl, mcpToken, featureId, checklistContext, verbose);
|
|
18
|
+
const systemPrompt = createFeatureAnalysisSystemPrompt(config, mcpServerUrl, mcpToken, featureId);
|
|
19
|
+
const initialAnalysisPrompt = context.analysisPrompt;
|
|
20
|
+
const maxIterations = options.maxVerificationIterations || 10;
|
|
21
|
+
let currentIteration = 0;
|
|
22
|
+
let currentPrompt = initialAnalysisPrompt;
|
|
23
|
+
let structuredAnalysisResult = null;
|
|
24
|
+
let verificationResult = null;
|
|
25
|
+
if (verbose) {
|
|
26
|
+
logInfo('Starting Claude Code query with pre-fetched information...');
|
|
27
|
+
}
|
|
28
|
+
// Iterative improvement loop: analysis → save draft → verification → update ready or delete → re-analysis
|
|
29
|
+
let currentDraftUserStoryIds = [];
|
|
30
|
+
let currentDraftTestCaseIds = [];
|
|
31
|
+
while (currentIteration < maxIterations) {
|
|
32
|
+
currentIteration++;
|
|
33
|
+
if (verbose && currentIteration > 1) {
|
|
34
|
+
logInfo(`\n🔄 Iteration ${currentIteration}/${maxIterations}: Improving analysis based on verification feedback...`);
|
|
35
|
+
}
|
|
36
|
+
// Log iteration start (for iterations after the first)
|
|
37
|
+
if (currentIteration > 1) {
|
|
38
|
+
await logFeaturePhaseEvent(mcpServerUrl, mcpToken, {
|
|
39
|
+
featureId,
|
|
40
|
+
eventType: 'phase_started',
|
|
41
|
+
phase: 'feature_analysis',
|
|
42
|
+
result: 'info',
|
|
43
|
+
metadata: {
|
|
44
|
+
iteration: currentIteration,
|
|
45
|
+
max_iterations: maxIterations,
|
|
46
|
+
re_analysis: true,
|
|
47
|
+
timestamp: new Date().toISOString(),
|
|
48
|
+
},
|
|
49
|
+
}, verbose);
|
|
50
|
+
}
|
|
51
|
+
// Execute analysis query
|
|
52
|
+
structuredAnalysisResult = await executeAnalysisQuery(currentPrompt, systemPrompt, config, verbose);
|
|
53
|
+
// No result produced, break out
|
|
54
|
+
if (!structuredAnalysisResult) {
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
// Log analysis completion for this iteration
|
|
58
|
+
await logFeaturePhaseEvent(mcpServerUrl, mcpToken, {
|
|
59
|
+
featureId,
|
|
60
|
+
eventType: 'phase_completed',
|
|
61
|
+
phase: 'feature_analysis',
|
|
62
|
+
result: 'success',
|
|
63
|
+
metadata: {
|
|
64
|
+
iteration: currentIteration,
|
|
65
|
+
max_iterations: maxIterations,
|
|
66
|
+
analysis_step: 'completed',
|
|
67
|
+
user_stories_count: structuredAnalysisResult.created_user_stories?.length || 0,
|
|
68
|
+
test_cases_count: structuredAnalysisResult.created_test_cases?.length || 0,
|
|
69
|
+
timestamp: new Date().toISOString(),
|
|
70
|
+
},
|
|
71
|
+
}, verbose);
|
|
72
|
+
// Save artifacts as draft and get their IDs
|
|
73
|
+
const { userStoryIds, testCaseIds } = await saveAnalysisArtifactsAsDraft(mcpServerUrl, mcpToken, featureId, structuredAnalysisResult.created_user_stories || [], structuredAnalysisResult.created_test_cases || [], verbose);
|
|
74
|
+
currentDraftUserStoryIds = userStoryIds;
|
|
75
|
+
currentDraftTestCaseIds = testCaseIds;
|
|
76
|
+
// Perform verification cycle
|
|
77
|
+
const verificationCycle = await performVerificationCycle(structuredAnalysisResult, checklistContext || null, context.featureContext, config, currentIteration, maxIterations, mcpServerUrl, mcpToken, featureId, verbose);
|
|
78
|
+
verificationResult = verificationCycle.verificationResult;
|
|
79
|
+
// If verification passed, update artifacts to ready and exit
|
|
80
|
+
if (verificationCycle.passed) {
|
|
81
|
+
if (verbose) {
|
|
82
|
+
logInfo('✅ Verification passed! Updating artifacts to ready status...');
|
|
83
|
+
}
|
|
84
|
+
await updateArtifactsToReady(mcpServerUrl, mcpToken, currentDraftUserStoryIds, currentDraftTestCaseIds, verbose);
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
// Verification failed
|
|
88
|
+
if (currentIteration < maxIterations && verificationCycle.nextPrompt) {
|
|
89
|
+
// We have more iterations - delete draft artifacts and retry
|
|
90
|
+
if (verbose) {
|
|
91
|
+
logInfo('🗑️ Deleting draft artifacts for re-analysis...');
|
|
92
|
+
}
|
|
93
|
+
await deleteArtifacts(mcpServerUrl, mcpToken, currentDraftUserStoryIds, currentDraftTestCaseIds, verbose);
|
|
94
|
+
// Continue with improvement prompt
|
|
95
|
+
currentPrompt = verificationCycle.nextPrompt;
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
// Max iterations reached or no next prompt - exit loop
|
|
99
|
+
// Draft artifacts remain in database for manual review
|
|
100
|
+
if (verbose) {
|
|
101
|
+
logInfo('⚠️ Max iterations reached. Draft artifacts kept for manual review.');
|
|
102
|
+
}
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// Handle results
|
|
107
|
+
if (!structuredAnalysisResult) {
|
|
108
|
+
return buildNoResultsError(featureId, context.featureContext);
|
|
109
|
+
}
|
|
110
|
+
const { created_user_stories, created_test_cases, status, checklist_results, checklist_item_results, } = structuredAnalysisResult;
|
|
111
|
+
// If no checklist was used, update draft artifacts to ready now
|
|
112
|
+
if (!checklistContext ||
|
|
113
|
+
checklistContext.checklists.length === 0 ||
|
|
114
|
+
!verificationResult) {
|
|
115
|
+
if (currentDraftUserStoryIds.length > 0 ||
|
|
116
|
+
currentDraftTestCaseIds.length > 0) {
|
|
117
|
+
if (verbose) {
|
|
118
|
+
logInfo('✅ No checklist verification needed. Updating artifacts to ready status...');
|
|
119
|
+
}
|
|
120
|
+
await updateArtifactsToReady(mcpServerUrl, mcpToken, currentDraftUserStoryIds, currentDraftTestCaseIds, verbose);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// Check if verification failed after all iterations
|
|
124
|
+
// Note: Artifacts are already saved as draft in the database
|
|
125
|
+
// If verification failed, they remain as draft for manual review
|
|
126
|
+
if (verificationResult &&
|
|
127
|
+
verificationResult.rejected_count > 0 &&
|
|
128
|
+
checklistContext &&
|
|
129
|
+
checklistContext.checklists.length > 0) {
|
|
130
|
+
logError(`❌ Final result: Checklist verification FAILED after ${currentIteration} iterations`);
|
|
131
|
+
logError(` Draft artifacts (${currentDraftUserStoryIds.length} user stories, ${currentDraftTestCaseIds.length} test cases) kept for manual review`);
|
|
132
|
+
return buildVerificationFailureResult(featureId, context.featureContext, verificationResult, checklist_results, checklist_item_results, currentIteration);
|
|
133
|
+
}
|
|
134
|
+
// Return success result
|
|
135
|
+
// Note: Artifacts have already been saved and updated to 'ready' status (if verification passed)
|
|
136
|
+
// or remain as draft (if verification failed)
|
|
137
|
+
return buildAnalysisResult(featureId, context.featureContext, structuredAnalysisResult, currentIteration);
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
logError(`Feature analysis failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
141
|
+
return {
|
|
142
|
+
featureId,
|
|
143
|
+
productInfo: null,
|
|
144
|
+
featureInfo: null,
|
|
145
|
+
existingUserStories: [],
|
|
146
|
+
existingTestCases: [],
|
|
147
|
+
createdUserStories: [],
|
|
148
|
+
createdTestCases: [],
|
|
149
|
+
summary: `Analysis failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
150
|
+
status: 'error',
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
/**
|
|
155
|
+
* Prepare all context information needed for analysis
|
|
156
|
+
*/
|
|
157
|
+
async function prepareAnalysisContext(mcpServerUrl, mcpToken, featureId, checklistContext, verbose) {
|
|
158
|
+
if (verbose) {
|
|
159
|
+
logInfo('Fetching feature analysis context via MCP endpoints...');
|
|
160
|
+
}
|
|
161
|
+
const featureContext = await fetchFeatureAnalysisContext(mcpServerUrl, mcpToken, featureId, verbose);
|
|
162
|
+
const { content: contextInfo, downloadedImages } = await formatFeatureAnalysisContext(featureContext);
|
|
163
|
+
if (verbose && downloadedImages.length > 0) {
|
|
164
|
+
logInfo(`Downloaded ${downloadedImages.length} images for Claude Code:`);
|
|
165
|
+
downloadedImages.forEach((img) => {
|
|
166
|
+
logInfo(` - ${img.url} -> ${img.localPath}`);
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
let finalContextInfo = contextInfo;
|
|
170
|
+
// Add feedbacks context to the analysis prompt
|
|
171
|
+
try {
|
|
172
|
+
const feedbacksContext = await getFeedbacksForPhase({ featureId, mcpServerUrl, mcpToken, verbose }, 'feature-analysis');
|
|
173
|
+
if (feedbacksContext.feedbacks.length > 0) {
|
|
174
|
+
const feedbacksInfo = formatFeedbacksForContext(feedbacksContext);
|
|
175
|
+
finalContextInfo = finalContextInfo + '\n\n' + feedbacksInfo;
|
|
176
|
+
if (verbose) {
|
|
177
|
+
logInfo(`Added ${feedbacksContext.feedbacks.length} human feedbacks to context`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
catch (error) {
|
|
182
|
+
// Don't fail if feedbacks fetch fails - just log and continue
|
|
183
|
+
if (verbose) {
|
|
184
|
+
logInfo(`Note: Could not fetch feedbacks (${error instanceof Error ? error.message : String(error)})`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
// Add checklist context to the analysis prompt
|
|
188
|
+
if (checklistContext && checklistContext.checklists.length > 0) {
|
|
189
|
+
const checklistInfo = formatChecklistsForContext(checklistContext);
|
|
190
|
+
finalContextInfo = finalContextInfo + '\n\n' + checklistInfo;
|
|
191
|
+
if (verbose) {
|
|
192
|
+
logInfo(`Added ${checklistContext.checklists.length} checklists to analysis context`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
const analysisPrompt = createFeatureAnalysisPromptWithContext(featureId, finalContextInfo);
|
|
196
|
+
return { featureContext, analysisPrompt };
|
|
197
|
+
}
|
|
198
|
+
export const checkFeatureAnalysisRequirements = async () => {
|
|
199
|
+
try {
|
|
200
|
+
// Check if Claude Code SDK is available
|
|
201
|
+
const claudeCode = await import('@anthropic-ai/claude-code');
|
|
202
|
+
return claudeCode && typeof claudeCode.query === 'function';
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
console.log('Feature analysis requirements check failed:', error instanceof Error ? error.message : error);
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { createUserStories, createTestCases } from '../../api/features/index.js';
|
|
2
|
+
import type { FeatureInfo, UserStory, TestCase } from '../../types/features.js';
|
|
3
|
+
import { type ProductInfo } from '../../api/products.js';
|
|
4
|
+
import { ChecklistPhaseContext } from '../../services/checklist.js';
|
|
5
|
+
export interface FeatureAnalysisContext {
|
|
6
|
+
feature: FeatureInfo;
|
|
7
|
+
product: ProductInfo;
|
|
8
|
+
existing_user_stories: UserStory[];
|
|
9
|
+
existing_test_cases: TestCase[];
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Fetch all feature analysis context information via MCP endpoints
|
|
13
|
+
*/
|
|
14
|
+
export declare function fetchFeatureAnalysisContext(featureId: string, verbose?: boolean): Promise<FeatureAnalysisContext>;
|
|
15
|
+
export { createUserStories, createTestCases };
|
|
16
|
+
/**
|
|
17
|
+
* Format the context into a readable string for Claude Code
|
|
18
|
+
*/
|
|
19
|
+
export declare function formatContextForPrompt(context: FeatureAnalysisContext): string;
|
|
20
|
+
/**
|
|
21
|
+
* Prepare all context information needed for analysis
|
|
22
|
+
*/
|
|
23
|
+
export declare function prepareAnalysisContext(featureId: string, checklistContext: ChecklistPhaseContext | null | undefined, verbose?: boolean): Promise<{
|
|
24
|
+
featureContext: FeatureAnalysisContext;
|
|
25
|
+
analysisPrompt: string;
|
|
26
|
+
}>;
|