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:
@@ -12,6 +12,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/steveyegge/gastown/internal/mail"
|
||||||
"github.com/steveyegge/gastown/internal/rig"
|
"github.com/steveyegge/gastown/internal/rig"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -341,6 +342,8 @@ func (m *Manager) ProcessMR(mr *MergeRequest) MergeResult {
|
|||||||
// Abort the merge
|
// Abort the merge
|
||||||
m.gitRun("merge", "--abort")
|
m.gitRun("merge", "--abort")
|
||||||
m.completeMR(mr, MRFailed, "merge conflict - polecat must rebase")
|
m.completeMR(mr, MRFailed, "merge conflict - polecat must rebase")
|
||||||
|
// Notify worker about conflict
|
||||||
|
m.notifyWorkerConflict(mr)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
result.Error = fmt.Sprintf("merge failed: %v", err)
|
result.Error = fmt.Sprintf("merge failed: %v", err)
|
||||||
@@ -374,6 +377,9 @@ func (m *Manager) ProcessMR(mr *MergeRequest) MergeResult {
|
|||||||
result.Success = true
|
result.Success = true
|
||||||
m.completeMR(mr, MRMerged, "")
|
m.completeMR(mr, MRMerged, "")
|
||||||
|
|
||||||
|
// Notify worker of success
|
||||||
|
m.notifyWorkerMerged(mr)
|
||||||
|
|
||||||
// Optionally delete the merged branch
|
// Optionally delete the merged branch
|
||||||
m.gitRun("push", "origin", "--delete", mr.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))
|
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 ""
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user