Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 22 additions & 3 deletions internal/cmd/sling_schedule.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/steveyegge/gastown/internal/beads"
"github.com/steveyegge/gastown/internal/config"
"github.com/steveyegge/gastown/internal/events"
"github.com/steveyegge/gastown/internal/rig"
"github.com/steveyegge/gastown/internal/scheduler/capacity"
"github.com/steveyegge/gastown/internal/style"
"github.com/steveyegge/gastown/internal/workspace"
Expand Down Expand Up @@ -252,16 +253,34 @@ func resolveRigForBead(townRoot, beadID string) string {
}

// resolveFormula determines the formula name from user flags and rig settings.
// It checks the rig's workflow.default_formula setting before falling back to
// the hardcoded "mol-polecat-work" default.
// Resolution order:
// 1. Explicit --formula flag
// 2. Rig property layers (wisp → bead → system default "mol-polecat-work")
// 3. Rig settings file (workflow.default_formula in settings/config.json)
// 4. Hardcoded fallback "mol-polecat-work"
//
// The property layers are the primary mechanism, supporting:
//
// gt rig config set <rig> default_formula mol-evolve # wisp layer
// gt rig config set <rig> default_formula mol-evolve --global # bead layer
func resolveFormula(explicit string, hookRawBead bool, townRoot, rigName string) string {
if hookRawBead {
return ""
}
if explicit != "" {
return explicit
}
// Check rig's default_formula setting (issue gt-boc).
// Check rig property layers: wisp → bead → system default (issue gt-y18).
if townRoot != "" && rigName != "" {
r := &rig.Rig{
Name: rigName,
Path: filepath.Join(townRoot, rigName),
}
if df := r.GetStringConfig("default_formula"); df != "" {
return df
}
}
// Fallback: check rig settings file (legacy path, issue gt-boc).
if townRoot != "" && rigName != "" {
rigPath := filepath.Join(townRoot, rigName)
if df := config.GetDefaultFormula(rigPath); df != "" {
Expand Down
78 changes: 78 additions & 0 deletions internal/cmd/sling_schedule_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ package cmd

import (
"os"
"path/filepath"
"testing"

"github.com/steveyegge/gastown/internal/wisp"
)

// TestAreScheduledFailClosed verifies that areScheduled fails closed when
Expand Down Expand Up @@ -40,3 +43,78 @@ func TestAreScheduledEmptyInput(t *testing.T) {
t.Errorf("areScheduled([]) should return empty map, got %d entries", len(result))
}
}

// TestResolveFormula verifies formula resolution precedence:
// explicit flag > wisp layer > bead layer > system default > settings file > hardcoded fallback.
func TestResolveFormula(t *testing.T) {
t.Parallel()

t.Run("explicit flag wins", func(t *testing.T) {
t.Parallel()
got := resolveFormula("mol-evolve", false, "/tmp/nonexistent", "myrig")
if got != "mol-evolve" {
t.Errorf("got %q, want %q", got, "mol-evolve")
}
})

t.Run("hookRawBead returns empty", func(t *testing.T) {
t.Parallel()
got := resolveFormula("mol-evolve", true, "/tmp/nonexistent", "myrig")
if got != "" {
t.Errorf("got %q, want empty", got)
}
})

t.Run("system default mol-polecat-work", func(t *testing.T) {
t.Parallel()
tmpDir := t.TempDir()
rigName := "testrig"
_ = os.MkdirAll(filepath.Join(tmpDir, rigName), 0o755)
got := resolveFormula("", false, tmpDir, rigName)
if got != "mol-polecat-work" {
t.Errorf("got %q, want %q", got, "mol-polecat-work")
}
})

t.Run("wisp layer overrides system default", func(t *testing.T) {
t.Parallel()
tmpDir := t.TempDir()
rigName := "testrig"
_ = os.MkdirAll(filepath.Join(tmpDir, rigName), 0o755)

wispCfg := wisp.NewConfig(tmpDir, rigName)
if err := wispCfg.Set("default_formula", "mol-evolve"); err != nil {
t.Fatalf("wisp set: %v", err)
}

got := resolveFormula("", false, tmpDir, rigName)
if got != "mol-evolve" {
t.Errorf("got %q, want %q", got, "mol-evolve")
}
})

t.Run("explicit flag overrides wisp layer", func(t *testing.T) {
t.Parallel()
tmpDir := t.TempDir()
rigName := "testrig"
_ = os.MkdirAll(filepath.Join(tmpDir, rigName), 0o755)

wispCfg := wisp.NewConfig(tmpDir, rigName)
if err := wispCfg.Set("default_formula", "mol-evolve"); err != nil {
t.Fatalf("wisp set: %v", err)
}

got := resolveFormula("mol-custom", false, tmpDir, rigName)
if got != "mol-custom" {
t.Errorf("got %q, want %q", got, "mol-custom")
}
})

t.Run("empty rigName falls back to hardcoded default", func(t *testing.T) {
t.Parallel()
got := resolveFormula("", false, "/tmp/nonexistent", "")
if got != "mol-polecat-work" {
t.Errorf("got %q, want %q", got, "mol-polecat-work")
}
})
}
1 change: 1 addition & 0 deletions internal/rig/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ var SystemDefaults = map[string]interface{}{
"priority_adjustment": 0,
"dnd": false,
"polecat_branch_template": "", // Empty = use default behavior (polecat/{name}/...)
"default_formula": "mol-polecat-work",
}

// StackingKeys defines which keys use stacking semantics (values add up).
Expand Down
Loading