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:
@@ -649,6 +649,8 @@ func cookFormulaToSubgraphWithVars(f *formula.Formula, protoID string, vars map[
|
||||
}
|
||||
}
|
||||
}
|
||||
// Attach recommended phase from formula (bd-mol cleanup: warn on pour of vapor formulas)
|
||||
subgraph.Phase = f.Phase
|
||||
return subgraph, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -911,6 +911,11 @@ func runDiagnostics(path string) doctorResult {
|
||||
result.Checks = append(result.Checks, staleMoleculesCheck)
|
||||
// Don't fail overall check for stale molecules, just warn
|
||||
|
||||
// Check 26b: Persistent mol- issues (should have been ephemeral)
|
||||
persistentMolCheck := convertDoctorCheck(doctor.CheckPersistentMolIssues(path))
|
||||
result.Checks = append(result.Checks, persistentMolCheck)
|
||||
// Don't fail overall check for persistent mol issues, just warn
|
||||
|
||||
// Check 27: Expired tombstones (maintenance, bd-bqcc)
|
||||
tombstonesExpiredCheck := convertDoctorCheck(doctor.CheckExpiredTombstones(path))
|
||||
result.Checks = append(result.Checks, tombstonesExpiredCheck)
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,20 +21,29 @@ var pourCmd = &cobra.Command{
|
||||
Short: "Instantiate a proto as a persistent mol (solid -> liquid)",
|
||||
Long: `Pour a proto into a persistent mol - like pouring molten metal into a mold.
|
||||
|
||||
This is the chemistry-inspired command for creating persistent work from templates.
|
||||
This is the chemistry-inspired command for creating PERSISTENT work from templates.
|
||||
The resulting mol lives in .beads/ (permanent storage) and is synced with git.
|
||||
|
||||
Phase transition: Proto (solid) -> pour -> Mol (liquid)
|
||||
|
||||
Use pour for:
|
||||
- Feature work that spans sessions
|
||||
- Important work needing audit trail
|
||||
- Anything you might need to reference later
|
||||
WHEN TO USE POUR vs WISP:
|
||||
pour (liquid): Persistent work that needs audit trail
|
||||
- Feature implementations spanning multiple sessions
|
||||
- Work you may need to reference later
|
||||
- Anything worth preserving in git history
|
||||
|
||||
wisp (vapor): Ephemeral work that auto-cleans up
|
||||
- Release workflows (one-time execution)
|
||||
- Patrol cycles (deacon, witness, refinery)
|
||||
- Health checks and diagnostics
|
||||
- Any operational workflow without audit value
|
||||
|
||||
TIP: Formulas can specify phase:"vapor" to recommend wisp usage.
|
||||
If you pour a vapor-phase formula, you'll get a warning.
|
||||
|
||||
Examples:
|
||||
bd mol pour mol-feature --var name=auth # Create persistent mol from proto
|
||||
bd mol pour mol-release --var version=1.0 # Release workflow
|
||||
bd mol pour mol-review --var pr=123 # Code review workflow`,
|
||||
bd mol pour mol-feature --var name=auth # Persistent feature work
|
||||
bd mol pour mol-review --var pr=123 # Persistent code review`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: runPour,
|
||||
}
|
||||
@@ -85,6 +94,18 @@ func runPour(cmd *cobra.Command, args []string) {
|
||||
subgraph = sg
|
||||
protoID = sg.Root.ID
|
||||
isFormula = true
|
||||
|
||||
// Warn if formula recommends vapor phase (bd-mol cleanup)
|
||||
if sg.Phase == "vapor" {
|
||||
fmt.Fprintf(os.Stderr, "%s Formula %q recommends vapor phase (ephemeral)\n", ui.RenderWarn("⚠"), args[0])
|
||||
fmt.Fprintf(os.Stderr, " Consider using: bd mol wisp %s", args[0])
|
||||
for _, v := range varFlags {
|
||||
fmt.Fprintf(os.Stderr, " --var %s", v)
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
fmt.Fprintf(os.Stderr, " Pour creates persistent issues that sync to git.\n")
|
||||
fmt.Fprintf(os.Stderr, " Wisp creates ephemeral issues that auto-cleanup.\n\n")
|
||||
}
|
||||
}
|
||||
|
||||
if subgraph == nil {
|
||||
|
||||
@@ -31,6 +31,7 @@ type TemplateSubgraph struct {
|
||||
Dependencies []*types.Dependency // All dependencies within the subgraph
|
||||
IssueMap map[string]*types.Issue // ID -> Issue for quick lookup
|
||||
VarDefs map[string]formula.VarDef // Variable definitions from formula (for defaults)
|
||||
Phase string // Recommended phase: "liquid" (pour) or "vapor" (wisp)
|
||||
}
|
||||
|
||||
// InstantiateResult holds the result of template instantiation
|
||||
|
||||
@@ -29,15 +29,28 @@ import (
|
||||
var wispCmd = &cobra.Command{
|
||||
Use: "wisp [proto-id]",
|
||||
Short: "Create or manage wisps (ephemeral molecules)",
|
||||
Long: `Create or manage wisps - ephemeral molecules for operational workflows.
|
||||
Long: `Create or manage wisps - EPHEMERAL molecules for operational workflows.
|
||||
|
||||
When called with a proto-id argument, creates a wisp from that proto.
|
||||
When called with a subcommand (list, gc), manages existing wisps.
|
||||
|
||||
Wisps are issues with Ephemeral=true in the main database. They're stored
|
||||
locally but NOT exported to JSONL (and thus not synced via git).
|
||||
They're used for patrol cycles, operational loops, and other workflows
|
||||
that shouldn't accumulate in the shared issue database.
|
||||
|
||||
WHEN TO USE WISP vs POUR:
|
||||
wisp (vapor): Ephemeral work that auto-cleans up
|
||||
- Release workflows (one-time execution)
|
||||
- Patrol cycles (deacon, witness, refinery)
|
||||
- Health checks and diagnostics
|
||||
- Any operational workflow without audit value
|
||||
|
||||
pour (liquid): Persistent work that needs audit trail
|
||||
- Feature implementations spanning multiple sessions
|
||||
- Work you may need to reference later
|
||||
- Anything worth preserving in git history
|
||||
|
||||
TIP: Formulas can specify phase:"vapor" to recommend wisp usage.
|
||||
If you use pour on a vapor-phase formula, you'll get a warning.
|
||||
|
||||
The wisp lifecycle:
|
||||
1. Create: bd mol wisp <proto> or bd create --ephemeral
|
||||
@@ -46,9 +59,10 @@ The wisp lifecycle:
|
||||
4. Or burn: bd mol burn <id> (deletes without creating digest)
|
||||
|
||||
Examples:
|
||||
bd mol wisp mol-patrol # Create wisp from proto
|
||||
bd mol wisp list # List all wisps
|
||||
bd mol wisp gc # Garbage collect old wisps
|
||||
bd mol wisp beads-release --var version=1.0 # Release workflow
|
||||
bd mol wisp mol-patrol # Ephemeral patrol cycle
|
||||
bd mol wisp list # List all wisps
|
||||
bd mol wisp gc # Garbage collect old wisps
|
||||
|
||||
Subcommands:
|
||||
list List all wisps in current context
|
||||
|
||||
Reference in New Issue
Block a user