feat(formula): add default formula configuration at rig level (#297)
Allow `gt formula run` to be called without a formula name by configuring a default in the rig's settings/config.json under workflow.default_formula. Co-authored-by: Brett VanderVeen <brett.vanderveen@gfs.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -11,6 +11,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/steveyegge/gastown/internal/config"
|
||||||
"github.com/steveyegge/gastown/internal/style"
|
"github.com/steveyegge/gastown/internal/style"
|
||||||
"github.com/steveyegge/gastown/internal/workspace"
|
"github.com/steveyegge/gastown/internal/workspace"
|
||||||
"golang.org/x/text/cases"
|
"golang.org/x/text/cases"
|
||||||
@@ -92,17 +93,20 @@ Examples:
|
|||||||
}
|
}
|
||||||
|
|
||||||
var formulaRunCmd = &cobra.Command{
|
var formulaRunCmd = &cobra.Command{
|
||||||
Use: "run <name>",
|
Use: "run [name]",
|
||||||
Short: "Execute a formula",
|
Short: "Execute a formula",
|
||||||
Long: `Execute a formula by pouring it and dispatching work.
|
Long: `Execute a formula by pouring it and dispatching work.
|
||||||
|
|
||||||
This command:
|
This command:
|
||||||
1. Looks up the formula by name
|
1. Looks up the formula by name (or uses default from rig config)
|
||||||
2. Pours it to create a molecule (or uses existing proto)
|
2. Pours it to create a molecule (or uses existing proto)
|
||||||
3. Dispatches the molecule to available workers
|
3. Dispatches the molecule to available workers
|
||||||
|
|
||||||
For PR-based workflows, use --pr to specify the GitHub PR number.
|
For PR-based workflows, use --pr to specify the GitHub PR number.
|
||||||
|
|
||||||
|
If no formula name is provided, uses the default formula configured in
|
||||||
|
the rig's settings/config.json under workflow.default_formula.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
--pr=N Run formula on GitHub PR #N
|
--pr=N Run formula on GitHub PR #N
|
||||||
--rig=NAME Target specific rig (default: current or gastown)
|
--rig=NAME Target specific rig (default: current or gastown)
|
||||||
@@ -110,10 +114,11 @@ Options:
|
|||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
gt formula run shiny # Run formula in current rig
|
gt formula run shiny # Run formula in current rig
|
||||||
|
gt formula run # Run default formula from rig config
|
||||||
gt formula run shiny --pr=123 # Run on PR #123
|
gt formula run shiny --pr=123 # Run on PR #123
|
||||||
gt formula run security-audit --rig=beads # Run in specific rig
|
gt formula run security-audit --rig=beads # Run in specific rig
|
||||||
gt formula run release --dry-run # Preview execution`,
|
gt formula run release --dry-run # Preview execution`,
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.MaximumNArgs(1),
|
||||||
RunE: runFormulaRun,
|
RunE: runFormulaRun,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,7 +198,46 @@ func runFormulaShow(cmd *cobra.Command, args []string) error {
|
|||||||
// For convoy-type formulas, it creates a convoy bead, creates leg beads,
|
// For convoy-type formulas, it creates a convoy bead, creates leg beads,
|
||||||
// and slings each leg to a separate polecat with leg-specific prompts.
|
// and slings each leg to a separate polecat with leg-specific prompts.
|
||||||
func runFormulaRun(cmd *cobra.Command, args []string) error {
|
func runFormulaRun(cmd *cobra.Command, args []string) error {
|
||||||
formulaName := args[0]
|
// Determine target rig first (needed for default formula lookup)
|
||||||
|
targetRig := formulaRunRig
|
||||||
|
var rigPath string
|
||||||
|
if targetRig == "" {
|
||||||
|
// Try to detect from current directory
|
||||||
|
townRoot, err := workspace.FindFromCwd()
|
||||||
|
if err == nil && townRoot != "" {
|
||||||
|
rigName, r, rigErr := findCurrentRig(townRoot)
|
||||||
|
if rigErr == nil && rigName != "" {
|
||||||
|
targetRig = rigName
|
||||||
|
if r != nil {
|
||||||
|
rigPath = r.Path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if targetRig == "" {
|
||||||
|
targetRig = "gastown" // Default
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If rig specified, construct path
|
||||||
|
townRoot, err := workspace.FindFromCwd()
|
||||||
|
if err == nil && townRoot != "" {
|
||||||
|
rigPath = filepath.Join(townRoot, targetRig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get formula name from args or default
|
||||||
|
var formulaName string
|
||||||
|
if len(args) > 0 {
|
||||||
|
formulaName = args[0]
|
||||||
|
} else {
|
||||||
|
// Try to get default formula from rig config
|
||||||
|
if rigPath != "" {
|
||||||
|
formulaName = config.GetDefaultFormula(rigPath)
|
||||||
|
}
|
||||||
|
if formulaName == "" {
|
||||||
|
return fmt.Errorf("no formula specified and no default formula configured\n\nTo set a default formula, add to your rig's settings/config.json:\n \"workflow\": {\n \"default_formula\": \"<formula-name>\"\n }")
|
||||||
|
}
|
||||||
|
fmt.Printf("%s Using default formula: %s\n", style.Dim.Render("Note:"), formulaName)
|
||||||
|
}
|
||||||
|
|
||||||
// Find the formula file
|
// Find the formula file
|
||||||
formulaPath, err := findFormulaFile(formulaName)
|
formulaPath, err := findFormulaFile(formulaName)
|
||||||
@@ -207,22 +251,6 @@ func runFormulaRun(cmd *cobra.Command, args []string) error {
|
|||||||
return fmt.Errorf("parsing formula: %w", err)
|
return fmt.Errorf("parsing formula: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine target rig
|
|
||||||
targetRig := formulaRunRig
|
|
||||||
if targetRig == "" {
|
|
||||||
// Try to detect from current directory
|
|
||||||
townRoot, err := workspace.FindFromCwd()
|
|
||||||
if err == nil && townRoot != "" {
|
|
||||||
rigName, _, rigErr := findCurrentRig(townRoot)
|
|
||||||
if rigErr == nil && rigName != "" {
|
|
||||||
targetRig = rigName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if targetRig == "" {
|
|
||||||
targetRig = "gastown" // Default
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle dry-run mode
|
// Handle dry-run mode
|
||||||
if formulaRunDryRun {
|
if formulaRunDryRun {
|
||||||
return dryRunFormula(f, formulaName, targetRig)
|
return dryRunFormula(f, formulaName, targetRig)
|
||||||
|
|||||||
@@ -1227,6 +1227,21 @@ func ExpectedPaneCommands(rc *RuntimeConfig) []string {
|
|||||||
return []string{filepath.Base(rc.Command)}
|
return []string{filepath.Base(rc.Command)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetDefaultFormula returns the default formula for a rig from settings/config.json.
|
||||||
|
// Returns empty string if no default is configured.
|
||||||
|
// rigPath is the path to the rig directory (e.g., ~/gt/gastown).
|
||||||
|
func GetDefaultFormula(rigPath string) string {
|
||||||
|
settingsPath := RigSettingsPath(rigPath)
|
||||||
|
settings, err := LoadRigSettings(settingsPath)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if settings.Workflow == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return settings.Workflow.DefaultFormula
|
||||||
|
}
|
||||||
|
|
||||||
// GetRigPrefix returns the beads prefix for a rig from rigs.json.
|
// GetRigPrefix returns the beads prefix for a rig from rigs.json.
|
||||||
// Falls back to "gt" if the rig isn't found or has no prefix configured.
|
// Falls back to "gt" if the rig isn't found or has no prefix configured.
|
||||||
// townRoot is the path to the town directory (e.g., ~/gt).
|
// townRoot is the path to the town directory (e.g., ~/gt).
|
||||||
|
|||||||
@@ -180,6 +180,13 @@ type RigConfig struct {
|
|||||||
Beads *BeadsConfig `json:"beads,omitempty"`
|
Beads *BeadsConfig `json:"beads,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WorkflowConfig represents workflow settings for a rig.
|
||||||
|
type WorkflowConfig struct {
|
||||||
|
// DefaultFormula is the formula to use when `gt formula run` is called without arguments.
|
||||||
|
// If empty, no default is set and a formula name must be provided.
|
||||||
|
DefaultFormula string `json:"default_formula,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
// RigSettings represents per-rig behavioral configuration (settings/config.json).
|
// RigSettings represents per-rig behavioral configuration (settings/config.json).
|
||||||
type RigSettings struct {
|
type RigSettings struct {
|
||||||
Type string `json:"type"` // "rig-settings"
|
Type string `json:"type"` // "rig-settings"
|
||||||
@@ -188,6 +195,7 @@ type RigSettings struct {
|
|||||||
Theme *ThemeConfig `json:"theme,omitempty"` // tmux theme settings
|
Theme *ThemeConfig `json:"theme,omitempty"` // tmux theme settings
|
||||||
Namepool *NamepoolConfig `json:"namepool,omitempty"` // polecat name pool settings
|
Namepool *NamepoolConfig `json:"namepool,omitempty"` // polecat name pool settings
|
||||||
Crew *CrewConfig `json:"crew,omitempty"` // crew startup settings
|
Crew *CrewConfig `json:"crew,omitempty"` // crew startup settings
|
||||||
|
Workflow *WorkflowConfig `json:"workflow,omitempty"` // workflow settings
|
||||||
Runtime *RuntimeConfig `json:"runtime,omitempty"` // LLM runtime settings (deprecated: use Agent)
|
Runtime *RuntimeConfig `json:"runtime,omitempty"` // LLM runtime settings (deprecated: use Agent)
|
||||||
|
|
||||||
// Agent selects which agent preset to use for this rig.
|
// Agent selects which agent preset to use for this rig.
|
||||||
|
|||||||
Reference in New Issue
Block a user