diff --git a/internal/cmd/done.go b/internal/cmd/done.go index ba44e85a..2933f1ed 100644 --- a/internal/cmd/done.go +++ b/internal/cmd/done.go @@ -264,6 +264,16 @@ func runDone(cmd *cobra.Command, args []string) error { return fmt.Errorf("branch '%s' has 0 commits ahead of %s; nothing to merge\nMake and commit changes first, or use --status DEFERRED to exit without completing", branch, originDefault) } + // CRITICAL: Push branch BEFORE creating MR bead (hq-6dk53, hq-a4ksk) + // The MR bead triggers Refinery to process this branch. If the branch + // isn't pushed yet, Refinery finds nothing to merge. The worktree gets + // nuked at the end of gt done, so the commits are lost forever. + fmt.Printf("Pushing branch to remote...\n") + if err := g.Push("origin", branch, false); err != nil { + return fmt.Errorf("pushing branch '%s' to origin: %w\nCommits exist locally but failed to push. Fix the issue and retry.", branch, err) + } + fmt.Printf("%s Branch pushed to origin\n", style.Bold.Render("✓")) + if issueID == "" { return fmt.Errorf("cannot determine source issue from branch '%s'; use --issue to specify", branch) } diff --git a/internal/cmd/mayor.go b/internal/cmd/mayor.go index 0ccd4ebc..fcb50fe7 100644 --- a/internal/cmd/mayor.go +++ b/internal/cmd/mayor.go @@ -4,8 +4,11 @@ import ( "fmt" "github.com/spf13/cobra" + "github.com/steveyegge/gastown/internal/config" "github.com/steveyegge/gastown/internal/mayor" + "github.com/steveyegge/gastown/internal/session" "github.com/steveyegge/gastown/internal/style" + "github.com/steveyegge/gastown/internal/tmux" "github.com/steveyegge/gastown/internal/workspace" ) @@ -142,6 +145,14 @@ func runMayorAttach(cmd *cobra.Command, args []string) error { return err } + townRoot, err := workspace.FindFromCwdOrError() + if err != nil { + return fmt.Errorf("finding workspace: %w", err) + } + + t := tmux.NewTmux() + sessionID := mgr.SessionName() + running, err := mgr.IsRunning() if err != nil { return fmt.Errorf("checking session: %w", err) @@ -152,10 +163,45 @@ func runMayorAttach(cmd *cobra.Command, args []string) error { if err := mgr.Start(mayorAgentOverride); err != nil { return err } + } else { + // Session exists - check if runtime is still running (hq-95xfq) + // If runtime exited or sitting at shell, restart with proper context + agentCfg, _, err := config.ResolveAgentConfigWithOverride(townRoot, townRoot, mayorAgentOverride) + if err != nil { + return fmt.Errorf("resolving agent: %w", err) + } + if !t.IsAgentRunning(sessionID, config.ExpectedPaneCommands(agentCfg)...) { + // Runtime has exited, restart it with proper context + fmt.Println("Runtime exited, restarting with context...") + + paneID, err := t.GetPaneID(sessionID) + if err != nil { + return fmt.Errorf("getting pane ID: %w", err) + } + + // Build startup beacon for context (like gt handoff does) + beacon := session.FormatStartupNudge(session.StartupNudgeConfig{ + Recipient: "mayor", + Sender: "human", + Topic: "attach", + }) + + // Build startup command with beacon + startupCmd, err := config.BuildAgentStartupCommandWithAgentOverride("mayor", "mayor", "", beacon, mayorAgentOverride) + if err != nil { + return fmt.Errorf("building startup command: %w", err) + } + + if err := t.RespawnPane(paneID, startupCmd); err != nil { + return fmt.Errorf("restarting runtime: %w", err) + } + + fmt.Printf("%s Mayor restarted with context\n", style.Bold.Render("✓")) + } } // Use shared attach helper (smart: links if inside tmux, attaches if outside) - return attachToTmuxSession(mgr.SessionName()) + return attachToTmuxSession(sessionID) } func runMayorStatus(cmd *cobra.Command, args []string) error {