Merge pull request #637 from AutoMaker-Org/feature/v0.13.0rc-1768936017583-e6ni

feat: implement pipeline step exclusion functionality
This commit is contained in:
Shirone
2026-01-21 11:59:08 +00:00
committed by GitHub
20 changed files with 782 additions and 24 deletions

View File

@@ -16,6 +16,16 @@ export type {
ProviderMessage,
InstallationStatus,
ModelDefinition,
AgentDefinition,
ReasoningEffort,
SystemPromptPreset,
ConversationMessage,
ContentBlock,
ValidationResult,
McpServerConfig,
McpStdioServerConfig,
McpSSEServerConfig,
McpHttpServerConfig,
} from './types.js';
// Claude provider

View File

@@ -19,4 +19,7 @@ export type {
InstallationStatus,
ValidationResult,
ModelDefinition,
AgentDefinition,
ReasoningEffort,
SystemPromptPreset,
} from '@automaker/types';

View File

@@ -1281,7 +1281,11 @@ export class AutoModeService {
// Check for pipeline steps and execute them
const pipelineConfig = await pipelineService.getPipelineConfig(projectPath);
const sortedSteps = [...(pipelineConfig?.steps || [])].sort((a, b) => a.order - b.order);
// Filter out excluded pipeline steps and sort by order
const excludedStepIds = new Set(feature.excludedPipelineSteps || []);
const sortedSteps = [...(pipelineConfig?.steps || [])]
.sort((a, b) => a.order - b.order)
.filter((step) => !excludedStepIds.has(step.id));
if (sortedSteps.length > 0) {
// Execute pipeline steps sequentially
@@ -1743,15 +1747,76 @@ Complete the pipeline step instructions above. Review the previous work and appl
): Promise<void> {
const featureId = feature.id;
const sortedSteps = [...pipelineConfig.steps].sort((a, b) => a.order - b.order);
// Sort all steps first
const allSortedSteps = [...pipelineConfig.steps].sort((a, b) => a.order - b.order);
// Validate step index
if (startFromStepIndex < 0 || startFromStepIndex >= sortedSteps.length) {
// Get the current step we're resuming from (using the index from unfiltered list)
if (startFromStepIndex < 0 || startFromStepIndex >= allSortedSteps.length) {
throw new Error(`Invalid step index: ${startFromStepIndex}`);
}
const currentStep = allSortedSteps[startFromStepIndex];
// Get steps to execute (from startFromStepIndex onwards)
const stepsToExecute = sortedSteps.slice(startFromStepIndex);
// Filter out excluded pipeline steps
const excludedStepIds = new Set(feature.excludedPipelineSteps || []);
// Check if the current step is excluded
// If so, use getNextStatus to find the appropriate next step
if (excludedStepIds.has(currentStep.id)) {
console.log(
`[AutoMode] Current step ${currentStep.id} is excluded for feature ${featureId}, finding next valid step`
);
const nextStatus = pipelineService.getNextStatus(
`pipeline_${currentStep.id}`,
pipelineConfig,
feature.skipTests ?? false,
feature.excludedPipelineSteps
);
// If next status is not a pipeline step, feature is done
if (!pipelineService.isPipelineStatus(nextStatus)) {
await this.updateFeatureStatus(projectPath, featureId, nextStatus);
this.emitAutoModeEvent('auto_mode_feature_complete', {
featureId,
featureName: feature.title,
branchName: feature.branchName ?? null,
passes: true,
message: 'Pipeline completed (remaining steps excluded)',
projectPath,
});
return;
}
// Find the next step and update the start index
const nextStepId = pipelineService.getStepIdFromStatus(nextStatus);
const nextStepIndex = allSortedSteps.findIndex((s) => s.id === nextStepId);
if (nextStepIndex === -1) {
throw new Error(`Next step ${nextStepId} not found in pipeline config`);
}
startFromStepIndex = nextStepIndex;
}
// Get steps to execute (from startFromStepIndex onwards, excluding excluded steps)
const stepsToExecute = allSortedSteps
.slice(startFromStepIndex)
.filter((step) => !excludedStepIds.has(step.id));
// If no steps left to execute, complete the feature
if (stepsToExecute.length === 0) {
const finalStatus = feature.skipTests ? 'waiting_approval' : 'verified';
await this.updateFeatureStatus(projectPath, featureId, finalStatus);
this.emitAutoModeEvent('auto_mode_feature_complete', {
featureId,
featureName: feature.title,
branchName: feature.branchName ?? null,
passes: true,
message: 'Pipeline completed (all remaining steps excluded)',
projectPath,
});
return;
}
// Use the filtered steps for counting
const sortedSteps = allSortedSteps.filter((step) => !excludedStepIds.has(step.id));
console.log(
`[AutoMode] Resuming pipeline for feature ${featureId} from step ${startFromStepIndex + 1}/${sortedSteps.length}`

View File

@@ -234,51 +234,75 @@ export class PipelineService {
*
* Determines what status a feature should transition to based on current status.
* Flow: in_progress -> pipeline_step_0 -> pipeline_step_1 -> ... -> final status
* Steps in the excludedStepIds array will be skipped.
*
* @param currentStatus - Current feature status
* @param config - Pipeline configuration (or null if no pipeline)
* @param skipTests - Whether to skip tests (affects final status)
* @param excludedStepIds - Optional array of step IDs to skip
* @returns The next status in the pipeline flow
*/
getNextStatus(
currentStatus: FeatureStatusWithPipeline,
config: PipelineConfig | null,
skipTests: boolean
skipTests: boolean,
excludedStepIds?: string[]
): FeatureStatusWithPipeline {
const steps = config?.steps || [];
const exclusions = new Set(excludedStepIds || []);
// Sort steps by order
const sortedSteps = [...steps].sort((a, b) => a.order - b.order);
// Sort steps by order and filter out excluded steps
const sortedSteps = [...steps]
.sort((a, b) => a.order - b.order)
.filter((step) => !exclusions.has(step.id));
// If no pipeline steps, use original logic
// If no pipeline steps (or all excluded), use original logic
if (sortedSteps.length === 0) {
if (currentStatus === 'in_progress') {
// If coming from in_progress or already in a pipeline step, go to final status
if (currentStatus === 'in_progress' || currentStatus.startsWith('pipeline_')) {
return skipTests ? 'waiting_approval' : 'verified';
}
return currentStatus;
}
// Coming from in_progress -> go to first pipeline step
// Coming from in_progress -> go to first non-excluded pipeline step
if (currentStatus === 'in_progress') {
return `pipeline_${sortedSteps[0].id}`;
}
// Coming from a pipeline step -> go to next step or final status
// Coming from a pipeline step -> go to next non-excluded step or final status
if (currentStatus.startsWith('pipeline_')) {
const currentStepId = currentStatus.replace('pipeline_', '');
const currentIndex = sortedSteps.findIndex((s) => s.id === currentStepId);
if (currentIndex === -1) {
// Step not found, go to final status
// Current step not found in filtered list (might be excluded or invalid)
// Find next valid step after this one from the original sorted list
const allSortedSteps = [...steps].sort((a, b) => a.order - b.order);
const originalIndex = allSortedSteps.findIndex((s) => s.id === currentStepId);
if (originalIndex === -1) {
// Step truly doesn't exist, go to final status
return skipTests ? 'waiting_approval' : 'verified';
}
// Find the next non-excluded step after the current one
for (let i = originalIndex + 1; i < allSortedSteps.length; i++) {
if (!exclusions.has(allSortedSteps[i].id)) {
return `pipeline_${allSortedSteps[i].id}`;
}
}
// No more non-excluded steps, go to final status
return skipTests ? 'waiting_approval' : 'verified';
}
if (currentIndex < sortedSteps.length - 1) {
// Go to next step
// Go to next non-excluded step
return `pipeline_${sortedSteps[currentIndex + 1].id}`;
}
// Last step completed, go to final status
// Last non-excluded step completed, go to final status
return skipTests ? 'waiting_approval' : 'verified';
}