diff --git a/internal/cmd/sling.go b/internal/cmd/sling.go index 74a58f89..b13f3151 100644 --- a/internal/cmd/sling.go +++ b/internal/cmd/sling.go @@ -35,6 +35,13 @@ Examples: gt sling gt-abc crew # Sling to crew worker gt sling gt-abc gastown/crew/max # Sling to specific agent +Formula scaffolding (--on flag): + gt sling shiny --on gt-abc # Apply shiny formula to existing work + gt sling mol-review --on gt-abc crew # Apply review formula, sling to crew + +When --on is specified, the first argument is a formula name (not a bead). +The formula shapes execution of the target bead, creating wisp scaffolding. + Compare: gt hook # Just attach (no action) gt sling # Attach + start now (keep context) @@ -46,20 +53,33 @@ The propulsion principle: if it's on your hook, YOU RUN IT.`, } var ( - slingSubject string - slingMessage string - slingDryRun bool + slingSubject string + slingMessage string + slingDryRun bool + slingOnTarget string // --on flag: target bead when slinging a formula ) func init() { slingCmd.Flags().StringVarP(&slingSubject, "subject", "s", "", "Context subject for the work") slingCmd.Flags().StringVarP(&slingMessage, "message", "m", "", "Context message for the work") slingCmd.Flags().BoolVarP(&slingDryRun, "dry-run", "n", false, "Show what would be done") + slingCmd.Flags().StringVar(&slingOnTarget, "on", "", "Apply formula to existing bead (implies wisp scaffolding)") rootCmd.AddCommand(slingCmd) } func runSling(cmd *cobra.Command, args []string) error { - beadID := args[0] + // Determine if we're in formula mode (--on flag) + var beadID string + var formulaName string + + if slingOnTarget != "" { + // Formula mode: gt sling --on + formulaName = args[0] + beadID = slingOnTarget + } else { + // Normal mode: gt sling + beadID = args[0] + } // Polecats cannot sling - check early before writing anything if polecatName := os.Getenv("GT_POLECAT"); polecatName != "" { @@ -71,6 +91,13 @@ func runSling(cmd *cobra.Command, args []string) error { return err } + // If formula specified, verify it exists + if formulaName != "" { + if err := verifyFormulaExists(formulaName); err != nil { + return err + } + } + // Determine target agent (self or specified) var targetAgent string var targetPane string @@ -128,12 +155,21 @@ func runSling(cmd *cobra.Command, args []string) error { sw := wisp.NewSlungWork(beadID, targetAgent) sw.Subject = slingSubject sw.Context = slingMessage + sw.Formula = formulaName - fmt.Printf("%s Slinging %s to %s...\n", style.Bold.Render("🎯"), beadID, targetAgent) + // Display what we're doing + if formulaName != "" { + fmt.Printf("%s Slinging formula %s on %s to %s...\n", style.Bold.Render("🎯"), formulaName, beadID, targetAgent) + } else { + fmt.Printf("%s Slinging %s to %s...\n", style.Bold.Render("🎯"), beadID, targetAgent) + } if slingDryRun { fmt.Printf("Would create wisp: %s\n", wisp.HookPath(hookRoot, targetAgent)) fmt.Printf(" bead_id: %s\n", beadID) + if formulaName != "" { + fmt.Printf(" formula: %s\n", formulaName) + } fmt.Printf(" agent: %s\n", targetAgent) fmt.Printf(" hook_root: %s\n", hookRoot) if slingSubject != "" { @@ -294,3 +330,24 @@ func detectCloneRoot() (string, error) { } return strings.TrimSpace(string(out)), nil } + +// verifyFormulaExists checks that the formula exists using bd formula show. +// Formulas can be proto beads (mol-*) or formula files (.formula.json). +func verifyFormulaExists(formulaName string) error { + // Try as a proto bead first (mol-* prefix is common) + cmd := exec.Command("bd", "show", formulaName, "--json") + if err := cmd.Run(); err == nil { + return nil // Found as a proto + } + + // Try with mol- prefix + cmd = exec.Command("bd", "show", "mol-"+formulaName, "--json") + if err := cmd.Run(); err == nil { + return nil // Found as mol- + } + + // TODO: Check for .formula.json file in search paths + // For now, we require the formula to exist as a proto + + return fmt.Errorf("formula '%s' not found (try 'bd cook' to create it from a .formula.json file)", formulaName) +} diff --git a/internal/wisp/types.go b/internal/wisp/types.go index 1a829a96..138abf05 100644 --- a/internal/wisp/types.go +++ b/internal/wisp/types.go @@ -53,6 +53,11 @@ type SlungWork struct { // BeadID is the issue/bead to work on (e.g., "gt-xxx"). BeadID string `json:"bead_id"` + // Formula is the optional formula/form to apply to the work. + // When set, this creates scaffolding around the target bead. + // Used by `gt sling --on `. + Formula string `json:"formula,omitempty"` + // Context is optional additional context from the slinger. Context string `json:"context,omitempty"`