feat(mol): add 'bd mol seed --patrol' command (#1149)

* Fix doctor DB-JSONL sync check to exclude ephemeral wisps

The DB-JSONL Sync check was showing a false positive warning when wisps
(ephemeral issues) existed in the database. Wisps are intentionally
excluded from JSONL exports, so they shouldn't be counted when comparing
database and JSONL issue counts.

Changes:
- Updated CheckDatabaseJSONLSync to exclude ephemeral issues from count
  using 'WHERE ephemeral = 0 OR ephemeral IS NULL'
- Added 'ephemeral' column to test database schema
- Added test case to verify ephemeral issues are excluded from count
- Updated TestCheckDatabaseJSONLSync_MoleculePrefix to include ephemeral column

This prevents false warnings like 'database has 92 issues, JSONL has 30'
when the difference is entirely due to wisps that should not be exported.

* feat(mol): add 'bd mol seed --patrol' command

Adds `bd mol seed` command to verify formula accessibility before
patrols attempt to spawn work.

## Problem

Gas Town's `gt rig add` calls `bd mol seed --patrol` but this command
didn't exist in beads, causing the call to always fail and fall back
to creating non-functional placeholder beads.

## Solution

Implemented `bd mol seed` with two modes:

1. **With --patrol flag**: Verifies all three patrol formulas are accessible
   - mol-deacon-patrol
   - mol-witness-patrol
   - mol-refinery-patrol

2. **Without --patrol**: Verifies a specific formula is accessible

## Implementation

The command uses `resolveAndCookFormulaWithVars` to verify formulas:
- Checks formula search paths (.beads/formulas/, ~/.beads/formulas/, $GT_ROOT/.beads/formulas/)
- Validates formula syntax and resolution
- Confirms formula can be cooked to subgraph

## Usage

```bash
bd mol seed --patrol                    # Verify all patrol formulas
bd mol seed mol-feature                 # Verify specific formula
bd mol seed mol-review --var name=test  # With variable substitution
```

## Testing

-  Command compiles without errors
-  Help text displays correctly
-  `--patrol` succeeds when formulas accessible (town level)
-  `--patrol` fails with clear error when formulas missing (rig level)
-  Follows existing beads command patterns (cobra, flags, error handling)

## Impact

- Enables Gas Town's intended patrol formula verification flow
- Eliminates creation of unnecessary placeholder beads
- Provides health check command for formula accessibility
- Foundation for future seed commands (data initialization, etc.)

Fixes the missing command referenced in steveyegge/gastown#715

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Roland Tritsch <roland@ailtir.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Roland Tritsch
2026-01-20 22:06:57 +00:00
committed by GitHub
parent 09355eee8c
commit c1ac69da3e

136
cmd/bd/mol_seed.go Normal file
View File

@@ -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)
}