refactor: Add CleanupStatus type to replace raw strings (gt-77gq7)

Introduces a typed CleanupStatus with constants:
- CleanupClean, CleanupUncommitted, CleanupStash, CleanupUnpushed, CleanupUnknown

Adds helper methods:
- IsSafe(): true for clean status
- RequiresRecovery(): true for uncommitted/stash/unpushed
- CanForceRemove(): true if force flag can bypass

Updated files to use the new type:
- internal/polecat/types.go: Type definition and methods
- internal/polecat/manager.go: Validation logic
- internal/witness/handlers.go: Nuke safety checks
- internal/cmd/done.go: Status reporting
- internal/cmd/polecat.go: Recovery status checks

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
blackfinger
2026-01-05 00:19:00 -08:00
committed by Steve Yegge
parent 18578b3030
commit 904a773ade
5 changed files with 122 additions and 67 deletions

View File

@@ -11,6 +11,7 @@ import (
"github.com/steveyegge/gastown/internal/events"
"github.com/steveyegge/gastown/internal/git"
"github.com/steveyegge/gastown/internal/mail"
"github.com/steveyegge/gastown/internal/polecat"
"github.com/steveyegge/gastown/internal/rig"
"github.com/steveyegge/gastown/internal/style"
"github.com/steveyegge/gastown/internal/workspace"
@@ -429,8 +430,8 @@ func updateAgentStateOnDone(cwd, townRoot, exitType, _ string) { // issueID unus
// ZFC #10: Self-report cleanup status
// Compute git state and report so Witness can decide removal safety
cleanupStatus := computeCleanupStatus(cwd)
if cleanupStatus != "" {
if err := bd.UpdateAgentCleanupStatus(agentBeadID, cleanupStatus); err != nil {
if cleanupStatus != polecat.CleanupUnknown {
if err := bd.UpdateAgentCleanupStatus(agentBeadID, string(cleanupStatus)); err != nil {
// Log warning instead of silent ignore
fmt.Fprintf(os.Stderr, "Warning: couldn't update agent %s cleanup status: %v\n", agentBeadID, err)
return
@@ -461,23 +462,23 @@ func getDispatcherFromBead(cwd, issueID string) string {
// computeCleanupStatus checks git state and returns the cleanup status.
// Returns the most critical issue: has_unpushed > has_stash > has_uncommitted > clean
func computeCleanupStatus(cwd string) string {
func computeCleanupStatus(cwd string) polecat.CleanupStatus {
g := git.NewGit(cwd)
status, err := g.CheckUncommittedWork()
if err != nil {
// If we can't check, report unknown - Witness should be cautious
return "unknown"
return polecat.CleanupUnknown
}
// Check in priority order (most critical first)
if status.UnpushedCommits > 0 {
return "has_unpushed"
return polecat.CleanupUnpushed
}
if status.StashCount > 0 {
return "has_stash"
return polecat.CleanupStash
}
if status.HasUncommittedChanges {
return "has_uncommitted"
return polecat.CleanupUncommitted
}
return "clean"
return polecat.CleanupClean
}

View File

@@ -933,12 +933,12 @@ func getGitState(worktreePath string) (*GitState, error) {
// RecoveryStatus represents whether a polecat needs recovery or is safe to nuke.
type RecoveryStatus struct {
Rig string `json:"rig"`
Polecat string `json:"polecat"`
CleanupStatus string `json:"cleanup_status"`
NeedsRecovery bool `json:"needs_recovery"`
Verdict string `json:"verdict"` // SAFE_TO_NUKE or NEEDS_RECOVERY
Branch string `json:"branch,omitempty"`
Rig string `json:"rig"`
Polecat string `json:"polecat"`
CleanupStatus polecat.CleanupStatus `json:"cleanup_status"`
NeedsRecovery bool `json:"needs_recovery"`
Verdict string `json:"verdict"` // SAFE_TO_NUKE or NEEDS_RECOVERY
Branch string `json:"branch,omitempty"`
Issue string `json:"issue,omitempty"`
}
@@ -978,38 +978,35 @@ func runPolecatCheckRecovery(cmd *cobra.Command, args []string) error {
// This handles polecats that haven't self-reported yet
gitState, gitErr := getGitState(p.ClonePath)
if gitErr != nil {
status.CleanupStatus = "unknown"
status.CleanupStatus = polecat.CleanupUnknown
status.NeedsRecovery = true
status.Verdict = "NEEDS_RECOVERY"
} else if gitState.Clean {
status.CleanupStatus = "clean"
status.CleanupStatus = polecat.CleanupClean
status.NeedsRecovery = false
status.Verdict = "SAFE_TO_NUKE"
} else if gitState.UnpushedCommits > 0 {
status.CleanupStatus = "has_unpushed"
status.CleanupStatus = polecat.CleanupUnpushed
status.NeedsRecovery = true
status.Verdict = "NEEDS_RECOVERY"
} else if gitState.StashCount > 0 {
status.CleanupStatus = "has_stash"
status.CleanupStatus = polecat.CleanupStash
status.NeedsRecovery = true
status.Verdict = "NEEDS_RECOVERY"
} else {
status.CleanupStatus = "has_uncommitted"
status.CleanupStatus = polecat.CleanupUncommitted
status.NeedsRecovery = true
status.Verdict = "NEEDS_RECOVERY"
}
} else {
// Use cleanup_status from agent bead
status.CleanupStatus = fields.CleanupStatus
switch fields.CleanupStatus {
case "clean":
status.CleanupStatus = polecat.CleanupStatus(fields.CleanupStatus)
if status.CleanupStatus.IsSafe() {
status.NeedsRecovery = false
status.Verdict = "SAFE_TO_NUKE"
case "has_uncommitted", "has_unpushed", "has_stash":
status.NeedsRecovery = true
status.Verdict = "NEEDS_RECOVERY"
default:
// Unknown or empty - be conservative
} else {
// RequiresRecovery covers uncommitted, stash, unpushed
// Unknown/empty also treated conservatively
status.NeedsRecovery = true
status.Verdict = "NEEDS_RECOVERY"
}
@@ -1238,19 +1235,20 @@ func runPolecatNuke(cmd *cobra.Command, args []string) error {
}
} else {
// Check cleanup_status from agent bead
switch fields.CleanupStatus {
case "clean":
cleanupStatus := polecat.CleanupStatus(fields.CleanupStatus)
switch cleanupStatus {
case polecat.CleanupClean:
// OK
case "has_unpushed":
case polecat.CleanupUnpushed:
reasons = append(reasons, "has unpushed commits")
case "has_uncommitted":
case polecat.CleanupUncommitted:
reasons = append(reasons, "has uncommitted changes")
case "has_stash":
case polecat.CleanupStash:
reasons = append(reasons, "has stashed changes")
case "unknown", "":
case polecat.CleanupUnknown, "":
reasons = append(reasons, "cleanup status unknown")
default:
reasons = append(reasons, fmt.Sprintf("cleanup status: %s", fields.CleanupStatus))
reasons = append(reasons, fmt.Sprintf("cleanup status: %s", cleanupStatus))
}
// Check 3: Work on hook (check both Issue.HookBead from slot and fields.HookBead)
@@ -1337,10 +1335,11 @@ func runPolecatNuke(cmd *cobra.Command, args []string) error {
}
fmt.Printf(" - Hook: %s\n", style.Dim.Render("unknown (no agent bead)"))
} else {
if fields.CleanupStatus == "clean" {
cleanupStatus := polecat.CleanupStatus(fields.CleanupStatus)
if cleanupStatus.IsSafe() {
fmt.Printf(" - Git state: %s\n", style.Success.Render("clean"))
} else if fields.CleanupStatus != "" {
fmt.Printf(" - Git state: %s (%s)\n", style.Error.Render("dirty"), fields.CleanupStatus)
} else if cleanupStatus.RequiresRecovery() {
fmt.Printf(" - Git state: %s (%s)\n", style.Error.Render("dirty"), cleanupStatus)
} else {
fmt.Printf(" - Git state: %s\n", style.Warning.Render("unknown"))
}