fix(refinery): Send MERGE_FAILED to Witness when merge is rejected

When the Refinery detects a build error or test failure and refuses
to merge, the polecat was never notified. This fixes the notification
pipeline by:

1. Adding MERGE_FAILED protocol support to Witness:
   - PatternMergeFailed regex pattern
   - ProtoMergeFailed protocol type constant
   - MergeFailedPayload struct with all failure details
   - ParseMergeFailed parser function
   - ClassifyMessage case for MERGE_FAILED

2. Adding HandleMergeFailed handler to Witness:
   - Parses the failure notification
   - Sends HIGH priority mail to polecat with fix instructions
   - Includes branch, issue, failure type, and error details

3. Adding mail notification in Refinery's handleFailureFromQueue:
   - Creates mail.Router for sending protocol messages
   - Sends MERGE_FAILED to Witness when merge fails
   - Includes failure type (build/tests/conflict) and error

4. Adding comprehensive unit tests:
   - TestParseMergeFailed for full body parsing
   - TestParseMergeFailed_MinimalBody for minimal body
   - TestParseMergeFailed_InvalidSubject for error handling
   - ClassifyMessage test cases for MERGE_FAILED

Fixes #114

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
markov-kernel
2026-01-05 14:59:30 +01:00
committed by Steve Yegge
parent 9cb14cc41a
commit 6fe25c757c
4 changed files with 183 additions and 0 deletions

View File

@@ -16,7 +16,9 @@ import (
"github.com/steveyegge/gastown/internal/beads"
"github.com/steveyegge/gastown/internal/git"
"github.com/steveyegge/gastown/internal/mail"
"github.com/steveyegge/gastown/internal/mrqueue"
"github.com/steveyegge/gastown/internal/protocol"
"github.com/steveyegge/gastown/internal/rig"
)
@@ -80,6 +82,7 @@ type Engineer struct {
workDir string
output io.Writer // Output destination for user-facing messages
eventLogger *mrqueue.EventLogger
router *mail.Router // Mail router for sending protocol messages
// stopCh is used for graceful shutdown
stopCh chan struct{}
@@ -100,6 +103,7 @@ func NewEngineer(r *rig.Rig) *Engineer {
workDir: r.Path,
output: os.Stdout,
eventLogger: mrqueue.NewEventLoggerFromRig(r.Path),
router: mail.NewRouter(r.Path),
stopCh: make(chan struct{}),
}
}
@@ -562,6 +566,21 @@ func (e *Engineer) handleFailureFromQueue(mr *mrqueue.MR, result ProcessResult)
_, _ = fmt.Fprintf(e.output, "[Engineer] Warning: failed to log merge_failed event: %v\n", err)
}
// Notify Witness of the failure so polecat can be alerted
// Determine failure type from result
failureType := "build"
if result.Conflict {
failureType = "conflict"
} else if result.TestsFailed {
failureType = "tests"
}
msg := protocol.NewMergeFailedMessage(e.rig.Name, mr.Worker, mr.Branch, mr.SourceIssue, mr.Target, failureType, result.Error)
if err := e.router.Send(msg); err != nil {
fmt.Fprintf(e.output, "[Engineer] Warning: failed to send MERGE_FAILED to witness: %v\n", err)
} else {
fmt.Fprintf(e.output, "[Engineer] Notified witness of merge failure for %s\n", mr.Worker)
}
// If this was a conflict, create a conflict-resolution task for dispatch
// and block the MR until the task is resolved (non-blocking delegation)
if result.Conflict {