edsger 0.2.2 ā 0.2.4
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/README.md +17 -0
- package/dist/api/features/batch-operations.d.ts +16 -0
- package/dist/api/features/batch-operations.js +100 -0
- package/dist/api/features/index.d.ts +1 -0
- package/dist/api/features/index.js +1 -0
- package/dist/api/features/test-cases.d.ts +8 -0
- package/dist/api/features/test-cases.js +45 -0
- package/dist/api/features/user-stories.d.ts +8 -0
- package/dist/api/features/user-stories.js +45 -0
- package/dist/cli/commands/refactor-command.d.ts +2 -0
- package/dist/cli/commands/refactor-command.js +107 -0
- package/dist/cli/index.js +7 -0
- package/dist/cli.d.ts +2 -2
- package/dist/cli.js +4 -99
- 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 +2 -0
- package/dist/phases/code-implementation/analyzer.js +308 -179
- package/dist/phases/code-implementation-verification/index.d.ts +1 -0
- package/dist/phases/code-implementation-verification/index.js +1 -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/feature-analysis/analyzer-helpers.d.ts +62 -0
- package/dist/phases/feature-analysis/analyzer-helpers.js +450 -0
- package/dist/phases/feature-analysis/analyzer.d.ts +1 -0
- package/dist/phases/feature-analysis/analyzer.js +132 -213
- package/dist/phases/feature-analysis-verification/index.d.ts +1 -0
- package/dist/phases/feature-analysis-verification/index.js +1 -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/pull-request/creator.js +10 -9
- package/dist/phases/technical-design/analyzer-helpers.d.ts +37 -0
- package/dist/phases/technical-design/analyzer-helpers.js +144 -0
- package/dist/phases/technical-design/analyzer.d.ts +3 -0
- package/dist/phases/technical-design/analyzer.js +282 -312
- package/dist/phases/technical-design-verification/index.d.ts +1 -0
- package/dist/phases/technical-design-verification/index.js +1 -0
- package/dist/phases/technical-design-verification/verifier.d.ts +36 -0
- package/dist/phases/technical-design-verification/verifier.js +147 -0
- package/dist/prompts/checklist-verification.d.ts +11 -0
- package/dist/prompts/checklist-verification.js +153 -0
- package/dist/prompts/code-implementation-improvement.d.ts +5 -0
- package/dist/prompts/code-implementation-improvement.js +108 -0
- package/dist/prompts/code-implementation-verification.d.ts +3 -0
- package/dist/prompts/code-implementation-verification.js +176 -0
- package/dist/prompts/feature-analysis-improvement.d.ts +8 -0
- package/dist/prompts/feature-analysis-improvement.js +109 -0
- package/dist/prompts/feature-analysis.js +1 -1
- package/dist/prompts/formatters.d.ts +17 -4
- package/dist/prompts/formatters.js +41 -12
- package/dist/prompts/technical-design-improvement.d.ts +5 -0
- package/dist/prompts/technical-design-improvement.js +93 -0
- package/dist/prompts/technical-design-verification.d.ts +11 -0
- package/dist/prompts/technical-design-verification.js +134 -0
- package/dist/prompts/technical-design.js +1 -1
- package/dist/services/audit-logs.d.ts +60 -0
- package/dist/services/audit-logs.js +115 -0
- package/dist/services/checklist.d.ts +1 -0
- package/dist/types/index.d.ts +19 -0
- package/dist/utils/image-downloader.d.ts +32 -0
- package/dist/utils/image-downloader.js +144 -0
- package/dist/workflow-runner/executors/phase-executor.js +56 -12
- package/package.json +1 -1
- package/dist/api/features.d.ts +0 -100
- package/dist/api/features.js +0 -219
- package/dist/logger.d.ts +0 -19
- package/dist/logger.js +0 -52
- package/dist/types.d.ts +0 -99
- package/dist/types.js +0 -1
- package/dist/workflow-runner/config/stage-configs.d.ts +0 -5
- package/dist/workflow-runner/config/stage-configs.js +0 -34
- package/dist/workflow-runner/core/feature-filter.test.d.ts +0 -4
- package/dist/workflow-runner/core/feature-filter.test.js +0 -127
- package/dist/workflow-runner/executors/stage-executor.d.ts +0 -8
- package/dist/workflow-runner/executors/stage-executor.js +0 -49
- package/dist/workflow-runner/feature-fetcher.d.ts +0 -41
- package/dist/workflow-runner/feature-fetcher.js +0 -121
- package/dist/workflow-runner/feature-service.d.ts +0 -17
- package/dist/workflow-runner/feature-service.js +0 -60
- package/dist/workflow-runner/pipeline.d.ts +0 -18
- package/dist/workflow-runner/pipeline.js +0 -197
- package/dist/workflow-runner/processor.d.ts +0 -40
- package/dist/workflow-runner/processor.js +0 -191
- package/dist/workflow-runner/status-updater.d.ts +0 -27
- package/dist/workflow-runner/status-updater.js +0 -80
- package/dist/workflow-runner/types.d.ts +0 -48
- package/dist/workflow-runner/types.js +0 -4
|
@@ -1,22 +1,10 @@
|
|
|
1
|
-
import { query } from '@anthropic-ai/claude-code';
|
|
2
1
|
import { logInfo, logError } from '../../utils/logger.js';
|
|
3
|
-
import { saveDataViaHttp } from './http-fallback.js';
|
|
4
2
|
import { fetchFeatureAnalysisContext, } from './context-fetcher.js';
|
|
5
|
-
import { createUserStories } from '../../api/features/index.js';
|
|
6
|
-
import { createTestCases } from '../../api/features/index.js';
|
|
7
3
|
import { formatFeatureAnalysisContext } from '../../prompts/formatters.js';
|
|
8
4
|
import { createFeatureAnalysisSystemPrompt, createFeatureAnalysisPromptWithContext, } from '../../prompts/feature-analysis.js';
|
|
9
5
|
import { formatChecklistsForContext, } from '../../services/checklist.js';
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
type: 'user',
|
|
13
|
-
message: { role: 'user', content: content },
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
async function* prompt(analysisPrompt) {
|
|
17
|
-
yield userMessage(analysisPrompt);
|
|
18
|
-
await new Promise((res) => setTimeout(res, 10000));
|
|
19
|
-
}
|
|
6
|
+
import { executeAnalysisQuery, performVerificationCycle, saveAnalysisArtifactsAsDraft, updateArtifactsToReady, deleteArtifacts, buildAnalysisResult, buildVerificationFailureResult, buildNoResultsError, } from './analyzer-helpers.js';
|
|
7
|
+
import { logFeaturePhaseEvent } from '../../services/audit-logs.js';
|
|
20
8
|
export const analyzeFeatureWithMCP = async (options, config, checklistContext) => {
|
|
21
9
|
const { featureId, mcpServerUrl, mcpToken, verbose } = options;
|
|
22
10
|
if (verbose) {
|
|
@@ -24,224 +12,128 @@ export const analyzeFeatureWithMCP = async (options, config, checklistContext) =
|
|
|
24
12
|
logInfo(`Using MCP server: ${mcpServerUrl}`);
|
|
25
13
|
}
|
|
26
14
|
try {
|
|
27
|
-
// Fetch
|
|
28
|
-
|
|
29
|
-
logInfo('Fetching feature analysis context via MCP endpoints...');
|
|
30
|
-
}
|
|
31
|
-
const context = await fetchFeatureAnalysisContext(mcpServerUrl, mcpToken, featureId, verbose);
|
|
15
|
+
// Fetch and prepare context
|
|
16
|
+
const context = await prepareAnalysisContext(mcpServerUrl, mcpToken, featureId, checklistContext, verbose);
|
|
32
17
|
const systemPrompt = createFeatureAnalysisSystemPrompt(config, mcpServerUrl, mcpToken, featureId);
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
let
|
|
36
|
-
|
|
37
|
-
const checklistInfo = formatChecklistsForContext(checklistContext);
|
|
38
|
-
finalContextInfo = contextInfo + '\n\n' + checklistInfo;
|
|
39
|
-
if (verbose) {
|
|
40
|
-
logInfo(`Added ${checklistContext.checklists.length} checklists to analysis context`);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
const analysisPrompt = createFeatureAnalysisPromptWithContext(featureId, finalContextInfo);
|
|
44
|
-
let lastAssistantResponse = '';
|
|
18
|
+
const initialAnalysisPrompt = context.analysisPrompt;
|
|
19
|
+
const maxIterations = options.maxVerificationIterations || 10;
|
|
20
|
+
let currentIteration = 0;
|
|
21
|
+
let currentPrompt = initialAnalysisPrompt;
|
|
45
22
|
let structuredAnalysisResult = null;
|
|
23
|
+
let verificationResult = null;
|
|
46
24
|
if (verbose) {
|
|
47
25
|
logInfo('Starting Claude Code query with pre-fetched information...');
|
|
48
26
|
}
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
permissionMode: 'bypassPermissions',
|
|
57
|
-
},
|
|
58
|
-
})) {
|
|
59
|
-
if (verbose) {
|
|
60
|
-
logInfo(`Received message type: ${message.type}`);
|
|
27
|
+
// Iterative improvement loop: analysis ā save draft ā verification ā update ready or delete ā re-analysis
|
|
28
|
+
let currentDraftUserStoryIds = [];
|
|
29
|
+
let currentDraftTestCaseIds = [];
|
|
30
|
+
while (currentIteration < maxIterations) {
|
|
31
|
+
currentIteration++;
|
|
32
|
+
if (verbose && currentIteration > 1) {
|
|
33
|
+
logInfo(`\nš Iteration ${currentIteration}/${maxIterations}: Improving analysis based on verification feedback...`);
|
|
61
34
|
}
|
|
62
|
-
//
|
|
63
|
-
if (
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
35
|
+
// Log iteration start (for iterations after the first)
|
|
36
|
+
if (currentIteration > 1) {
|
|
37
|
+
await logFeaturePhaseEvent(mcpServerUrl, mcpToken, {
|
|
38
|
+
featureId,
|
|
39
|
+
eventType: 'phase_started',
|
|
40
|
+
phase: 'feature_analysis',
|
|
41
|
+
result: 'info',
|
|
42
|
+
metadata: {
|
|
43
|
+
iteration: currentIteration,
|
|
44
|
+
max_iterations: maxIterations,
|
|
45
|
+
re_analysis: true,
|
|
46
|
+
timestamp: new Date().toISOString(),
|
|
47
|
+
},
|
|
48
|
+
}, verbose);
|
|
77
49
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
const responseText = message.result || lastAssistantResponse;
|
|
84
|
-
let jsonResult = null;
|
|
85
|
-
// First try to extract JSON from markdown code block
|
|
86
|
-
const jsonBlockMatch = responseText.match(/```json\s*\n([\s\S]*?)\n\s*```/);
|
|
87
|
-
if (jsonBlockMatch) {
|
|
88
|
-
jsonResult = JSON.parse(jsonBlockMatch[1]);
|
|
89
|
-
}
|
|
90
|
-
else {
|
|
91
|
-
// Try to parse the entire response as JSON
|
|
92
|
-
jsonResult = JSON.parse(responseText);
|
|
93
|
-
}
|
|
94
|
-
if (jsonResult && jsonResult.analysis) {
|
|
95
|
-
structuredAnalysisResult = jsonResult.analysis;
|
|
96
|
-
}
|
|
97
|
-
else {
|
|
98
|
-
throw new Error('Invalid JSON structure');
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
catch (error) {
|
|
102
|
-
logError(`Failed to parse structured analysis result: ${error}`);
|
|
103
|
-
structuredAnalysisResult = {
|
|
104
|
-
status: 'error',
|
|
105
|
-
summary: 'Failed to parse analysis results from Claude Code response',
|
|
106
|
-
created_user_stories: [],
|
|
107
|
-
created_test_cases: [],
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
else {
|
|
112
|
-
logError(`\nā ļø Analysis incomplete: ${message.subtype}`);
|
|
113
|
-
if (message.subtype === 'error_max_turns') {
|
|
114
|
-
logError('š” Try increasing timeout or reducing complexity');
|
|
115
|
-
}
|
|
116
|
-
// Try to parse results from the last assistant response
|
|
117
|
-
if (lastAssistantResponse) {
|
|
118
|
-
try {
|
|
119
|
-
const responseText = lastAssistantResponse;
|
|
120
|
-
let jsonResult = null;
|
|
121
|
-
const jsonBlockMatch = responseText.match(/```json\s*\n([\s\S]*?)\n\s*```/);
|
|
122
|
-
if (jsonBlockMatch) {
|
|
123
|
-
jsonResult = JSON.parse(jsonBlockMatch[1]);
|
|
124
|
-
if (jsonResult && jsonResult.analysis) {
|
|
125
|
-
structuredAnalysisResult = jsonResult.analysis;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
catch (error) {
|
|
130
|
-
logError(`Failed to parse assistant response: ${error}`);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
50
|
+
// Execute analysis query
|
|
51
|
+
structuredAnalysisResult = await executeAnalysisQuery(currentPrompt, systemPrompt, config, verbose);
|
|
52
|
+
// No result produced, break out
|
|
53
|
+
if (!structuredAnalysisResult) {
|
|
54
|
+
break;
|
|
134
55
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
56
|
+
// Log analysis completion for this iteration
|
|
57
|
+
await logFeaturePhaseEvent(mcpServerUrl, mcpToken, {
|
|
58
|
+
featureId,
|
|
59
|
+
eventType: 'phase_completed',
|
|
60
|
+
phase: 'feature_analysis',
|
|
61
|
+
result: 'success',
|
|
62
|
+
metadata: {
|
|
63
|
+
iteration: currentIteration,
|
|
64
|
+
max_iterations: maxIterations,
|
|
65
|
+
analysis_step: 'completed',
|
|
66
|
+
user_stories_count: structuredAnalysisResult.created_user_stories?.length || 0,
|
|
67
|
+
test_cases_count: structuredAnalysisResult.created_test_cases?.length || 0,
|
|
68
|
+
timestamp: new Date().toISOString(),
|
|
69
|
+
},
|
|
70
|
+
}, verbose);
|
|
71
|
+
// Save artifacts as draft and get their IDs
|
|
72
|
+
const { userStoryIds, testCaseIds } = await saveAnalysisArtifactsAsDraft(mcpServerUrl, mcpToken, featureId, structuredAnalysisResult.created_user_stories || [], structuredAnalysisResult.created_test_cases || [], verbose);
|
|
73
|
+
currentDraftUserStoryIds = userStoryIds;
|
|
74
|
+
currentDraftTestCaseIds = testCaseIds;
|
|
75
|
+
// Perform verification cycle
|
|
76
|
+
const verificationCycle = await performVerificationCycle(structuredAnalysisResult, checklistContext || null, context.featureContext, config, currentIteration, maxIterations, mcpServerUrl, mcpToken, featureId, verbose);
|
|
77
|
+
verificationResult = verificationCycle.verificationResult;
|
|
78
|
+
// If verification passed, update artifacts to ready and exit
|
|
79
|
+
if (verificationCycle.passed) {
|
|
140
80
|
if (verbose) {
|
|
141
|
-
logInfo('
|
|
142
|
-
}
|
|
143
|
-
const userStoriesCreated = await createUserStories(mcpServerUrl, mcpToken, featureId, created_user_stories, verbose);
|
|
144
|
-
if (!userStoriesCreated && verbose) {
|
|
145
|
-
logError('ā ļø Failed to save user stories');
|
|
81
|
+
logInfo('ā
Verification passed! Updating artifacts to ready status...');
|
|
146
82
|
}
|
|
83
|
+
await updateArtifactsToReady(mcpServerUrl, mcpToken, currentDraftUserStoryIds, currentDraftTestCaseIds, verbose);
|
|
84
|
+
break;
|
|
147
85
|
}
|
|
148
|
-
|
|
86
|
+
// Verification failed
|
|
87
|
+
if (currentIteration < maxIterations && verificationCycle.nextPrompt) {
|
|
88
|
+
// We have more iterations - delete draft artifacts and retry
|
|
149
89
|
if (verbose) {
|
|
150
|
-
logInfo('
|
|
151
|
-
}
|
|
152
|
-
const testCasesCreated = await createTestCases(mcpServerUrl, mcpToken, featureId, created_test_cases, verbose);
|
|
153
|
-
if (!testCasesCreated && verbose) {
|
|
154
|
-
logError('ā ļø Failed to save test cases');
|
|
90
|
+
logInfo('šļø Deleting draft artifacts for re-analysis...');
|
|
155
91
|
}
|
|
92
|
+
await deleteArtifacts(mcpServerUrl, mcpToken, currentDraftUserStoryIds, currentDraftTestCaseIds, verbose);
|
|
93
|
+
// Continue with improvement prompt
|
|
94
|
+
currentPrompt = verificationCycle.nextPrompt;
|
|
156
95
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
96
|
+
else {
|
|
97
|
+
// Max iterations reached or no next prompt - exit loop
|
|
98
|
+
// Draft artifacts remain in database for manual review
|
|
160
99
|
if (verbose) {
|
|
161
|
-
logInfo('
|
|
100
|
+
logInfo('ā ļø Max iterations reached. Draft artifacts kept for manual review.');
|
|
162
101
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
logError('ā HTTP fallback also failed');
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// Handle results
|
|
106
|
+
if (!structuredAnalysisResult) {
|
|
107
|
+
return buildNoResultsError(featureId, context.featureContext);
|
|
108
|
+
}
|
|
109
|
+
const { created_user_stories, created_test_cases, status, checklist_results, checklist_item_results, } = structuredAnalysisResult;
|
|
110
|
+
// If no checklist was used, update draft artifacts to ready now
|
|
111
|
+
if (!checklistContext ||
|
|
112
|
+
checklistContext.checklists.length === 0 ||
|
|
113
|
+
!verificationResult) {
|
|
114
|
+
if (currentDraftUserStoryIds.length > 0 ||
|
|
115
|
+
currentDraftTestCaseIds.length > 0) {
|
|
116
|
+
if (verbose) {
|
|
117
|
+
logInfo('ā
No checklist verification needed. Updating artifacts to ready status...');
|
|
180
118
|
}
|
|
119
|
+
await updateArtifactsToReady(mcpServerUrl, mcpToken, currentDraftUserStoryIds, currentDraftTestCaseIds, verbose);
|
|
181
120
|
}
|
|
182
|
-
return {
|
|
183
|
-
featureId,
|
|
184
|
-
productInfo: context.product,
|
|
185
|
-
featureInfo: context.feature,
|
|
186
|
-
existingUserStories: context.existing_user_stories.map((story) => ({
|
|
187
|
-
...story,
|
|
188
|
-
status: story.status,
|
|
189
|
-
created_at: story.created_at || new Date().toISOString(),
|
|
190
|
-
updated_at: story.updated_at || new Date().toISOString(),
|
|
191
|
-
})),
|
|
192
|
-
existingTestCases: context.existing_test_cases.map((testCase) => ({
|
|
193
|
-
...testCase,
|
|
194
|
-
created_at: testCase.created_at || new Date().toISOString(),
|
|
195
|
-
updated_at: testCase.updated_at || new Date().toISOString(),
|
|
196
|
-
})),
|
|
197
|
-
createdUserStories: (created_user_stories || []).map((story) => ({
|
|
198
|
-
id: '',
|
|
199
|
-
title: story.title,
|
|
200
|
-
description: story.description,
|
|
201
|
-
status: story.status || 'draft',
|
|
202
|
-
created_at: new Date().toISOString(),
|
|
203
|
-
updated_at: new Date().toISOString(),
|
|
204
|
-
})),
|
|
205
|
-
createdTestCases: (created_test_cases || []).map((testCase) => ({
|
|
206
|
-
id: '',
|
|
207
|
-
name: testCase.name,
|
|
208
|
-
description: testCase.description,
|
|
209
|
-
is_critical: testCase.is_critical || false,
|
|
210
|
-
created_at: new Date().toISOString(),
|
|
211
|
-
updated_at: new Date().toISOString(),
|
|
212
|
-
})),
|
|
213
|
-
summary: structuredAnalysisResult.summary || 'Analysis completed',
|
|
214
|
-
status: structuredAnalysisResult.status === 'success' ? 'success' : 'error',
|
|
215
|
-
data: {
|
|
216
|
-
checklist_results,
|
|
217
|
-
checklist_item_results,
|
|
218
|
-
},
|
|
219
|
-
};
|
|
220
121
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
createdUserStories: [],
|
|
237
|
-
createdTestCases: [],
|
|
238
|
-
summary: 'No analysis results received',
|
|
239
|
-
status: 'error',
|
|
240
|
-
data: {
|
|
241
|
-
checklist_results: undefined,
|
|
242
|
-
checklist_item_results: undefined,
|
|
243
|
-
},
|
|
244
|
-
};
|
|
122
|
+
// Check if verification failed after all iterations
|
|
123
|
+
// Note: Artifacts are already saved as draft in the database
|
|
124
|
+
// If verification failed, they remain as draft for manual review
|
|
125
|
+
if (verificationResult &&
|
|
126
|
+
verificationResult.rejected_count > 0 &&
|
|
127
|
+
checklistContext &&
|
|
128
|
+
checklistContext.checklists.length > 0) {
|
|
129
|
+
logError(`ā Final result: Checklist verification FAILED after ${currentIteration} iterations`);
|
|
130
|
+
logError(` Draft artifacts (${currentDraftUserStoryIds.length} user stories, ${currentDraftTestCaseIds.length} test cases) kept for manual review`);
|
|
131
|
+
return buildVerificationFailureResult(featureId, context.featureContext, verificationResult, checklist_results, checklist_item_results, currentIteration);
|
|
132
|
+
}
|
|
133
|
+
// Return success result
|
|
134
|
+
// Note: Artifacts have already been saved and updated to 'ready' status (if verification passed)
|
|
135
|
+
// or remain as draft (if verification failed)
|
|
136
|
+
return buildAnalysisResult(featureId, context.featureContext, structuredAnalysisResult, currentIteration);
|
|
245
137
|
}
|
|
246
138
|
catch (error) {
|
|
247
139
|
logError(`Feature analysis failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -258,6 +150,33 @@ export const analyzeFeatureWithMCP = async (options, config, checklistContext) =
|
|
|
258
150
|
};
|
|
259
151
|
}
|
|
260
152
|
};
|
|
153
|
+
/**
|
|
154
|
+
* Prepare all context information needed for analysis
|
|
155
|
+
*/
|
|
156
|
+
async function prepareAnalysisContext(mcpServerUrl, mcpToken, featureId, checklistContext, verbose) {
|
|
157
|
+
if (verbose) {
|
|
158
|
+
logInfo('Fetching feature analysis context via MCP endpoints...');
|
|
159
|
+
}
|
|
160
|
+
const featureContext = await fetchFeatureAnalysisContext(mcpServerUrl, mcpToken, featureId, verbose);
|
|
161
|
+
const { content: contextInfo, downloadedImages } = await formatFeatureAnalysisContext(featureContext);
|
|
162
|
+
if (verbose && downloadedImages.length > 0) {
|
|
163
|
+
logInfo(`Downloaded ${downloadedImages.length} images for Claude Code:`);
|
|
164
|
+
downloadedImages.forEach((img) => {
|
|
165
|
+
logInfo(` - ${img.url} -> ${img.localPath}`);
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
// Add checklist context to the analysis prompt
|
|
169
|
+
let finalContextInfo = contextInfo;
|
|
170
|
+
if (checklistContext && checklistContext.checklists.length > 0) {
|
|
171
|
+
const checklistInfo = formatChecklistsForContext(checklistContext);
|
|
172
|
+
finalContextInfo = contextInfo + '\n\n' + checklistInfo;
|
|
173
|
+
if (verbose) {
|
|
174
|
+
logInfo(`Added ${checklistContext.checklists.length} checklists to analysis context`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
const analysisPrompt = createFeatureAnalysisPromptWithContext(featureId, finalContextInfo);
|
|
178
|
+
return { featureContext, analysisPrompt };
|
|
179
|
+
}
|
|
261
180
|
export const checkFeatureAnalysisRequirements = async () => {
|
|
262
181
|
try {
|
|
263
182
|
// Check if Claude Code SDK is available
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { verifyChecklistCompliance, type ChecklistItemVerificationResult, type ChecklistVerificationResult, type VerifyChecklistOptions, } from './verifier.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { verifyChecklistCompliance, } from './verifier.js';
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checklist verification agent for feature analysis
|
|
3
|
+
* This agent independently reviews checklist compliance to ensure objectivity
|
|
4
|
+
*/
|
|
5
|
+
import { EdsgerConfig } from '../../types/index.js';
|
|
6
|
+
import { ChecklistPhaseContext } from '../../services/checklist.js';
|
|
7
|
+
import { FeatureAnalysisContext } from '../feature-analysis/context-fetcher.js';
|
|
8
|
+
export interface ChecklistItemVerificationResult {
|
|
9
|
+
checklist_item_id: string;
|
|
10
|
+
is_satisfied: boolean;
|
|
11
|
+
verification_status: 'confirmed' | 'rejected' | 'uncertain';
|
|
12
|
+
verification_reason: string;
|
|
13
|
+
concerns?: string[];
|
|
14
|
+
improvement_suggestions?: string[];
|
|
15
|
+
}
|
|
16
|
+
export interface ChecklistVerificationResult {
|
|
17
|
+
all_verified: boolean;
|
|
18
|
+
total_items: number;
|
|
19
|
+
confirmed_count: number;
|
|
20
|
+
rejected_count: number;
|
|
21
|
+
uncertain_count: number;
|
|
22
|
+
item_verifications: ChecklistItemVerificationResult[];
|
|
23
|
+
summary: string;
|
|
24
|
+
overall_suggestions?: string[];
|
|
25
|
+
}
|
|
26
|
+
export interface VerifyChecklistOptions {
|
|
27
|
+
checklistContext: ChecklistPhaseContext;
|
|
28
|
+
analysisContext: FeatureAnalysisContext;
|
|
29
|
+
createdUserStories: any[];
|
|
30
|
+
createdTestCases: any[];
|
|
31
|
+
verbose?: boolean;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Verify checklist compliance using an independent AI agent
|
|
35
|
+
* This agent acts as a "challenger" to validate the claims made by the analysis agent
|
|
36
|
+
*/
|
|
37
|
+
export declare function verifyChecklistCompliance(options: VerifyChecklistOptions, config: EdsgerConfig): Promise<ChecklistVerificationResult>;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checklist verification agent for feature analysis
|
|
3
|
+
* This agent independently reviews checklist compliance to ensure objectivity
|
|
4
|
+
*/
|
|
5
|
+
import { query } from '@anthropic-ai/claude-code';
|
|
6
|
+
import { logInfo, logError } from '../../utils/logger.js';
|
|
7
|
+
import { createChecklistVerificationPrompt, createChecklistVerificationSystemPrompt, } from '../../prompts/checklist-verification.js';
|
|
8
|
+
function userMessage(content) {
|
|
9
|
+
return {
|
|
10
|
+
type: 'user',
|
|
11
|
+
message: { role: 'user', content: content },
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
async function* prompt(verificationPrompt) {
|
|
15
|
+
yield userMessage(verificationPrompt);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Verify checklist compliance using an independent AI agent
|
|
19
|
+
* This agent acts as a "challenger" to validate the claims made by the analysis agent
|
|
20
|
+
*/
|
|
21
|
+
export async function verifyChecklistCompliance(options, config) {
|
|
22
|
+
const { checklistContext, analysisContext, createdUserStories, createdTestCases, verbose, } = options;
|
|
23
|
+
const totalChecklistItems = checklistContext.checklists.reduce((sum, checklist) => sum + checklist.items.length, 0);
|
|
24
|
+
if (verbose) {
|
|
25
|
+
logInfo('š Starting checklist verification...');
|
|
26
|
+
logInfo(` Verifying ${totalChecklistItems} checklist items`);
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
const systemPrompt = createChecklistVerificationSystemPrompt(config);
|
|
30
|
+
const verificationPrompt = createChecklistVerificationPrompt({
|
|
31
|
+
checklistContext,
|
|
32
|
+
analysisContext,
|
|
33
|
+
createdUserStories,
|
|
34
|
+
createdTestCases,
|
|
35
|
+
});
|
|
36
|
+
let lastAssistantResponse = '';
|
|
37
|
+
let verificationResult = null;
|
|
38
|
+
if (verbose) {
|
|
39
|
+
logInfo('š¤ Starting verification agent query...');
|
|
40
|
+
}
|
|
41
|
+
// Use Claude Code SDK for verification
|
|
42
|
+
for await (const message of query({
|
|
43
|
+
prompt: prompt(verificationPrompt),
|
|
44
|
+
options: {
|
|
45
|
+
appendSystemPrompt: systemPrompt,
|
|
46
|
+
model: config.claude.model || 'sonnet',
|
|
47
|
+
maxTurns: 100,
|
|
48
|
+
permissionMode: 'bypassPermissions',
|
|
49
|
+
},
|
|
50
|
+
})) {
|
|
51
|
+
if (verbose) {
|
|
52
|
+
logInfo(` Received message type: ${message.type}`);
|
|
53
|
+
}
|
|
54
|
+
// Capture assistant responses
|
|
55
|
+
if (message.type === 'assistant' && message.message?.content) {
|
|
56
|
+
for (const content of message.message.content) {
|
|
57
|
+
if (content.type === 'text') {
|
|
58
|
+
lastAssistantResponse += content.text + '\n';
|
|
59
|
+
if (verbose) {
|
|
60
|
+
console.log(`\nš ${content.text}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (message.type === 'result') {
|
|
66
|
+
if (message.subtype === 'success') {
|
|
67
|
+
logInfo('\nā
Verification completed, parsing results...');
|
|
68
|
+
try {
|
|
69
|
+
const responseText = message.result || lastAssistantResponse;
|
|
70
|
+
// Try to extract JSON from markdown code block
|
|
71
|
+
const jsonBlockMatch = responseText.match(/```json\s*\n([\s\S]*?)\n\s*```/);
|
|
72
|
+
let jsonResult = null;
|
|
73
|
+
if (jsonBlockMatch) {
|
|
74
|
+
jsonResult = JSON.parse(jsonBlockMatch[1]);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
jsonResult = JSON.parse(responseText);
|
|
78
|
+
}
|
|
79
|
+
if (jsonResult && jsonResult.verification) {
|
|
80
|
+
verificationResult = jsonResult.verification;
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
throw new Error('Invalid verification JSON structure');
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
logError(`Failed to parse verification result: ${error}`);
|
|
88
|
+
// Return default "uncertain" result
|
|
89
|
+
verificationResult = createUncertainVerificationResult(checklistContext, 'Failed to parse verification response');
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
logError(`\nā ļø Verification incomplete: ${message.subtype}`);
|
|
94
|
+
verificationResult = createUncertainVerificationResult(checklistContext, `Verification incomplete: ${message.subtype}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (!verificationResult) {
|
|
99
|
+
verificationResult = createUncertainVerificationResult(checklistContext, 'No verification result received');
|
|
100
|
+
}
|
|
101
|
+
if (verbose) {
|
|
102
|
+
logInfo('\nš Verification Summary:');
|
|
103
|
+
logInfo(` Total items: ${verificationResult.total_items}`);
|
|
104
|
+
logInfo(` ā
Confirmed: ${verificationResult.confirmed_count}`);
|
|
105
|
+
logInfo(` ā Rejected: ${verificationResult.rejected_count}`);
|
|
106
|
+
logInfo(` ā ļø Uncertain: ${verificationResult.uncertain_count}`);
|
|
107
|
+
logInfo(` Summary: ${verificationResult.summary}`);
|
|
108
|
+
if (verificationResult.rejected_count > 0) {
|
|
109
|
+
logInfo('\nā Rejected items:');
|
|
110
|
+
verificationResult.item_verifications
|
|
111
|
+
.filter((v) => v.verification_status === 'rejected')
|
|
112
|
+
.forEach((v) => {
|
|
113
|
+
logInfo(` - ${v.checklist_item_id}`);
|
|
114
|
+
logInfo(` Reason: ${v.verification_reason}`);
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return verificationResult;
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
logError(`Checklist verification failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
122
|
+
return createUncertainVerificationResult(checklistContext, `Verification error: ${error instanceof Error ? error.message : String(error)}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Create a default "uncertain" verification result when verification fails
|
|
127
|
+
*/
|
|
128
|
+
function createUncertainVerificationResult(checklistContext, reason) {
|
|
129
|
+
// Get all checklist items
|
|
130
|
+
const allItems = checklistContext.checklists.flatMap((checklist) => checklist.items.map((item) => ({
|
|
131
|
+
checklist_item_id: item.id,
|
|
132
|
+
})));
|
|
133
|
+
return {
|
|
134
|
+
all_verified: false,
|
|
135
|
+
total_items: allItems.length,
|
|
136
|
+
confirmed_count: 0,
|
|
137
|
+
rejected_count: 0,
|
|
138
|
+
uncertain_count: allItems.length,
|
|
139
|
+
item_verifications: allItems.map((item) => ({
|
|
140
|
+
checklist_item_id: item.checklist_item_id,
|
|
141
|
+
is_satisfied: false,
|
|
142
|
+
verification_status: 'uncertain',
|
|
143
|
+
verification_reason: reason,
|
|
144
|
+
})),
|
|
145
|
+
summary: `Verification could not be completed: ${reason}`,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
@@ -55,7 +55,8 @@ const switchToMainBranch = (mainBranch = 'main', verbose) => {
|
|
|
55
55
|
const generatePRTitle = (featureName) => {
|
|
56
56
|
// Remove feature ID prefix if present (e.g., "FEAT-123: Feature Name" -> "Feature Name")
|
|
57
57
|
const cleanName = featureName.replace(/^[A-Z]+-\d+:\s*/, '');
|
|
58
|
-
|
|
58
|
+
// Convert to lowercase for conventional commits format
|
|
59
|
+
return `feat: ${cleanName.toLowerCase()}`;
|
|
59
60
|
};
|
|
60
61
|
/**
|
|
61
62
|
* Generate pull request body with feature details
|
|
@@ -108,28 +109,28 @@ export async function createPullRequest(config, feature) {
|
|
|
108
109
|
if (currentBranch.startsWith('dev/')) {
|
|
109
110
|
const featureId = currentBranch.replace('dev/', '');
|
|
110
111
|
targetBranch = `feat/${featureId}`;
|
|
111
|
-
// Create the feat/ branch from
|
|
112
|
+
// Create the feat/ branch from base branch (main)
|
|
112
113
|
if (verbose) {
|
|
113
|
-
console.log(`š Creating target branch: ${targetBranch} from ${
|
|
114
|
+
console.log(`š Creating target branch: ${targetBranch} from ${baseBranch}`);
|
|
114
115
|
}
|
|
115
116
|
try {
|
|
116
117
|
// Push current branch to remote first
|
|
117
118
|
pushBranch(currentBranch, verbose);
|
|
118
|
-
// Get the
|
|
119
|
-
const { data:
|
|
119
|
+
// Get the base branch (main) SHA to create feat/ branch from
|
|
120
|
+
const { data: baseBranchData } = await octokit.repos.getBranch({
|
|
120
121
|
owner,
|
|
121
122
|
repo,
|
|
122
|
-
branch:
|
|
123
|
+
branch: baseBranch,
|
|
123
124
|
});
|
|
124
|
-
// Create the feat/ branch
|
|
125
|
+
// Create the feat/ branch from main
|
|
125
126
|
await octokit.git.createRef({
|
|
126
127
|
owner,
|
|
127
128
|
repo,
|
|
128
129
|
ref: `refs/heads/${targetBranch}`,
|
|
129
|
-
sha:
|
|
130
|
+
sha: baseBranchData.commit.sha,
|
|
130
131
|
});
|
|
131
132
|
if (verbose) {
|
|
132
|
-
console.log(`ā
Created target branch: ${targetBranch}`);
|
|
133
|
+
console.log(`ā
Created target branch: ${targetBranch} from ${baseBranch}`);
|
|
133
134
|
}
|
|
134
135
|
}
|
|
135
136
|
catch (error) {
|