gt-4v1eo: Implement ephemeral protos - cook formulas inline

Major refactor of molecular chemistry to make protos ephemeral:
- Formulas are now cooked directly to in-memory TemplateSubgraph
- No more proto beads stored in the database

Changes:
- cook.go: Add cookFormulaToSubgraph() and resolveAndCookFormula()
  for in-memory formula cooking
- template.go: Add VarDefs field to TemplateSubgraph for default
  value handling, add extractRequiredVariables() and
  applyVariableDefaults() helpers
- pour.go: Try formula loading first for any name (not just mol-)
- wisp.go: Same pattern as pour
- mol_bond.go: Use resolveOrCookToSubgraph() for in-memory subgraphs
- mol_catalog.go: List formulas from disk instead of DB proto beads
- mol_distill.go: Output .formula.json files instead of proto beads

Flow: Formula (.formula.json) -> pour/wisp (cook inline) -> Mol/Wisp

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Steve Yegge
2025-12-25 17:20:10 -08:00
parent ec85577589
commit 54a051aba3
7 changed files with 807 additions and 407 deletions

View File

@@ -10,6 +10,7 @@ import (
"time"
"github.com/spf13/cobra"
"github.com/steveyegge/beads/internal/formula"
"github.com/steveyegge/beads/internal/rpc"
"github.com/steveyegge/beads/internal/storage"
"github.com/steveyegge/beads/internal/types"
@@ -25,10 +26,11 @@ var variablePattern = regexp.MustCompile(`\{\{([a-zA-Z_][a-zA-Z0-9_]*)\}\}`)
// TemplateSubgraph holds a template epic and all its descendants
type TemplateSubgraph struct {
Root *types.Issue // The template epic
Issues []*types.Issue // All issues in the subgraph (including root)
Dependencies []*types.Dependency // All dependencies within the subgraph
IssueMap map[string]*types.Issue // ID -> Issue for quick lookup
Root *types.Issue // The template epic
Issues []*types.Issue // All issues in the subgraph (including root)
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)
}
// InstantiateResult holds the result of template instantiation
@@ -787,6 +789,57 @@ func extractAllVariables(subgraph *TemplateSubgraph) []string {
return extractVariables(allText)
}
// extractRequiredVariables returns only variables that don't have defaults.
// If VarDefs is available (from a cooked formula), uses it to filter out defaulted vars.
// Otherwise, falls back to returning all variables.
func extractRequiredVariables(subgraph *TemplateSubgraph) []string {
allVars := extractAllVariables(subgraph)
// If no VarDefs, assume all variables are required
if subgraph.VarDefs == nil || len(subgraph.VarDefs) == 0 {
return allVars
}
// Filter to only required variables (no default and marked as required, or not defined in VarDefs)
var required []string
for _, v := range allVars {
def, exists := subgraph.VarDefs[v]
// A variable is required if:
// 1. It's not defined in VarDefs at all, OR
// 2. It's defined with Required=true and no Default, OR
// 3. It's defined with no Default (even if Required is false)
if !exists {
required = append(required, v)
} else if def.Default == "" {
required = append(required, v)
}
// If exists and has default, it's not required
}
return required
}
// applyVariableDefaults merges formula default values with provided variables.
// Returns a new map with defaults applied for any missing variables.
func applyVariableDefaults(vars map[string]string, subgraph *TemplateSubgraph) map[string]string {
if subgraph.VarDefs == nil {
return vars
}
result := make(map[string]string)
for k, v := range vars {
result[k] = v
}
// Apply defaults for missing variables
for name, def := range subgraph.VarDefs {
if _, exists := result[name]; !exists && def.Default != "" {
result[name] = def.Default
}
}
return result
}
// substituteVariables replaces {{variable}} with values
func substituteVariables(text string, vars map[string]string) string {
return variablePattern.ReplaceAllStringFunc(text, func(match string) string {