From d5f4188ed665b35566bc346c284646e57e33c9ad Mon Sep 17 00:00:00 2001 From: Steve Yegge Date: Sun, 21 Dec 2025 11:28:05 -0800 Subject: [PATCH] feat: add --mail flag to gt rig reset to clear stale messages Closes gt-48bs: gt rig reset now clears stale mail messages. - Non-pinned messages are closed with reason 'Cleared during reset' - Pinned messages have their content cleared but remain open - Works with both --mail flag and default reset (all state) --- internal/beads/beads.go | 54 +++++++++++++++++++++++++++++++++++++++++ internal/cmd/rig.go | 23 +++++++++++++++--- 2 files changed, 74 insertions(+), 3 deletions(-) diff --git a/internal/beads/beads.go b/internal/beads/beads.go index ce072392..9fa6ff20 100644 --- a/internal/beads/beads.go +++ b/internal/beads/beads.go @@ -547,6 +547,60 @@ func (b *Beads) ClearHandoffContent(role string) error { return b.Update(issue.ID, UpdateOptions{Description: &empty}) } +// ClearMailResult contains statistics from a ClearMail operation. +type ClearMailResult struct { + Closed int // Number of messages closed + Cleared int // Number of pinned messages cleared (content removed) +} + +// ClearMail closes or clears all open messages. +// Non-pinned messages are closed with the given reason. +// Pinned messages have their description cleared but remain open. +func (b *Beads) ClearMail(reason string) (*ClearMailResult, error) { + // List all open messages + issues, err := b.List(ListOptions{ + Status: "open", + Type: "message", + Priority: -1, + }) + if err != nil { + return nil, fmt.Errorf("listing messages: %w", err) + } + + result := &ClearMailResult{} + + // Separate pinned from non-pinned + var toClose []string + var toClear []*Issue + + for _, issue := range issues { + if issue.Status == StatusPinned { + toClear = append(toClear, issue) + } else { + toClose = append(toClose, issue.ID) + } + } + + // Close non-pinned messages in batch + if len(toClose) > 0 { + if err := b.CloseWithReason(reason, toClose...); err != nil { + return nil, fmt.Errorf("closing messages: %w", err) + } + result.Closed = len(toClose) + } + + // Clear pinned messages + empty := "" + for _, issue := range toClear { + if err := b.Update(issue.ID, UpdateOptions{Description: &empty}); err != nil { + return nil, fmt.Errorf("clearing pinned message %s: %w", issue.ID, err) + } + result.Cleared++ + } + + return result, nil +} + // MRFields holds the structured fields for a merge-request issue. // These fields are stored as key: value lines in the issue description. type MRFields struct { diff --git a/internal/cmd/rig.go b/internal/cmd/rig.go index b5277626..bbda723f 100644 --- a/internal/cmd/rig.go +++ b/internal/cmd/rig.go @@ -71,14 +71,15 @@ var rigRemoveCmd = &cobra.Command{ var rigResetCmd = &cobra.Command{ Use: "reset", - Short: "Reset rig state (handoff content, etc.)", + Short: "Reset rig state (handoff content, mail, etc.)", Long: `Reset various rig state. By default, resets all resettable state. Use flags to reset specific items. Examples: gt rig reset # Reset all state - gt rig reset --handoff # Clear handoff content only`, + gt rig reset --handoff # Clear handoff content only + gt rig reset --mail # Clear stale mail messages only`, RunE: runRigReset, } @@ -113,6 +114,7 @@ var ( rigAddPrefix string rigAddCrew string rigResetHandoff bool + rigResetMail bool rigResetRole string rigShutdownForce bool rigShutdownNuclear bool @@ -130,6 +132,7 @@ func init() { rigAddCmd.Flags().StringVar(&rigAddCrew, "crew", "main", "Default crew workspace name") rigResetCmd.Flags().BoolVar(&rigResetHandoff, "handoff", false, "Clear handoff content") + rigResetCmd.Flags().BoolVar(&rigResetMail, "mail", false, "Clear stale mail messages") rigResetCmd.Flags().StringVar(&rigResetRole, "role", "", "Role to reset (default: auto-detect from cwd)") rigShutdownCmd.Flags().BoolVarP(&rigShutdownForce, "force", "f", false, "Force immediate shutdown") @@ -319,7 +322,7 @@ func runRigReset(cmd *cobra.Command, args []string) error { } // If no specific flags, reset all; otherwise only reset what's specified - resetAll := !rigResetHandoff + resetAll := !rigResetHandoff && !rigResetMail bd := beads.New(townRoot) @@ -331,6 +334,20 @@ func runRigReset(cmd *cobra.Command, args []string) error { fmt.Printf("%s Cleared handoff content for %s\n", style.Success.Render("✓"), roleKey) } + // Clear stale mail messages + if resetAll || rigResetMail { + result, err := bd.ClearMail("Cleared during reset") + if err != nil { + return fmt.Errorf("clearing mail: %w", err) + } + if result.Closed > 0 || result.Cleared > 0 { + fmt.Printf("%s Cleared mail: %d closed, %d pinned cleared\n", + style.Success.Render("✓"), result.Closed, result.Cleared) + } else { + fmt.Printf("%s No mail to clear\n", style.Success.Render("✓")) + } + } + return nil }