fix(doctor): check patrol formulas instead of placeholder beads (#715)
Changes patrol-molecules-exist check to verify that patrol formulas are accessible via `bd formula list` instead of looking for placeholder molecule beads created by `bd create --type=molecule`. ## Problem The check was looking for molecule-type beads with titles "Deacon Patrol", "Witness Patrol", and "Refinery Patrol". These placeholder beads serve no functional purpose because: 1. Patrols actually use `bd mol wisp mol-deacon-patrol` which cooks formulas inline (on-the-fly) 2. The formulas already exist at the town level in .beads/formulas/ 3. The placeholder beads are never referenced by any patrol code ## Solution - Check for formula names (mol-deacon-patrol, mol-witness-patrol, mol-refinery-patrol) instead of bead titles - Use `bd formula list` instead of `bd list --type=molecule` - Remove Fix() method that created unnecessary placeholder beads - Update messages to reflect that formulas should exist in search paths ## Impact - Check now verifies what patrols actually need (formulas) - Eliminates creation of unnecessary placeholder beads - More accurate health check for patrol system Co-authored-by: Roland Tritsch <roland@ailtir.com> Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -15,35 +15,35 @@ import (
|
|||||||
"github.com/steveyegge/gastown/internal/templates"
|
"github.com/steveyegge/gastown/internal/templates"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PatrolMoleculesExistCheck verifies that patrol molecules exist for each rig.
|
// PatrolMoleculesExistCheck verifies that patrol formulas are accessible.
|
||||||
|
// Patrols use `bd mol wisp <formula-name>` to spawn workflows, so the formulas
|
||||||
|
// must exist in the formula search path (.beads/formulas/, ~/.beads/formulas/, or $GT_ROOT/.beads/formulas/).
|
||||||
type PatrolMoleculesExistCheck struct {
|
type PatrolMoleculesExistCheck struct {
|
||||||
FixableCheck
|
BaseCheck
|
||||||
missingMols map[string][]string // rig -> missing molecule titles
|
missingFormulas map[string][]string // rig -> missing formula names
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPatrolMoleculesExistCheck creates a new patrol molecules exist check.
|
// NewPatrolMoleculesExistCheck creates a new patrol formulas exist check.
|
||||||
func NewPatrolMoleculesExistCheck() *PatrolMoleculesExistCheck {
|
func NewPatrolMoleculesExistCheck() *PatrolMoleculesExistCheck {
|
||||||
return &PatrolMoleculesExistCheck{
|
return &PatrolMoleculesExistCheck{
|
||||||
FixableCheck: FixableCheck{
|
BaseCheck: BaseCheck{
|
||||||
BaseCheck: BaseCheck{
|
CheckName: "patrol-molecules-exist",
|
||||||
CheckName: "patrol-molecules-exist",
|
CheckDescription: "Check if patrol formulas are accessible",
|
||||||
CheckDescription: "Check if patrol molecules exist for each rig",
|
CheckCategory: CategoryPatrol,
|
||||||
CheckCategory: CategoryPatrol,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// patrolMolecules are the required patrol molecule titles.
|
// patrolFormulas are the required patrol formula names.
|
||||||
var patrolMolecules = []string{
|
var patrolFormulas = []string{
|
||||||
"Deacon Patrol",
|
"mol-deacon-patrol",
|
||||||
"Witness Patrol",
|
"mol-witness-patrol",
|
||||||
"Refinery Patrol",
|
"mol-refinery-patrol",
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run checks if patrol molecules exist.
|
// Run checks if patrol formulas are accessible.
|
||||||
func (c *PatrolMoleculesExistCheck) Run(ctx *CheckContext) *CheckResult {
|
func (c *PatrolMoleculesExistCheck) Run(ctx *CheckContext) *CheckResult {
|
||||||
c.missingMols = make(map[string][]string)
|
c.missingFormulas = make(map[string][]string)
|
||||||
|
|
||||||
rigs, err := discoverRigs(ctx.TownRoot)
|
rigs, err := discoverRigs(ctx.TownRoot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -66,9 +66,9 @@ func (c *PatrolMoleculesExistCheck) Run(ctx *CheckContext) *CheckResult {
|
|||||||
var details []string
|
var details []string
|
||||||
for _, rigName := range rigs {
|
for _, rigName := range rigs {
|
||||||
rigPath := filepath.Join(ctx.TownRoot, rigName)
|
rigPath := filepath.Join(ctx.TownRoot, rigName)
|
||||||
missing := c.checkPatrolMolecules(rigPath)
|
missing := c.checkPatrolFormulas(rigPath)
|
||||||
if len(missing) > 0 {
|
if len(missing) > 0 {
|
||||||
c.missingMols[rigName] = missing
|
c.missingFormulas[rigName] = missing
|
||||||
details = append(details, fmt.Sprintf("%s: missing %v", rigName, missing))
|
details = append(details, fmt.Sprintf("%s: missing %v", rigName, missing))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -77,73 +77,42 @@ func (c *PatrolMoleculesExistCheck) Run(ctx *CheckContext) *CheckResult {
|
|||||||
return &CheckResult{
|
return &CheckResult{
|
||||||
Name: c.Name(),
|
Name: c.Name(),
|
||||||
Status: StatusWarning,
|
Status: StatusWarning,
|
||||||
Message: fmt.Sprintf("%d rig(s) missing patrol molecules", len(c.missingMols)),
|
Message: fmt.Sprintf("%d rig(s) missing patrol formulas", len(c.missingFormulas)),
|
||||||
Details: details,
|
Details: details,
|
||||||
FixHint: "Run 'gt doctor --fix' to create missing patrol molecules",
|
FixHint: "Formulas should exist in .beads/formulas/ at town or rig level, or in ~/.beads/formulas/",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &CheckResult{
|
return &CheckResult{
|
||||||
Name: c.Name(),
|
Name: c.Name(),
|
||||||
Status: StatusOK,
|
Status: StatusOK,
|
||||||
Message: fmt.Sprintf("All %d rig(s) have patrol molecules", len(rigs)),
|
Message: fmt.Sprintf("All %d rig(s) have patrol formulas accessible", len(rigs)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkPatrolMolecules returns missing patrol molecule titles for a rig.
|
// checkPatrolFormulas returns missing patrol formula names for a rig.
|
||||||
func (c *PatrolMoleculesExistCheck) checkPatrolMolecules(rigPath string) []string {
|
func (c *PatrolMoleculesExistCheck) checkPatrolFormulas(rigPath string) []string {
|
||||||
// List molecules using bd
|
// List formulas accessible from this rig using bd formula list
|
||||||
cmd := exec.Command("bd", "list", "--type=molecule")
|
// This checks .beads/formulas/, ~/.beads/formulas/, and $GT_ROOT/.beads/formulas/
|
||||||
|
cmd := exec.Command("bd", "formula", "list")
|
||||||
cmd.Dir = rigPath
|
cmd.Dir = rigPath
|
||||||
output, err := cmd.Output()
|
output, err := cmd.Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return patrolMolecules // Can't check, assume all missing
|
// Can't check formulas, assume all missing
|
||||||
|
return patrolFormulas
|
||||||
}
|
}
|
||||||
|
|
||||||
outputStr := string(output)
|
outputStr := string(output)
|
||||||
var missing []string
|
var missing []string
|
||||||
for _, mol := range patrolMolecules {
|
for _, formulaName := range patrolFormulas {
|
||||||
if !strings.Contains(outputStr, mol) {
|
// Formula list output includes the formula name without extension
|
||||||
missing = append(missing, mol)
|
if !strings.Contains(outputStr, formulaName) {
|
||||||
|
missing = append(missing, formulaName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return missing
|
return missing
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fix creates missing patrol molecules.
|
|
||||||
func (c *PatrolMoleculesExistCheck) Fix(ctx *CheckContext) error {
|
|
||||||
for rigName, missing := range c.missingMols {
|
|
||||||
rigPath := filepath.Join(ctx.TownRoot, rigName)
|
|
||||||
for _, mol := range missing {
|
|
||||||
desc := getPatrolMoleculeDesc(mol)
|
|
||||||
cmd := exec.Command("bd", "create", //nolint:gosec // G204: args are constructed internally
|
|
||||||
"--type=molecule",
|
|
||||||
"--title="+mol,
|
|
||||||
"--description="+desc,
|
|
||||||
"--priority=2",
|
|
||||||
)
|
|
||||||
cmd.Dir = rigPath
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
return fmt.Errorf("creating %s in %s: %w", mol, rigName, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getPatrolMoleculeDesc(title string) string {
|
|
||||||
switch title {
|
|
||||||
case "Deacon Patrol":
|
|
||||||
return "Mayor's daemon patrol loop for handling callbacks, health checks, and cleanup."
|
|
||||||
case "Witness Patrol":
|
|
||||||
return "Per-rig worker monitor patrol loop with progressive nudging."
|
|
||||||
case "Refinery Patrol":
|
|
||||||
return "Merge queue processor patrol loop with verification gates."
|
|
||||||
default:
|
|
||||||
return "Patrol molecule"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PatrolHooksWiredCheck verifies that hooks trigger patrol execution.
|
// PatrolHooksWiredCheck verifies that hooks trigger patrol execution.
|
||||||
type PatrolHooksWiredCheck struct {
|
type PatrolHooksWiredCheck struct {
|
||||||
FixableCheck
|
FixableCheck
|
||||||
|
|||||||
Reference in New Issue
Block a user