feat(done): Add --phase-complete flag for gate-based phase handoffs
Add support for signaling phase completion when a polecat needs to wait on a gate before continuing. The --phase-complete flag with --gate ID allows polecats to hand off control while awaiting external conditions. Changes: - done.go: Add --phase-complete and --gate flags, PHASE_COMPLETE exit type - protocol.go: Add Gate field to PolecatDonePayload - handlers.go: Handle PHASE_COMPLETE by recycling session (keep worktree) - beads.go: Add AddGateWaiter method for gate registration This enables multi-phase molecule workflows with async coordination (bd-gxb4) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -28,6 +28,7 @@ type HandlerResult struct {
|
||||
|
||||
// HandlePolecatDone processes a POLECAT_DONE message from a polecat.
|
||||
// For ESCALATED/DEFERRED exits (no pending MR), auto-nukes if clean.
|
||||
// For PHASE_COMPLETE exits, recycles the polecat (session ends, worktree kept).
|
||||
// For exits with pending MR, creates a cleanup wisp to wait for MERGED.
|
||||
func HandlePolecatDone(workDir, rigName string, msg *mail.Message) *HandlerResult {
|
||||
result := &HandlerResult{
|
||||
@@ -42,6 +43,18 @@ func HandlePolecatDone(workDir, rigName string, msg *mail.Message) *HandlerResul
|
||||
return result
|
||||
}
|
||||
|
||||
// Handle PHASE_COMPLETE: recycle polecat (session ends but worktree stays)
|
||||
// The polecat is registered as a waiter on the gate and will be re-dispatched
|
||||
// when the gate closes via gt gate wake.
|
||||
if payload.Exit == "PHASE_COMPLETE" {
|
||||
result.Handled = true
|
||||
result.Action = fmt.Sprintf("phase-complete for %s (gate=%s) - session recycled, awaiting gate", payload.PolecatName, payload.Gate)
|
||||
// Note: The polecat has already registered itself as a gate waiter via bd
|
||||
// The gate wake mechanism (gt gate wake) will send mail when gate closes
|
||||
// A new polecat will be dispatched to continue the molecule from the next step
|
||||
return result
|
||||
}
|
||||
|
||||
// Check if this polecat has a pending MR
|
||||
// ESCALATED/DEFERRED exits typically have no MR pending
|
||||
hasPendingMR := payload.MRID != "" || payload.Exit == "COMPLETED"
|
||||
|
||||
@@ -45,10 +45,11 @@ const (
|
||||
// PolecatDonePayload contains parsed data from a POLECAT_DONE message.
|
||||
type PolecatDonePayload struct {
|
||||
PolecatName string
|
||||
Exit string // MERGED, ESCALATED, DEFERRED
|
||||
Exit string // COMPLETED, ESCALATED, DEFERRED, PHASE_COMPLETE
|
||||
IssueID string
|
||||
MRID string
|
||||
Branch string
|
||||
Gate string // Gate ID when Exit is PHASE_COMPLETE
|
||||
}
|
||||
|
||||
// HelpPayload contains parsed data from a HELP message.
|
||||
@@ -101,9 +102,10 @@ func ClassifyMessage(subject string) ProtocolType {
|
||||
// Subject format: POLECAT_DONE <polecat-name>
|
||||
// Body format:
|
||||
//
|
||||
// Exit: MERGED|ESCALATED|DEFERRED
|
||||
// Exit: COMPLETED|ESCALATED|DEFERRED|PHASE_COMPLETE
|
||||
// Issue: <issue-id>
|
||||
// MR: <mr-id>
|
||||
// Gate: <gate-id>
|
||||
// Branch: <branch>
|
||||
func ParsePolecatDone(subject, body string) (*PolecatDonePayload, error) {
|
||||
matches := PatternPolecatDone.FindStringSubmatch(subject)
|
||||
@@ -124,6 +126,8 @@ func ParsePolecatDone(subject, body string) (*PolecatDonePayload, error) {
|
||||
payload.IssueID = strings.TrimSpace(strings.TrimPrefix(line, "Issue:"))
|
||||
} else if strings.HasPrefix(line, "MR:") {
|
||||
payload.MRID = strings.TrimSpace(strings.TrimPrefix(line, "MR:"))
|
||||
} else if strings.HasPrefix(line, "Gate:") {
|
||||
payload.Gate = strings.TrimSpace(strings.TrimPrefix(line, "Gate:"))
|
||||
} else if strings.HasPrefix(line, "Branch:") {
|
||||
payload.Branch = strings.TrimSpace(strings.TrimPrefix(line, "Branch:"))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user