feat(down): add --polecats flag and deprecate gt stop command
Issue #336: Consolidate down/shutdown/stop commands Changes: - Add `gt down --polecats` flag to stop all polecat sessions - Deprecate `gt stop` command (prints warning, directs to `gt down --polecats`) - Update help text to clarify down vs shutdown distinction: - down = pause (reversible, keeps worktrees) - shutdown = done (permanent cleanup) - Integrate --polecats with new --dry-run mode from recent PR Note: The issue proposed renaming --nuke to --tmux, but PR #330 just landed with --nuke having better safety (GT_NUKE_ACKNOWLEDGED env var), so keeping --nuke as-is. The new --polecats flag absorbs gt stop functionality as proposed. Closes #336 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
committed by
Steve Yegge
parent
3246c7c6b7
commit
dab619b3d0
@@ -12,8 +12,12 @@ import (
|
||||
"github.com/gofrs/flock"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/steveyegge/gastown/internal/beads"
|
||||
"github.com/steveyegge/gastown/internal/config"
|
||||
"github.com/steveyegge/gastown/internal/daemon"
|
||||
"github.com/steveyegge/gastown/internal/events"
|
||||
"github.com/steveyegge/gastown/internal/git"
|
||||
"github.com/steveyegge/gastown/internal/polecat"
|
||||
"github.com/steveyegge/gastown/internal/rig"
|
||||
"github.com/steveyegge/gastown/internal/session"
|
||||
"github.com/steveyegge/gastown/internal/style"
|
||||
"github.com/steveyegge/gastown/internal/tmux"
|
||||
@@ -29,10 +33,15 @@ var downCmd = &cobra.Command{
|
||||
Use: "down",
|
||||
GroupID: GroupServices,
|
||||
Short: "Stop all Gas Town services",
|
||||
Long: `Stop all Gas Town long-lived services.
|
||||
Long: `Stop Gas Town services (reversible pause).
|
||||
|
||||
This gracefully shuts down all infrastructure agents:
|
||||
Shutdown levels (progressively more aggressive):
|
||||
gt down Stop infrastructure (default)
|
||||
gt down --polecats Also stop all polecat sessions
|
||||
gt down --all Also stop bd daemons/activity
|
||||
gt down --nuke Also kill the tmux server (DESTRUCTIVE)
|
||||
|
||||
Infrastructure agents stopped:
|
||||
• Refineries - Per-rig work processors
|
||||
• Witnesses - Per-rig polecat managers
|
||||
• Mayor - Global work coordinator
|
||||
@@ -40,28 +49,29 @@ This gracefully shuts down all infrastructure agents:
|
||||
• Deacon - Health orchestrator
|
||||
• Daemon - Go background process
|
||||
|
||||
With --all, also stops resurrection layer (bd daemon/activity) and verifies
|
||||
shutdown. Polecats are NOT stopped - use 'gt swarm stop' for that.
|
||||
This is a "pause" operation - use 'gt start' to bring everything back up.
|
||||
For permanent cleanup (removing worktrees), use 'gt shutdown' instead.
|
||||
|
||||
Flags:
|
||||
--all Stop bd daemons/activity, verify complete shutdown
|
||||
--nuke Kill entire tmux server (DESTRUCTIVE!)
|
||||
--dry-run Preview what would be stopped
|
||||
--force Skip graceful shutdown, use SIGKILL`,
|
||||
Use cases:
|
||||
• Taking a break (stop token consumption)
|
||||
• Clean shutdown before system maintenance
|
||||
• Resetting the town to a clean state`,
|
||||
RunE: runDown,
|
||||
}
|
||||
|
||||
var (
|
||||
downQuiet bool
|
||||
downForce bool
|
||||
downAll bool
|
||||
downNuke bool
|
||||
downDryRun bool
|
||||
downQuiet bool
|
||||
downForce bool
|
||||
downAll bool
|
||||
downNuke bool
|
||||
downDryRun bool
|
||||
downPolecats bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
downCmd.Flags().BoolVarP(&downQuiet, "quiet", "q", false, "Only show errors")
|
||||
downCmd.Flags().BoolVarP(&downForce, "force", "f", false, "Force kill without graceful shutdown")
|
||||
downCmd.Flags().BoolVarP(&downPolecats, "polecats", "p", false, "Also stop all polecat sessions")
|
||||
downCmd.Flags().BoolVarP(&downAll, "all", "a", false, "Stop bd daemons/activity and verify shutdown")
|
||||
downCmd.Flags().BoolVar(&downNuke, "nuke", false, "Kill entire tmux server (DESTRUCTIVE - kills non-GT sessions!)")
|
||||
downCmd.Flags().BoolVar(&downDryRun, "dry-run", false, "Preview what would be stopped without taking action")
|
||||
@@ -94,6 +104,32 @@ func runDown(cmd *cobra.Command, args []string) error {
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
rigs := discoverRigs(townRoot)
|
||||
|
||||
// Phase 0.5: Stop polecats if --polecats
|
||||
if downPolecats {
|
||||
if downDryRun {
|
||||
fmt.Println("Would stop polecats...")
|
||||
} else {
|
||||
fmt.Println("Stopping polecats...")
|
||||
}
|
||||
polecatsStopped := stopAllPolecats(t, townRoot, rigs, downForce, downDryRun)
|
||||
if downDryRun {
|
||||
if polecatsStopped > 0 {
|
||||
printDownStatus("Polecats", true, fmt.Sprintf("%d would stop", polecatsStopped))
|
||||
} else {
|
||||
printDownStatus("Polecats", true, "none running")
|
||||
}
|
||||
} else {
|
||||
if polecatsStopped > 0 {
|
||||
printDownStatus("Polecats", true, fmt.Sprintf("%d stopped", polecatsStopped))
|
||||
} else {
|
||||
printDownStatus("Polecats", true, "none running")
|
||||
}
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// Phase 1: Stop bd resurrection layer (--all only)
|
||||
if downAll {
|
||||
daemonsKilled, activityKilled, err := beads.StopAllBdProcesses(downDryRun, downForce)
|
||||
@@ -122,8 +158,6 @@ func runDown(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
}
|
||||
|
||||
rigs := discoverRigs(townRoot)
|
||||
|
||||
// Phase 2a: Stop refineries
|
||||
for _, rigName := range rigs {
|
||||
sessionName := fmt.Sprintf("gt-%s-refinery", rigName)
|
||||
@@ -261,6 +295,9 @@ func runDown(cmd *cobra.Command, args []string) error {
|
||||
stoppedServices = append(stoppedServices, fmt.Sprintf("%s/refinery", rigName))
|
||||
stoppedServices = append(stoppedServices, fmt.Sprintf("%s/witness", rigName))
|
||||
}
|
||||
if downPolecats {
|
||||
stoppedServices = append(stoppedServices, "polecats")
|
||||
}
|
||||
if downAll {
|
||||
stoppedServices = append(stoppedServices, "bd-processes")
|
||||
}
|
||||
@@ -276,6 +313,52 @@ func runDown(cmd *cobra.Command, args []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// stopAllPolecats stops all polecat sessions across all rigs.
|
||||
// Returns the number of polecats stopped (or would be stopped in dry-run).
|
||||
func stopAllPolecats(t *tmux.Tmux, townRoot string, rigNames []string, force bool, dryRun bool) int {
|
||||
stopped := 0
|
||||
|
||||
// Load rigs config
|
||||
rigsConfigPath := filepath.Join(townRoot, "mayor", "rigs.json")
|
||||
rigsConfig, err := config.LoadRigsConfig(rigsConfigPath)
|
||||
if err != nil {
|
||||
rigsConfig = &config.RigsConfig{Rigs: make(map[string]config.RigEntry)}
|
||||
}
|
||||
|
||||
g := git.NewGit(townRoot)
|
||||
rigMgr := rig.NewManager(townRoot, rigsConfig, g)
|
||||
|
||||
for _, rigName := range rigNames {
|
||||
r, err := rigMgr.GetRig(rigName)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
polecatMgr := polecat.NewSessionManager(t, r)
|
||||
infos, err := polecatMgr.List()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, info := range infos {
|
||||
if dryRun {
|
||||
stopped++
|
||||
fmt.Printf(" %s [%s] %s would stop\n", style.Dim.Render("○"), rigName, info.Polecat)
|
||||
continue
|
||||
}
|
||||
err := polecatMgr.Stop(info.Polecat, force)
|
||||
if err == nil {
|
||||
stopped++
|
||||
fmt.Printf(" %s [%s] %s stopped\n", style.SuccessPrefix, rigName, info.Polecat)
|
||||
} else {
|
||||
fmt.Printf(" %s [%s] %s: %s\n", style.ErrorPrefix, rigName, info.Polecat, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return stopped
|
||||
}
|
||||
|
||||
func printDownStatus(name string, ok bool, detail string) {
|
||||
if downQuiet && ok {
|
||||
return
|
||||
|
||||
@@ -66,11 +66,15 @@ To stop Gas Town, use 'gt shutdown'.`,
|
||||
var shutdownCmd = &cobra.Command{
|
||||
Use: "shutdown",
|
||||
GroupID: GroupServices,
|
||||
Short: "Shutdown Gas Town",
|
||||
Short: "Shutdown Gas Town with cleanup",
|
||||
Long: `Shutdown Gas Town by stopping agents and cleaning up polecats.
|
||||
|
||||
By default, preserves crew sessions (your persistent workspaces).
|
||||
Prompts for confirmation before stopping.
|
||||
This is the "done for the day" command - it stops everything AND removes
|
||||
polecat worktrees/branches. For a reversible pause, use 'gt down' instead.
|
||||
|
||||
Comparison:
|
||||
gt down - Pause (stop processes, keep worktrees) - reversible
|
||||
gt shutdown - Done (stop + cleanup worktrees) - permanent cleanup
|
||||
|
||||
After killing sessions, polecats are cleaned up:
|
||||
- Worktrees are removed
|
||||
@@ -78,9 +82,9 @@ After killing sessions, polecats are cleaned up:
|
||||
- Polecats with uncommitted work are SKIPPED (protected)
|
||||
|
||||
Shutdown levels (progressively more aggressive):
|
||||
(default) - Stop infrastructure (Mayor, Deacon, Witnesses, Refineries, Polecats)
|
||||
(default) - Stop infrastructure + polecats + cleanup
|
||||
--all - Also stop crew sessions
|
||||
--polecats-only - Only stop polecats (leaves everything else running)
|
||||
--polecats-only - Only stop polecats (leaves infrastructure running)
|
||||
|
||||
Use --force or --yes to skip confirmation prompt.
|
||||
Use --graceful to allow agents time to save state before killing.
|
||||
|
||||
@@ -23,15 +23,19 @@ var (
|
||||
)
|
||||
|
||||
var stopCmd = &cobra.Command{
|
||||
Use: "stop",
|
||||
GroupID: GroupServices,
|
||||
Short: "Emergency stop for sessions",
|
||||
Long: `Emergency stop command for Gas Town sessions.
|
||||
Use: "stop",
|
||||
GroupID: GroupServices,
|
||||
Short: "Emergency stop for sessions (deprecated: use 'gt down --polecats')",
|
||||
Deprecated: "use 'gt down --polecats' instead",
|
||||
Long: `DEPRECATED: This command is deprecated. Use 'gt down --polecats' instead.
|
||||
|
||||
Emergency stop command for Gas Town sessions.
|
||||
|
||||
Stops all running polecat sessions across rigs. Use for emergency shutdown
|
||||
when you need to halt all agent activity immediately.
|
||||
|
||||
Examples:
|
||||
gt down --polecats # Stop all polecats (preferred)
|
||||
gt stop --all # Kill ALL sessions across all rigs
|
||||
gt stop --rig wyvern # Kill all sessions in the wyvern rig
|
||||
gt stop --all --graceful # Try graceful shutdown first`,
|
||||
@@ -55,6 +59,10 @@ type StopResult struct {
|
||||
}
|
||||
|
||||
func runStop(cmd *cobra.Command, args []string) error {
|
||||
// Print deprecation warning
|
||||
fmt.Printf("%s 'gt stop' is deprecated. Use 'gt down --polecats' instead.\n\n",
|
||||
style.Warning.Render("Warning:"))
|
||||
|
||||
if !stopAll && stopRig == "" {
|
||||
return fmt.Errorf("must specify --all or --rig <name>")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user