edsger 0.51.0 → 0.53.0

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 (188) hide show
  1. package/.claude/settings.local.json +23 -3
  2. package/.env.local +12 -0
  3. package/dist/commands/find-smells/index.d.ts +21 -0
  4. package/dist/commands/find-smells/index.js +65 -0
  5. package/dist/index.js +29 -0
  6. package/dist/phases/find-bugs/index.js +7 -92
  7. package/dist/phases/find-bugs/state.d.ts +10 -35
  8. package/dist/phases/find-bugs/state.js +12 -120
  9. package/dist/phases/find-features/index.js +16 -83
  10. package/dist/phases/find-features/prompts.d.ts +7 -1
  11. package/dist/phases/find-features/prompts.js +31 -11
  12. package/dist/phases/find-features/state.d.ts +15 -19
  13. package/dist/phases/find-features/state.js +17 -89
  14. package/dist/phases/find-features/types.d.ts +1 -1
  15. package/dist/phases/find-shared/git.d.ts +24 -0
  16. package/dist/phases/find-shared/git.js +60 -0
  17. package/dist/phases/find-shared/mcp.d.ts +33 -0
  18. package/dist/phases/find-shared/mcp.js +69 -0
  19. package/dist/phases/find-shared/scan-state.d.ts +33 -0
  20. package/dist/phases/find-shared/scan-state.js +112 -0
  21. package/dist/phases/find-smells/index.d.ts +47 -0
  22. package/dist/phases/find-smells/index.js +278 -0
  23. package/dist/phases/find-smells/prompts.d.ts +30 -0
  24. package/dist/phases/find-smells/prompts.js +129 -0
  25. package/dist/phases/find-smells/state.d.ts +21 -0
  26. package/dist/phases/find-smells/state.js +17 -0
  27. package/dist/phases/find-smells/types.d.ts +51 -0
  28. package/dist/phases/find-smells/types.js +64 -0
  29. package/dist/phases/pr-execution/context.js +40 -32
  30. package/dist/phases/pr-splitting/context.js +18 -13
  31. package/dist/utils/github-repo-info.d.ts +13 -1
  32. package/dist/utils/github-repo-info.js +32 -6
  33. package/package.json +1 -1
  34. package/vitest.config.ts +2 -0
  35. package/dist/api/__tests__/app-store.test.d.ts +0 -7
  36. package/dist/api/__tests__/app-store.test.js +0 -60
  37. package/dist/api/__tests__/intelligence.test.d.ts +0 -11
  38. package/dist/api/__tests__/intelligence.test.js +0 -315
  39. package/dist/api/features/__tests__/feature-utils.test.d.ts +0 -4
  40. package/dist/api/features/__tests__/feature-utils.test.js +0 -370
  41. package/dist/api/features/__tests__/status-updater.test.d.ts +0 -4
  42. package/dist/api/features/__tests__/status-updater.test.js +0 -88
  43. package/dist/api/features/approval-checker.d.ts +0 -20
  44. package/dist/api/features/approval-checker.js +0 -152
  45. package/dist/api/features/batch-operations.d.ts +0 -17
  46. package/dist/api/features/batch-operations.js +0 -100
  47. package/dist/api/features/feature-utils.d.ts +0 -23
  48. package/dist/api/features/feature-utils.js +0 -80
  49. package/dist/api/features/get-feature.d.ts +0 -5
  50. package/dist/api/features/get-feature.js +0 -21
  51. package/dist/api/features/index.d.ts +0 -8
  52. package/dist/api/features/index.js +0 -10
  53. package/dist/api/features/status-updater.d.ts +0 -41
  54. package/dist/api/features/status-updater.js +0 -122
  55. package/dist/api/features/test-cases.d.ts +0 -29
  56. package/dist/api/features/test-cases.js +0 -110
  57. package/dist/api/features/update-feature.d.ts +0 -20
  58. package/dist/api/features/update-feature.js +0 -83
  59. package/dist/api/features/user-stories.d.ts +0 -21
  60. package/dist/api/features/user-stories.js +0 -88
  61. package/dist/commands/agent-workflow/feature-worker.d.ts +0 -14
  62. package/dist/commands/agent-workflow/feature-worker.js +0 -65
  63. package/dist/commands/build/__tests__/build.test.d.ts +0 -5
  64. package/dist/commands/build/__tests__/build.test.js +0 -206
  65. package/dist/commands/build/__tests__/detect-project.test.d.ts +0 -6
  66. package/dist/commands/build/__tests__/detect-project.test.js +0 -160
  67. package/dist/commands/build/__tests__/run-build.test.d.ts +0 -6
  68. package/dist/commands/build/__tests__/run-build.test.js +0 -433
  69. package/dist/commands/intelligence/__tests__/command.test.d.ts +0 -4
  70. package/dist/commands/intelligence/__tests__/command.test.js +0 -48
  71. package/dist/commands/workflow/core/__tests__/feature-filter.test.d.ts +0 -5
  72. package/dist/commands/workflow/core/__tests__/feature-filter.test.js +0 -316
  73. package/dist/commands/workflow/core/__tests__/pipeline-evaluator.test.d.ts +0 -4
  74. package/dist/commands/workflow/core/__tests__/pipeline-evaluator.test.js +0 -397
  75. package/dist/commands/workflow/core/__tests__/state-manager.test.d.ts +0 -4
  76. package/dist/commands/workflow/core/__tests__/state-manager.test.js +0 -384
  77. package/dist/commands/workflow/core/feature-filter.d.ts +0 -16
  78. package/dist/commands/workflow/core/feature-filter.js +0 -47
  79. package/dist/commands/workflow/feature-coordinator.d.ts +0 -18
  80. package/dist/commands/workflow/feature-coordinator.js +0 -161
  81. package/dist/config/__tests__/config.test.d.ts +0 -4
  82. package/dist/config/__tests__/config.test.js +0 -286
  83. package/dist/config/__tests__/feature-status.test.d.ts +0 -4
  84. package/dist/config/__tests__/feature-status.test.js +0 -111
  85. package/dist/config/feature-status.d.ts +0 -56
  86. package/dist/config/feature-status.js +0 -130
  87. package/dist/errors/__tests__/index.test.d.ts +0 -4
  88. package/dist/errors/__tests__/index.test.js +0 -349
  89. package/dist/phases/app-store-generation/__tests__/agent.test.d.ts +0 -5
  90. package/dist/phases/app-store-generation/__tests__/agent.test.js +0 -142
  91. package/dist/phases/app-store-generation/__tests__/context.test.d.ts +0 -4
  92. package/dist/phases/app-store-generation/__tests__/context.test.js +0 -284
  93. package/dist/phases/app-store-generation/__tests__/prompts.test.d.ts +0 -4
  94. package/dist/phases/app-store-generation/__tests__/prompts.test.js +0 -122
  95. package/dist/phases/app-store-generation/__tests__/screenshot-composer.test.d.ts +0 -5
  96. package/dist/phases/app-store-generation/__tests__/screenshot-composer.test.js +0 -826
  97. package/dist/phases/code-review/__tests__/diff-utils.test.d.ts +0 -1
  98. package/dist/phases/code-review/__tests__/diff-utils.test.js +0 -101
  99. package/dist/phases/feature-analysis/agent.d.ts +0 -13
  100. package/dist/phases/feature-analysis/agent.js +0 -112
  101. package/dist/phases/feature-analysis/context.d.ts +0 -24
  102. package/dist/phases/feature-analysis/context.js +0 -138
  103. package/dist/phases/feature-analysis/index.d.ts +0 -8
  104. package/dist/phases/feature-analysis/index.js +0 -199
  105. package/dist/phases/feature-analysis/outcome.d.ts +0 -40
  106. package/dist/phases/feature-analysis/outcome.js +0 -280
  107. package/dist/phases/feature-analysis/prompts.d.ts +0 -10
  108. package/dist/phases/feature-analysis/prompts.js +0 -212
  109. package/dist/phases/feature-analysis-verification/agent.d.ts +0 -33
  110. package/dist/phases/feature-analysis-verification/agent.js +0 -124
  111. package/dist/phases/feature-analysis-verification/index.d.ts +0 -25
  112. package/dist/phases/feature-analysis-verification/index.js +0 -92
  113. package/dist/phases/feature-analysis-verification/prompts.d.ts +0 -10
  114. package/dist/phases/feature-analysis-verification/prompts.js +0 -100
  115. package/dist/phases/intelligence-analysis/__tests__/context.test.d.ts +0 -4
  116. package/dist/phases/intelligence-analysis/__tests__/context.test.js +0 -192
  117. package/dist/phases/intelligence-analysis/__tests__/matching.test.d.ts +0 -13
  118. package/dist/phases/intelligence-analysis/__tests__/matching.test.js +0 -154
  119. package/dist/phases/intelligence-analysis/__tests__/orchestration.test.d.ts +0 -5
  120. package/dist/phases/intelligence-analysis/__tests__/orchestration.test.js +0 -378
  121. package/dist/phases/intelligence-analysis/__tests__/prompts.test.d.ts +0 -4
  122. package/dist/phases/intelligence-analysis/__tests__/prompts.test.js +0 -33
  123. package/dist/phases/pr-execution/__tests__/file-assigner.test.d.ts +0 -1
  124. package/dist/phases/pr-execution/__tests__/file-assigner.test.js +0 -303
  125. package/dist/phases/pr-resolve/__tests__/checklist-learner.test.d.ts +0 -1
  126. package/dist/phases/pr-resolve/__tests__/checklist-learner.test.js +0 -157
  127. package/dist/phases/pr-resolve/__tests__/prompts.test.d.ts +0 -1
  128. package/dist/phases/pr-resolve/__tests__/prompts.test.js +0 -116
  129. package/dist/phases/pr-resolve/__tests__/resolve-mapping.test.d.ts +0 -1
  130. package/dist/phases/pr-resolve/__tests__/resolve-mapping.test.js +0 -138
  131. package/dist/phases/pr-resolve/__tests__/types.test.d.ts +0 -1
  132. package/dist/phases/pr-resolve/__tests__/types.test.js +0 -43
  133. package/dist/phases/pr-resolve/__tests__/workspace.test.d.ts +0 -1
  134. package/dist/phases/pr-resolve/__tests__/workspace.test.js +0 -111
  135. package/dist/phases/pr-review/__tests__/prompts.test.d.ts +0 -1
  136. package/dist/phases/pr-review/__tests__/prompts.test.js +0 -49
  137. package/dist/phases/pr-review/__tests__/review-comments.test.d.ts +0 -1
  138. package/dist/phases/pr-review/__tests__/review-comments.test.js +0 -110
  139. package/dist/phases/pr-shared/__tests__/agent-utils.test.d.ts +0 -1
  140. package/dist/phases/pr-shared/__tests__/agent-utils.test.js +0 -91
  141. package/dist/phases/pr-shared/__tests__/context.test.d.ts +0 -1
  142. package/dist/phases/pr-shared/__tests__/context.test.js +0 -94
  143. package/dist/phases/pr-splitting/__tests__/import-dep-validator.test.d.ts +0 -1
  144. package/dist/phases/pr-splitting/__tests__/import-dep-validator.test.js +0 -331
  145. package/dist/phases/run-sheet/render.d.ts +0 -60
  146. package/dist/phases/run-sheet/render.js +0 -297
  147. package/dist/phases/smoke-test/__tests__/agent.test.d.ts +0 -4
  148. package/dist/phases/smoke-test/__tests__/agent.test.js +0 -84
  149. package/dist/phases/smoke-test/__tests__/github.test.d.ts +0 -9
  150. package/dist/phases/smoke-test/__tests__/github.test.js +0 -120
  151. package/dist/phases/smoke-test/__tests__/snapshot.test.d.ts +0 -8
  152. package/dist/phases/smoke-test/__tests__/snapshot.test.js +0 -93
  153. package/dist/phases/smoke-test/github.d.ts +0 -54
  154. package/dist/phases/smoke-test/github.js +0 -101
  155. package/dist/phases/smoke-test/snapshot.d.ts +0 -27
  156. package/dist/phases/smoke-test/snapshot.js +0 -157
  157. package/dist/services/coaching/__tests__/coaching-agent.test.d.ts +0 -1
  158. package/dist/services/coaching/__tests__/coaching-agent.test.js +0 -74
  159. package/dist/services/coaching/__tests__/coaching-loop.test.d.ts +0 -1
  160. package/dist/services/coaching/__tests__/coaching-loop.test.js +0 -59
  161. package/dist/services/coaching/__tests__/self-rating.test.d.ts +0 -1
  162. package/dist/services/coaching/__tests__/self-rating.test.js +0 -188
  163. package/dist/services/lifecycle-agent/__tests__/phase-criteria.test.d.ts +0 -4
  164. package/dist/services/lifecycle-agent/__tests__/phase-criteria.test.js +0 -133
  165. package/dist/services/lifecycle-agent/__tests__/transition-rules.test.d.ts +0 -4
  166. package/dist/services/lifecycle-agent/__tests__/transition-rules.test.js +0 -336
  167. package/dist/services/lifecycle-agent/index.d.ts +0 -24
  168. package/dist/services/lifecycle-agent/index.js +0 -25
  169. package/dist/services/lifecycle-agent/phase-criteria.d.ts +0 -57
  170. package/dist/services/lifecycle-agent/phase-criteria.js +0 -335
  171. package/dist/services/lifecycle-agent/transition-rules.d.ts +0 -60
  172. package/dist/services/lifecycle-agent/transition-rules.js +0 -184
  173. package/dist/services/lifecycle-agent/types.d.ts +0 -190
  174. package/dist/services/lifecycle-agent/types.js +0 -12
  175. package/dist/services/phase-hooks/__tests__/bindings-fetcher.test.d.ts +0 -1
  176. package/dist/services/phase-hooks/__tests__/bindings-fetcher.test.js +0 -122
  177. package/dist/services/phase-hooks/__tests__/hook-executor.test.d.ts +0 -1
  178. package/dist/services/phase-hooks/__tests__/hook-executor.test.js +0 -321
  179. package/dist/services/phase-hooks/__tests__/hook-runner.test.d.ts +0 -1
  180. package/dist/services/phase-hooks/__tests__/hook-runner.test.js +0 -261
  181. package/dist/services/phase-hooks/__tests__/plugin-loader.test.d.ts +0 -1
  182. package/dist/services/phase-hooks/__tests__/plugin-loader.test.js +0 -158
  183. package/dist/services/video/__tests__/video-pipeline.test.d.ts +0 -6
  184. package/dist/services/video/__tests__/video-pipeline.test.js +0 -249
  185. package/dist/types/features.d.ts +0 -35
  186. package/dist/types/features.js +0 -1
  187. package/dist/workspace/__tests__/workspace-manager.test.d.ts +0 -7
  188. package/dist/workspace/__tests__/workspace-manager.test.js +0 -52
