feat(mrqueue): Add MQ priority objective function (gt-si8rq.1)
Implement ScoreMR function for merge queue priority ordering with: - Convoy age factor (prevents starvation of old convoys) - Priority factor (P0 beats P4) - Retry penalty (prevents thrashing on conflict-prone MRs) - MR age tiebreaker (FIFO within same priority) Added fields to MR struct: - RetryCount for conflict retry tracking - ConvoyID and ConvoyCreatedAt for convoy linkage Includes comprehensive unit tests and documentation. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -28,6 +28,11 @@ type MR struct {
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
AgentBead string `json:"agent_bead,omitempty"` // Agent bead ID that created this MR (for traceability)
|
||||
|
||||
// Priority scoring fields
|
||||
RetryCount int `json:"retry_count,omitempty"` // Conflict retry count for priority penalty
|
||||
ConvoyID string `json:"convoy_id,omitempty"` // Parent convoy ID if part of a convoy
|
||||
ConvoyCreatedAt *time.Time `json:"convoy_created_at,omitempty"` // Convoy creation time for starvation prevention
|
||||
|
||||
// Claiming fields for parallel refinery workers
|
||||
ClaimedBy string `json:"claimed_by,omitempty"` // Worker ID that claimed this MR
|
||||
ClaimedAt *time.Time `json:"claimed_at,omitempty"` // When the MR was claimed
|
||||
@@ -102,6 +107,7 @@ func (q *Queue) Submit(mr *MR) error {
|
||||
}
|
||||
|
||||
// List returns all pending MRs, sorted by priority then creation time.
|
||||
// Deprecated: Use ListByScore for priority-aware ordering.
|
||||
func (q *Queue) List() ([]*MR, error) {
|
||||
entries, err := os.ReadDir(q.dir)
|
||||
if err != nil {
|
||||
@@ -135,6 +141,43 @@ func (q *Queue) List() ([]*MR, error) {
|
||||
return mrs, nil
|
||||
}
|
||||
|
||||
// ListByScore returns all pending MRs sorted by priority score (highest first).
|
||||
// Uses the ScoreMR function which considers:
|
||||
// - Convoy age (prevents starvation)
|
||||
// - Issue priority (P0-P4)
|
||||
// - Retry count (prevents thrashing)
|
||||
// - MR age (FIFO tiebreaker)
|
||||
func (q *Queue) ListByScore() ([]*MR, error) {
|
||||
entries, err := os.ReadDir(q.dir)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil, nil // Empty queue
|
||||
}
|
||||
return nil, fmt.Errorf("reading mq directory: %w", err)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
var mrs []*MR
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".json") {
|
||||
continue
|
||||
}
|
||||
|
||||
mr, err := q.load(filepath.Join(q.dir, entry.Name()))
|
||||
if err != nil {
|
||||
continue // Skip malformed files
|
||||
}
|
||||
mrs = append(mrs, mr)
|
||||
}
|
||||
|
||||
// Sort by score (higher first = higher priority)
|
||||
sort.Slice(mrs, func(i, j int) bool {
|
||||
return mrs[i].ScoreAt(now) > mrs[j].ScoreAt(now)
|
||||
})
|
||||
|
||||
return mrs, nil
|
||||
}
|
||||
|
||||
// Get retrieves a specific MR by ID.
|
||||
func (q *Queue) Get(id string) (*MR, error) {
|
||||
path := filepath.Join(q.dir, id+".json")
|
||||
|
||||
Reference in New Issue
Block a user