feat: add pour warning for vapor-phase formulas, improve help text, add doctor check

- Add Phase field to Formula type to indicate recommended instantiation phase
- Add warning in 'bd mol pour' when formula has phase="vapor"
- Improve pour/wisp help text with clear comparison of when to use each
- Add CheckPersistentMolIssues doctor check to detect mol- issues in JSONL
- Update beads-release.formula.json with phase="vapor"

This helps prevent accidental persistence of ephemeral workflow issues.
This commit is contained in:
Steve Yegge
2025-12-28 01:34:01 -08:00
parent b5ab4f2a3b
commit dfc796589f
7 changed files with 137 additions and 14 deletions

View File

@@ -350,3 +350,78 @@ func resolveBeadsDir(beadsDir string) string {
return target
}
// CheckPersistentMolIssues detects mol- prefixed issues that should have been ephemeral.
// When users run "bd mol pour" on formulas that should use "bd mol wisp", the resulting
// issues get the "mol-" prefix but persist in JSONL. These should be cleaned up.
func CheckPersistentMolIssues(path string) DoctorCheck {
beadsDir := resolveBeadsDir(filepath.Join(path, ".beads"))
jsonlPath := filepath.Join(beadsDir, "issues.jsonl")
if _, err := os.Stat(jsonlPath); os.IsNotExist(err) {
return DoctorCheck{
Name: "Persistent Mol Issues",
Status: StatusOK,
Message: "N/A (no JSONL file)",
Category: CategoryMaintenance,
}
}
// Read JSONL and count mol- prefixed issues that are not ephemeral
file, err := os.Open(jsonlPath) // #nosec G304 - path constructed safely
if err != nil {
return DoctorCheck{
Name: "Persistent Mol Issues",
Status: StatusOK,
Message: "N/A (unable to read JSONL)",
Category: CategoryMaintenance,
}
}
defer file.Close()
var molCount int
var molIDs []string
decoder := json.NewDecoder(file)
for {
var issue types.Issue
if err := decoder.Decode(&issue); err != nil {
break
}
// Skip deleted issues (tombstones)
if issue.DeletedAt != nil {
continue
}
// Look for mol- prefix that shouldn't be in JSONL
// (ephemeral issues have Ephemeral=true and don't get exported)
if strings.HasPrefix(issue.ID, "mol-") && !issue.Ephemeral {
molCount++
if len(molIDs) < 3 {
molIDs = append(molIDs, issue.ID)
}
}
}
if molCount == 0 {
return DoctorCheck{
Name: "Persistent Mol Issues",
Status: StatusOK,
Message: "No persistent mol- issues",
Category: CategoryMaintenance,
}
}
detail := fmt.Sprintf("Example: %v", molIDs)
if molCount > 3 {
detail += fmt.Sprintf(" (+%d more)", molCount-3)
}
return DoctorCheck{
Name: "Persistent Mol Issues",
Status: StatusWarning,
Message: fmt.Sprintf("%d mol- issue(s) in JSONL should be ephemeral", molCount),
Detail: detail,
Fix: "Run 'bd delete <id> --force' to remove, or use 'bd mol wisp' instead of 'bd mol pour'",
Category: CategoryMaintenance,
}
}