@@ -1,370 +0,0 @@
1
- /**
2
- * Unit tests for feature utility functions
3
- */
4
- import assert from 'node:assert';
5
- import { describe, it } from 'node:test';
6
- import { filterFeaturesByStatus, sortFeaturesByUpdatedAt, } from '../feature-utils.js';
7
- void describe('Feature Utils', () => {
8
- void describe('sortFeaturesByUpdatedAt', () => {
9
- void it('should sort features by updated_at in ascending order (oldest first)', () => {
10
- const features = [
11
- {
12
- id: '1',
13
- name: 'Feature 1',
14
- status: 'ready_for_ai',
15
- product_id: 'test-product',
16
- updated_at: '2024-01-15T10:00:00Z',
17
- },
18
- {
19
- id: '2',
20
- name: 'Feature 2',
21
- status: 'ready_for_ai',
22
- product_id: 'test-product',
23
- updated_at: '2024-01-10T10:00:00Z',
24
- },
25
- {
26
- id: '3',
27
- name: 'Feature 3',
28
- status: 'ready_for_ai',
29
- product_id: 'test-product',
30
- updated_at: '2024-01-20T10:00:00Z',
31
- },
32
- ];
33
- const sorted = sortFeaturesByUpdatedAt(features);
34
- // Should be sorted oldest first: Feature 2, Feature 1, Feature 3
35
- assert.strictEqual(sorted[0].id, '2', 'First should be Feature 2 (oldest)');
36
- assert.strictEqual(sorted[1].id, '1', 'Second should be Feature 1');
37
- assert.strictEqual(sorted[2].id, '3', 'Third should be Feature 3 (newest)');
38
- });
39
- void it('should handle features with missing updated_at dates', () => {
40
- const features = [
41
- {
42
- id: '1',
43
- name: 'Feature 1',
44
- status: 'ready_for_ai',
45
- product_id: 'test-product',
46
- updated_at: '2024-01-15T10:00:00Z',
47
- },
48
- {
49
- id: '2',
50
- name: 'Feature 2',
51
- status: 'ready_for_ai',
52
- product_id: 'test-product',
53
- // Missing updated_at
54
- },
55
- {
56
- id: '3',
57
- name: 'Feature 3',
58
- status: 'ready_for_ai',
59
- product_id: 'test-product',
60
- updated_at: '2024-01-10T10:00:00Z',
61
- },
62
- ];
63
- const sorted = sortFeaturesByUpdatedAt(features);
64
- // Feature without updated_at should be treated as epoch (1970-01-01)
65
- // and sorted first
66
- assert.strictEqual(sorted[0].id, '2', 'Feature without date should come first');
67
- assert.strictEqual(sorted[1].id, '3', 'Feature 3 should be second (older date)');
68
- assert.strictEqual(sorted[2].id, '1', 'Feature 1 should be third (newer date)');
69
- });
70
- void it('should handle empty array', () => {
71
- const features = [];
72
- const sorted = sortFeaturesByUpdatedAt(features);
73
- assert.strictEqual(sorted.length, 0, 'Empty array should remain empty');
74
- });
75
- void it('should handle single feature', () => {
76
- const features = [
77
- {
78
- id: '1',
79
- name: 'Feature 1',
80
- status: 'ready_for_ai',
81
- product_id: 'test-product',
82
- updated_at: '2024-01-15T10:00:00Z',
83
- },
84
- ];
85
- const sorted = sortFeaturesByUpdatedAt(features);
86
- assert.strictEqual(sorted.length, 1, 'Should return single feature');
87
- assert.strictEqual(sorted[0].id, '1', 'Should be the same feature');
88
- });
89
- void it('should create a new array and not mutate the original', () => {
90
- const features = [
91
- {
92
- id: '1',
93
- name: 'Feature 1',
94
- status: 'ready_for_ai',
95
- product_id: 'test-product',
96
- updated_at: '2024-01-15T10:00:00Z',
97
- },
98
- {
99
- id: '2',
100
- name: 'Feature 2',
101
- status: 'ready_for_ai',
102
- product_id: 'test-product',
103
- updated_at: '2024-01-10T10:00:00Z',
104
- },
105
- ];
106
- const originalOrder = features.map((f) => f.id);
107
- const sorted = sortFeaturesByUpdatedAt(features);
108
- const originalAfterSort = features.map((f) => f.id);
109
- // Original array should be unchanged
110
- assert.deepStrictEqual(originalAfterSort, originalOrder, 'Original array should not be mutated');
111
- // Sorted array should be different
112
- assert.notDeepStrictEqual(sorted.map((f) => f.id), originalOrder, 'Sorted array should have different order');
113
- });
114
- void it('should handle features with same updated_at timestamps consistently', () => {
115
- const sameTimestamp = '2024-01-15T10:00:00Z';
116
- const features = [
117
- {
118
- id: '1',
119
- name: 'Feature 1',
120
- status: 'ready_for_ai',
121
- product_id: 'test-product',
122
- updated_at: sameTimestamp,
123
- },
124
- {
125
- id: '2',
126
- name: 'Feature 2',
127
- status: 'ready_for_ai',
128
- product_id: 'test-product',
129
- updated_at: sameTimestamp,
130
- },
131
- {
132
- id: '3',
133
- name: 'Feature 3',
134
- status: 'ready_for_ai',
135
- product_id: 'test-product',
136
- updated_at: sameTimestamp,
137
- },
138
- ];
139
- const sorted = sortFeaturesByUpdatedAt(features);
140
- assert.strictEqual(sorted.length, 3, 'Should return all features');
141
- // Order should be stable (same as original for equal timestamps)
142
- // Note: JavaScript sort is stable in modern engines
143
- });
144
- void it('should handle various date formats correctly', () => {
145
- const features = [
146
- {
147
- id: '1',
148
- name: 'Feature 1',
149
- status: 'ready_for_ai',
150
- product_id: 'test-product',
151
- updated_at: '2024-01-15', // Date only
152
- },
153
- {
154
- id: '2',
155
- name: 'Feature 2',
156
- status: 'ready_for_ai',
157
- product_id: 'test-product',
158
- updated_at: '2024-01-10T10:00:00.000Z', // Full ISO format
159
- },
160
- {
161
- id: '3',
162
- name: 'Feature 3',
163
- status: 'ready_for_ai',
164
- product_id: 'test-product',
165
- updated_at: '2024-01-20T15:30:00Z', // ISO with time
166
- },
167
- ];
168
- const sorted = sortFeaturesByUpdatedAt(features);
169
- // Should handle different date formats and sort correctly
170
- assert.strictEqual(sorted[0].id, '2', 'Feature 2 should be first (oldest)');
171
- assert.strictEqual(sorted[1].id, '1', 'Feature 1 should be second');
172
- assert.strictEqual(sorted[2].id, '3', 'Feature 3 should be third (newest)');
173
- });
174
- });
175
- void describe('filterFeaturesByStatus', () => {
176
- void it('should filter features by exact status match', () => {
177
- const features = [
178
- {
179
- id: '1',
180
- name: 'Feature 1',
181
- status: 'ready_for_ai',
182
- product_id: 'test-product',
183
- },
184
- {
185
- id: '2',
186
- name: 'Feature 2',
187
- status: 'feature_analysis',
188
- product_id: 'test-product',
189
- },
190
- {
191
- id: '3',
192
- name: 'Feature 3',
193
- status: 'ready_for_ai',
194
- product_id: 'test-product',
195
- },
196
- ];
197
- const filtered = filterFeaturesByStatus(features, 'ready_for_ai');
198
- assert.strictEqual(filtered.length, 2, 'Should return 2 ready_for_ai features');
199
- assert.strictEqual(filtered[0].id, '1', 'Should include Feature 1');
200
- assert.strictEqual(filtered[1].id, '3', 'Should include Feature 3');
201
- });
202
- void it('should return empty array when no features match status', () => {
203
- const features = [
204
- {
205
- id: '1',
206
- name: 'Feature 1',
207
- status: 'feature_analysis',
208
- product_id: 'test-product',
209
- },
210
- {
211
- id: '2',
212
- name: 'Feature 2',
213
- status: 'code_implementation',
214
- product_id: 'test-product',
215
- },
216
- ];
217
- const filtered = filterFeaturesByStatus(features, 'ready_for_ai');
218
- assert.strictEqual(filtered.length, 0, 'Should return empty array');
219
- });
220
- void it('should handle empty input array', () => {
221
- const features = [];
222
- const filtered = filterFeaturesByStatus(features, 'ready_for_ai');
223
- assert.strictEqual(filtered.length, 0, 'Should return empty array');
224
- });
225
- });
226
- void describe('Integration Tests - Workflow Processing Order', () => {
227
- void it('should process features in oldest-first order for workflow execution', () => {
228
- // This test simulates the expected behavior for workflow processing
229
- const simulatedWorkflowFeatures = [
230
- {
231
- id: 'urgent-feature',
232
- name: 'Urgent Feature',
233
- status: 'ready_for_ai',
234
- product_id: 'test-product',
235
- updated_at: '2024-01-01T08:00:00Z', // Very old - should be processed first
236
- },
237
- {
238
- id: 'recent-feature',
239
- name: 'Recent Feature',
240
- status: 'ready_for_ai',
241
- product_id: 'test-product',
242
- updated_at: '2024-01-20T10:00:00Z', // Recent - should be processed last
243
- },
244
- {
245
- id: 'medium-feature',
246
- name: 'Medium Feature',
247
- status: 'ready_for_ai',
248
- product_id: 'test-product',
249
- updated_at: '2024-01-10T12:00:00Z', // Medium age - should be processed second
250
- },
251
- ];
252
- const processingOrder = sortFeaturesByUpdatedAt(simulatedWorkflowFeatures);
253
- // Verify processing order matches expected workflow behavior
254
- assert.strictEqual(processingOrder[0].id, 'urgent-feature', 'Oldest feature should be processed first in workflow');
255
- assert.strictEqual(processingOrder[1].id, 'medium-feature', 'Medium-age feature should be processed second in workflow');
256
- assert.strictEqual(processingOrder[2].id, 'recent-feature', 'Newest feature should be processed last in workflow');
257
- // Verify timestamps are in correct ascending order
258
- const timestamps = processingOrder.map((f) => new Date(f.updated_at || 0).getTime());
259
- for (let i = 1; i < timestamps.length; i++) {
260
- assert.ok(timestamps[i] >= timestamps[i - 1], `Feature ${i} should have timestamp >= feature ${i - 1}`);
261
- }
262
- });
263
- void it('should maintain consistent ordering across multiple calls', () => {
264
- const features = [
265
- {
266
- id: '1',
267
- name: 'Feature 1',
268
- status: 'ready_for_ai',
269
- product_id: 'test-product',
270
- updated_at: '2024-01-15T10:00:00Z',
271
- },
272
- {
273
- id: '2',
274
- name: 'Feature 2',
275
- status: 'ready_for_ai',
276
- product_id: 'test-product',
277
- updated_at: '2024-01-10T10:00:00Z',
278
- },
279
- {
280
- id: '3',
281
- name: 'Feature 3',
282
- status: 'ready_for_ai',
283
- product_id: 'test-product',
284
- updated_at: '2024-01-20T10:00:00Z',
285
- },
286
- ];
287
- // Sort multiple times to ensure consistency
288
- const sorted1 = sortFeaturesByUpdatedAt(features);
289
- const sorted2 = sortFeaturesByUpdatedAt(features);
290
- const sorted3 = sortFeaturesByUpdatedAt(features);
291
- // All should produce the same order
292
- assert.deepStrictEqual(sorted1.map((f) => f.id), sorted2.map((f) => f.id), 'First and second sort should produce same order');
293
- assert.deepStrictEqual(sorted2.map((f) => f.id), sorted3.map((f) => f.id), 'Second and third sort should produce same order');
294
- });
295
- void it('should verify oldest-first ordering is critical for fair workflow processing', () => {
296
- // Test that demonstrates why oldest-first ordering is important
297
- const featuresWithVariousAges = [
298
- {
299
- id: 'very-old-bug',
300
- name: 'Critical Bug Fix',
301
- status: 'ready_for_ai',
302
- product_id: 'test-product',
303
- updated_at: '2024-01-01T00:00:00Z', // Very old, should be highest priority
304
- },
305
- {
306
- id: 'new-enhancement',
307
- name: 'Nice to Have Feature',
308
- status: 'ready_for_ai',
309
- product_id: 'test-product',
310
- updated_at: '2024-01-25T00:00:00Z', // Recent, should be lowest priority
311
- },
312
- {
313
- id: 'medium-priority',
314
- name: 'Important Feature',
315
- status: 'ready_for_ai',
316
- product_id: 'test-product',
317
- updated_at: '2024-01-15T00:00:00Z', // Medium age, should be middle priority
318
- },
319
- ];
320
- const orderedFeatures = sortFeaturesByUpdatedAt(featuresWithVariousAges);
321
- // The oldest feature (potentially a bug that's been waiting) should be processed first
322
- assert.strictEqual(orderedFeatures[0].id, 'very-old-bug');
323
- assert.strictEqual(orderedFeatures[1].id, 'medium-priority');
324
- assert.strictEqual(orderedFeatures[2].id, 'new-enhancement');
325
- // Verify the business logic: older items get processed before newer ones
326
- // This ensures fairness and prevents newer features from always jumping ahead
327
- const ages = orderedFeatures.map((f) => new Date(f.updated_at ?? '').getTime());
328
- assert.ok(ages[0] <= ages[1] && ages[1] <= ages[2], 'Features should be processed in age order (oldest first)');
329
- });
330
- });
331
- void describe('getReadyForAIFeatures integration behavior', () => {
332
- void it('should use sortFeaturesByUpdatedAt to ensure oldest-first processing', () => {
333
- // This test verifies the integration between getReadyForAIFeatures and sortFeaturesByUpdatedAt
334
- // We can test this indirectly by testing the sorting function that getReadyForAIFeatures uses
335
- const mockApiResponse = [
336
- {
337
- id: 'feat-1',
338
- name: 'Feature Added Last Week',
339
- status: 'ready_for_ai',
340
- product_id: 'product-123',
341
- updated_at: '2024-01-18T10:00:00Z',
342
- },
343
- {
344
- id: 'feat-2',
345
- name: 'Feature Added Yesterday',
346
- status: 'ready_for_ai',
347
- product_id: 'product-123',
348
- updated_at: '2024-01-24T15:30:00Z',
349
- },
350
- {
351
- id: 'feat-3',
352
- name: 'Feature Added Last Month',
353
- status: 'ready_for_ai',
354
- product_id: 'product-123',
355
- updated_at: '2024-01-05T08:00:00Z',
356
- },
357
- ];
358
- // This simulates what getReadyForAIFeatures does internally
359
- const sortedForWorkflow = sortFeaturesByUpdatedAt(mockApiResponse);
360
- // Verify oldest-first ordering that getReadyForAIFeatures should provide
361
- assert.strictEqual(sortedForWorkflow[0].id, 'feat-3', 'Oldest feature should be first');
362
- assert.strictEqual(sortedForWorkflow[1].id, 'feat-1', 'Second oldest should be second');
363
- assert.strictEqual(sortedForWorkflow[2].id, 'feat-2', 'Newest feature should be last');
364
- // Verify the function returns the features in the correct order for workflow processing
365
- const expectedOrder = ['feat-3', 'feat-1', 'feat-2'];
366
- const actualOrder = sortedForWorkflow.map((f) => f.id);
367
- assert.deepStrictEqual(actualOrder, expectedOrder, 'Features should be ordered for fair workflow processing');
368
- });
369
- });
370
- });
@@ -1,4 +0,0 @@
1
- /**
2
- * Unit tests for status progression logic
3
- */
4
- export {};
@@ -1,88 +0,0 @@
1
- /**
2
- * Unit tests for status progression logic
3
- */
4
- import assert from 'node:assert';
5
- import { describe, it } from 'node:test';
6
- import { STATUS_PROGRESSION_ORDER } from '../../../config/feature-status.js';
7
- import { isForwardProgression } from '../status-updater.js';
8
- void describe('Status Progression Logic', () => {
9
- void describe('isForwardProgression', () => {
10
- void it('should allow forward progression', () => {
11
- assert.strictEqual(isForwardProgression('backlog', 'ready_for_ai'), true, 'Should allow progression from backlog to ready_for_ai');
12
- assert.strictEqual(isForwardProgression('feature_analysis', 'technical_design'), true, 'Should allow progression from feature_analysis to technical_design');
13
- assert.strictEqual(isForwardProgression('code_implementation', 'shipped'), true, 'Should allow progression from code_implementation to shipped');
14
- });
15
- void it('should allow staying at same status', () => {
16
- assert.strictEqual(isForwardProgression('backlog', 'backlog'), true, 'Should allow staying at backlog status');
17
- assert.strictEqual(isForwardProgression('feature_analysis', 'feature_analysis'), true, 'Should allow staying at feature_analysis status');
18
- assert.strictEqual(isForwardProgression('shipped', 'shipped'), true, 'Should allow staying at shipped status');
19
- });
20
- void it('should prevent backward progression', () => {
21
- assert.strictEqual(isForwardProgression('ready_for_ai', 'backlog'), false, 'Should prevent regression from ready_for_ai to backlog');
22
- assert.strictEqual(isForwardProgression('technical_design', 'feature_analysis'), false, 'Should prevent regression from technical_design to feature_analysis');
23
- assert.strictEqual(isForwardProgression('shipped', 'code_implementation'), false, 'Should prevent regression from shipped to code_implementation');
24
- });
25
- void it('should handle edge cases correctly', () => {
26
- // First status to last status
27
- assert.strictEqual(isForwardProgression('backlog', 'shipped'), true, 'Should allow progression from first to last status');
28
- // Last status to first status
29
- assert.strictEqual(isForwardProgression('shipped', 'backlog'), false, 'Should prevent regression from last to first status');
30
- });
31
- void it('should handle testing workflow correctly', () => {
32
- // Allow progression through testing states
33
- assert.strictEqual(isForwardProgression('functional_testing', 'testing_in_progress'), true, 'Should allow progression to testing_in_progress');
34
- assert.strictEqual(isForwardProgression('testing_in_progress', 'testing_passed'), true, 'Should allow progression to testing_passed');
35
- // Allow retry after failed testing
36
- assert.strictEqual(isForwardProgression('testing_in_progress', 'testing_failed'), true, 'Should allow marking tests as failed');
37
- // Prevent going back to testing_in_progress from passed
38
- assert.strictEqual(isForwardProgression('testing_passed', 'testing_in_progress'), false, 'Should prevent going back from testing_passed to testing_in_progress');
39
- });
40
- });
41
- void describe('STATUS_PROGRESSION_ORDER', () => {
42
- void it('should contain all expected statuses', () => {
43
- const expectedStatuses = [
44
- 'backlog',
45
- 'ready_for_ai',
46
- 'feature_analysis',
47
- 'feature_analysis_verification',
48
- 'technical_design',
49
- 'technical_design_verification',
50
- 'branch_planning',
51
- 'branch_planning_verification',
52
- 'code_implementation',
53
- 'code_implementation_verification',
54
- 'code_refine',
55
- 'code_refine_verification',
56
- 'bug_fixing',
57
- 'code_review',
58
- 'functional_testing',
59
- 'testing_in_progress',
60
- 'testing_passed',
61
- 'testing_failed',
62
- 'ready_for_review',
63
- 'shipped',
64
- ];
65
- assert.strictEqual(STATUS_PROGRESSION_ORDER.length, expectedStatuses.length, 'Should contain the correct number of statuses');
66
- for (const status of expectedStatuses) {
67
- assert.ok(STATUS_PROGRESSION_ORDER.includes(status), `Should include status: ${status}`);
68
- }
69
- });
70
- void it('should have correct ordering for key workflow phases', () => {
71
- const backlogIndex = STATUS_PROGRESSION_ORDER.indexOf('backlog');
72
- const readyForDevIndex = STATUS_PROGRESSION_ORDER.indexOf('ready_for_ai');
73
- const featureAnalysisIndex = STATUS_PROGRESSION_ORDER.indexOf('feature_analysis');
74
- const technicalDesignIndex = STATUS_PROGRESSION_ORDER.indexOf('technical_design');
75
- const codeImplementationIndex = STATUS_PROGRESSION_ORDER.indexOf('code_implementation');
76
- const shippedIndex = STATUS_PROGRESSION_ORDER.indexOf('shipped');
77
- assert.ok(backlogIndex < readyForDevIndex, 'backlog should come before ready_for_ai');
78
- assert.ok(readyForDevIndex < featureAnalysisIndex, 'ready_for_ai should come before feature_analysis');
79
- assert.ok(featureAnalysisIndex < technicalDesignIndex, 'feature_analysis should come before technical_design');
80
- assert.ok(technicalDesignIndex < codeImplementationIndex, 'technical_design should come before code_implementation');
81
- assert.ok(codeImplementationIndex < shippedIndex, 'code_implementation should come before shipped');
82
- });
83
- void it('should not have duplicate statuses', () => {
84
- const uniqueStatuses = new Set(STATUS_PROGRESSION_ORDER);
85
- assert.strictEqual(uniqueStatuses.size, STATUS_PROGRESSION_ORDER.length, 'Should not contain duplicate statuses');
86
- });
87
- });
88
- });
@@ -1,20 +0,0 @@
1
- /**
2
- * Approval workflow integration for feature phases
3
- * Checks if current feature status has been approved before executing next phase
4
- */
5
- interface ApprovalCheckResult {
6
- canProceed: boolean;
7
- requiresApproval: boolean;
8
- approvalId?: string;
9
- message?: string;
10
- }
11
- /**
12
- * Check if current feature status has been approved before executing next phase
13
- * This should be called BEFORE executing a phase, not after
14
- *
15
- * @param featureId - Feature ID
16
- * @param verbose - Verbose logging
17
- * @returns Promise<ApprovalCheckResult> - Whether the phase can proceed
18
- */
19
- export declare function checkApprovalBeforePhaseExecution(featureId: string, verbose?: boolean): Promise<ApprovalCheckResult>;
20
- export {};
@@ -1,152 +0,0 @@
1
- /**
2
- * Approval workflow integration for feature phases
3
- * Checks if current feature status has been approved before executing next phase
4
- */
5
- import { logError, logInfo, logWarning } from '../../utils/logger.js';
6
- import { callMcpEndpoint } from '../mcp-client.js';
7
- import { getFeature } from './get-feature.js';
8
- /**
9
- * Check if current feature status has been approved before executing next phase
10
- * This should be called BEFORE executing a phase, not after
11
- *
12
- * @param featureId - Feature ID
13
- * @param verbose - Verbose logging
14
- * @returns Promise<ApprovalCheckResult> - Whether the phase can proceed
15
- */
16
- export async function checkApprovalBeforePhaseExecution(featureId, verbose = false) {
17
- try {
18
- // 1. Get current feature to check its status and product_id
19
- const feature = await getFeature(featureId, verbose);
20
- const currentStatus = feature.status;
21
- const productId = feature.product_id;
22
- if (verbose) {
23
- logInfo(`🔍 Checking approval for current status: ${currentStatus}`);
24
- logInfo(`📦 Product ID: ${productId}`);
25
- }
26
- // 2. Check if current status requires approval
27
- const requiresApprovalResult = (await callMcpEndpoint('approvals/requires_approval', {
28
- product_id: productId,
29
- feature_status: currentStatus,
30
- }));
31
- if (verbose) {
32
- logInfo(`📋 MCP requires_approval response: ${JSON.stringify(requiresApprovalResult)}`);
33
- }
34
- const requiresApproval = requiresApprovalResult?.requires_approval === true;
35
- if (!requiresApproval) {
36
- if (verbose) {
37
- logInfo(`✅ Current status ${currentStatus} does not require approval. Proceeding.`);
38
- }
39
- return {
40
- canProceed: true,
41
- requiresApproval: false,
42
- };
43
- }
44
- // 3. Status requires approval - check if already approved
45
- if (verbose) {
46
- logInfo(`🔒 Current status ${currentStatus} requires approval`);
47
- }
48
- const existingApprovals = (await callMcpEndpoint('approvals/feature_approvals', {
49
- feature_id: featureId,
50
- }));
51
- // Check if there's an approved approval for the current status
52
- const approvedApproval = existingApprovals?.approvals?.find((approval) => approval.requested_status === currentStatus &&
53
- approval.approval_status === 'approved');
54
- if (approvedApproval) {
55
- if (verbose) {
56
- logInfo(`✅ Found approved approval for ${currentStatus}. Proceeding.`);
57
- }
58
- return {
59
- canProceed: true,
60
- requiresApproval: true,
61
- approvalId: approvedApproval.id,
62
- };
63
- }
64
- // Check if there's already a pending approval for current status
65
- const pendingApproval = existingApprovals?.approvals?.find((approval) => approval.requested_status === currentStatus &&
66
- approval.approval_status === 'pending');
67
- if (pendingApproval) {
68
- if (verbose) {
69
- logWarning(`⏳ Approval already pending for ${currentStatus}. Waiting for review.`);
70
- }
71
- return {
72
- canProceed: false,
73
- requiresApproval: true,
74
- approvalId: pendingApproval.id,
75
- message: `Waiting for approval of current status: ${currentStatus}`,
76
- };
77
- }
78
- // No pending or approved approval - need to create one
79
- if (verbose) {
80
- logInfo(`🔔 No approval exists for ${currentStatus}. Creating approval request...`);
81
- }
82
- // Create approval request
83
- const approvalId = await createApprovalRequestWithEmail(featureId, productId, currentStatus, verbose);
84
- if (approvalId) {
85
- return {
86
- canProceed: false,
87
- requiresApproval: true,
88
- approvalId,
89
- message: `Approval request created for status: ${currentStatus}`,
90
- };
91
- }
92
- logError('Failed to create approval request');
93
- return {
94
- canProceed: false,
95
- requiresApproval: true,
96
- message: `Failed to create approval request for status: ${currentStatus}`,
97
- };
98
- }
99
- catch (error) {
100
- const errorMessage = error instanceof Error ? error.message : String(error);
101
- logError(`Error checking approval before phase execution: ${errorMessage}`);
102
- // On error, block execution to be safe
103
- return {
104
- canProceed: false,
105
- requiresApproval: true,
106
- message: `Error checking approval: ${errorMessage}`,
107
- };
108
- }
109
- }
110
- /**
111
- * Create an approval request (emails are sent automatically by the MCP handler)
112
- * This is for the CURRENT status, not the next status
113
- */
114
- async function createApprovalRequestWithEmail(featureId, productId, currentStatus, verbose = false) {
115
- try {
116
- if (verbose) {
117
- logInfo(`Creating approval request for current status: ${currentStatus}`);
118
- }
119
- // Create approval request via MCP endpoint
120
- // Note: We're requesting approval for the current status
121
- // The approval is asking: "Can we proceed from this status?"
122
- // Email notifications are sent automatically by the MCP handler
123
- const result = (await callMcpEndpoint('approvals/create', {
124
- feature_id: featureId,
125
- requested_status: currentStatus,
126
- previous_status: null, // We're approving current status, not transitioning
127
- }));
128
- const approvalId = result?.approval_id;
129
- if (!approvalId) {
130
- logError('Failed to create approval request: No approval_id returned');
131
- return null;
132
- }
133
- if (verbose) {
134
- logInfo(`✅ Approval request created: ${approvalId}`);
135
- // Log email results if available
136
- if (result?.emails_sent) {
137
- const sentCount = result.emails_sent.filter((e) => e.status === 'sent').length;
138
- const failedCount = result.emails_sent.filter((e) => e.status !== 'sent').length;
139
- logInfo(`📧 Sent approval emails to ${sentCount} assignee(s)`);
140
- if (failedCount > 0) {
141
- logWarning(`⚠️ Failed to send ${failedCount} email(s)`);
142
- }
143
- }
144
- }
145
- return approvalId;
146
- }
147
- catch (error) {
148
- const errorMessage = error instanceof Error ? error.message : String(error);
149
- logError(`Failed to create approval request: ${errorMessage}`);
150
- return null;
151
- }
152
- }
@@ -1,17 +0,0 @@
1
- import { type TestCaseStatus, type UserStoryStatus } from '../../types/index.js';
2
- /**
3
- * Batch update user story statuses
4
- */
5
- export declare function batchUpdateUserStoryStatus(userStoryIds: string[], status: UserStoryStatus, verbose?: boolean): Promise<boolean>;
6
- /**
7
- * Batch update test case statuses
8
- */
9
- export declare function batchUpdateTestCaseStatus(testCaseIds: string[], status: TestCaseStatus, verbose?: boolean): Promise<boolean>;
10
- /**
11
- * Batch delete user stories
12
- */
13
- export declare function batchDeleteUserStories(userStoryIds: string[], verbose?: boolean): Promise<boolean>;
14
- /**
15
- * Batch delete test cases
16
- */
17
- export declare function batchDeleteTestCases(testCaseIds: string[], verbose?: boolean): Promise<boolean>;