Add mol-polecat-work, mol-polecat-lease, mol-gastown-boot formulas; fix spawn.go to use catalog ID
This commit is contained in:
@@ -197,6 +197,36 @@ Examples:
|
||||
RunE: runCrewPristine,
|
||||
}
|
||||
|
||||
var crewNextCmd = &cobra.Command{
|
||||
Use: "next",
|
||||
Short: "Switch to next crew session in same rig",
|
||||
Long: `Switch to the next crew session in the same rig.
|
||||
|
||||
Cycles through crew sessions alphabetically. When you reach the last
|
||||
crew, wraps around to the first.
|
||||
|
||||
This command is bound to C-b n in crew sessions for quick switching.
|
||||
|
||||
Examples:
|
||||
gt crew next # Switch to next crew in same rig`,
|
||||
RunE: runCrewNext,
|
||||
}
|
||||
|
||||
var crewPrevCmd = &cobra.Command{
|
||||
Use: "prev",
|
||||
Short: "Switch to previous crew session in same rig",
|
||||
Long: `Switch to the previous crew session in the same rig.
|
||||
|
||||
Cycles through crew sessions in reverse alphabetical order. When you
|
||||
reach the first crew, wraps around to the last.
|
||||
|
||||
This command is bound to C-b p in crew sessions for quick switching.
|
||||
|
||||
Examples:
|
||||
gt crew prev # Switch to previous crew in same rig`,
|
||||
RunE: runCrewPrev,
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Add flags
|
||||
crewAddCmd.Flags().StringVar(&crewRig, "rig", "", "Rig to create crew workspace in")
|
||||
@@ -236,6 +266,8 @@ func init() {
|
||||
crewCmd.AddCommand(crewRenameCmd)
|
||||
crewCmd.AddCommand(crewPristineCmd)
|
||||
crewCmd.AddCommand(crewRestartCmd)
|
||||
crewCmd.AddCommand(crewNextCmd)
|
||||
crewCmd.AddCommand(crewPrevCmd)
|
||||
|
||||
rootCmd.AddCommand(crewCmd)
|
||||
}
|
||||
|
||||
79
internal/cmd/crew_cycle.go
Normal file
79
internal/cmd/crew_cycle.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"sort"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// cycleCrewSession switches to the next or previous crew session in the same rig.
|
||||
// direction: 1 for next, -1 for previous
|
||||
func cycleCrewSession(direction int) error {
|
||||
// Get current session
|
||||
currentSession := getCurrentTmuxSession()
|
||||
if currentSession == "" {
|
||||
return fmt.Errorf("not in a tmux session")
|
||||
}
|
||||
|
||||
// Parse rig name from current session
|
||||
rigName, _, ok := parseCrewSessionName(currentSession)
|
||||
if !ok {
|
||||
return fmt.Errorf("not in a crew session (expected gt-<rig>-crew-<name>)")
|
||||
}
|
||||
|
||||
// Find all crew sessions for this rig
|
||||
sessions, err := findRigCrewSessions(rigName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("listing sessions: %w", err)
|
||||
}
|
||||
|
||||
if len(sessions) == 0 {
|
||||
return fmt.Errorf("no crew sessions found for rig %s", rigName)
|
||||
}
|
||||
|
||||
// Sort for consistent ordering
|
||||
sort.Strings(sessions)
|
||||
|
||||
// Find current position
|
||||
currentIdx := -1
|
||||
for i, s := range sessions {
|
||||
if s == currentSession {
|
||||
currentIdx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if currentIdx == -1 {
|
||||
// Current session not in list (shouldn't happen)
|
||||
return fmt.Errorf("current session not found in crew list")
|
||||
}
|
||||
|
||||
// Calculate target index (with wrapping)
|
||||
targetIdx := (currentIdx + direction + len(sessions)) % len(sessions)
|
||||
|
||||
if targetIdx == currentIdx {
|
||||
// Only one session, nothing to switch to
|
||||
fmt.Printf("Only one crew session in rig %s\n", rigName)
|
||||
return nil
|
||||
}
|
||||
|
||||
targetSession := sessions[targetIdx]
|
||||
|
||||
// Switch to target session
|
||||
cmd := exec.Command("tmux", "switch-client", "-t", targetSession)
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("switching to %s: %w", targetSession, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func runCrewNext(cmd *cobra.Command, args []string) error {
|
||||
return cycleCrewSession(1)
|
||||
}
|
||||
|
||||
func runCrewPrev(cmd *cobra.Command, args []string) error {
|
||||
return cycleCrewSession(-1)
|
||||
}
|
||||
@@ -239,3 +239,74 @@ func ensureMainBranch(dir, roleName string) bool {
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// parseCrewSessionName extracts rig and crew name from a tmux session name.
|
||||
// Format: gt-<rig>-crew-<name>
|
||||
// Returns empty strings and false if the format doesn't match.
|
||||
func parseCrewSessionName(sessionName string) (rigName, crewName string, ok bool) {
|
||||
// Must start with "gt-" and contain "-crew-"
|
||||
if !strings.HasPrefix(sessionName, "gt-") {
|
||||
return "", "", false
|
||||
}
|
||||
|
||||
// Remove "gt-" prefix
|
||||
rest := sessionName[3:]
|
||||
|
||||
// Find "-crew-" separator
|
||||
idx := strings.Index(rest, "-crew-")
|
||||
if idx == -1 {
|
||||
return "", "", false
|
||||
}
|
||||
|
||||
rigName = rest[:idx]
|
||||
crewName = rest[idx+6:] // len("-crew-") = 6
|
||||
|
||||
if rigName == "" || crewName == "" {
|
||||
return "", "", false
|
||||
}
|
||||
|
||||
return rigName, crewName, true
|
||||
}
|
||||
|
||||
// getCurrentTmuxSession returns the current tmux session name.
|
||||
// Returns empty string if not in tmux.
|
||||
func getCurrentTmuxSession() string {
|
||||
if os.Getenv("TMUX") == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
cmd := exec.Command("tmux", "display-message", "-p", "#{session_name}")
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return strings.TrimSpace(string(out))
|
||||
}
|
||||
|
||||
// findRigCrewSessions returns all crew sessions for a given rig, sorted alphabetically.
|
||||
// Uses tmux list-sessions to find sessions matching gt-<rig>-crew-* pattern.
|
||||
func findRigCrewSessions(rigName string) ([]string, error) {
|
||||
cmd := exec.Command("tmux", "list-sessions", "-F", "#{session_name}")
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
// No tmux server or no sessions
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
prefix := fmt.Sprintf("gt-%s-crew-", rigName)
|
||||
var sessions []string
|
||||
|
||||
for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") {
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(line, prefix) {
|
||||
sessions = append(sessions, line)
|
||||
}
|
||||
}
|
||||
|
||||
// Sessions are already sorted by tmux, but sort explicitly for consistency
|
||||
// (alphabetical by session name means alphabetical by crew name)
|
||||
return sessions, nil
|
||||
}
|
||||
|
||||
@@ -104,9 +104,8 @@ func runSpawn(cmd *cobra.Command, args []string) error {
|
||||
// Auto-use mol-polecat-work for issue-based spawns (Phase 3: Polecat Work Cycle)
|
||||
// This gives polecats a structured workflow with checkpoints for crash recovery.
|
||||
// Can be overridden with explicit --molecule flag.
|
||||
// Note: gt-lwuu is the proto ID for mol-polecat-work
|
||||
if spawnIssue != "" && spawnMolecule == "" {
|
||||
spawnMolecule = "gt-lwuu"
|
||||
spawnMolecule = "mol-polecat-work"
|
||||
}
|
||||
|
||||
// Find workspace first (needed for rig inference)
|
||||
|
||||
Reference in New Issue
Block a user