From c1dde066ff8d0b7e6056da1339b7e2819275e428 Mon Sep 17 00:00:00 2001 From: Steve Yegge Date: Fri, 19 Dec 2025 23:22:50 -0800 Subject: [PATCH] feat(handoff): Auto-submit MR to merge queue when polecat shuts down MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a polecat runs `gt handoff` (default: shutdown action), the current branch is now automatically submitted to the merge queue if it follows the polecat// naming convention. This streamlines the polecat workflow: - Work on assigned issue - Commit changes - Run `gt handoff` (automatically submits MR + retires) The Refinery will process the merge request. If MR submission fails, a warning is printed but handoff continues. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- internal/cmd/handoff.go | 65 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 62 insertions(+), 3 deletions(-) diff --git a/internal/cmd/handoff.go b/internal/cmd/handoff.go index 7c405e9d..a9ec7037 100644 --- a/internal/cmd/handoff.go +++ b/internal/cmd/handoff.go @@ -31,14 +31,21 @@ var handoffCmd = &cobra.Command{ This command initiates graceful retirement: 1. Verifies git state is clean -2. Sends handoff mail to yourself (for cycle) -3. Sends lifecycle request to your manager -4. Sets requesting state and waits for retirement +2. For polecats (shutdown): auto-submits MR to merge queue +3. Sends handoff mail to yourself (for cycle) +4. Sends lifecycle request to your manager +5. Sets requesting state and waits for retirement Your manager (daemon for Mayor/Witness, witness for polecats) will verify the request and terminate your session. For cycle/restart, a new session starts and reads your handoff mail to continue work. +Polecat auto-MR: +When a polecat runs 'gt handoff' (default: shutdown), the current branch +is automatically submitted to the merge queue if it follows the +polecat// naming convention. The Refinery will process +the merge request. + Flags: --cycle Restart with handoff mail (default for Mayor/Witness) --restart Fresh restart, no handoff context @@ -96,6 +103,17 @@ func runHandoff(cmd *cobra.Command, args []string) error { } } + // For polecats shutting down with work complete, auto-submit MR to merge queue + if role == RolePolecat && action == HandoffShutdown { + if err := submitMRForPolecat(); err != nil { + // Non-fatal: warn but continue with handoff + fmt.Printf("%s Could not auto-submit MR: %v\n", style.Warning.Render("Warning:"), err) + fmt.Println(style.Dim.Render(" You may need to run 'gt mq submit' manually")) + } else { + fmt.Printf("%s Auto-submitted work to merge queue\n", style.Bold.Render("✓")) + } + } + // For cycle, update handoff bead for successor if action == HandoffCycle { if err := sendHandoffMail(role, townRoot); err != nil { @@ -334,6 +352,47 @@ Please verify state and execute lifecycle action. return nil } +// submitMRForPolecat submits the current branch to the merge queue. +// This is called automatically when a polecat shuts down with completed work. +func submitMRForPolecat() error { + // Check if we're on a polecat branch with work to submit + cmd := exec.Command("git", "branch", "--show-current") + out, err := cmd.Output() + if err != nil { + return fmt.Errorf("getting current branch: %w", err) + } + branch := strings.TrimSpace(string(out)) + + // Skip if on main/master (no work to submit) + if branch == "main" || branch == "master" || branch == "" { + return nil // Nothing to submit, that's OK + } + + // Check if branch follows polecat// pattern + parts := strings.Split(branch, "/") + if len(parts) < 3 || parts[0] != "polecat" { + // Not a polecat work branch, skip + return nil + } + + // Run gt mq submit + submitCmd := exec.Command("gt", "mq", "submit") + submitOutput, err := submitCmd.CombinedOutput() + if err != nil { + return fmt.Errorf("%s", strings.TrimSpace(string(submitOutput))) + } + + // Print the submit output (trimmed) + output := strings.TrimSpace(string(submitOutput)) + if output != "" { + for _, line := range strings.Split(output, "\n") { + fmt.Printf(" %s\n", line) + } + } + + return nil +} + // setRequestingState updates state.json to indicate we're requesting lifecycle action. func setRequestingState(role Role, action HandoffAction, townRoot string) error { // Determine state file location based on role