diff --git a/cmd/bd/mol_seed.go b/cmd/bd/mol_seed.go new file mode 100644 index 00000000..a0eb46c2 --- /dev/null +++ b/cmd/bd/mol_seed.go @@ -0,0 +1,136 @@ +package main + +import ( + "fmt" + "os" + "strings" + + "github.com/spf13/cobra" +) + +var molSeedCmd = &cobra.Command{ + Use: "seed [formula-name]", + Short: "Verify formula accessibility or seed patrol formulas", + Long: `Verify that formulas are accessible and can be cooked. + +The seed command checks formula search paths to ensure formulas exist +and can be loaded. This is useful for verifying system health before +patrols attempt to spawn work. + +WITH --patrol FLAG: + Verifies all three patrol formulas are accessible: + - mol-deacon-patrol + - mol-witness-patrol + - mol-refinery-patrol + +WITHOUT --patrol: + Verifies the specified formula is accessible. + +Formula search paths (checked in order): + 1. .beads/formulas/ (project level) + 2. ~/.beads/formulas/ (user level) + 3. $GT_ROOT/.beads/formulas/ (orchestrator level, if GT_ROOT set) + +Examples: + bd mol seed --patrol # Verify all patrol formulas + bd mol seed mol-feature # Verify specific formula + bd mol seed mol-review --var name=test # Verify with variable substitution`, + Args: cobra.MaximumNArgs(1), + Run: runMolSeed, +} + +func runMolSeed(cmd *cobra.Command, args []string) { + patrol, _ := cmd.Flags().GetBool("patrol") + varFlags, _ := cmd.Flags().GetStringArray("var") + + // Parse variables (for formula condition filtering if needed) + vars := make(map[string]string) + for _, v := range varFlags { + parts := strings.SplitN(v, "=", 2) + if len(parts) != 2 { + fmt.Fprintf(os.Stderr, "Error: invalid variable format '%s', expected 'key=value'\n", v) + os.Exit(1) + } + vars[parts[0]] = parts[1] + } + + if patrol { + // Verify all patrol formulas + if err := verifyPatrolFormulas(vars); err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } + if !jsonOutput { + fmt.Println("✓ All patrol formulas accessible") + } else { + outputJSON(map[string]interface{}{ + "status": "ok", + "formulas": []string{"mol-deacon-patrol", "mol-witness-patrol", "mol-refinery-patrol"}, + }) + } + return + } + + if len(args) == 0 { + fmt.Fprintf(os.Stderr, "Error: formula name required (or use --patrol flag)\n") + os.Exit(1) + } + + // Verify single formula + formulaName := args[0] + if err := verifyFormula(formulaName, vars); err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } + + if !jsonOutput { + fmt.Printf("✓ Formula %q accessible\n", formulaName) + } else { + outputJSON(map[string]interface{}{ + "status": "ok", + "formula": formulaName, + }) + } +} + +// verifyPatrolFormulas checks that all patrol formulas are accessible +func verifyPatrolFormulas(vars map[string]string) error { + patrolFormulas := []string{ + "mol-deacon-patrol", + "mol-witness-patrol", + "mol-refinery-patrol", + } + + var missing []string + for _, formula := range patrolFormulas { + if err := verifyFormula(formula, vars); err != nil { + missing = append(missing, formula) + } + } + + if len(missing) > 0 { + return fmt.Errorf("patrol formulas not accessible: %v", missing) + } + + return nil +} + +// verifyFormula checks if a formula can be loaded and cooked +func verifyFormula(formulaName string, vars map[string]string) error { + // Try to cook the formula - this verifies: + // 1. Formula exists in search path + // 2. Formula syntax is valid + // 3. Formula can be resolved (extends, etc.) + // 4. Formula can be cooked to subgraph + _, err := resolveAndCookFormulaWithVars(formulaName, nil, vars) + if err != nil { + return fmt.Errorf("formula %q not accessible: %w", formulaName, err) + } + return nil +} + +func init() { + molSeedCmd.Flags().Bool("patrol", false, "Verify all patrol formulas (mol-deacon-patrol, mol-witness-patrol, mol-refinery-patrol)") + molSeedCmd.Flags().StringArray("var", []string{}, "Variable substitution for condition filtering (key=value)") + molCmd.AddCommand(molSeedCmd) +}