diff --git a/internal/sdlc/feature.go b/internal/sdlc/feature.go index 164d3b0..bbfb0bc 100644 --- a/internal/sdlc/feature.go +++ b/internal/sdlc/feature.go @@ -140,7 +140,13 @@ func (f *Feature) CanTransitionTo(target FeaturePhase, cfg *Config) error { currentIdx := PhaseIndex(f.Phase) targetIdx := PhaseIndex(target) - if targetIdx <= currentIdx { + // Idempotent: if already in target phase, return success + if targetIdx == currentIdx { + return nil + } + + // Reject backward transitions + if targetIdx < currentIdx { return fmt.Errorf("%w: cannot move from %s to %s (backward)", ErrInvalidTransition, f.Phase, target) } diff --git a/internal/sdlc/feature_test.go b/internal/sdlc/feature_test.go index ea6d37b..0624841 100644 --- a/internal/sdlc/feature_test.go +++ b/internal/sdlc/feature_test.go @@ -128,9 +128,9 @@ func TestCanTransitionTo(t *testing.T) { t.Error("CanTransitionTo(planned) = nil, want error (skip)") } - // Invalid: draft -> draft (backward) - if err := f.CanTransitionTo(PhaseDraft, cfg); err == nil { - t.Error("CanTransitionTo(draft) = nil, want error (backward)") + // Idempotent: draft -> draft (allowed, returns nil) + if err := f.CanTransitionTo(PhaseDraft, cfg); err != nil { + t.Errorf("CanTransitionTo(draft) = %v, want nil (idempotent)", err) } // Invalid phase