From aa3c1d41ef46035ec159ddfe545f975b3f8f464e Mon Sep 17 00:00:00 2001 From: Steve Yegge Date: Fri, 19 Dec 2025 01:45:56 -0800 Subject: [PATCH] Add done/finish and reset commands for polecat state management MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds two new commands to transition polecats back to idle state: - gt polecat done (alias: finish): Transitions from working/done/stuck states to idle, clearing the assigned issue. For normal workflow when work is complete but session was not properly cleaned up. - gt polecat reset: Force resets any state to idle. For recovery when polecat is stuck in an unexpected state. Both commands check that the session is stopped before modifying state. Fixes gt-s3m0 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- internal/cmd/polecat.go | 89 +++++++++++++++++++++++++++++++++++++ internal/polecat/manager.go | 36 +++++++++++++++ 2 files changed, 125 insertions(+) diff --git a/internal/cmd/polecat.go b/internal/cmd/polecat.go index e73accc3..39ca75ca 100644 --- a/internal/cmd/polecat.go +++ b/internal/cmd/polecat.go @@ -110,6 +110,39 @@ Example: RunE: runPolecatSleep, } +var polecatDoneCmd = &cobra.Command{ + Use: "done /", + Aliases: []string{"finish"}, + Short: "Mark polecat as done with work and return to idle", + Long: `Mark polecat as done with work and return to idle. + +Transitions: working/done/stuck → idle +Clears the assigned issue. +Fails if session is running (stop first). + +Example: + gt polecat done gastown/Toast + gt polecat finish gastown/Toast`, + Args: cobra.ExactArgs(1), + RunE: runPolecatDone, +} + +var polecatResetCmd = &cobra.Command{ + Use: "reset /", + Short: "Force reset polecat to idle state", + Long: `Force reset polecat to idle state. + +Transitions: any state → idle +Clears the assigned issue. +Use when polecat is stuck in an unexpected state. +Fails if session is running (stop first). + +Example: + gt polecat reset gastown/Toast`, + Args: cobra.ExactArgs(1), + RunE: runPolecatReset, +} + func init() { // List flags polecatListCmd.Flags().BoolVar(&polecatListJSON, "json", false, "Output as JSON") @@ -124,6 +157,8 @@ func init() { polecatCmd.AddCommand(polecatRemoveCmd) polecatCmd.AddCommand(polecatWakeCmd) polecatCmd.AddCommand(polecatSleepCmd) + polecatCmd.AddCommand(polecatDoneCmd) + polecatCmd.AddCommand(polecatResetCmd) rootCmd.AddCommand(polecatCmd) } @@ -360,3 +395,57 @@ func runPolecatSleep(cmd *cobra.Command, args []string) error { fmt.Printf("%s Polecat %s is now idle.\n", style.SuccessPrefix, polecatName) return nil } + +func runPolecatDone(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 + } + + // Check if session is running + t := tmux.NewTmux() + sessMgr := session.NewManager(t, r) + running, _ := sessMgr.IsRunning(polecatName) + if running { + return fmt.Errorf("session is running. Stop it first with: gt session stop %s/%s", rigName, polecatName) + } + + if err := mgr.Finish(polecatName); err != nil { + return fmt.Errorf("finishing polecat: %w", err) + } + + fmt.Printf("%s Polecat %s is now idle.\n", style.SuccessPrefix, polecatName) + return nil +} + +func runPolecatReset(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 + } + + // Check if session is running + t := tmux.NewTmux() + sessMgr := session.NewManager(t, r) + running, _ := sessMgr.IsRunning(polecatName) + if running { + return fmt.Errorf("session is running. Stop it first with: gt session stop %s/%s", rigName, polecatName) + } + + if err := mgr.Reset(polecatName); err != nil { + return fmt.Errorf("resetting polecat: %w", err) + } + + fmt.Printf("%s Polecat %s has been reset to idle.\n", style.SuccessPrefix, polecatName) + return nil +} diff --git a/internal/polecat/manager.go b/internal/polecat/manager.go index dfc41c47..45cc4be3 100644 --- a/internal/polecat/manager.go +++ b/internal/polecat/manager.go @@ -245,6 +245,42 @@ func (m *Manager) Sleep(name string) error { return m.SetState(name, StateIdle) } +// Finish transitions a polecat from working/done/stuck to idle and clears the issue. +func (m *Manager) Finish(name string) error { + polecat, err := m.Get(name) + if err != nil { + return err + } + + // Only allow finishing from working-related states + switch polecat.State { + case StateWorking, StateDone, StateStuck: + // OK to finish + default: + return fmt.Errorf("polecat is not in a finishing state (state: %s)", polecat.State) + } + + polecat.Issue = "" + polecat.State = StateIdle + polecat.UpdatedAt = time.Now() + + return m.saveState(polecat) +} + +// Reset forces a polecat to idle state regardless of current state. +func (m *Manager) Reset(name string) error { + polecat, err := m.Get(name) + if err != nil { + return err + } + + polecat.Issue = "" + polecat.State = StateIdle + polecat.UpdatedAt = time.Now() + + return m.saveState(polecat) +} + // saveState persists polecat state to disk. func (m *Manager) saveState(polecat *Polecat) error { data, err := json.MarshalIndent(polecat, "", " ")