feat(mq): Add priority-ordered queue display and processing (gt-si8rq.6)

Implements priority scoring for merge queue ordering:

## Changes to gt mq list
- Add SCORE column showing priority score (higher = process first)
- Sort MRs by score descending instead of simple priority
- Add CONVOY column showing convoy ID if tracked

## New gt mq next command
- Returns highest-score MR ready for processing
- Supports --strategy=fifo for FIFO ordering fallback
- Supports --quiet for just printing MR ID
- Supports --json for programmatic access

## Changes to Refinery
- Queue() now sorts by priority score instead of simple priority
- Uses ScoreMR from mrqueue package for consistent scoring

## MR Fields Extended
- Added retry_count, last_conflict_sha, conflict_task_id
- Added convoy_id, convoy_created_at for convoy tracking
- These fields feed into priority scoring function

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
nux
2026-01-02 17:16:29 -08:00
committed by Steve Yegge
parent 1e1221883b
commit 8517ff0650
4 changed files with 388 additions and 37 deletions

View File

@@ -18,6 +18,7 @@ import (
"github.com/steveyegge/gastown/internal/config"
"github.com/steveyegge/gastown/internal/events"
"github.com/steveyegge/gastown/internal/mail"
"github.com/steveyegge/gastown/internal/mrqueue"
"github.com/steveyegge/gastown/internal/rig"
"github.com/steveyegge/gastown/internal/tmux"
"github.com/steveyegge/gastown/internal/util"
@@ -273,14 +274,25 @@ func (m *Manager) Queue() ([]QueueItem, error) {
})
}
// Sort issues by priority (P0 first, then P1, etc.)
sort.Slice(issues, func(i, j int) bool {
return issues[i].Priority < issues[j].Priority
// Score and sort issues by priority score (highest first)
now := time.Now()
type scoredIssue struct {
issue *beads.Issue
score float64
}
scored := make([]scoredIssue, 0, len(issues))
for _, issue := range issues {
score := m.calculateIssueScore(issue, now)
scored = append(scored, scoredIssue{issue: issue, score: score})
}
sort.Slice(scored, func(i, j int) bool {
return scored[i].score > scored[j].score
})
// Convert beads issues to queue items
for _, issue := range issues {
mr := m.issueToMR(issue)
// Convert scored issues to queue items
for _, s := range scored {
mr := m.issueToMR(s.issue)
if mr != nil {
// Skip if this is the currently processing MR
if ref.CurrentMR != nil && ref.CurrentMR.ID == mr.ID {
@@ -298,6 +310,39 @@ func (m *Manager) Queue() ([]QueueItem, error) {
return items, nil
}
// calculateIssueScore computes the priority score for an MR issue.
// Higher scores mean higher priority (process first).
func (m *Manager) calculateIssueScore(issue *beads.Issue, now time.Time) float64 {
fields := beads.ParseMRFields(issue)
// Parse MR creation time
mrCreatedAt := parseTime(issue.CreatedAt)
if mrCreatedAt.IsZero() {
mrCreatedAt = now // Fallback
}
// Build score input
input := mrqueue.ScoreInput{
Priority: issue.Priority,
MRCreatedAt: mrCreatedAt,
Now: now,
}
// Add fields from MR metadata if available
if fields != nil {
input.RetryCount = fields.RetryCount
// Parse convoy created at if available
if fields.ConvoyCreatedAt != "" {
if convoyTime := parseTime(fields.ConvoyCreatedAt); !convoyTime.IsZero() {
input.ConvoyCreatedAt = &convoyTime
}
}
}
return mrqueue.ScoreMRWithDefaults(input)
}
// issueToMR converts a beads issue to a MergeRequest.
func (m *Manager) issueToMR(issue *beads.Issue) *MergeRequest {
if issue == nil {