Skip to content

Commit f13de05

Browse files
authored
Merge pull request #69 from Obedience-Corp/develop
Hybrid phases, create workflow, gate fixes
2 parents e12646c + 2cdfd5d commit f13de05

2 files changed

Lines changed: 63 additions & 0 deletions

File tree

internal/commands/next/next.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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).
532593
func hasSequenceDirs(phasePath string) bool {
533594
entries, err := os.ReadDir(phasePath)

internal/guidance/selection/output.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -768,7 +768,9 @@ func buildGateSection(task *TaskInfo) string {
768768
// Task location
769769
taskRelPath := filepath.Join(task.PhaseName, task.SequenceName, task.Name+".md")
770770
sb.WriteString(labelValue("Task", ui.Value(task.Name, ui.TaskColor)))
771+
sb.WriteString("\n")
771772
sb.WriteString(labelValue("Path", ui.Dim(taskRelPath)))
773+
sb.WriteString("\n")
772774
sb.WriteString(labelValue("Type", ui.Value(fmt.Sprintf("gate (%s)", fm.GateType))))
773775
sb.WriteString("\n")
774776

0 commit comments

Comments
 (0)