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,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
+ }>;