feat: Add on_complete/for-each runtime expansion types (gt-8tmz.8)

Implements the schema and validation for runtime dynamic expansion:

- Add OnCompleteSpec type for step completion triggers
- Add on_complete field to Step struct
- Validate for_each path format (must start with "output.")
- Validate bond is required with for_each (and vice versa)
- Validate parallel and sequential are mutually exclusive
- Add cloneOnComplete for proper step cloning
- Add comprehensive tests for parsing and validation

The runtime executor (in gastown) will interpret these fields to:
- Bond N molecules when step completes based on output.collection
- Run bonded molecules in parallel or sequential order
- Pass item/index context via vars substitution

🤖 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 16:07:57 -08:00
parent 8b5168f1a3
commit a42fac8cb9
3 changed files with 335 additions and 0 deletions

View File

@@ -196,10 +196,29 @@ func cloneStep(s *Step) *Step {
clone.Labels = make([]string, len(s.Labels))
copy(clone.Labels, s.Labels)
}
// Deep copy OnComplete if present (gt-8tmz.8)
if s.OnComplete != nil {
clone.OnComplete = cloneOnComplete(s.OnComplete)
}
// Don't deep copy children here - ApplyAdvice handles that recursively
return &clone
}
// cloneOnComplete creates a deep copy of an OnCompleteSpec (gt-8tmz.8).
func cloneOnComplete(oc *OnCompleteSpec) *OnCompleteSpec {
if oc == nil {
return nil
}
clone := *oc
if len(oc.Vars) > 0 {
clone.Vars = make(map[string]string, len(oc.Vars))
for k, v := range oc.Vars {
clone.Vars[k] = v
}
}
return &clone
}
// appendUnique appends an item to a slice if not already present.
func appendUnique(slice []string, item string) []string {
for _, s := range slice {