@@ -204,6 +204,15 @@ func runNext(cmd *cobra.Command, args []string) error {
204204 }
205205 }
206206
207+ // Check if an earlier phase has incomplete after-workflow steps
208+ // that should run before jumping to later phases
209+ if result .Task != nil {
210+ earlierWorkflow , ewErr := findEarlierIncompleteAfterWorkflow (ctx , festivalPath , result .Task .PhaseName )
211+ if ewErr == nil && earlierWorkflow != "" {
212+ return runWorkflowMode (ctx , festivalPath , earlierWorkflow )
213+ }
214+ }
215+
207216 // If selector says festival is complete, check for remaining incomplete workflow phases
208217 if result .FestivalComplete {
209218 incompleteWorkflow , wErr := findFirstIncompleteWorkflowPhase (ctx , festivalPath )
@@ -528,6 +537,58 @@ func findFirstIncompleteWorkflowPhase(ctx context.Context, festivalPath string)
528537 return "" , nil
529538}
530539
540+ // findEarlierIncompleteAfterWorkflow scans phases in numerical order before the given phase
541+ // and returns the first one with an incomplete after-position workflow. This handles hybrid
542+ // phases where sequences are complete but the after-workflow steps remain.
543+ func findEarlierIncompleteAfterWorkflow (ctx context.Context , festivalPath , currentPhaseName string ) (string , error ) {
544+ entries , err := os .ReadDir (festivalPath )
545+ if err != nil {
546+ return "" , err
547+ }
548+
549+ var phases []string
550+ for _ , entry := range entries {
551+ if entry .IsDir () && isNumberedDir (entry .Name ()) {
552+ phases = append (phases , entry .Name ())
553+ }
554+ }
555+ sort .Strings (phases )
556+
557+ store := progress .NewStore (festivalPath )
558+ storeLoaded := store .Load (ctx ) == nil
559+
560+ for _ , phaseName := range phases {
561+ // Stop before the current task's phase
562+ if phaseName >= currentPhaseName {
563+ break
564+ }
565+
566+ phasePath := filepath .Join (festivalPath , phaseName )
567+ workflowPath := filepath .Join (phasePath , "WORKFLOW.md" )
568+ if _ , statErr := os .Stat (workflowPath ); statErr != nil {
569+ continue
570+ }
571+
572+ // Only consider after-position workflows (default)
573+ position := shared .WorkflowPositionForPhase (phasePath )
574+ if position == frontmatter .WorkflowPositionBefore {
575+ continue
576+ }
577+
578+ // Check if workflow is incomplete
579+ if storeLoaded {
580+ state , ok := store .WorkflowPhaseState (phaseName )
581+ if ok && state .TotalSteps > 0 && state .IsComplete () {
582+ continue // Workflow is complete
583+ }
584+ }
585+
586+ return phasePath , nil
587+ }
588+
589+ return "" , nil
590+ }
591+
531592// hasSequenceDirs checks if a phase directory contains numbered subdirectories (sequences).
532593func hasSequenceDirs (phasePath string ) bool {
533594 entries , err := os .ReadDir (phasePath )
0 commit comments