diff --git a/internal/cmd/convoy.go b/internal/cmd/convoy.go index c1af286b..c2484583 100644 --- a/internal/cmd/convoy.go +++ b/internal/cmd/convoy.go @@ -78,6 +78,7 @@ var ( convoyCloseReason string convoyCloseNotify string convoyCheckDryRun bool + convoyEpic string // --epic: link convoy to parent epic (Goals layer) ) var convoyCmd = &cobra.Command{ diff --git a/internal/cmd/sling.go b/internal/cmd/sling.go index 4370d5f2..d7f3dcdd 100644 --- a/internal/cmd/sling.go +++ b/internal/cmd/sling.go @@ -398,7 +398,7 @@ func runSling(cmd *cobra.Command, args []string) error { fmt.Printf("Would create convoy 'Work: %s'\n", info.Title) fmt.Printf("Would add tracking relation to %s\n", beadID) } else { - convoyID, err := createAutoConvoy(beadID, info.Title) + convoyID, err := createAutoConvoy(beadID, info.Title, slingEpic) if err != nil { // Log warning but don't fail - convoy is optional fmt.Printf("%s Could not create auto-convoy: %v\n", style.Dim.Render("Warning:"), err) diff --git a/internal/cmd/sling_batch.go b/internal/cmd/sling_batch.go index 8fbda8d5..5b42cfd1 100644 --- a/internal/cmd/sling_batch.go +++ b/internal/cmd/sling_batch.go @@ -87,7 +87,7 @@ func runBatchSling(beadIDs []string, rigName string, townBeadsDir string) error if !slingNoConvoy { existingConvoy := isTrackedByConvoy(beadID) if existingConvoy == "" { - convoyID, err := createAutoConvoy(beadID, info.Title) + convoyID, err := createAutoConvoy(beadID, info.Title, slingEpic) if err != nil { fmt.Printf(" %s Could not create auto-convoy: %v\n", style.Dim.Render("Warning:"), err) } else { diff --git a/internal/cmd/sling_convoy.go b/internal/cmd/sling_convoy.go index 62bc6484..3d04873d 100644 --- a/internal/cmd/sling_convoy.go +++ b/internal/cmd/sling_convoy.go @@ -61,8 +61,9 @@ func isTrackedByConvoy(beadID string) string { } // createAutoConvoy creates an auto-convoy for a single issue and tracks it. +// If epicID is provided, links the convoy to the parent epic. // Returns the created convoy ID. -func createAutoConvoy(beadID, beadTitle string) (string, error) { +func createAutoConvoy(beadID, beadTitle string, epicID string) (string, error) { townRoot, err := workspace.FindFromCwd() if err != nil { return "", fmt.Errorf("finding town root: %w", err) @@ -77,6 +78,9 @@ func createAutoConvoy(beadID, beadTitle string) (string, error) { // Create convoy with title "Work: " convoyTitle := fmt.Sprintf("Work: %s", beadTitle) description := fmt.Sprintf("Auto-created convoy tracking %s", beadID) + if epicID != "" { + description += fmt.Sprintf("\nParent-Epic: %s", epicID) + } createArgs := []string{ "create", @@ -109,6 +113,19 @@ func createAutoConvoy(beadID, beadTitle string) (string, error) { fmt.Printf("%s Could not add tracking relation: %v\n", style.Dim.Render("Warning:"), err) } + // Link convoy to parent epic if specified (Goals layer) + if epicID != "" { + epicDepArgs := []string{"--no-daemon", "dep", "add", convoyID, epicID, "--type=child_of"} + epicDepCmd := exec.Command("bd", epicDepArgs...) + epicDepCmd.Dir = townBeads + epicDepCmd.Stderr = os.Stderr + + if err := epicDepCmd.Run(); err != nil { + // Epic link failed - log warning but continue + fmt.Printf("%s Could not link convoy to epic: %v\n", style.Dim.Render("Warning:"), err) + } + } + return convoyID, nil }