rdev/cmd/sdlc/cmd_branch.go
jordan 56e3f83955 feat: add auth scopes, OpenAPI docs, SDLC guides, and code quality improvements
- Add auth.RequireScope() to all handler routes for proper authorization
- Add SDLC OpenAPI endpoint documentation (state, features, tasks, branches, merge, archive, orchestrator)
- Add SDLC documentation guides (getting-started, cli-reference, api-reference, command-catalog)
- Add artifact_test.go for SDLC artifact coverage
- Add CLAUDE.md rules: auth scopes requirement, error wrapping with %w
- Fix error wrapping to use %w instead of %v throughout codebase
- Improve CLI merge command with conflict detection and resolution
- Fix handler tests to include auth middleware for RequireScope
- Add cookbook tree runner scripts for automated testing

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 13:55:50 -07:00

189 lines
4.2 KiB
Go

package main
import (
"fmt"
"os/exec"
"time"
"github.com/orchard9/rdev/internal/sdlc"
"github.com/spf13/cobra"
)
var branchCmd = &cobra.Command{
Use: "branch",
Short: "Manage feature branches",
}
var branchCreateCmd = &cobra.Command{
Use: "create <slug>",
Short: "Create a feature branch",
Args: cobra.ExactArgs(1),
RunE: func(_ *cobra.Command, args []string) error {
root := mustResolveRoot()
slug := args[0]
cfg, err := sdlc.LoadConfig(root)
if err != nil {
return err
}
// Create the branch manifest
manifest, err := sdlc.CreateBranch(root, slug, cfg)
if err != nil {
return err
}
// Create the git branch
branchName := manifest.Name
gitCmd := exec.Command("git", "checkout", "-b", branchName)
gitCmd.Dir = root
if out, err := gitCmd.CombinedOutput(); err != nil {
return fmt.Errorf("git checkout -b %s: %s: %w", branchName, string(out), err)
}
// Record the action in state
state, err := sdlc.LoadState(root)
if err != nil {
return err
}
state.RecordAction("CREATE_BRANCH", slug, "cli", "success")
if err := state.Save(root); err != nil {
return err
}
if jsonOutput {
return printJSON(manifest)
}
fmt.Printf("Created branch: %s (from %s)\n", branchName, manifest.BaseBranch)
return nil
},
}
var branchStatusCmd = &cobra.Command{
Use: "status <slug>",
Short: "Show branch status and merge checklist",
Args: cobra.ExactArgs(1),
RunE: func(_ *cobra.Command, args []string) error {
root := mustResolveRoot()
slug := args[0]
f, err := sdlc.LoadFeature(root, slug)
if err != nil {
return err
}
if f.Branch == "" {
if jsonOutput {
return printJSON(map[string]string{"error": "no branch associated with feature"})
}
fmt.Printf("Feature %s has no branch.\n", slug)
return nil
}
manifest, err := sdlc.LoadBranch(root, f.Branch)
if err != nil {
return err
}
checklist, err := sdlc.MergeChecklist(root, slug)
if err != nil {
return err
}
if jsonOutput {
return printJSON(map[string]any{
"branch": manifest,
"checklist": checklist,
"ready": len(checklist) == 0,
})
}
fmt.Printf("Branch: %s\n", manifest.Name)
fmt.Printf("Feature: %s\n", manifest.Feature)
fmt.Printf("Base: %s\n", manifest.BaseBranch)
fmt.Printf("Created: %s\n", manifest.CreatedAt.Format(time.RFC3339))
if manifest.LastSyncAt != nil {
fmt.Printf("Last sync: %s\n", manifest.LastSyncAt.Format(time.RFC3339))
}
fmt.Println()
if len(checklist) == 0 {
fmt.Println("Merge status: READY")
} else {
fmt.Println("Merge status: NOT READY")
fmt.Println("Unmet gates:")
for _, gate := range checklist {
fmt.Printf(" - %s\n", gate)
}
}
return nil
},
}
var branchSyncCmd = &cobra.Command{
Use: "sync <slug>",
Short: "Sync feature branch with base branch",
Args: cobra.ExactArgs(1),
RunE: func(_ *cobra.Command, args []string) error {
root := mustResolveRoot()
slug := args[0]
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
}
// Fetch and rebase
fetchCmd := exec.Command("git", "fetch", "origin")
fetchCmd.Dir = root
if out, err := fetchCmd.CombinedOutput(); err != nil {
return fmt.Errorf("git fetch: %s: %w", string(out), err)
}
rebaseCmd := exec.Command("git", "rebase", "origin/"+manifest.BaseBranch)
rebaseCmd.Dir = root
if out, err := rebaseCmd.CombinedOutput(); err != nil {
return fmt.Errorf("git rebase origin/%s: %s: %w", manifest.BaseBranch, string(out), err)
}
// Update last sync time
now := time.Now().UTC()
manifest.LastSyncAt = &now
if err := sdlc.SaveBranch(root, manifest); err != nil {
return err
}
if jsonOutput {
return printJSON(map[string]string{
"feature": slug,
"branch": manifest.Name,
"synced": "true",
"synced_at": now.Format(time.RFC3339),
})
}
fmt.Printf("Synced %s with origin/%s\n", manifest.Name, manifest.BaseBranch)
return nil
},
}
func init() {
branchCmd.AddCommand(
branchCreateCmd,
branchStatusCmd,
branchSyncCmd,
)
rootCmd.AddCommand(branchCmd)
}