feat: add refinery mail notifications for conflict handling

- notifyWorkerConflict: Mail worker with rebase instructions on conflict
- notifyWorkerMerged: Mail worker on successful merge
- findTownRoot: Walk up to find workspace for mail routing
- High priority notification for conflicts
- Integrates with mail system for agent communication

This completes the semantic merge handling for MVP - conflicts
are intelligently handled by notifying workers rather than
silently failing.

Closes gt-kmn.4

Generated with Claude Code

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Steve Yegge
2025-12-16 14:24:19 -08:00
parent f8c177e17b
commit a1de1c74d6

View File

@@ -12,6 +12,7 @@ import (
"strings"
"time"
"github.com/steveyegge/gastown/internal/mail"
"github.com/steveyegge/gastown/internal/rig"
)
@@ -341,6 +342,8 @@ func (m *Manager) ProcessMR(mr *MergeRequest) MergeResult {
// Abort the merge
m.gitRun("merge", "--abort")
m.completeMR(mr, MRFailed, "merge conflict - polecat must rebase")
// Notify worker about conflict
m.notifyWorkerConflict(mr)
return result
}
result.Error = fmt.Sprintf("merge failed: %v", err)
@@ -374,6 +377,9 @@ func (m *Manager) ProcessMR(mr *MergeRequest) MergeResult {
result.Success = true
m.completeMR(mr, MRMerged, "")
// Notify worker of success
m.notifyWorkerMerged(mr)
// Optionally delete the merged branch
m.gitRun("push", "origin", "--delete", mr.Branch)
@@ -488,3 +494,76 @@ func formatAge(t time.Time) string {
}
return fmt.Sprintf("%dd ago", int(d.Hours()/24))
}
// notifyWorkerConflict sends a conflict notification to a polecat.
func (m *Manager) notifyWorkerConflict(mr *MergeRequest) {
// Find town root by walking up from rig path
townRoot := findTownRoot(m.workDir)
if townRoot == "" {
return
}
router := mail.NewRouter(townRoot)
msg := mail.NewMessage(
fmt.Sprintf("%s/refinery", m.rig.Name),
fmt.Sprintf("%s/%s", m.rig.Name, mr.Worker),
"Merge conflict - rebase required",
fmt.Sprintf(`Your branch %s has conflicts with %s.
Please rebase your changes:
git fetch origin
git rebase origin/%s
git push -f
Then the Refinery will retry the merge.`,
mr.Branch, mr.TargetBranch, mr.TargetBranch),
)
msg.Priority = mail.PriorityHigh
router.Send(msg)
}
// notifyWorkerMerged sends a success notification to a polecat.
func (m *Manager) notifyWorkerMerged(mr *MergeRequest) {
townRoot := findTownRoot(m.workDir)
if townRoot == "" {
return
}
router := mail.NewRouter(townRoot)
msg := mail.NewMessage(
fmt.Sprintf("%s/refinery", m.rig.Name),
fmt.Sprintf("%s/%s", m.rig.Name, mr.Worker),
"Work merged successfully",
fmt.Sprintf(`Your branch %s has been merged to %s.
Issue: %s
Thank you for your contribution!`,
mr.Branch, mr.TargetBranch, mr.IssueID),
)
router.Send(msg)
}
// findTownRoot walks up directories to find the town root.
func findTownRoot(startPath string) string {
path := startPath
for {
// Check for mayor/ subdirectory (indicates town root)
if _, err := os.Stat(filepath.Join(path, "mayor")); err == nil {
return path
}
// Check for config.json with type: workspace
configPath := filepath.Join(path, "config.json")
if data, err := os.ReadFile(configPath); err == nil {
if strings.Contains(string(data), `"type": "workspace"`) {
return path
}
}
parent := filepath.Dir(path)
if parent == path {
break // Reached root
}
path = parent
}
return ""
}