edsger 0.26.0 → 0.26.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 +28 -0
- 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/config/phase-configs.js +5 -0
- package/dist/commands/workflow/executors/phase-executor.d.ts +2 -2
- package/dist/commands/workflow/executors/phase-executor.js +2 -2
- package/dist/commands/workflow/feature-coordinator.js +3 -1
- package/dist/commands/workflow/phase-orchestrator.js +66 -3
- 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/config/feature-status.js +3 -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/context.d.ts +26 -0
- package/dist/phases/pr-execution/context.js +156 -0
- package/dist/phases/pr-execution/index.d.ts +20 -0
- package/dist/phases/pr-execution/index.js +287 -0
- package/dist/phases/pr-execution/outcome.d.ts +26 -0
- package/dist/phases/pr-execution/outcome.js +34 -0
- package/dist/phases/pr-execution/pr-executor.d.ts +28 -0
- package/dist/phases/pr-execution/pr-executor.js +152 -0
- package/dist/phases/pr-execution/prompts.d.ts +17 -0
- package/dist/phases/pr-execution/prompts.js +208 -0
- package/dist/phases/pr-splitting/context.d.ts +16 -2
- package/dist/phases/pr-splitting/context.js +127 -4
- package/dist/phases/pr-splitting/index.d.ts +7 -0
- package/dist/phases/pr-splitting/index.js +58 -52
- package/dist/phases/pr-splitting/prompts.d.ts +4 -4
- package/dist/phases/pr-splitting/prompts.js +42 -30
- 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/audit-logs.d.ts +2 -2
- package/dist/services/feature-branches.d.ts +77 -0
- package/dist/services/feature-branches.js +205 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/pipeline.d.ts +1 -1
- package/dist/utils/github-repo-info.d.ts +14 -0
- package/dist/utils/github-repo-info.js +19 -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,497 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration tests for status updater functionality
|
|
3
|
+
* Tests the complete flow including MCP calls and error handling
|
|
4
|
+
*/
|
|
5
|
+
import { describe, it, beforeEach, afterEach } from 'node:test';
|
|
6
|
+
import assert from 'node:assert';
|
|
7
|
+
import { updateFeatureStatus, updateFeatureStatusForPhase, getStatusForPhase, } from '../status-updater.js';
|
|
8
|
+
// Mock modules
|
|
9
|
+
let mockCallMcpEndpoint;
|
|
10
|
+
let mockGetFeature;
|
|
11
|
+
let originalCallMcpEndpoint;
|
|
12
|
+
let originalGetFeature;
|
|
13
|
+
// Test data
|
|
14
|
+
const mockFeatureInfo = {
|
|
15
|
+
id: 'test-feature-id',
|
|
16
|
+
name: 'Test Feature',
|
|
17
|
+
description: 'Test feature description',
|
|
18
|
+
status: 'backlog',
|
|
19
|
+
product_id: 'test-product-id',
|
|
20
|
+
execution_mode: 'auto',
|
|
21
|
+
created_at: '2025-11-13T00:00:00Z',
|
|
22
|
+
updated_at: '2025-11-13T00:00:00Z',
|
|
23
|
+
};
|
|
24
|
+
// Setup mocks before tests
|
|
25
|
+
const setupMocks = () => {
|
|
26
|
+
// Mock successful MCP call
|
|
27
|
+
mockCallMcpEndpoint = async (method, params) => {
|
|
28
|
+
if (method === 'features/update') {
|
|
29
|
+
return { success: true };
|
|
30
|
+
}
|
|
31
|
+
throw new Error(`Unexpected method: ${method}`);
|
|
32
|
+
};
|
|
33
|
+
// Mock successful getFeature call
|
|
34
|
+
mockGetFeature = async (featureId, verbose) => {
|
|
35
|
+
return { ...mockFeatureInfo, id: featureId };
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
// Restore original functions
|
|
39
|
+
const restoreMocks = () => {
|
|
40
|
+
if (originalCallMcpEndpoint) {
|
|
41
|
+
// Note: In a real test environment, we would need proper module mocking
|
|
42
|
+
// This is a simplified version for demonstration
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
describe('Status Updater Integration Tests', () => {
|
|
46
|
+
beforeEach(() => {
|
|
47
|
+
setupMocks();
|
|
48
|
+
});
|
|
49
|
+
afterEach(() => {
|
|
50
|
+
restoreMocks();
|
|
51
|
+
});
|
|
52
|
+
describe('updateFeatureStatus', () => {
|
|
53
|
+
it('should successfully update feature status when progression is valid', async () => {
|
|
54
|
+
// Set current status to backlog
|
|
55
|
+
mockGetFeature = async (featureId) => ({
|
|
56
|
+
...mockFeatureInfo,
|
|
57
|
+
id: featureId,
|
|
58
|
+
status: 'backlog',
|
|
59
|
+
});
|
|
60
|
+
let mcpCalled = false;
|
|
61
|
+
let updateParams = null;
|
|
62
|
+
mockCallMcpEndpoint = async (method, params) => {
|
|
63
|
+
if (method === 'features/update') {
|
|
64
|
+
mcpCalled = true;
|
|
65
|
+
updateParams = params;
|
|
66
|
+
return { success: true };
|
|
67
|
+
}
|
|
68
|
+
throw new Error(`Unexpected method: ${method}`);
|
|
69
|
+
};
|
|
70
|
+
const result = await updateFeatureStatus({
|
|
71
|
+
featureId: 'test-feature-id',
|
|
72
|
+
status: 'ready_for_dev',
|
|
73
|
+
verbose: true,
|
|
74
|
+
});
|
|
75
|
+
assert.strictEqual(result, true, 'Should return true for successful update');
|
|
76
|
+
assert.strictEqual(mcpCalled, true, 'Should call MCP endpoint');
|
|
77
|
+
assert.deepStrictEqual(updateParams, {
|
|
78
|
+
feature_id: 'test-feature-id',
|
|
79
|
+
status: 'ready_for_dev',
|
|
80
|
+
}, 'Should pass correct parameters to MCP');
|
|
81
|
+
});
|
|
82
|
+
it('should prevent status regression', async () => {
|
|
83
|
+
// Set current status to ready_for_dev
|
|
84
|
+
mockGetFeature = async (featureId) => ({
|
|
85
|
+
...mockFeatureInfo,
|
|
86
|
+
id: featureId,
|
|
87
|
+
status: 'ready_for_dev',
|
|
88
|
+
});
|
|
89
|
+
let mcpCalled = false;
|
|
90
|
+
mockCallMcpEndpoint = async () => {
|
|
91
|
+
mcpCalled = true;
|
|
92
|
+
return { success: true };
|
|
93
|
+
};
|
|
94
|
+
const result = await updateFeatureStatus({
|
|
95
|
+
featureId: 'test-feature-id',
|
|
96
|
+
status: 'backlog', // This is a regression
|
|
97
|
+
verbose: true,
|
|
98
|
+
});
|
|
99
|
+
assert.strictEqual(result, false, 'Should return false for regression attempt');
|
|
100
|
+
assert.strictEqual(mcpCalled, false, 'Should not call MCP endpoint for regression');
|
|
101
|
+
});
|
|
102
|
+
it('should allow staying at the same status', async () => {
|
|
103
|
+
// Set current status to feature_analysis
|
|
104
|
+
mockGetFeature = async (featureId) => ({
|
|
105
|
+
...mockFeatureInfo,
|
|
106
|
+
id: featureId,
|
|
107
|
+
status: 'feature_analysis',
|
|
108
|
+
});
|
|
109
|
+
let mcpCalled = false;
|
|
110
|
+
mockCallMcpEndpoint = async () => {
|
|
111
|
+
mcpCalled = true;
|
|
112
|
+
return { success: true };
|
|
113
|
+
};
|
|
114
|
+
const result = await updateFeatureStatus({
|
|
115
|
+
featureId: 'test-feature-id',
|
|
116
|
+
status: 'feature_analysis', // Same status
|
|
117
|
+
verbose: true,
|
|
118
|
+
});
|
|
119
|
+
assert.strictEqual(result, true, 'Should return true when status is already at target');
|
|
120
|
+
assert.strictEqual(mcpCalled, false, 'Should not call MCP when status is unchanged');
|
|
121
|
+
});
|
|
122
|
+
it('should handle getFeature errors gracefully', async () => {
|
|
123
|
+
// Mock getFeature to fail
|
|
124
|
+
mockGetFeature = async () => {
|
|
125
|
+
throw new Error('Feature not found');
|
|
126
|
+
};
|
|
127
|
+
let mcpCalled = false;
|
|
128
|
+
mockCallMcpEndpoint = async () => {
|
|
129
|
+
mcpCalled = true;
|
|
130
|
+
return { success: true };
|
|
131
|
+
};
|
|
132
|
+
const result = await updateFeatureStatus({
|
|
133
|
+
featureId: 'invalid-feature-id',
|
|
134
|
+
status: 'ready_for_dev',
|
|
135
|
+
verbose: true,
|
|
136
|
+
});
|
|
137
|
+
assert.strictEqual(result, false, 'Should return false when current status cannot be retrieved');
|
|
138
|
+
assert.strictEqual(mcpCalled, false, 'Should not call MCP when getFeature fails');
|
|
139
|
+
});
|
|
140
|
+
it('should handle MCP endpoint errors gracefully', async () => {
|
|
141
|
+
// Set current status to backlog
|
|
142
|
+
mockGetFeature = async (featureId) => ({
|
|
143
|
+
...mockFeatureInfo,
|
|
144
|
+
id: featureId,
|
|
145
|
+
status: 'backlog',
|
|
146
|
+
});
|
|
147
|
+
// Mock MCP call to fail
|
|
148
|
+
mockCallMcpEndpoint = async () => {
|
|
149
|
+
throw new Error('MCP server error');
|
|
150
|
+
};
|
|
151
|
+
const result = await updateFeatureStatus({
|
|
152
|
+
featureId: 'test-feature-id',
|
|
153
|
+
status: 'ready_for_dev',
|
|
154
|
+
verbose: true,
|
|
155
|
+
});
|
|
156
|
+
assert.strictEqual(result, false, 'Should return false when MCP call fails');
|
|
157
|
+
});
|
|
158
|
+
it('should handle complex status progressions correctly', async () => {
|
|
159
|
+
// Test progression from middle of workflow to end
|
|
160
|
+
mockGetFeature = async (featureId) => ({
|
|
161
|
+
...mockFeatureInfo,
|
|
162
|
+
id: featureId,
|
|
163
|
+
status: 'code_implementation',
|
|
164
|
+
});
|
|
165
|
+
let mcpCalled = false;
|
|
166
|
+
let updateParams = null;
|
|
167
|
+
mockCallMcpEndpoint = async (method, params) => {
|
|
168
|
+
mcpCalled = true;
|
|
169
|
+
updateParams = params;
|
|
170
|
+
return { success: true };
|
|
171
|
+
};
|
|
172
|
+
const result = await updateFeatureStatus({
|
|
173
|
+
featureId: 'test-feature-id',
|
|
174
|
+
status: 'shipped',
|
|
175
|
+
verbose: false, // Test non-verbose mode
|
|
176
|
+
});
|
|
177
|
+
assert.strictEqual(result, true, 'Should allow forward progression');
|
|
178
|
+
assert.strictEqual(mcpCalled, true, 'Should call MCP endpoint');
|
|
179
|
+
assert.strictEqual(updateParams.status, 'shipped', 'Should pass correct target status');
|
|
180
|
+
});
|
|
181
|
+
it('should handle testing workflow edge cases', async () => {
|
|
182
|
+
// Test progression from testing_failed back to testing_in_progress (retry)
|
|
183
|
+
mockGetFeature = async (featureId) => ({
|
|
184
|
+
...mockFeatureInfo,
|
|
185
|
+
id: featureId,
|
|
186
|
+
status: 'testing_failed',
|
|
187
|
+
});
|
|
188
|
+
let updateParams = null;
|
|
189
|
+
mockCallMcpEndpoint = async (method, params) => {
|
|
190
|
+
updateParams = params;
|
|
191
|
+
return { success: true };
|
|
192
|
+
};
|
|
193
|
+
const result = await updateFeatureStatus({
|
|
194
|
+
featureId: 'test-feature-id',
|
|
195
|
+
status: 'testing_in_progress',
|
|
196
|
+
verbose: true,
|
|
197
|
+
});
|
|
198
|
+
// This should be prevented because testing_in_progress comes before testing_failed
|
|
199
|
+
// in the progression order
|
|
200
|
+
assert.strictEqual(result, false, 'Should prevent regression from testing_failed to testing_in_progress');
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
describe('updateFeatureStatusForPhase', () => {
|
|
204
|
+
it('should successfully update status for known phases', async () => {
|
|
205
|
+
mockGetFeature = async (featureId) => ({
|
|
206
|
+
...mockFeatureInfo,
|
|
207
|
+
id: featureId,
|
|
208
|
+
status: 'backlog',
|
|
209
|
+
});
|
|
210
|
+
let mcpCalled = false;
|
|
211
|
+
let updateParams = null;
|
|
212
|
+
mockCallMcpEndpoint = async (method, params) => {
|
|
213
|
+
mcpCalled = true;
|
|
214
|
+
updateParams = params;
|
|
215
|
+
return { success: true };
|
|
216
|
+
};
|
|
217
|
+
const result = await updateFeatureStatusForPhase('test-feature-id', 'feature-analysis', true);
|
|
218
|
+
assert.strictEqual(result, true, 'Should return true for known phase');
|
|
219
|
+
assert.strictEqual(mcpCalled, true, 'Should call MCP endpoint');
|
|
220
|
+
assert.strictEqual(updateParams.status, 'feature_analysis', 'Should map phase to correct status');
|
|
221
|
+
});
|
|
222
|
+
it('should reject unknown phases', async () => {
|
|
223
|
+
let mcpCalled = false;
|
|
224
|
+
mockCallMcpEndpoint = async () => {
|
|
225
|
+
mcpCalled = true;
|
|
226
|
+
return { success: true };
|
|
227
|
+
};
|
|
228
|
+
const result = await updateFeatureStatusForPhase('test-feature-id', 'unknown-phase', true);
|
|
229
|
+
assert.strictEqual(result, false, 'Should return false for unknown phase');
|
|
230
|
+
assert.strictEqual(mcpCalled, false, 'Should not call MCP for unknown phase');
|
|
231
|
+
});
|
|
232
|
+
it('should handle all mapped phases correctly', async () => {
|
|
233
|
+
const phaseStatusPairs = [
|
|
234
|
+
['feature-analysis', 'feature_analysis'],
|
|
235
|
+
['feature-analysis-verification', 'feature_analysis_verification'],
|
|
236
|
+
['technical-design', 'technical_design'],
|
|
237
|
+
['technical-design-verification', 'technical_design_verification'],
|
|
238
|
+
['code-implementation', 'code_implementation'],
|
|
239
|
+
['code-implementation-verification', 'code_implementation_verification'],
|
|
240
|
+
['code-refine', 'code_refine'],
|
|
241
|
+
['code-refine-verification', 'code_refine_verification'],
|
|
242
|
+
['bug-fixing', 'bug_fixing'],
|
|
243
|
+
['code-review', 'code_review'],
|
|
244
|
+
['pull-request', 'pull_request'],
|
|
245
|
+
['functional-testing', 'functional_testing'],
|
|
246
|
+
['deployment', 'deployment'],
|
|
247
|
+
['testing-in-progress', 'testing_in_progress'],
|
|
248
|
+
['testing-passed', 'testing_passed'],
|
|
249
|
+
['testing-failed', 'testing_failed'],
|
|
250
|
+
];
|
|
251
|
+
for (const [phase, expectedStatus] of phaseStatusPairs) {
|
|
252
|
+
mockGetFeature = async (featureId) => ({
|
|
253
|
+
...mockFeatureInfo,
|
|
254
|
+
id: featureId,
|
|
255
|
+
status: 'backlog', // Start from backlog each time
|
|
256
|
+
});
|
|
257
|
+
let capturedStatus = null;
|
|
258
|
+
mockCallMcpEndpoint = async (method, params) => {
|
|
259
|
+
if (method === 'features/update') {
|
|
260
|
+
capturedStatus = params.status;
|
|
261
|
+
return { success: true };
|
|
262
|
+
}
|
|
263
|
+
return {};
|
|
264
|
+
};
|
|
265
|
+
const result = await updateFeatureStatusForPhase('test-feature-id', phase, false);
|
|
266
|
+
assert.strictEqual(result, true, `Phase ${phase} should be processed successfully`);
|
|
267
|
+
assert.strictEqual(capturedStatus, expectedStatus, `Phase ${phase} should map to status ${expectedStatus}`);
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
it('should propagate errors from updateFeatureStatus', async () => {
|
|
271
|
+
mockGetFeature = async () => {
|
|
272
|
+
throw new Error('Database connection failed');
|
|
273
|
+
};
|
|
274
|
+
const result = await updateFeatureStatusForPhase('test-feature-id', 'feature-analysis', true);
|
|
275
|
+
assert.strictEqual(result, false, 'Should return false when underlying update fails');
|
|
276
|
+
});
|
|
277
|
+
it('should respect regression prevention in phase updates', async () => {
|
|
278
|
+
// Set current status to shipped
|
|
279
|
+
mockGetFeature = async (featureId) => ({
|
|
280
|
+
...mockFeatureInfo,
|
|
281
|
+
id: featureId,
|
|
282
|
+
status: 'shipped',
|
|
283
|
+
});
|
|
284
|
+
let mcpCalled = false;
|
|
285
|
+
mockCallMcpEndpoint = async () => {
|
|
286
|
+
mcpCalled = true;
|
|
287
|
+
return { success: true };
|
|
288
|
+
};
|
|
289
|
+
// Try to update to a phase that would regress the status
|
|
290
|
+
const result = await updateFeatureStatusForPhase('test-feature-id', 'feature-analysis', // This would be regression from shipped
|
|
291
|
+
true);
|
|
292
|
+
assert.strictEqual(result, false, 'Should prevent regression even when phase is valid');
|
|
293
|
+
assert.strictEqual(mcpCalled, false, 'Should not call MCP for regression attempt');
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
describe('getStatusForPhase', () => {
|
|
297
|
+
it('should return correct status for all known phases', () => {
|
|
298
|
+
const testCases = [
|
|
299
|
+
['feature-analysis', 'feature_analysis'],
|
|
300
|
+
['technical-design', 'technical_design'],
|
|
301
|
+
['code-implementation', 'code_implementation'],
|
|
302
|
+
['functional-testing', 'functional_testing'],
|
|
303
|
+
['deployment', 'deployment'],
|
|
304
|
+
['testing-passed', 'testing_passed'],
|
|
305
|
+
];
|
|
306
|
+
for (const [phase, expectedStatus] of testCases) {
|
|
307
|
+
const result = getStatusForPhase(phase);
|
|
308
|
+
assert.strictEqual(result, expectedStatus, `Phase ${phase} should map to ${expectedStatus}`);
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
it('should return null for unknown phases', () => {
|
|
312
|
+
const unknownPhases = [
|
|
313
|
+
'unknown-phase',
|
|
314
|
+
'invalid-phase',
|
|
315
|
+
'some-random-string',
|
|
316
|
+
'',
|
|
317
|
+
'feature_analysis', // Wrong format (underscore instead of hyphen)
|
|
318
|
+
'technical_design', // Wrong format
|
|
319
|
+
];
|
|
320
|
+
for (const phase of unknownPhases) {
|
|
321
|
+
const result = getStatusForPhase(phase);
|
|
322
|
+
assert.strictEqual(result, null, `Unknown phase ${phase} should return null`);
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
it('should handle edge cases', () => {
|
|
326
|
+
// Test case sensitivity
|
|
327
|
+
assert.strictEqual(getStatusForPhase('Feature-Analysis'), null, 'Should be case sensitive');
|
|
328
|
+
// Test whitespace
|
|
329
|
+
assert.strictEqual(getStatusForPhase(' feature-analysis '), null, 'Should not handle whitespace');
|
|
330
|
+
// Test empty string
|
|
331
|
+
assert.strictEqual(getStatusForPhase(''), null, 'Should return null for empty string');
|
|
332
|
+
});
|
|
333
|
+
it('should maintain consistency with phase naming conventions', () => {
|
|
334
|
+
// All returned statuses should use snake_case
|
|
335
|
+
const phases = [
|
|
336
|
+
'feature-analysis',
|
|
337
|
+
'technical-design',
|
|
338
|
+
'code-implementation',
|
|
339
|
+
'functional-testing',
|
|
340
|
+
];
|
|
341
|
+
for (const phase of phases) {
|
|
342
|
+
const status = getStatusForPhase(phase);
|
|
343
|
+
if (status) {
|
|
344
|
+
assert.ok(!status.includes('-'), `Status ${status} should not contain hyphens`);
|
|
345
|
+
if (status.includes('_')) {
|
|
346
|
+
assert.ok(status.split('_').length > 1, `Multi-word status ${status} should use snake_case`);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
});
|
|
352
|
+
describe('Async Behavior and Breaking Changes', () => {
|
|
353
|
+
it('should handle updateFeatureStatusForPhase as async function', async () => {
|
|
354
|
+
mockGetFeature = async (featureId) => ({
|
|
355
|
+
...mockFeatureInfo,
|
|
356
|
+
id: featureId,
|
|
357
|
+
status: 'backlog',
|
|
358
|
+
});
|
|
359
|
+
mockCallMcpEndpoint = async () => ({ success: true });
|
|
360
|
+
// Verify the function returns a Promise
|
|
361
|
+
const resultPromise = updateFeatureStatusForPhase('test-feature-id', 'feature-analysis');
|
|
362
|
+
assert.ok(resultPromise instanceof Promise, 'updateFeatureStatusForPhase should return a Promise');
|
|
363
|
+
const result = await resultPromise;
|
|
364
|
+
assert.strictEqual(typeof result, 'boolean', 'Promise should resolve to boolean');
|
|
365
|
+
});
|
|
366
|
+
it('should handle concurrent status updates correctly', async () => {
|
|
367
|
+
let mcpCallCount = 0;
|
|
368
|
+
const mcpCalls = [];
|
|
369
|
+
mockGetFeature = async (featureId) => ({
|
|
370
|
+
...mockFeatureInfo,
|
|
371
|
+
id: featureId,
|
|
372
|
+
status: 'backlog',
|
|
373
|
+
});
|
|
374
|
+
mockCallMcpEndpoint = async (method, params) => {
|
|
375
|
+
mcpCallCount++;
|
|
376
|
+
mcpCalls.push({ method, params });
|
|
377
|
+
// Simulate some delay
|
|
378
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
379
|
+
return { success: true };
|
|
380
|
+
};
|
|
381
|
+
// Start multiple updates concurrently
|
|
382
|
+
const promises = [
|
|
383
|
+
updateFeatureStatusForPhase('feature-1', 'feature-analysis'),
|
|
384
|
+
updateFeatureStatusForPhase('feature-2', 'technical-design'),
|
|
385
|
+
updateFeatureStatusForPhase('feature-3', 'code-implementation'),
|
|
386
|
+
];
|
|
387
|
+
const results = await Promise.all(promises);
|
|
388
|
+
assert.strictEqual(results.length, 3, 'Should handle all concurrent updates');
|
|
389
|
+
assert.ok(results.every(r => r === true), 'All updates should succeed');
|
|
390
|
+
assert.strictEqual(mcpCallCount, 3, 'Should make correct number of MCP calls');
|
|
391
|
+
});
|
|
392
|
+
it('should maintain proper error isolation in concurrent updates', async () => {
|
|
393
|
+
let callCount = 0;
|
|
394
|
+
mockGetFeature = async (featureId) => {
|
|
395
|
+
callCount++;
|
|
396
|
+
if (featureId === 'failing-feature') {
|
|
397
|
+
throw new Error('Feature not found');
|
|
398
|
+
}
|
|
399
|
+
return {
|
|
400
|
+
...mockFeatureInfo,
|
|
401
|
+
id: featureId,
|
|
402
|
+
status: 'backlog',
|
|
403
|
+
};
|
|
404
|
+
};
|
|
405
|
+
mockCallMcpEndpoint = async () => ({ success: true });
|
|
406
|
+
// Mix successful and failing updates
|
|
407
|
+
const promises = [
|
|
408
|
+
updateFeatureStatusForPhase('feature-1', 'feature-analysis'),
|
|
409
|
+
updateFeatureStatusForPhase('failing-feature', 'technical-design'),
|
|
410
|
+
updateFeatureStatusForPhase('feature-3', 'code-implementation'),
|
|
411
|
+
];
|
|
412
|
+
const results = await Promise.all(promises);
|
|
413
|
+
assert.strictEqual(results[0], true, 'First update should succeed');
|
|
414
|
+
assert.strictEqual(results[1], false, 'Second update should fail');
|
|
415
|
+
assert.strictEqual(results[2], true, 'Third update should succeed');
|
|
416
|
+
});
|
|
417
|
+
});
|
|
418
|
+
describe('Error Handling and Edge Cases', () => {
|
|
419
|
+
it('should handle malformed feature data gracefully', async () => {
|
|
420
|
+
// Mock getFeature to return malformed data
|
|
421
|
+
mockGetFeature = async () => ({
|
|
422
|
+
id: 'test-feature',
|
|
423
|
+
// Missing required fields
|
|
424
|
+
status: null,
|
|
425
|
+
});
|
|
426
|
+
mockCallMcpEndpoint = async () => ({ success: true });
|
|
427
|
+
const result = await updateFeatureStatus({
|
|
428
|
+
featureId: 'test-feature-id',
|
|
429
|
+
status: 'ready_for_dev',
|
|
430
|
+
verbose: true,
|
|
431
|
+
});
|
|
432
|
+
assert.strictEqual(result, false, 'Should handle malformed feature data gracefully');
|
|
433
|
+
});
|
|
434
|
+
it('should handle network timeouts and retries', async () => {
|
|
435
|
+
let attemptCount = 0;
|
|
436
|
+
mockGetFeature = async (featureId) => ({
|
|
437
|
+
...mockFeatureInfo,
|
|
438
|
+
id: featureId,
|
|
439
|
+
status: 'backlog',
|
|
440
|
+
});
|
|
441
|
+
mockCallMcpEndpoint = async () => {
|
|
442
|
+
attemptCount++;
|
|
443
|
+
if (attemptCount < 2) {
|
|
444
|
+
throw new Error('Network timeout');
|
|
445
|
+
}
|
|
446
|
+
return { success: true };
|
|
447
|
+
};
|
|
448
|
+
const result = await updateFeatureStatus({
|
|
449
|
+
featureId: 'test-feature-id',
|
|
450
|
+
status: 'ready_for_dev',
|
|
451
|
+
});
|
|
452
|
+
// Since we don't implement retry logic, this should fail
|
|
453
|
+
assert.strictEqual(result, false, 'Should handle network errors');
|
|
454
|
+
assert.strictEqual(attemptCount, 1, 'Should only attempt once');
|
|
455
|
+
});
|
|
456
|
+
it('should handle very long feature IDs and status names', async () => {
|
|
457
|
+
const longFeatureId = 'a'.repeat(1000);
|
|
458
|
+
const validStatus = 'feature_analysis';
|
|
459
|
+
mockGetFeature = async (featureId) => ({
|
|
460
|
+
...mockFeatureInfo,
|
|
461
|
+
id: featureId,
|
|
462
|
+
status: 'backlog',
|
|
463
|
+
});
|
|
464
|
+
let receivedParams = null;
|
|
465
|
+
mockCallMcpEndpoint = async (method, params) => {
|
|
466
|
+
receivedParams = params;
|
|
467
|
+
return { success: true };
|
|
468
|
+
};
|
|
469
|
+
const result = await updateFeatureStatus({
|
|
470
|
+
featureId: longFeatureId,
|
|
471
|
+
status: validStatus,
|
|
472
|
+
});
|
|
473
|
+
assert.strictEqual(result, true, 'Should handle long feature IDs');
|
|
474
|
+
assert.strictEqual(receivedParams?.feature_id, longFeatureId, 'Should pass through long feature ID correctly');
|
|
475
|
+
});
|
|
476
|
+
it('should handle status transitions at workflow boundaries', async () => {
|
|
477
|
+
// Test transition from last testing status to deployment
|
|
478
|
+
mockGetFeature = async (featureId) => ({
|
|
479
|
+
...mockFeatureInfo,
|
|
480
|
+
id: featureId,
|
|
481
|
+
status: 'testing_passed',
|
|
482
|
+
});
|
|
483
|
+
let updateParams = null;
|
|
484
|
+
mockCallMcpEndpoint = async (method, params) => {
|
|
485
|
+
updateParams = params;
|
|
486
|
+
return { success: true };
|
|
487
|
+
};
|
|
488
|
+
const result = await updateFeatureStatus({
|
|
489
|
+
featureId: 'test-feature-id',
|
|
490
|
+
status: 'deployment',
|
|
491
|
+
verbose: true,
|
|
492
|
+
});
|
|
493
|
+
assert.strictEqual(result, true, 'Should allow progression from testing to deployment');
|
|
494
|
+
assert.strictEqual(updateParams.status, 'deployment', 'Should update to deployment status');
|
|
495
|
+
});
|
|
496
|
+
});
|
|
497
|
+
});
|
|
@@ -7,6 +7,7 @@ import { analyseTestCases } from '../../../phases/test-cases-analysis/index.js';
|
|
|
7
7
|
import { generateTechnicalDesign } from '../../../phases/technical-design/index.js';
|
|
8
8
|
import { planFeatureBranches } from '../../../phases/branch-planning/index.js';
|
|
9
9
|
import { splitFeatureIntoPRs } from '../../../phases/pr-splitting/index.js';
|
|
10
|
+
import { executeFeaturePRs } from '../../../phases/pr-execution/index.js';
|
|
10
11
|
import { implementFeatureCode } from '../../../phases/code-implementation/index.js';
|
|
11
12
|
import { runFunctionalTesting } from '../../../phases/functional-testing/analyzer.js';
|
|
12
13
|
import { writeCodeTests } from '../../../phases/code-testing/analyzer.js';
|
|
@@ -100,6 +101,10 @@ export const phaseConfigs = [
|
|
|
100
101
|
name: 'pr-splitting',
|
|
101
102
|
execute: splitFeatureIntoPRs,
|
|
102
103
|
},
|
|
104
|
+
{
|
|
105
|
+
name: 'pr-execution',
|
|
106
|
+
execute: executeFeaturePRs,
|
|
107
|
+
},
|
|
103
108
|
{
|
|
104
109
|
name: 'functional-testing',
|
|
105
110
|
execute: runFunctionalTesting,
|
|
@@ -4,5 +4,5 @@
|
|
|
4
4
|
import { EdsgerConfig } from '../../../types/index.js';
|
|
5
5
|
import { PipelinePhaseOptions, PipelineResult, PhaseConfig } from '../../../types/pipeline.js';
|
|
6
6
|
export declare const createPhaseRunner: (phaseConfig: PhaseConfig) => (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>;
|
|
7
|
-
declare const runFeatureAnalysisPhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>, runUserStoriesAnalysisPhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>, runTestCasesAnalysisPhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>, runTechnicalDesignPhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>, runBranchPlanningPhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>, runCodeImplementationPhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>, runPRSplittingPhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>, runFunctionalTestingPhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>, runCodeTestingPhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>, runCodeRefinePhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>, runCodeRefineVerificationPhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>, runCodeReviewPhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>, runAutonomousPhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>;
|
|
8
|
-
export { runFeatureAnalysisPhase, runUserStoriesAnalysisPhase, runTestCasesAnalysisPhase, runTechnicalDesignPhase, runBranchPlanningPhase, runCodeImplementationPhase, runPRSplittingPhase, runFunctionalTestingPhase, runCodeTestingPhase, runCodeRefinePhase, runCodeRefineVerificationPhase, runCodeReviewPhase, runAutonomousPhase, };
|
|
7
|
+
declare const runFeatureAnalysisPhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>, runUserStoriesAnalysisPhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>, runTestCasesAnalysisPhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>, runTechnicalDesignPhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>, runBranchPlanningPhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>, runCodeImplementationPhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>, runPRSplittingPhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>, runPRExecutionPhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>, runFunctionalTestingPhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>, runCodeTestingPhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>, runCodeRefinePhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>, runCodeRefineVerificationPhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>, runCodeReviewPhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>, runAutonomousPhase: (options: PipelinePhaseOptions, config: EdsgerConfig) => Promise<PipelineResult>;
|
|
8
|
+
export { runFeatureAnalysisPhase, runUserStoriesAnalysisPhase, runTestCasesAnalysisPhase, runTechnicalDesignPhase, runBranchPlanningPhase, runCodeImplementationPhase, runPRSplittingPhase, runPRExecutionPhase, runFunctionalTestingPhase, runCodeTestingPhase, runCodeRefinePhase, runCodeRefineVerificationPhase, runCodeReviewPhase, runAutonomousPhase, };
|
|
@@ -262,6 +262,6 @@ export const createPhaseRunner = (phaseConfig) => async (options, config) => {
|
|
|
262
262
|
}
|
|
263
263
|
};
|
|
264
264
|
// Create phase runners using the configuration
|
|
265
|
-
const [runFeatureAnalysisPhase, runUserStoriesAnalysisPhase, runTestCasesAnalysisPhase, runTechnicalDesignPhase, runBranchPlanningPhase, runCodeImplementationPhase, runPRSplittingPhase, runFunctionalTestingPhase, runCodeTestingPhase, runCodeRefinePhase, runCodeRefineVerificationPhase, runCodeReviewPhase, runAutonomousPhase,] = phaseConfigs.map(createPhaseRunner);
|
|
265
|
+
const [runFeatureAnalysisPhase, runUserStoriesAnalysisPhase, runTestCasesAnalysisPhase, runTechnicalDesignPhase, runBranchPlanningPhase, runCodeImplementationPhase, runPRSplittingPhase, runPRExecutionPhase, runFunctionalTestingPhase, runCodeTestingPhase, runCodeRefinePhase, runCodeRefineVerificationPhase, runCodeReviewPhase, runAutonomousPhase,] = phaseConfigs.map(createPhaseRunner);
|
|
266
266
|
// Export individual phase runners for granular control
|
|
267
|
-
export { runFeatureAnalysisPhase, runUserStoriesAnalysisPhase, runTestCasesAnalysisPhase, runTechnicalDesignPhase, runBranchPlanningPhase, runCodeImplementationPhase, runPRSplittingPhase, runFunctionalTestingPhase, runCodeTestingPhase, runCodeRefinePhase, runCodeRefineVerificationPhase, runCodeReviewPhase, runAutonomousPhase, };
|
|
267
|
+
export { runFeatureAnalysisPhase, runUserStoriesAnalysisPhase, runTestCasesAnalysisPhase, runTechnicalDesignPhase, runBranchPlanningPhase, runCodeImplementationPhase, runPRSplittingPhase, runPRExecutionPhase, runFunctionalTestingPhase, runCodeTestingPhase, runCodeRefinePhase, runCodeRefineVerificationPhase, runCodeReviewPhase, runAutonomousPhase, };
|
|
@@ -13,7 +13,7 @@ import { getFeature } from '../../api/features/get-feature.js';
|
|
|
13
13
|
import { markWorkflowPhaseCompleted } from '../../api/features/update-feature.js';
|
|
14
14
|
import { logError, logInfo, logWarning } from '../../utils/logger.js';
|
|
15
15
|
import { logPhaseResult } from '../../utils/pipeline-logger.js';
|
|
16
|
-
import { runFeatureAnalysisPhase, runUserStoriesAnalysisPhase, runTestCasesAnalysisPhase, runTechnicalDesignPhase, runBranchPlanningPhase, runCodeImplementationPhase, runFunctionalTestingPhase, runCodeRefinePhase, runCodeReviewPhase, runAutonomousPhase, } from './executors/phase-executor.js';
|
|
16
|
+
import { runFeatureAnalysisPhase, runUserStoriesAnalysisPhase, runTestCasesAnalysisPhase, runTechnicalDesignPhase, runBranchPlanningPhase, runCodeImplementationPhase, runPRSplittingPhase, runPRExecutionPhase, runFunctionalTestingPhase, runCodeRefinePhase, runCodeReviewPhase, runAutonomousPhase, } from './executors/phase-executor.js';
|
|
17
17
|
/**
|
|
18
18
|
* Map workflow phase names (underscore format) to phase runner functions
|
|
19
19
|
* Note: code_refine includes built-in verification loop (similar to technical_design)
|
|
@@ -25,6 +25,8 @@ const PHASE_RUNNERS = {
|
|
|
25
25
|
technical_design: runTechnicalDesignPhase,
|
|
26
26
|
branch_planning: runBranchPlanningPhase,
|
|
27
27
|
code_implementation: runCodeImplementationPhase,
|
|
28
|
+
pr_splitting: runPRSplittingPhase,
|
|
29
|
+
pr_execution: runPRExecutionPhase,
|
|
28
30
|
functional_testing: runFunctionalTestingPhase,
|
|
29
31
|
code_review: runCodeReviewPhase,
|
|
30
32
|
code_refine: runCodeRefinePhase,
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* Uses functional programming principles for composability
|
|
15
15
|
*/
|
|
16
16
|
import { updateFeatureStatusForPhase, markWorkflowPhaseCompleted, } from '../../api/features/index.js';
|
|
17
|
-
import { runFeatureAnalysisPhase, runUserStoriesAnalysisPhase, runTestCasesAnalysisPhase, runTechnicalDesignPhase, runCodeImplementationPhase, runPRSplittingPhase, runFunctionalTestingPhase, runCodeTestingPhase, runCodeReviewPhase, } from './executors/phase-executor.js';
|
|
17
|
+
import { runFeatureAnalysisPhase, runUserStoriesAnalysisPhase, runTestCasesAnalysisPhase, runTechnicalDesignPhase, runCodeImplementationPhase, runPRSplittingPhase, runPRExecutionPhase, runFunctionalTestingPhase, runCodeTestingPhase, runCodeReviewPhase, } from './executors/phase-executor.js';
|
|
18
18
|
import { logPipelineStart, logPhaseResult, logPipelineComplete, shouldContinuePipeline, } from '../../utils/pipeline-logger.js';
|
|
19
19
|
import { handleTestFailuresWithRetry } from '../../phases/functional-testing/test-retry-handler.js';
|
|
20
20
|
import { handleCodeRefineWithRetry } from '../../phases/code-refine/retry-handler.js';
|
|
@@ -83,6 +83,11 @@ export const runPipelineByMode = async (options, config, executionMode) => {
|
|
|
83
83
|
return await runOnlyPRSplitting(options, config);
|
|
84
84
|
case 'from_pr_splitting':
|
|
85
85
|
return await runFromPRSplitting(options, config);
|
|
86
|
+
// PR Execution
|
|
87
|
+
case 'only_pr_execution':
|
|
88
|
+
return await runOnlyPRExecution(options, config);
|
|
89
|
+
case 'from_pr_execution':
|
|
90
|
+
return await runFromPRExecution(options, config);
|
|
86
91
|
// Functional Testing
|
|
87
92
|
case 'only_functional_testing':
|
|
88
93
|
return await runOnlyFunctionalTesting(options, config);
|
|
@@ -328,18 +333,76 @@ const runOnlyPRSplitting = async (options, config) => {
|
|
|
328
333
|
};
|
|
329
334
|
/**
|
|
330
335
|
* Run from PR splitting to end
|
|
331
|
-
* Flow: pr-splitting →
|
|
336
|
+
* Flow: pr-splitting → (human review) → pr-execution → testing → ...
|
|
332
337
|
*/
|
|
333
338
|
const runFromPRSplitting = async (options, config) => {
|
|
334
339
|
const { featureId, verbose } = options;
|
|
335
340
|
logPipelineStart(featureId, verbose);
|
|
336
341
|
const results = [];
|
|
337
|
-
// 1. PR Splitting
|
|
342
|
+
// 1. PR Splitting (planning only)
|
|
338
343
|
const prSplittingResult = await runPRSplittingPhase(options, config);
|
|
339
344
|
results.push(await logAndMarkPhaseCompleted(prSplittingResult, verbose));
|
|
340
345
|
if (!shouldContinuePipeline(results)) {
|
|
341
346
|
return finalizePipelineExecution(options, results, verbose);
|
|
342
347
|
}
|
|
348
|
+
// 2. PR Execution (create branches, push, create GitHub PRs)
|
|
349
|
+
const prExecutionResult = await runPRExecutionPhase(options, config);
|
|
350
|
+
results.push(await logAndMarkPhaseCompleted(prExecutionResult, verbose));
|
|
351
|
+
if (!shouldContinuePipeline(results)) {
|
|
352
|
+
return finalizePipelineExecution(options, results, verbose);
|
|
353
|
+
}
|
|
354
|
+
// 3. Functional Testing with retry loop for bug fixes
|
|
355
|
+
const testingResult = await handleTestFailuresWithRetry({
|
|
356
|
+
options,
|
|
357
|
+
config,
|
|
358
|
+
results,
|
|
359
|
+
verbose,
|
|
360
|
+
});
|
|
361
|
+
const finalStatus = testingResult.status === 'success' ? 'testing_passed' : 'testing_failed';
|
|
362
|
+
await updateFeatureStatusForPhase(featureId, finalStatus === 'testing_passed' ? 'testing-passed' : 'testing-failed', verbose);
|
|
363
|
+
if (testingResult.status === 'success') {
|
|
364
|
+
const prCreated = await handlePullRequestCreation({
|
|
365
|
+
featureId,
|
|
366
|
+
results,
|
|
367
|
+
testingResult,
|
|
368
|
+
verbose,
|
|
369
|
+
});
|
|
370
|
+
if (prCreated) {
|
|
371
|
+
await continueWithCodeReviewAndRefine(options, config, results, verbose);
|
|
372
|
+
}
|
|
373
|
+
else {
|
|
374
|
+
if (verbose) {
|
|
375
|
+
logInfo('⚠️ Skipping code review and refine workflow: pull request creation failed');
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
return finalizePipelineExecution(options, results, verbose);
|
|
380
|
+
};
|
|
381
|
+
/**
|
|
382
|
+
* Run only PR execution phase
|
|
383
|
+
*/
|
|
384
|
+
const runOnlyPRExecution = async (options, config) => {
|
|
385
|
+
const { featureId, verbose } = options;
|
|
386
|
+
logPipelineStart(featureId, verbose);
|
|
387
|
+
const results = [];
|
|
388
|
+
const prExecutionResult = await runPRExecutionPhase(options, config);
|
|
389
|
+
results.push(await logAndMarkPhaseCompleted(prExecutionResult, verbose));
|
|
390
|
+
return finalizePipelineExecution(options, results, verbose);
|
|
391
|
+
};
|
|
392
|
+
/**
|
|
393
|
+
* Run from PR execution to end
|
|
394
|
+
* Flow: pr-execution → testing → code-review → code-refine
|
|
395
|
+
*/
|
|
396
|
+
const runFromPRExecution = async (options, config) => {
|
|
397
|
+
const { featureId, verbose } = options;
|
|
398
|
+
logPipelineStart(featureId, verbose);
|
|
399
|
+
const results = [];
|
|
400
|
+
// 1. PR Execution
|
|
401
|
+
const prExecutionResult = await runPRExecutionPhase(options, config);
|
|
402
|
+
results.push(await logAndMarkPhaseCompleted(prExecutionResult, verbose));
|
|
403
|
+
if (!shouldContinuePipeline(results)) {
|
|
404
|
+
return finalizePipelineExecution(options, results, verbose);
|
|
405
|
+
}
|
|
343
406
|
// 2. Functional Testing with retry loop for bug fixes
|
|
344
407
|
const testingResult = await handleTestFailuresWithRetry({
|
|
345
408
|
options,
|