Add mol-polecat-work, mol-polecat-lease, mol-gastown-boot formulas; fix spawn.go to use catalog ID
This commit is contained in:
3021
.beads/issues.jsonl
3021
.beads/issues.jsonl
File diff suppressed because one or more lines are too long
@@ -197,6 +197,36 @@ Examples:
|
|||||||
RunE: runCrewPristine,
|
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() {
|
func init() {
|
||||||
// Add flags
|
// Add flags
|
||||||
crewAddCmd.Flags().StringVar(&crewRig, "rig", "", "Rig to create crew workspace in")
|
crewAddCmd.Flags().StringVar(&crewRig, "rig", "", "Rig to create crew workspace in")
|
||||||
@@ -236,6 +266,8 @@ func init() {
|
|||||||
crewCmd.AddCommand(crewRenameCmd)
|
crewCmd.AddCommand(crewRenameCmd)
|
||||||
crewCmd.AddCommand(crewPristineCmd)
|
crewCmd.AddCommand(crewPristineCmd)
|
||||||
crewCmd.AddCommand(crewRestartCmd)
|
crewCmd.AddCommand(crewRestartCmd)
|
||||||
|
crewCmd.AddCommand(crewNextCmd)
|
||||||
|
crewCmd.AddCommand(crewPrevCmd)
|
||||||
|
|
||||||
rootCmd.AddCommand(crewCmd)
|
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
|
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)
|
// 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.
|
// This gives polecats a structured workflow with checkpoints for crash recovery.
|
||||||
// Can be overridden with explicit --molecule flag.
|
// Can be overridden with explicit --molecule flag.
|
||||||
// Note: gt-lwuu is the proto ID for mol-polecat-work
|
|
||||||
if spawnIssue != "" && spawnMolecule == "" {
|
if spawnIssue != "" && spawnMolecule == "" {
|
||||||
spawnMolecule = "gt-lwuu"
|
spawnMolecule = "mol-polecat-work"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find workspace first (needed for rig inference)
|
// Find workspace first (needed for rig inference)
|
||||||
|
|||||||
@@ -596,3 +596,19 @@ func (t *Tmux) SwitchClient(targetSession string) error {
|
|||||||
_, err := t.run("switch-client", "-t", targetSession)
|
_, err := t.run("switch-client", "-t", targetSession)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetCrewCycleBindings sets up C-b n/p to cycle through crew sessions in the same rig.
|
||||||
|
// This allows quick switching between crew members without using the session picker.
|
||||||
|
func (t *Tmux) SetCrewCycleBindings(session string) error {
|
||||||
|
// C-b n → gt crew next (switch to next crew session)
|
||||||
|
if _, err := t.run("bind-key", "-T", "prefix", "n",
|
||||||
|
"run-shell", "gt crew next"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// C-b p → gt crew prev (switch to previous crew session)
|
||||||
|
if _, err := t.run("bind-key", "-T", "prefix", "p",
|
||||||
|
"run-shell", "gt crew prev"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user