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:
@@ -103,39 +103,48 @@ func (m *Manager) agentBeadID(name string) string {
|
||||
}
|
||||
|
||||
// getCleanupStatusFromBead reads the cleanup_status from the polecat's agent bead.
|
||||
// Returns empty string if the bead doesn't exist or has no cleanup_status.
|
||||
// Returns CleanupUnknown if the bead doesn't exist or has no cleanup_status.
|
||||
// ZFC #10: This is the ZFC-compliant way to check if removal is safe.
|
||||
func (m *Manager) getCleanupStatusFromBead(name string) string {
|
||||
func (m *Manager) getCleanupStatusFromBead(name string) CleanupStatus {
|
||||
agentID := m.agentBeadID(name)
|
||||
_, fields, err := m.beads.GetAgentBead(agentID)
|
||||
if err != nil || fields == nil {
|
||||
return ""
|
||||
return CleanupUnknown
|
||||
}
|
||||
return fields.CleanupStatus
|
||||
if fields.CleanupStatus == "" {
|
||||
return CleanupUnknown
|
||||
}
|
||||
return CleanupStatus(fields.CleanupStatus)
|
||||
}
|
||||
|
||||
// checkCleanupStatus validates the cleanup status against removal safety rules.
|
||||
// Returns an error if removal should be blocked based on the status.
|
||||
// force=true: allow has_uncommitted, block has_stash and has_unpushed
|
||||
// force=false: block all non-clean statuses
|
||||
func (m *Manager) checkCleanupStatus(name, cleanupStatus string, force bool) error {
|
||||
switch cleanupStatus {
|
||||
case "clean":
|
||||
func (m *Manager) checkCleanupStatus(name string, status CleanupStatus, force bool) error {
|
||||
// Clean status is always safe
|
||||
if status.IsSafe() {
|
||||
return nil
|
||||
case "has_uncommitted":
|
||||
if force {
|
||||
return nil // force bypasses uncommitted changes
|
||||
}
|
||||
}
|
||||
|
||||
// With force, uncommitted changes can be bypassed
|
||||
if force && status.CanForceRemove() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Map status to appropriate error
|
||||
switch status {
|
||||
case CleanupUncommitted:
|
||||
return &UncommittedWorkError{
|
||||
PolecatName: name,
|
||||
Status: &git.UncommittedWorkStatus{HasUncommittedChanges: true},
|
||||
}
|
||||
case "has_stash":
|
||||
case CleanupStash:
|
||||
return &UncommittedWorkError{
|
||||
PolecatName: name,
|
||||
Status: &git.UncommittedWorkStatus{StashCount: 1},
|
||||
}
|
||||
case "has_unpushed":
|
||||
case CleanupUnpushed:
|
||||
return &UncommittedWorkError{
|
||||
PolecatName: name,
|
||||
Status: &git.UncommittedWorkStatus{UnpushedCommits: 1},
|
||||
@@ -301,7 +310,7 @@ func (m *Manager) RemoveWithOptions(name string, force, nuclear bool) error {
|
||||
// This is the ZFC-compliant path - trust what the polecat reported
|
||||
cleanupStatus := m.getCleanupStatusFromBead(name)
|
||||
|
||||
if cleanupStatus != "" && cleanupStatus != "unknown" {
|
||||
if cleanupStatus != CleanupUnknown {
|
||||
// ZFC path: Use polecat's self-reported status
|
||||
if err := m.checkCleanupStatus(name, cleanupStatus, force); err != nil {
|
||||
return err
|
||||
|
||||
@@ -78,3 +78,48 @@ func (p *Polecat) Summary() Summary {
|
||||
Issue: p.Issue,
|
||||
}
|
||||
}
|
||||
|
||||
// CleanupStatus represents the git state of a polecat for cleanup decisions.
|
||||
// The Witness uses this to determine whether it's safe to nuke a polecat worktree.
|
||||
type CleanupStatus string
|
||||
|
||||
const (
|
||||
// CleanupClean means the worktree has no uncommitted work and is safe to remove.
|
||||
CleanupClean CleanupStatus = "clean"
|
||||
|
||||
// CleanupUncommitted means there are uncommitted changes in the worktree.
|
||||
CleanupUncommitted CleanupStatus = "has_uncommitted"
|
||||
|
||||
// CleanupStash means there are stashed changes that would be lost.
|
||||
CleanupStash CleanupStatus = "has_stash"
|
||||
|
||||
// CleanupUnpushed means there are commits not pushed to the remote.
|
||||
CleanupUnpushed CleanupStatus = "has_unpushed"
|
||||
|
||||
// CleanupUnknown means the status could not be determined.
|
||||
CleanupUnknown CleanupStatus = "unknown"
|
||||
)
|
||||
|
||||
// IsSafe returns true if the status indicates it's safe to remove the worktree
|
||||
// without losing any work.
|
||||
func (s CleanupStatus) IsSafe() bool {
|
||||
return s == CleanupClean
|
||||
}
|
||||
|
||||
// RequiresRecovery returns true if the status indicates there is work that
|
||||
// needs to be recovered before removal. This includes uncommitted changes,
|
||||
// stashes, and unpushed commits.
|
||||
func (s CleanupStatus) RequiresRecovery() bool {
|
||||
switch s {
|
||||
case CleanupUncommitted, CleanupStash, CleanupUnpushed:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// CanForceRemove returns true if the status allows forced removal.
|
||||
// Uncommitted changes can be force-removed, but stashes and unpushed commits cannot.
|
||||
func (s CleanupStatus) CanForceRemove() bool {
|
||||
return s == CleanupClean || s == CleanupUncommitted
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user