feat(refinery): implement MR state transition validation (gt-h5n.3)

- Update MRStatus to use beads-style statuses (open, in_progress, closed)
- Add CloseReason enum for tracking why MRs were closed
- Implement ValidateTransition() to enforce valid state transitions:
  - open → in_progress (Engineer claims MR)
  - in_progress → closed (merge success or rejection)
  - in_progress → open (failure, reassign to worker)
  - open → closed (manual rejection)
  - closed → anything is blocked (immutable once closed)
- Add convenience methods: Claim(), Close(), Reopen(), SetStatus()
- Add status check methods: IsClosed(), IsOpen(), IsInProgress()
- Update ProcessMR and completeMR to use new state transition methods
- Update display code to handle new status values
- Add comprehensive tests for state transitions

Reference: docs/merge-queue-design.md#state-machine

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Steve Yegge
2025-12-17 14:54:52 -08:00
parent ec29ca0738
commit ce7ca52e98
5 changed files with 659 additions and 237 deletions

View File

@@ -274,14 +274,27 @@ func runRefineryQueue(cmd *cobra.Command, args []string) error {
status = style.Bold.Render("[processing]")
} else {
switch item.MR.Status {
case refinery.MRPending:
status = style.Dim.Render("[pending]")
case refinery.MRMerged:
status = style.Bold.Render("[merged]")
case refinery.MRFailed:
status = style.Dim.Render("[failed]")
case refinery.MRSkipped:
status = style.Dim.Render("[skipped]")
case refinery.MROpen:
if item.MR.Error != "" {
status = style.Dim.Render("[needs-rework]")
} else {
status = style.Dim.Render("[pending]")
}
case refinery.MRInProgress:
status = style.Bold.Render("[processing]")
case refinery.MRClosed:
switch item.MR.CloseReason {
case refinery.CloseReasonMerged:
status = style.Bold.Render("[merged]")
case refinery.CloseReasonRejected:
status = style.Dim.Render("[rejected]")
case refinery.CloseReasonConflict:
status = style.Dim.Render("[conflict]")
case refinery.CloseReasonSuperseded:
status = style.Dim.Render("[superseded]")
default:
status = style.Dim.Render("[closed]")
}
}
}