refactor(mrqueue): remove mrqueue package, use beads for MRs (gt-dqi)

Remove the mrqueue side-channel from gastown. The merge queue now uses
beads merge-request wisps exclusively, not parallel .beads/mq/*.json files.

Changes:
- Delete internal/mrqueue/ package (~830 lines removed)
- Move scoring logic to internal/refinery/score.go
- Update Refinery engineer to query beads via ReadyWithType("merge-request")
- Add MRInfo struct to replace mrqueue.MR
- Add ClaimMR/ReleaseMR methods using beads assignee field
- Update HandleMergeReady to not create duplicate queue entries
- Update gt refinery commands (claim, release, unclaimed) to use beads
- Stub out MQEventSource (no longer needed)

The Refinery now:
- Lists MRs via beads.ReadyWithType("merge-request")
- Claims via beads.Update(..., {Assignee: worker})
- Closes via beads.CloseWithReason("merged", mrID)
- Blocks on conflicts via beads.AddDependency(mrID, taskID)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
george
2026-01-12 23:48:56 -08:00
committed by Steve Yegge
parent f0192c8b3d
commit 58207a00ec
12 changed files with 312 additions and 1556 deletions

View File

@@ -10,7 +10,7 @@ import (
"github.com/spf13/cobra"
"github.com/steveyegge/gastown/internal/beads"
"github.com/steveyegge/gastown/internal/mrqueue"
"github.com/steveyegge/gastown/internal/refinery"
"github.com/steveyegge/gastown/internal/style"
)
@@ -260,7 +260,7 @@ func outputJSON(data interface{}) error {
return enc.Encode(data)
}
// calculateMRScore computes the priority score for an MR using the mrqueue scoring function.
// calculateMRScore computes the priority score for an MR using the refinery scoring function.
// Higher scores mean higher priority (process first).
func calculateMRScore(issue *beads.Issue, fields *beads.MRFields, now time.Time) float64 {
// Parse MR creation time
@@ -273,7 +273,7 @@ func calculateMRScore(issue *beads.Issue, fields *beads.MRFields, now time.Time)
}
// Build score input
input := mrqueue.ScoreInput{
input := refinery.ScoreInput{
Priority: issue.Priority,
MRCreatedAt: mrCreatedAt,
Now: now,
@@ -291,5 +291,5 @@ func calculateMRScore(issue *beads.Issue, fields *beads.MRFields, now time.Time)
}
}
return mrqueue.ScoreMRWithDefaults(input)
return refinery.ScoreMRWithDefaults(input)
}

View File

@@ -6,7 +6,7 @@ import (
"os"
"github.com/spf13/cobra"
"github.com/steveyegge/gastown/internal/mrqueue"
"github.com/steveyegge/gastown/internal/beads"
"github.com/steveyegge/gastown/internal/refinery"
"github.com/steveyegge/gastown/internal/rig"
"github.com/steveyegge/gastown/internal/style"
@@ -540,19 +540,23 @@ func runRefineryClaim(cmd *cobra.Command, args []string) error {
mrID := args[0]
workerID := getWorkerID()
// Find the queue from current working directory
q, err := mrqueue.NewFromWorkdir(".")
// Find beads from current working directory
townRoot, err := workspace.FindFromCwdOrError()
if err != nil {
return fmt.Errorf("finding merge queue: %w", err)
return fmt.Errorf("not in a Gas Town workspace: %w", err)
}
rigName, err := inferRigFromCwd(townRoot)
if err != nil {
return fmt.Errorf("could not determine rig: %w", err)
}
if err := q.Claim(mrID, workerID); err != nil {
if err == mrqueue.ErrNotFound {
return fmt.Errorf("MR %s not found in queue", mrID)
}
if err == mrqueue.ErrAlreadyClaimed {
return fmt.Errorf("MR %s is already claimed by another worker", mrID)
}
_, r, err := getRig(rigName)
if err != nil {
return err
}
eng := refinery.NewEngineer(r)
if err := eng.ClaimMR(mrID, workerID); err != nil {
return fmt.Errorf("claiming MR: %w", err)
}
@@ -563,12 +567,23 @@ func runRefineryClaim(cmd *cobra.Command, args []string) error {
func runRefineryRelease(cmd *cobra.Command, args []string) error {
mrID := args[0]
q, err := mrqueue.NewFromWorkdir(".")
// Find beads from current working directory
townRoot, err := workspace.FindFromCwdOrError()
if err != nil {
return fmt.Errorf("finding merge queue: %w", err)
return fmt.Errorf("not in a Gas Town workspace: %w", err)
}
rigName, err := inferRigFromCwd(townRoot)
if err != nil {
return fmt.Errorf("could not determine rig: %w", err)
}
if err := q.Release(mrID); err != nil {
_, r, err := getRig(rigName)
if err != nil {
return err
}
eng := refinery.NewEngineer(r)
if err := eng.ReleaseMR(mrID); err != nil {
return fmt.Errorf("releasing MR: %w", err)
}
@@ -587,10 +602,35 @@ func runRefineryUnclaimed(cmd *cobra.Command, args []string) error {
return err
}
q := mrqueue.New(r.Path)
unclaimed, err := q.ListUnclaimed()
// Query beads for merge-request issues without assignee
b := beads.New(r.Path)
issues, err := b.List(beads.ListOptions{
Status: "open",
Label: "gt:merge-request",
Priority: -1,
})
if err != nil {
return fmt.Errorf("listing unclaimed MRs: %w", err)
return fmt.Errorf("listing merge requests: %w", err)
}
// Filter for unclaimed (no assignee)
var unclaimed []*refinery.MRInfo
for _, issue := range issues {
if issue.Assignee != "" {
continue
}
fields := beads.ParseMRFields(issue)
if fields == nil {
continue
}
mr := &refinery.MRInfo{
ID: issue.ID,
Branch: fields.Branch,
Target: fields.Target,
Worker: fields.Worker,
Priority: issue.Priority,
}
unclaimed = append(unclaimed, mr)
}
// JSON output