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.
Files changed (129) hide show
  1. package/.claude/settings.local.json +28 -0
  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/config/phase-configs.js +5 -0
  8. package/dist/commands/workflow/executors/phase-executor.d.ts +2 -2
  9. package/dist/commands/workflow/executors/phase-executor.js +2 -2
  10. package/dist/commands/workflow/feature-coordinator.js +3 -1
  11. package/dist/commands/workflow/phase-orchestrator.js +66 -3
  12. package/dist/commands/workflow/pipeline-runner.d.ts +17 -0
  13. package/dist/commands/workflow/pipeline-runner.js +393 -0
  14. package/dist/commands/workflow/runner.d.ts +26 -0
  15. package/dist/commands/workflow/runner.js +119 -0
  16. package/dist/commands/workflow/workflow-runner.d.ts +26 -0
  17. package/dist/commands/workflow/workflow-runner.js +119 -0
  18. package/dist/config/feature-status.js +3 -0
  19. package/dist/index.js +0 -0
  20. package/dist/phases/code-implementation/analyzer-helpers.d.ts +28 -0
  21. package/dist/phases/code-implementation/analyzer-helpers.js +177 -0
  22. package/dist/phases/code-implementation/analyzer.d.ts +32 -0
  23. package/dist/phases/code-implementation/analyzer.js +629 -0
  24. package/dist/phases/code-implementation/context-fetcher.d.ts +17 -0
  25. package/dist/phases/code-implementation/context-fetcher.js +86 -0
  26. package/dist/phases/code-implementation/mcp-server.d.ts +1 -0
  27. package/dist/phases/code-implementation/mcp-server.js +93 -0
  28. package/dist/phases/code-implementation/prompts-improvement.d.ts +5 -0
  29. package/dist/phases/code-implementation/prompts-improvement.js +108 -0
  30. package/dist/phases/code-implementation-verification/verifier.d.ts +31 -0
  31. package/dist/phases/code-implementation-verification/verifier.js +196 -0
  32. package/dist/phases/code-refine/analyzer.d.ts +41 -0
  33. package/dist/phases/code-refine/analyzer.js +561 -0
  34. package/dist/phases/code-refine/context-fetcher.d.ts +94 -0
  35. package/dist/phases/code-refine/context-fetcher.js +423 -0
  36. package/dist/phases/code-refine-verification/analysis/llm-analyzer.d.ts +22 -0
  37. package/dist/phases/code-refine-verification/analysis/llm-analyzer.js +134 -0
  38. package/dist/phases/code-refine-verification/verifier.d.ts +47 -0
  39. package/dist/phases/code-refine-verification/verifier.js +597 -0
  40. package/dist/phases/code-review/analyzer.d.ts +29 -0
  41. package/dist/phases/code-review/analyzer.js +363 -0
  42. package/dist/phases/code-review/context-fetcher.d.ts +92 -0
  43. package/dist/phases/code-review/context-fetcher.js +296 -0
  44. package/dist/phases/feature-analysis/analyzer-helpers.d.ts +10 -0
  45. package/dist/phases/feature-analysis/analyzer-helpers.js +47 -0
  46. package/dist/phases/feature-analysis/analyzer.d.ts +11 -0
  47. package/dist/phases/feature-analysis/analyzer.js +208 -0
  48. package/dist/phases/feature-analysis/context-fetcher.d.ts +26 -0
  49. package/dist/phases/feature-analysis/context-fetcher.js +134 -0
  50. package/dist/phases/feature-analysis/http-fallback.d.ts +20 -0
  51. package/dist/phases/feature-analysis/http-fallback.js +95 -0
  52. package/dist/phases/feature-analysis/mcp-server.d.ts +1 -0
  53. package/dist/phases/feature-analysis/mcp-server.js +144 -0
  54. package/dist/phases/feature-analysis/prompts-improvement.d.ts +8 -0
  55. package/dist/phases/feature-analysis/prompts-improvement.js +109 -0
  56. package/dist/phases/feature-analysis-verification/verifier.d.ts +37 -0
  57. package/dist/phases/feature-analysis-verification/verifier.js +147 -0
  58. package/dist/phases/pr-execution/context.d.ts +26 -0
  59. package/dist/phases/pr-execution/context.js +156 -0
  60. package/dist/phases/pr-execution/index.d.ts +20 -0
  61. package/dist/phases/pr-execution/index.js +287 -0
  62. package/dist/phases/pr-execution/outcome.d.ts +26 -0
  63. package/dist/phases/pr-execution/outcome.js +34 -0
  64. package/dist/phases/pr-execution/pr-executor.d.ts +28 -0
  65. package/dist/phases/pr-execution/pr-executor.js +152 -0
  66. package/dist/phases/pr-execution/prompts.d.ts +17 -0
  67. package/dist/phases/pr-execution/prompts.js +208 -0
  68. package/dist/phases/pr-splitting/context.d.ts +16 -2
  69. package/dist/phases/pr-splitting/context.js +127 -4
  70. package/dist/phases/pr-splitting/index.d.ts +7 -0
  71. package/dist/phases/pr-splitting/index.js +58 -52
  72. package/dist/phases/pr-splitting/prompts.d.ts +4 -4
  73. package/dist/phases/pr-splitting/prompts.js +42 -30
  74. package/dist/phases/technical-design/analyzer-helpers.d.ts +25 -0
  75. package/dist/phases/technical-design/analyzer-helpers.js +39 -0
  76. package/dist/phases/technical-design/analyzer.d.ts +21 -0
  77. package/dist/phases/technical-design/analyzer.js +461 -0
  78. package/dist/phases/technical-design/context-fetcher.d.ts +12 -0
  79. package/dist/phases/technical-design/context-fetcher.js +39 -0
  80. package/dist/phases/technical-design/http-fallback.d.ts +17 -0
  81. package/dist/phases/technical-design/http-fallback.js +151 -0
  82. package/dist/phases/technical-design/mcp-server.d.ts +1 -0
  83. package/dist/phases/technical-design/mcp-server.js +157 -0
  84. package/dist/phases/technical-design/prompts-improvement.d.ts +5 -0
  85. package/dist/phases/technical-design/prompts-improvement.js +93 -0
  86. package/dist/phases/technical-design-verification/verifier.d.ts +53 -0
  87. package/dist/phases/technical-design-verification/verifier.js +170 -0
  88. package/dist/services/audit-logs.d.ts +2 -2
  89. package/dist/services/feature-branches.d.ts +77 -0
  90. package/dist/services/feature-branches.js +205 -0
  91. package/dist/types/index.d.ts +1 -1
  92. package/dist/types/pipeline.d.ts +1 -1
  93. package/dist/utils/github-repo-info.d.ts +14 -0
  94. package/dist/utils/github-repo-info.js +19 -0
  95. package/dist/workflow-runner/config/phase-configs.d.ts +5 -0
  96. package/dist/workflow-runner/config/phase-configs.js +120 -0
  97. package/dist/workflow-runner/core/feature-filter.d.ts +16 -0
  98. package/dist/workflow-runner/core/feature-filter.js +46 -0
  99. package/dist/workflow-runner/core/index.d.ts +8 -0
  100. package/dist/workflow-runner/core/index.js +12 -0
  101. package/dist/workflow-runner/core/pipeline-evaluator.d.ts +24 -0
  102. package/dist/workflow-runner/core/pipeline-evaluator.js +32 -0
  103. package/dist/workflow-runner/core/state-manager.d.ts +24 -0
  104. package/dist/workflow-runner/core/state-manager.js +42 -0
  105. package/dist/workflow-runner/core/workflow-logger.d.ts +20 -0
  106. package/dist/workflow-runner/core/workflow-logger.js +65 -0
  107. package/dist/workflow-runner/executors/phase-executor.d.ts +8 -0
  108. package/dist/workflow-runner/executors/phase-executor.js +248 -0
  109. package/dist/workflow-runner/feature-workflow-runner.d.ts +26 -0
  110. package/dist/workflow-runner/feature-workflow-runner.js +119 -0
  111. package/dist/workflow-runner/index.d.ts +2 -0
  112. package/dist/workflow-runner/index.js +2 -0
  113. package/dist/workflow-runner/pipeline-runner.d.ts +17 -0
  114. package/dist/workflow-runner/pipeline-runner.js +393 -0
  115. package/dist/workflow-runner/workflow-processor.d.ts +54 -0
  116. package/dist/workflow-runner/workflow-processor.js +170 -0
  117. package/package.json +1 -1
  118. package/dist/services/lifecycle-agent/__tests__/phase-criteria.test.d.ts +0 -4
  119. package/dist/services/lifecycle-agent/__tests__/phase-criteria.test.js +0 -133
  120. package/dist/services/lifecycle-agent/__tests__/transition-rules.test.d.ts +0 -4
  121. package/dist/services/lifecycle-agent/__tests__/transition-rules.test.js +0 -336
  122. package/dist/services/lifecycle-agent/index.d.ts +0 -24
  123. package/dist/services/lifecycle-agent/index.js +0 -25
  124. package/dist/services/lifecycle-agent/phase-criteria.d.ts +0 -57
  125. package/dist/services/lifecycle-agent/phase-criteria.js +0 -335
  126. package/dist/services/lifecycle-agent/transition-rules.d.ts +0 -60
  127. package/dist/services/lifecycle-agent/transition-rules.js +0 -184
  128. package/dist/services/lifecycle-agent/types.d.ts +0 -190
  129. 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 → functional-testing...code-reviewcode-refine
336
+ * Flow: pr-splitting → (human review) pr-executiontesting...
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,