feat: Add gt polecat recycle command (gt-j9ddg)
Implements session-preserving polecat recycle: - Kills Claude session (tmux kill-session) - Preserves sandbox (worktree and branch intact) - Updates agent bead state to 'stopped' - Leaves polecat ready for respawn on next molecule step 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -11,6 +11,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/steveyegge/gastown/internal/beads"
|
||||
"github.com/steveyegge/gastown/internal/git"
|
||||
"github.com/steveyegge/gastown/internal/polecat"
|
||||
"github.com/steveyegge/gastown/internal/rig"
|
||||
@@ -220,6 +221,26 @@ Examples:
|
||||
RunE: runPolecatGC,
|
||||
}
|
||||
|
||||
var polecatRecycleCmd = &cobra.Command{
|
||||
Use: "recycle <rig>/<polecat>",
|
||||
Short: "Kill session but preserve sandbox for respawn",
|
||||
Long: `Recycle a polecat session: kill the Claude session but preserve the sandbox.
|
||||
|
||||
This command:
|
||||
- Kills the tmux session (stopping the Claude agent)
|
||||
- Preserves the worktree and branch (sandbox intact)
|
||||
- Updates agent bead state to 'stopped'
|
||||
- Leaves everything ready for respawn on next step
|
||||
|
||||
Use this between molecule steps to give polecats fresh context.
|
||||
Use 'gt polecat nuke' for full cleanup after merge.
|
||||
|
||||
Examples:
|
||||
gt polecat recycle gastown/Toast`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: runPolecatRecycle,
|
||||
}
|
||||
|
||||
var polecatGitStateCmd = &cobra.Command{
|
||||
Use: "git-state <rig>/<polecat>",
|
||||
Short: "Show git state for pre-kill verification",
|
||||
@@ -274,6 +295,7 @@ func init() {
|
||||
polecatCmd.AddCommand(polecatStatusCmd)
|
||||
polecatCmd.AddCommand(polecatGitStateCmd)
|
||||
polecatCmd.AddCommand(polecatGCCmd)
|
||||
polecatCmd.AddCommand(polecatRecycleCmd)
|
||||
|
||||
rootCmd.AddCommand(polecatCmd)
|
||||
}
|
||||
@@ -1089,6 +1111,71 @@ func runPolecatGC(cmd *cobra.Command, args []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func runPolecatRecycle(cmd *cobra.Command, args []string) error {
|
||||
rigName, polecatName, err := parseAddress(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mgr, r, err := getPolecatManager(rigName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Verify polecat exists
|
||||
p, err := mgr.Get(polecatName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("polecat '%s' not found in rig '%s'", polecatName, rigName)
|
||||
}
|
||||
|
||||
fmt.Printf("Recycling polecat %s/%s...\n", rigName, polecatName)
|
||||
|
||||
// Check if session is running
|
||||
t := tmux.NewTmux()
|
||||
sessMgr := session.NewManager(t, r)
|
||||
running, _ := sessMgr.IsRunning(polecatName)
|
||||
|
||||
if running {
|
||||
// Kill the session (preserves sandbox)
|
||||
fmt.Printf(" Stopping session...\n")
|
||||
if err := sessMgr.Stop(polecatName, false); err != nil {
|
||||
// Try force stop
|
||||
if err := sessMgr.Stop(polecatName, true); err != nil {
|
||||
return fmt.Errorf("stopping session: %w", err)
|
||||
}
|
||||
}
|
||||
fmt.Printf(" %s Session stopped\n", style.Success.Render("✓"))
|
||||
} else {
|
||||
fmt.Printf(" %s Session not running\n", style.Dim.Render("○"))
|
||||
}
|
||||
|
||||
// Update agent bead state to 'stopped'
|
||||
// Agent bead ID format: <prefix>-polecat-<rig>-<name>
|
||||
// We need to get the prefix from the rig's beads config
|
||||
beadsDir := filepath.Join(r.Path, "mayor", "rig")
|
||||
bd := beads.New(beadsDir)
|
||||
|
||||
// Find the agent bead by searching for type=agent matching this polecat
|
||||
// Agent bead ID pattern: gt-polecat-<rig>-<name>
|
||||
agentBeadID := fmt.Sprintf("gt-polecat-%s-%s", rigName, polecatName)
|
||||
|
||||
// Try to update agent state
|
||||
fmt.Printf(" Updating agent state...\n")
|
||||
if err := bd.UpdateAgentState(agentBeadID, "stopped", nil); err != nil {
|
||||
// Non-fatal - agent bead might not exist yet
|
||||
fmt.Printf(" %s Agent bead not found (ok for new polecats)\n", style.Dim.Render("○"))
|
||||
} else {
|
||||
fmt.Printf(" %s Agent state: stopped\n", style.Success.Render("✓"))
|
||||
}
|
||||
|
||||
// Report sandbox preserved
|
||||
fmt.Printf(" %s Sandbox preserved: %s\n", style.Success.Render("✓"), style.Dim.Render(p.ClonePath))
|
||||
fmt.Printf(" %s Branch: %s\n", style.Success.Render("✓"), style.Dim.Render(p.Branch))
|
||||
|
||||
fmt.Printf("\n%s Polecat recycled. Ready for respawn.\n", style.SuccessPrefix)
|
||||
return nil
|
||||
}
|
||||
|
||||
// splitLines splits a string into non-empty lines.
|
||||
func splitLines(s string) []string {
|
||||
var lines []string
|
||||
|
||||
Reference in New Issue
Block a user