feat(witness): delay polecat cleanup until MR merges (gt-12hwb)
Phase 4 of local-only polecat branches: Handle conflict resolution edge case. Problem: If polecat worktree is nuked before MR merges, the local branch is gone and conflict resolution can't access it. Solution: Witness now defers cleanup for polecats with pending MRs: - HandlePolecatDone creates a cleanup wisp with "merge-requested" state - Polecat worktree preserved until MERGED signal arrives - HandleMerged then nukes the polecat (existing behavior) Also updated mol-polecat-conflict-resolve.formula.toml: - Removed fetch from origin (branches are local-only now) - Added instructions to fetch from source polecat's worktree - Added rig and source_polecat variables 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
committed by
Steve Yegge
parent
b79e4a7c3b
commit
63af29284b
@@ -145,20 +145,39 @@ git stash list # Should be empty
|
||||
|
||||
If dirty, clean up first (stash or discard).
|
||||
|
||||
**2. Fetch latest state:**
|
||||
**2. Fetch latest main:**
|
||||
```bash
|
||||
git fetch origin
|
||||
git fetch origin {{branch}}:refs/remotes/origin/{{branch}}
|
||||
```
|
||||
|
||||
**3. Checkout the branch:**
|
||||
**3. Locate the source polecat's worktree:**
|
||||
|
||||
The branch is local-only (not pushed to origin). Find the source polecat path
|
||||
from the task metadata. The path follows the pattern:
|
||||
```
|
||||
~/gt/<rig>/polecats/<polecat-name>
|
||||
```
|
||||
|
||||
Extract the source polecat name from the MR metadata:
|
||||
```bash
|
||||
git checkout -b temp-resolve origin/{{branch}}
|
||||
bd show {{original_mr}} --json | jq -r '.description' | grep -oP 'Source polecat: \K\S+'
|
||||
```
|
||||
|
||||
**4. Fetch the branch from the source polecat's worktree:**
|
||||
```bash
|
||||
# The source polecat's worktree still exists (cleanup is deferred until MR merges)
|
||||
SOURCE_POLECAT_PATH="$HOME/gt/{{rig}}/polecats/{{source_polecat}}"
|
||||
git fetch "$SOURCE_POLECAT_PATH" {{branch}}:{{branch}}
|
||||
```
|
||||
|
||||
**5. Checkout the branch:**
|
||||
```bash
|
||||
git checkout -b temp-resolve {{branch}}
|
||||
```
|
||||
|
||||
Using `temp-resolve` as the local branch name keeps things clear.
|
||||
|
||||
**4. Verify the branch state:**
|
||||
**6. Verify the branch state:**
|
||||
```bash
|
||||
git log --oneline -5 # Recent commits
|
||||
git log origin/main..HEAD # Commits not on main
|
||||
@@ -383,3 +402,11 @@ required = true
|
||||
[vars.branch]
|
||||
description = "The branch to rebase (extracted from task metadata)"
|
||||
required = true
|
||||
|
||||
[vars.rig]
|
||||
description = "The rig where the source polecat resides"
|
||||
required = true
|
||||
|
||||
[vars.source_polecat]
|
||||
description = "The name of the polecat whose local branch contains the work (extracted from MR metadata)"
|
||||
required = true
|
||||
|
||||
@@ -67,18 +67,34 @@ func HandlePolecatDone(workDir, rigName string, msg *mail.Message) *HandlerResul
|
||||
// ESCALATED/DEFERRED exits typically have no MR pending
|
||||
hasPendingMR := payload.MRID != "" || payload.Exit == "COMPLETED"
|
||||
|
||||
// Ephemeral model: try to auto-nuke immediately regardless of MR status
|
||||
// If cleanup_status is clean, the branch is pushed and polecat is recyclable.
|
||||
// The MR will be processed independently by the Refinery.
|
||||
// Local-only branches model: if there's a pending MR, DON'T nuke.
|
||||
// The polecat's local branch is needed for conflict resolution if merge fails.
|
||||
// Once the MR merges (MERGED signal), HandleMerged will nuke the polecat.
|
||||
if hasPendingMR {
|
||||
// Create cleanup wisp to track this polecat is waiting for merge
|
||||
wispID, err := createCleanupWisp(workDir, payload.PolecatName, payload.IssueID, payload.Branch)
|
||||
if err != nil {
|
||||
result.Error = fmt.Errorf("creating cleanup wisp: %w", err)
|
||||
return result
|
||||
}
|
||||
|
||||
// Update wisp state to indicate it's waiting for merge
|
||||
if err := UpdateCleanupWispState(workDir, wispID, "merge-requested"); err != nil {
|
||||
// Non-fatal - wisp was created, just couldn't update state
|
||||
result.Error = fmt.Errorf("updating wisp state: %w", err)
|
||||
}
|
||||
|
||||
result.Handled = true
|
||||
result.WispCreated = wispID
|
||||
result.Action = fmt.Sprintf("deferred cleanup for %s (pending MR=%s, local branch preserved for conflict resolution)", payload.PolecatName, payload.MRID)
|
||||
return result
|
||||
}
|
||||
|
||||
// No pending MR - try to auto-nuke immediately
|
||||
nukeResult := AutoNukeIfClean(workDir, rigName, payload.PolecatName)
|
||||
if nukeResult.Nuked {
|
||||
result.Handled = true
|
||||
if hasPendingMR {
|
||||
// Ephemeral model: polecat nuked, MR continues in Refinery
|
||||
result.Action = fmt.Sprintf("auto-nuked %s (ephemeral: exit=%s, MR=%s): %s", payload.PolecatName, payload.Exit, payload.MRID, nukeResult.Reason)
|
||||
} else {
|
||||
result.Action = fmt.Sprintf("auto-nuked %s (exit=%s, no MR): %s", payload.PolecatName, payload.Exit, nukeResult.Reason)
|
||||
}
|
||||
result.Action = fmt.Sprintf("auto-nuked %s (exit=%s, no MR): %s", payload.PolecatName, payload.Exit, nukeResult.Reason)
|
||||
return result
|
||||
}
|
||||
if nukeResult.Error != nil {
|
||||
@@ -87,8 +103,6 @@ func HandlePolecatDone(workDir, rigName string, msg *mail.Message) *HandlerResul
|
||||
}
|
||||
|
||||
// Couldn't auto-nuke (dirty state or verification failed) - create wisp for manual intervention
|
||||
// Note: Even with pending MR, if we can't auto-nuke it means something is wrong
|
||||
// (uncommitted changes, unpushed commits, etc.) that needs attention.
|
||||
wispID, err := createCleanupWisp(workDir, payload.PolecatName, payload.IssueID, payload.Branch)
|
||||
if err != nil {
|
||||
result.Error = fmt.Errorf("creating cleanup wisp: %w", err)
|
||||
@@ -97,11 +111,7 @@ func HandlePolecatDone(workDir, rigName string, msg *mail.Message) *HandlerResul
|
||||
|
||||
result.Handled = true
|
||||
result.WispCreated = wispID
|
||||
if hasPendingMR {
|
||||
result.Action = fmt.Sprintf("created cleanup wisp %s for %s (MR=%s, needs intervention: %s)", wispID, payload.PolecatName, payload.MRID, nukeResult.Reason)
|
||||
} else {
|
||||
result.Action = fmt.Sprintf("created cleanup wisp %s for %s (needs manual cleanup: %s)", wispID, payload.PolecatName, nukeResult.Reason)
|
||||
}
|
||||
result.Action = fmt.Sprintf("created cleanup wisp %s for %s (needs manual cleanup: %s)", wispID, payload.PolecatName, nukeResult.Reason)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user