feat(formula): Add inline step expansion (gt-8tmz.35)

Steps can now declare their own expansion using the Expand field:

  steps:
    - id: design
      expand: rule-of-five
      expand_vars:
        iterations: 3

This is more convenient than compose.expand for single-step expansions.
The step is replaced by the expansion template with variables substituted.

Reuses existing expandStep() and mergeVars() from gt-8tmz.34.

🤖 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 20:46:12 -08:00
parent f82c75c39e
commit e91f0de8d1
2 changed files with 95 additions and 0 deletions

View File

@@ -157,6 +157,15 @@ func runCook(cmd *cobra.Command, args []string) {
resolved.Steps = formula.ApplyAdvice(resolved.Steps, resolved.Advice)
}
// Apply inline step expansions (gt-8tmz.35)
// This processes Step.Expand fields before compose.expand/map rules
inlineExpandedSteps, err := formula.ApplyInlineExpansions(resolved.Steps, parser)
if err != nil {
fmt.Fprintf(os.Stderr, "Error applying inline expansions: %v\n", err)
os.Exit(1)
}
resolved.Steps = inlineExpandedSteps
// Apply expansion operators (gt-8tmz.3)
if resolved.Compose != nil && (len(resolved.Compose.Expand) > 0 || len(resolved.Compose.Map) > 0) {
expandedSteps, err := formula.ApplyExpansions(resolved.Steps, resolved.Compose, parser)
@@ -586,6 +595,13 @@ func resolveAndCookFormula(formulaName string, searchPaths []string) (*TemplateS
resolved.Steps = formula.ApplyAdvice(resolved.Steps, resolved.Advice)
}
// Apply inline step expansions (gt-8tmz.35)
inlineExpandedSteps, err := formula.ApplyInlineExpansions(resolved.Steps, parser)
if err != nil {
return nil, fmt.Errorf("applying inline expansions to %q: %w", formulaName, err)
}
resolved.Steps = inlineExpandedSteps
// Apply expansion operators (gt-8tmz.3)
if resolved.Compose != nil && (len(resolved.Compose.Expand) > 0 || len(resolved.Compose.Map) > 0) {
expandedSteps, err := formula.ApplyExpansions(resolved.Steps, resolved.Compose, parser)

View File

@@ -360,3 +360,82 @@ func UpdateDependenciesForExpansion(steps []*Step, expandedID string, lastExpand
return result
}
// ApplyInlineExpansions applies Step.Expand fields to inline expansions (gt-8tmz.35).
// Steps with the Expand field set are replaced by the referenced expansion template.
// The step's ExpandVars are passed as variable overrides to the expansion.
//
// This differs from compose.Expand in that the expansion is declared inline on the
// step itself rather than in a central compose section.
//
// Returns a new steps slice with inline expansions applied.
// The original steps slice is not modified.
func ApplyInlineExpansions(steps []*Step, parser *Parser) ([]*Step, error) {
if parser == nil {
return steps, nil
}
return applyInlineExpansionsRecursive(steps, parser, 0)
}
// applyInlineExpansionsRecursive handles inline expansions for a slice of steps.
// depth tracks recursion to prevent infinite expansion loops.
func applyInlineExpansionsRecursive(steps []*Step, parser *Parser, depth int) ([]*Step, error) {
if depth > DefaultMaxExpansionDepth {
return nil, fmt.Errorf("inline expansion depth limit exceeded: max %d levels", DefaultMaxExpansionDepth)
}
var result []*Step
for _, step := range steps {
// Check if this step has an inline expansion
if step.Expand != "" {
// Load the expansion formula
expFormula, err := parser.LoadByName(step.Expand)
if err != nil {
return nil, fmt.Errorf("inline expand on step %q: loading %q: %w", step.ID, step.Expand, err)
}
if expFormula.Type != TypeExpansion {
return nil, fmt.Errorf("inline expand on step %q: %q is not an expansion formula (type=%s)",
step.ID, step.Expand, expFormula.Type)
}
if len(expFormula.Template) == 0 {
return nil, fmt.Errorf("inline expand on step %q: %q has no template steps", step.ID, step.Expand)
}
// Merge formula default vars with step's ExpandVars overrides
vars := mergeVars(expFormula, step.ExpandVars)
// Expand the step using the template (reuse existing expandStep)
expandedSteps, err := expandStep(step, expFormula.Template, 0, vars)
if err != nil {
return nil, fmt.Errorf("inline expand on step %q: %w", step.ID, err)
}
// Recursively process expanded steps for nested inline expansions
processedSteps, err := applyInlineExpansionsRecursive(expandedSteps, parser, depth+1)
if err != nil {
return nil, err
}
result = append(result, processedSteps...)
} else {
// No inline expansion - keep the step, but process children recursively
clone := cloneStep(step)
if len(step.Children) > 0 {
processedChildren, err := applyInlineExpansionsRecursive(step.Children, parser, depth)
if err != nil {
return nil, err
}
clone.Children = processedChildren
}
result = append(result, clone)
}
}
return result, nil
}