diff --git a/internal/cmd/sling.go b/internal/cmd/sling.go index 55e7caad..4370d5f2 100644 --- a/internal/cmd/sling.go +++ b/internal/cmd/sling.go @@ -377,9 +377,21 @@ func runSling(cmd *cobra.Command, args []string) error { } } - // Auto-convoy: check if issue is already tracked by a convoy - // If not, create one for dashboard visibility (unless --no-convoy is set) - if !slingNoConvoy && formulaName == "" { + // Convoy handling: --convoy adds to existing, otherwise auto-create (unless --no-convoy) + if slingConvoy != "" { + // Use existing convoy specified by --convoy flag + if slingDryRun { + fmt.Printf("Would add to convoy %s\n", slingConvoy) + fmt.Printf("Would add tracking relation to %s\n", beadID) + } else { + if err := addToExistingConvoy(slingConvoy, beadID); err != nil { + return fmt.Errorf("adding to convoy: %w", err) + } + fmt.Printf("%s Added to convoy %s\n", style.Bold.Render("→"), slingConvoy) + } + } else if !slingNoConvoy && formulaName == "" { + // Auto-convoy: check if issue is already tracked by a convoy + // If not, create one for dashboard visibility existingConvoy := isTrackedByConvoy(beadID) if existingConvoy == "" { if slingDryRun { diff --git a/internal/cmd/sling_convoy.go b/internal/cmd/sling_convoy.go index 51429a84..62bc6484 100644 --- a/internal/cmd/sling_convoy.go +++ b/internal/cmd/sling_convoy.go @@ -112,6 +112,45 @@ func createAutoConvoy(beadID, beadTitle string) (string, error) { return convoyID, nil } +// addToExistingConvoy adds a bead to an existing convoy by creating a tracking relation. +// Returns an error if the convoy doesn't exist or the tracking relation fails. +func addToExistingConvoy(convoyID, beadID string) error { + townRoot, err := workspace.FindFromCwd() + if err != nil { + return fmt.Errorf("finding town root: %w", err) + } + + townBeads := filepath.Join(townRoot, ".beads") + dbPath := filepath.Join(townBeads, "beads.db") + + // Verify convoy exists and is open + query := fmt.Sprintf(` + SELECT id FROM issues + WHERE id = '%s' + AND issue_type = 'convoy' + AND status = 'open' + `, convoyID) + + queryCmd := exec.Command("sqlite3", dbPath, query) + out, err := queryCmd.Output() + if err != nil || strings.TrimSpace(string(out)) == "" { + return fmt.Errorf("convoy %s not found or not open", convoyID) + } + + // Add tracking relation: convoy tracks the issue + trackBeadID := formatTrackBeadID(beadID) + depArgs := []string{"--no-daemon", "dep", "add", convoyID, trackBeadID, "--type=tracks"} + depCmd := exec.Command("bd", depArgs...) + depCmd.Dir = townBeads + depCmd.Stderr = os.Stderr + + if err := depCmd.Run(); err != nil { + return fmt.Errorf("adding tracking relation: %w", err) + } + + return nil +} + // formatTrackBeadID formats a bead ID for use in convoy tracking dependencies. // Cross-rig beads (non-hq- prefixed) are formatted as external references // so the bd tool can resolve them when running from HQ context.