edsger 0.13.4 → 0.14.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.
- package/dist/api/features/update-feature.d.ts +8 -0
- package/dist/api/features/update-feature.js +52 -0
- package/dist/commands/workflow/feature-coordinator.js +1 -0
- package/dist/commands/workflow/phase-orchestrator.js +28 -17
- package/dist/config/feature-status.d.ts +18 -0
- package/dist/config/feature-status.js +33 -0
- package/dist/types/features.d.ts +2 -0
- package/dist/types/pipeline.d.ts +8 -1
- package/package.json +1 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { FeatureWorkflow } from '../../types/pipeline.js';
|
|
1
2
|
/**
|
|
2
3
|
* Update feature with new data
|
|
3
4
|
*/
|
|
@@ -6,8 +7,15 @@ export declare function updateFeature(featureId: string, updates: {
|
|
|
6
7
|
status?: string;
|
|
7
8
|
pull_request_url?: string;
|
|
8
9
|
execution_mode?: string;
|
|
10
|
+
workflow?: FeatureWorkflow;
|
|
9
11
|
}, verbose?: boolean): Promise<boolean>;
|
|
10
12
|
/**
|
|
11
13
|
* Update technical design for a feature
|
|
12
14
|
*/
|
|
13
15
|
export declare function updateTechnicalDesign(featureId: string, technicalDesign: string, verbose?: boolean): Promise<boolean>;
|
|
16
|
+
/**
|
|
17
|
+
* Mark a workflow phase as completed
|
|
18
|
+
* Fetches current workflow, updates the phase status, and saves back
|
|
19
|
+
* Accepts phase names in either format (hyphens or underscores)
|
|
20
|
+
*/
|
|
21
|
+
export declare function markWorkflowPhaseCompleted(featureId: string, phaseName: string, verbose?: boolean): Promise<boolean>;
|
|
@@ -29,3 +29,55 @@ export async function updateFeature(featureId, updates, verbose) {
|
|
|
29
29
|
export async function updateTechnicalDesign(featureId, technicalDesign, verbose) {
|
|
30
30
|
return updateFeature(featureId, { technical_design: technicalDesign }, verbose);
|
|
31
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Normalize phase name from pipeline format (hyphens) to workflow format (underscores)
|
|
34
|
+
* e.g., 'feature-analysis' -> 'feature_analysis'
|
|
35
|
+
*/
|
|
36
|
+
function normalizePhaseNameForWorkflow(phaseName) {
|
|
37
|
+
return phaseName.replace(/-/g, '_');
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Mark a workflow phase as completed
|
|
41
|
+
* Fetches current workflow, updates the phase status, and saves back
|
|
42
|
+
* Accepts phase names in either format (hyphens or underscores)
|
|
43
|
+
*/
|
|
44
|
+
export async function markWorkflowPhaseCompleted(featureId, phaseName, verbose) {
|
|
45
|
+
try {
|
|
46
|
+
// Normalize phase name to workflow format (underscores)
|
|
47
|
+
const normalizedPhaseName = normalizePhaseNameForWorkflow(phaseName);
|
|
48
|
+
if (verbose) {
|
|
49
|
+
logInfo(`Marking workflow phase '${normalizedPhaseName}' as completed for feature: ${featureId}`);
|
|
50
|
+
}
|
|
51
|
+
// Fetch current feature to get workflow
|
|
52
|
+
const response = (await callMcpEndpoint('features/get', {
|
|
53
|
+
feature_id: featureId,
|
|
54
|
+
}));
|
|
55
|
+
const feature = response?.features?.[0];
|
|
56
|
+
if (!feature) {
|
|
57
|
+
logError(`Feature not found: ${featureId}`);
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
const workflow = feature.workflow || [];
|
|
61
|
+
if (workflow.length === 0) {
|
|
62
|
+
if (verbose) {
|
|
63
|
+
logInfo(`No workflow defined for feature, skipping phase completion`);
|
|
64
|
+
}
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
// Update the phase status
|
|
68
|
+
const updatedWorkflow = workflow.map((p) => p.phase === normalizedPhaseName
|
|
69
|
+
? {
|
|
70
|
+
...p,
|
|
71
|
+
status: 'completed',
|
|
72
|
+
executed_at: new Date().toISOString(),
|
|
73
|
+
}
|
|
74
|
+
: p);
|
|
75
|
+
// Save updated workflow
|
|
76
|
+
return await updateFeature(featureId, { workflow: updatedWorkflow }, verbose);
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
80
|
+
logError(`Failed to mark workflow phase completed: ${errorMessage}`);
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -125,6 +125,7 @@ export function getExecutionModeDescription(mode) {
|
|
|
125
125
|
from_functional_testing: 'Execute from functional testing to end: testing',
|
|
126
126
|
from_pull_request: 'Execute from pull request creation to end: pull-request → code-review → code-refine → code-refine-verification',
|
|
127
127
|
from_code_review: 'Execute from code review to end: code-review → code-refine → code-refine-verification',
|
|
128
|
+
custom: 'Execute user-selected phases in order',
|
|
128
129
|
};
|
|
129
130
|
return descriptions[mode] || 'Unknown execution mode';
|
|
130
131
|
}
|
|
@@ -13,13 +13,24 @@
|
|
|
13
13
|
*
|
|
14
14
|
* Uses functional programming principles for composability
|
|
15
15
|
*/
|
|
16
|
-
import { updateFeatureStatusForPhase } from '../../api/features/index.js';
|
|
16
|
+
import { updateFeatureStatusForPhase, markWorkflowPhaseCompleted, } from '../../api/features/index.js';
|
|
17
17
|
import { runFeatureAnalysisPhase, runTechnicalDesignPhase, runCodeImplementationPhase, 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';
|
|
21
21
|
import { handlePullRequestCreation } from '../../phases/pull-request/handler.js';
|
|
22
22
|
import { logInfo } from '../../utils/logger.js';
|
|
23
|
+
/**
|
|
24
|
+
* Helper to log phase result and mark workflow phase as completed if successful
|
|
25
|
+
*/
|
|
26
|
+
const logAndMarkPhaseCompleted = async (result, verbose) => {
|
|
27
|
+
const logged = logPhaseResult(result, verbose);
|
|
28
|
+
// Mark workflow phase as completed if phase succeeded
|
|
29
|
+
if (result.status === 'success') {
|
|
30
|
+
await markWorkflowPhaseCompleted(result.featureId, result.phase, verbose);
|
|
31
|
+
}
|
|
32
|
+
return logged;
|
|
33
|
+
};
|
|
23
34
|
/**
|
|
24
35
|
* Orchestrate phase execution based on execution mode
|
|
25
36
|
* Routes to appropriate phase sequence based on mode (only_*, from_*, full_pipeline)
|
|
@@ -111,7 +122,7 @@ const runOnlyFeatureAnalysis = async (options, config) => {
|
|
|
111
122
|
logPipelineStart(featureId, verbose);
|
|
112
123
|
const results = [];
|
|
113
124
|
const analysisResult = await runFeatureAnalysisPhase(options, config);
|
|
114
|
-
results.push(
|
|
125
|
+
results.push(await logAndMarkPhaseCompleted(analysisResult, verbose));
|
|
115
126
|
return finalizePipelineExecution(options, results, verbose);
|
|
116
127
|
};
|
|
117
128
|
/**
|
|
@@ -122,7 +133,7 @@ const runOnlyTechnicalDesign = async (options, config) => {
|
|
|
122
133
|
logPipelineStart(featureId, verbose);
|
|
123
134
|
const results = [];
|
|
124
135
|
const designResult = await runTechnicalDesignPhase(options, config);
|
|
125
|
-
results.push(
|
|
136
|
+
results.push(await logAndMarkPhaseCompleted(designResult, verbose));
|
|
126
137
|
return finalizePipelineExecution(options, results, verbose);
|
|
127
138
|
};
|
|
128
139
|
/**
|
|
@@ -134,7 +145,7 @@ const runOnlyCodeImplementation = async (options, config) => {
|
|
|
134
145
|
logPipelineStart(featureId, verbose);
|
|
135
146
|
const results = [];
|
|
136
147
|
const implementationResult = await runCodeImplementationPhase(options, config);
|
|
137
|
-
results.push(
|
|
148
|
+
results.push(await logAndMarkPhaseCompleted(implementationResult, verbose));
|
|
138
149
|
// If implementation succeeded, create a pull request so the code can be reviewed
|
|
139
150
|
if (implementationResult.status === 'success') {
|
|
140
151
|
if (verbose) {
|
|
@@ -156,7 +167,7 @@ const runOnlyCodeImplementation = async (options, config) => {
|
|
|
156
167
|
: 'Pull request creation failed',
|
|
157
168
|
data: {},
|
|
158
169
|
};
|
|
159
|
-
results.push(
|
|
170
|
+
results.push(await logAndMarkPhaseCompleted(prResult, verbose));
|
|
160
171
|
}
|
|
161
172
|
return finalizePipelineExecution(options, results, verbose);
|
|
162
173
|
};
|
|
@@ -168,7 +179,7 @@ const runOnlyFunctionalTesting = async (options, config) => {
|
|
|
168
179
|
logPipelineStart(featureId, verbose);
|
|
169
180
|
const results = [];
|
|
170
181
|
const testingResult = await runFunctionalTestingPhase(options, config);
|
|
171
|
-
results.push(
|
|
182
|
+
results.push(await logAndMarkPhaseCompleted(testingResult, verbose));
|
|
172
183
|
return finalizePipelineExecution(options, results, verbose);
|
|
173
184
|
};
|
|
174
185
|
/**
|
|
@@ -180,19 +191,19 @@ const runFromFeatureAnalysis = async (options, config) => {
|
|
|
180
191
|
const results = [];
|
|
181
192
|
// 1. Feature Analysis
|
|
182
193
|
const analysisResult = await runFeatureAnalysisPhase(options, config);
|
|
183
|
-
results.push(
|
|
194
|
+
results.push(await logAndMarkPhaseCompleted(analysisResult, verbose));
|
|
184
195
|
if (!shouldContinuePipeline(results)) {
|
|
185
196
|
return finalizePipelineExecution(options, results, verbose);
|
|
186
197
|
}
|
|
187
198
|
// 2. Technical Design
|
|
188
199
|
const designResult = await runTechnicalDesignPhase(options, config);
|
|
189
|
-
results.push(
|
|
200
|
+
results.push(await logAndMarkPhaseCompleted(designResult, verbose));
|
|
190
201
|
if (!shouldContinuePipeline(results)) {
|
|
191
202
|
return finalizePipelineExecution(options, results, verbose);
|
|
192
203
|
}
|
|
193
204
|
// 3. Code Implementation
|
|
194
205
|
const implementationResult = await runCodeImplementationPhase(options, config);
|
|
195
|
-
results.push(
|
|
206
|
+
results.push(await logAndMarkPhaseCompleted(implementationResult, verbose));
|
|
196
207
|
if (!shouldContinuePipeline(results)) {
|
|
197
208
|
return finalizePipelineExecution(options, results, verbose);
|
|
198
209
|
}
|
|
@@ -235,13 +246,13 @@ const runFromTechnicalDesign = async (options, config) => {
|
|
|
235
246
|
const results = [];
|
|
236
247
|
// 1. Technical Design
|
|
237
248
|
const designResult = await runTechnicalDesignPhase(options, config);
|
|
238
|
-
results.push(
|
|
249
|
+
results.push(await logAndMarkPhaseCompleted(designResult, verbose));
|
|
239
250
|
if (!shouldContinuePipeline(results)) {
|
|
240
251
|
return finalizePipelineExecution(options, results, verbose);
|
|
241
252
|
}
|
|
242
253
|
// 2. Code Implementation
|
|
243
254
|
const implementationResult = await runCodeImplementationPhase(options, config);
|
|
244
|
-
results.push(
|
|
255
|
+
results.push(await logAndMarkPhaseCompleted(implementationResult, verbose));
|
|
245
256
|
if (!shouldContinuePipeline(results)) {
|
|
246
257
|
return finalizePipelineExecution(options, results, verbose);
|
|
247
258
|
}
|
|
@@ -284,7 +295,7 @@ const runFromCodeImplementation = async (options, config) => {
|
|
|
284
295
|
const results = [];
|
|
285
296
|
// 1. Code Implementation
|
|
286
297
|
const implementationResult = await runCodeImplementationPhase(options, config);
|
|
287
|
-
results.push(
|
|
298
|
+
results.push(await logAndMarkPhaseCompleted(implementationResult, verbose));
|
|
288
299
|
if (!shouldContinuePipeline(results)) {
|
|
289
300
|
return finalizePipelineExecution(options, results, verbose);
|
|
290
301
|
}
|
|
@@ -387,7 +398,7 @@ const runOnlyPullRequest = async (options, config) => {
|
|
|
387
398
|
: 'Pull request creation failed',
|
|
388
399
|
data: {},
|
|
389
400
|
};
|
|
390
|
-
results.push(
|
|
401
|
+
results.push(await logAndMarkPhaseCompleted(prResult, verbose));
|
|
391
402
|
return finalizePipelineExecution(options, results, verbose);
|
|
392
403
|
};
|
|
393
404
|
/**
|
|
@@ -416,7 +427,7 @@ const runOnlyCodeReview = async (options, config) => {
|
|
|
416
427
|
const results = [];
|
|
417
428
|
// Code Review - analyze PR and create review comments
|
|
418
429
|
const reviewResult = await runCodeReviewPhase(options, config);
|
|
419
|
-
results.push(
|
|
430
|
+
results.push(await logAndMarkPhaseCompleted(reviewResult, verbose));
|
|
420
431
|
return finalizePipelineExecution(options, results, verbose);
|
|
421
432
|
};
|
|
422
433
|
/**
|
|
@@ -428,7 +439,7 @@ const runFromCodeReview = async (options, config) => {
|
|
|
428
439
|
const results = [];
|
|
429
440
|
// 1. Code Review - analyze PR and create review comments
|
|
430
441
|
const reviewResult = await runCodeReviewPhase(options, config);
|
|
431
|
-
results.push(
|
|
442
|
+
results.push(await logAndMarkPhaseCompleted(reviewResult, verbose));
|
|
432
443
|
if (!shouldContinuePipeline(results)) {
|
|
433
444
|
return finalizePipelineExecution(options, results, verbose);
|
|
434
445
|
}
|
|
@@ -484,7 +495,7 @@ const continueWithCodeReviewAndRefine = async (options, config, results, verbose
|
|
|
484
495
|
}
|
|
485
496
|
// 1. Code Review - analyze PR and create review comments
|
|
486
497
|
const reviewResult = await runCodeReviewPhase(options, config);
|
|
487
|
-
results.push(
|
|
498
|
+
results.push(await logAndMarkPhaseCompleted(reviewResult, verbose));
|
|
488
499
|
if (!shouldContinuePipeline(results)) {
|
|
489
500
|
return;
|
|
490
501
|
}
|
|
@@ -503,5 +514,5 @@ const continueWithCodeReviewAndRefine = async (options, config, results, verbose
|
|
|
503
514
|
logInfo('\n📝 Starting code testing phase...');
|
|
504
515
|
}
|
|
505
516
|
const testingResult = await runCodeTestingPhase(options, config);
|
|
506
|
-
results.push(
|
|
517
|
+
results.push(await logAndMarkPhaseCompleted(testingResult, verbose));
|
|
507
518
|
};
|
|
@@ -36,3 +36,21 @@ export declare const STATUS_PROGRESSION_ORDER: readonly FeatureStatus[];
|
|
|
36
36
|
* and skip the status update to prevent unintended regression to 'backlog'.
|
|
37
37
|
*/
|
|
38
38
|
export declare const PHASE_STATUS_MAP: Record<string, FeatureStatus>;
|
|
39
|
+
/**
|
|
40
|
+
* Human-selectable statuses
|
|
41
|
+
* These are the statuses that a human user can manually set.
|
|
42
|
+
*
|
|
43
|
+
* Excluded statuses (system-managed):
|
|
44
|
+
* - *_verification statuses: Automatically entered after completing the corresponding phase
|
|
45
|
+
* - testing_in_progress: Automatically set when functional testing begins
|
|
46
|
+
* - testing_passed: Automatically set when all tests pass
|
|
47
|
+
* - testing_failed: Automatically set when tests fail
|
|
48
|
+
*
|
|
49
|
+
* These excluded statuses should not be shown in UI dropdowns for manual status changes
|
|
50
|
+
* because they represent intermediate states that are managed by the workflow system.
|
|
51
|
+
*/
|
|
52
|
+
export declare const HUMAN_SELECTABLE_STATUSES: readonly FeatureStatus[];
|
|
53
|
+
/**
|
|
54
|
+
* Check if a status can be manually selected by a human user
|
|
55
|
+
*/
|
|
56
|
+
export declare function isHumanSelectableStatus(status: FeatureStatus): boolean;
|
|
@@ -72,3 +72,36 @@ export const PHASE_STATUS_MAP = {
|
|
|
72
72
|
'testing-failed': 'testing_failed',
|
|
73
73
|
'ready-for-review': 'ready_for_review',
|
|
74
74
|
};
|
|
75
|
+
/**
|
|
76
|
+
* Human-selectable statuses
|
|
77
|
+
* These are the statuses that a human user can manually set.
|
|
78
|
+
*
|
|
79
|
+
* Excluded statuses (system-managed):
|
|
80
|
+
* - *_verification statuses: Automatically entered after completing the corresponding phase
|
|
81
|
+
* - testing_in_progress: Automatically set when functional testing begins
|
|
82
|
+
* - testing_passed: Automatically set when all tests pass
|
|
83
|
+
* - testing_failed: Automatically set when tests fail
|
|
84
|
+
*
|
|
85
|
+
* These excluded statuses should not be shown in UI dropdowns for manual status changes
|
|
86
|
+
* because they represent intermediate states that are managed by the workflow system.
|
|
87
|
+
*/
|
|
88
|
+
export const HUMAN_SELECTABLE_STATUSES = [
|
|
89
|
+
'backlog',
|
|
90
|
+
'ready_for_ai',
|
|
91
|
+
'feature_analysis',
|
|
92
|
+
'technical_design',
|
|
93
|
+
'code_implementation',
|
|
94
|
+
'code_refine',
|
|
95
|
+
'bug_fixing',
|
|
96
|
+
'code_review',
|
|
97
|
+
'pull_request',
|
|
98
|
+
'functional_testing',
|
|
99
|
+
'ready_for_review',
|
|
100
|
+
'shipped',
|
|
101
|
+
];
|
|
102
|
+
/**
|
|
103
|
+
* Check if a status can be manually selected by a human user
|
|
104
|
+
*/
|
|
105
|
+
export function isHumanSelectableStatus(status) {
|
|
106
|
+
return HUMAN_SELECTABLE_STATUSES.includes(status);
|
|
107
|
+
}
|
package/dist/types/features.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { FeatureWorkflow } from './pipeline.js';
|
|
1
2
|
export interface FeatureInfo {
|
|
2
3
|
id: string;
|
|
3
4
|
name: string;
|
|
@@ -6,6 +7,7 @@ export interface FeatureInfo {
|
|
|
6
7
|
status: string;
|
|
7
8
|
product_id: string;
|
|
8
9
|
execution_mode?: string;
|
|
10
|
+
workflow?: FeatureWorkflow | null;
|
|
9
11
|
pull_request_url?: string;
|
|
10
12
|
created_at?: string;
|
|
11
13
|
updated_at?: string;
|
package/dist/types/pipeline.d.ts
CHANGED
|
@@ -3,7 +3,14 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { EdsgerConfig } from './index.js';
|
|
5
5
|
import { ChecklistPhaseContext } from '../services/checklist.js';
|
|
6
|
-
export type ExecutionMode = 'full_pipeline' | 'only_feature_analysis' | 'only_technical_design' | 'only_code_implementation' | 'only_functional_testing' | 'only_pull_request' | 'only_code_refine' | 'only_code_review' | 'from_feature_analysis' | 'from_technical_design' | 'from_code_implementation' | 'from_functional_testing' | 'from_code_review' | 'from_pull_request';
|
|
6
|
+
export type ExecutionMode = 'full_pipeline' | 'only_feature_analysis' | 'only_technical_design' | 'only_code_implementation' | 'only_functional_testing' | 'only_pull_request' | 'only_code_refine' | 'only_code_review' | 'from_feature_analysis' | 'from_technical_design' | 'from_code_implementation' | 'from_functional_testing' | 'from_code_review' | 'from_pull_request' | 'custom';
|
|
7
|
+
export type WorkflowPhaseStatus = 'pending' | 'completed';
|
|
8
|
+
export interface WorkflowPhase {
|
|
9
|
+
phase: string;
|
|
10
|
+
status: WorkflowPhaseStatus;
|
|
11
|
+
executed_at?: string;
|
|
12
|
+
}
|
|
13
|
+
export type FeatureWorkflow = WorkflowPhase[];
|
|
7
14
|
export interface PipelinePhaseOptions {
|
|
8
15
|
readonly featureId: string;
|
|
9
16
|
readonly verbose?: boolean;
|