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

@@ -294,6 +294,56 @@ func HandleMerged(workDir, rigName string, msg *mail.Message) *HandlerResult {
return result
}
// HandleMergeFailed processes a MERGE_FAILED message from the Refinery.
// Notifies the polecat that their merge was rejected and rework is needed.
func HandleMergeFailed(workDir, rigName string, msg *mail.Message, router *mail.Router) *HandlerResult {
result := &HandlerResult{
MessageID: msg.ID,
ProtocolType: ProtoMergeFailed,
}
// Parse the message
payload, err := ParseMergeFailed(msg.Subject, msg.Body)
if err != nil {
result.Error = fmt.Errorf("parsing MERGE_FAILED: %w", err)
return result
}
// Notify the polecat about the failure
polecatAddr := fmt.Sprintf("%s/polecats/%s", rigName, payload.PolecatName)
notification := &mail.Message{
From: fmt.Sprintf("%s/witness", rigName),
To: polecatAddr,
Subject: fmt.Sprintf("Merge failed: %s", payload.FailureType),
Priority: mail.PriorityHigh,
Type: mail.TypeTask,
Body: fmt.Sprintf(`Your merge request was rejected.
Branch: %s
Issue: %s
Failure: %s
Error: %s
Please fix the issue and resubmit with 'gt done'.`,
payload.Branch,
payload.IssueID,
payload.FailureType,
payload.Error,
),
}
if err := router.Send(notification); err != nil {
result.Error = fmt.Errorf("sending failure notification: %w", err)
return result
}
result.Handled = true
result.MailSent = notification.ID
result.Action = fmt.Sprintf("notified %s of merge failure: %s - %s", payload.PolecatName, payload.FailureType, payload.Error)
return result
}
// HandleSwarmStart processes a SWARM_START message from the Mayor.
// Creates a swarm tracking wisp to monitor batch polecat work.
func HandleSwarmStart(workDir string, msg *mail.Message) *HandlerResult {