fix: Refinery queue uses beads MQ as source of truth (hq-eggh5)

The refinery was checking git branches instead of the beads merge queue.
This caused MRs to pile up when branches were deleted but MR beads remained.

- manager.go: Queue() now queries beads for type=merge-request issues
- refinery.md.tmpl: Updated queue-scan to use gt mq list
- mol-refinery-patrol.formula.toml: Updated queue-scan step instructions
This commit is contained in:
mayor
2025-12-31 02:02:26 -08:00
committed by beads/crew/dave
parent 7069f762e5
commit 36f17bbadd
3 changed files with 75 additions and 24 deletions

View File

@@ -99,27 +99,28 @@ id = "queue-scan"
title = "Scan merge queue" title = "Scan merge queue"
needs = ["inbox-check"] needs = ["inbox-check"]
description = """ description = """
Review the queue built from MERGE_READY messages in inbox-check. Check the beads merge queue - this is the SOURCE OF TRUTH for pending merges.
```bash ```bash
# Always fetch with prune to clean stale tracking refs:
git fetch --prune origin git fetch --prune origin
gt mq list <rig>
# For each queued merge request, verify the branch exists:
git branch -r | grep <branch>
# IMPORTANT: Before queuing, verify branch has unmerged commits:
git log origin/main..origin/<branch> --oneline | head -1
# If empty → branch already merged. Archive MERGE_READY, don't queue.
``` ```
The beads MQ tracks all pending merge requests. Do NOT rely on `git branch -r | grep polecat`
as branches may exist without MR beads, or MR beads may exist for already-merged work.
If queue empty, skip to context-check step. If queue empty, skip to context-check step.
If branch doesn't exist for a queued item: For each MR in the queue, verify the branch still exists:
- Mail witness: Branch not found, cannot merge ```bash
- Remove from queue git branch -r | grep <branch>
```
Track verified branch list for this cycle.""" If branch doesn't exist for a queued MR:
- Close the MR bead: `bd close <mr-id> --reason "Branch no longer exists"`
- Remove from processing queue
Track verified MR list for this cycle."""
[[steps]] [[steps]]
id = "process-branch" id = "process-branch"

View File

@@ -13,6 +13,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/steveyegge/gastown/internal/beads"
"github.com/steveyegge/gastown/internal/claude" "github.com/steveyegge/gastown/internal/claude"
"github.com/steveyegge/gastown/internal/config" "github.com/steveyegge/gastown/internal/config"
"github.com/steveyegge/gastown/internal/events" "github.com/steveyegge/gastown/internal/events"
@@ -239,14 +240,21 @@ func (m *Manager) Stop() error {
} }
// Queue returns the current merge queue. // Queue returns the current merge queue.
// Uses beads merge-request issues as the source of truth (not git branches).
func (m *Manager) Queue() ([]QueueItem, error) { func (m *Manager) Queue() ([]QueueItem, error) {
// Discover branches that look like polecat work branches // Query beads for open merge-request type issues
branches, err := m.discoverWorkBranches() // BeadsPath() returns the git-synced beads location
b := beads.New(m.rig.BeadsPath())
issues, err := b.List(beads.ListOptions{
Type: "merge-request",
Status: "open",
Priority: -1, // No priority filter
})
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("querying merge queue from beads: %w", err)
} }
// Load any pending MRs from state // Load any current processing state
ref, err := m.loadState() ref, err := m.loadState()
if err != nil { if err != nil {
return nil, err return nil, err
@@ -265,10 +273,14 @@ func (m *Manager) Queue() ([]QueueItem, error) {
}) })
} }
// Add discovered branches as pending // Convert beads issues to queue items
for _, branch := range branches { for _, issue := range issues {
mr := m.branchToMR(branch) mr := m.issueToBead(issue)
if mr != nil { if mr != nil {
// Skip if this is the currently processing MR
if ref.CurrentMR != nil && ref.CurrentMR.ID == mr.ID {
continue
}
items = append(items, QueueItem{ items = append(items, QueueItem{
Position: pos, Position: pos,
MR: mr, MR: mr,
@@ -281,6 +293,44 @@ func (m *Manager) Queue() ([]QueueItem, error) {
return items, nil return items, nil
} }
// issueToBead converts a beads issue to a MergeRequest.
func (m *Manager) issueToBead(issue *beads.Issue) *MergeRequest {
if issue == nil {
return nil
}
fields := beads.ParseMRFields(issue)
if fields == nil {
// No MR fields in description, construct from title/ID
return &MergeRequest{
ID: issue.ID,
IssueID: issue.ID,
Status: MROpen,
CreatedAt: parseTime(issue.CreatedAt),
TargetBranch: "main",
}
}
return &MergeRequest{
ID: issue.ID,
Branch: fields.Branch,
Worker: fields.Worker,
IssueID: fields.SourceIssue,
TargetBranch: fields.Target,
Status: MROpen,
CreatedAt: parseTime(issue.CreatedAt),
}
}
// parseTime parses a time string, returning zero time on error.
func parseTime(s string) time.Time {
t, err := time.Parse(time.RFC3339, s)
if err != nil {
t, _ = time.Parse("2006-01-02T15:04:05Z", s)
}
return t
}
// discoverWorkBranches finds branches that look like polecat work. // discoverWorkBranches finds branches that look like polecat work.
func (m *Manager) discoverWorkBranches() ([]string, error) { func (m *Manager) discoverWorkBranches() ([]string, error) {
cmd := exec.Command("git", "branch", "-r", "--list", "origin/polecat/*") cmd := exec.Command("git", "branch", "-r", "--list", "origin/polecat/*")

View File

@@ -198,12 +198,12 @@ gt mail inbox
# Process each message: lifecycle requests, escalations # Process each message: lifecycle requests, escalations
``` ```
**queue-scan**: Fetch remote and identify branches **queue-scan**: Check beads merge queue (source of truth)
```bash ```bash
git fetch origin git fetch --prune origin
git branch -r | grep polecat gt mq list {{ .RigName }}
gt refinery queue {{ .RigName }}
``` ```
The beads MQ is the source of truth for pending merges, not git branches.
If queue empty, skip to context-check step. If queue empty, skip to context-check step.
**process-branch**: Pick next branch, rebase on main **process-branch**: Pick next branch, rebase on main