package main import ( "fmt" "os/exec" "time" "github.com/orchard9/rdev/internal/sdlc" "github.com/spf13/cobra" ) var mergeStrategy string var mergeCmd = &cobra.Command{ Use: "merge ", Short: "Merge a feature branch after all gates pass", Args: cobra.ExactArgs(1), RunE: func(_ *cobra.Command, args []string) error { root := mustResolveRoot() slug := args[0] // Check merge readiness checklist, err := sdlc.MergeChecklist(root, slug) if err != nil { return err } if len(checklist) > 0 { if jsonOutput { printJSON(map[string]any{ "error": "merge not ready", "checklist": checklist, }) } return fmt.Errorf("%w: %v", sdlc.ErrMergeNotReady, checklist) } f, err := sdlc.LoadFeature(root, slug) if err != nil { return err } if f.Branch == "" { return fmt.Errorf("feature %s has no branch", slug) } manifest, err := sdlc.LoadBranch(root, f.Branch) if err != nil { return err } strategy := mergeStrategy if strategy == "" { strategy = "squash" } // Checkout main branch checkoutCmd := exec.Command("git", "checkout", manifest.BaseBranch) checkoutCmd.Dir = root if out, err := checkoutCmd.CombinedOutput(); err != nil { return fmt.Errorf("git checkout %s: %s: %w", manifest.BaseBranch, string(out), err) } // Merge mergeArgs := []string{"merge"} if strategy == "squash" { mergeArgs = append(mergeArgs, "--squash") } mergeArgs = append(mergeArgs, f.Branch) gitMerge := exec.Command("git", mergeArgs...) gitMerge.Dir = root if out, err := gitMerge.CombinedOutput(); err != nil { return fmt.Errorf("git merge %s: %s: %w", f.Branch, string(out), err) } // For squash merges, create the commit if strategy == "squash" { commitMsg := fmt.Sprintf("feat: %s\n\nMerged feature %s via SDLC orchestration.", f.Title, slug) commitCmd := exec.Command("git", "commit", "-m", commitMsg) commitCmd.Dir = root if out, err := commitCmd.CombinedOutput(); err != nil { return fmt.Errorf("git commit: %s: %w", string(out), err) } } // Update branch manifest now := time.Now().UTC() manifest.MergedAt = &now manifest.MergeStrategy = strategy if err := sdlc.SaveBranch(root, manifest); err != nil { return err } // Transition feature to released if err := f.Transition(sdlc.PhaseReleased); err != nil { return err } if err := f.Save(root); err != nil { return err } // Record action state, err := sdlc.LoadState(root) if err != nil { return err } state.RecordAction("merged", slug, "cli") if err := state.Save(root); err != nil { return err } if jsonOutput { printJSON(map[string]string{ "feature": slug, "branch": f.Branch, "strategy": strategy, "status": "merged", }) return nil } fmt.Printf("Merged: %s -> %s (strategy: %s)\n", f.Branch, manifest.BaseBranch, strategy) fmt.Printf("Feature %s transitioned to released.\n", slug) return nil }, } func init() { mergeCmd.Flags().StringVar(&mergeStrategy, "strategy", "squash", "merge strategy: squash or merge") rootCmd.AddCommand(mergeCmd) }