formula

package
v0.62.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 13, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Overview

Package formula provides advice operators for step transformations.

Advice operators are Lisp-style transformations that insert steps before, after, or around matching target steps. They enable cross-cutting concerns like logging, security scanning, or approval gates to be applied declaratively.

Supported patterns:

  • "design" - exact match
  • "*.implement" - suffix match (any step ending in .implement)
  • "shiny.*" - prefix match (any step starting with shiny.)
  • "*" - match all steps

Package formula provides condition evaluation for gates and loops.

Conditions are intentionally limited to keep evaluation decidable:

  • Step status checks: step.status == 'complete'
  • Step output access: step.output.approved == true
  • Aggregates: children(step).all(status == 'complete')
  • External checks: file.exists('go.mod'), env.CI == 'true'

No arbitrary code execution is allowed.

Package formula provides control flow operators for step transformation.

Control flow operators enable:

  • loop: Repeat a body of steps (fixed count or conditional)
  • branch: Fork-join parallel execution patterns
  • gate: Conditional waits before steps proceed

These operators are applied during formula cooking to transform the step graph before creating the proto bead.

Package formula provides expansion operators for macro-style step transformation.

Expansion operators replace target steps with template-expanded steps. Unlike advice operators which insert steps around targets, expansion operators completely replace the target with the expansion template.

Two operators are supported:

  • expand: Apply template to a single target step
  • map: Apply template to all steps matching a pattern

Templates use {target} and {target.description} placeholders that are substituted with the target step's values during expansion.

A maximum expansion depth (default 5) prevents runaway nested expansions. This allows massive work generation while providing a safety bound.

Package formula provides range expression evaluation for computed loops.

Range expressions enable loops with computed bounds (gt-8tmz.27):

range: "1..10"           // Simple integer range
range: "1..2^{disks}"    // Expression with variable
range: "{start}..{end}"  // Variable bounds

Supports: + - * / ^ (power) and parentheses. Variables use {name} syntax and are substituted from the vars map.

Package formula provides Step.Condition evaluation for compile-time step filtering.

Step.Condition is simpler than the runtime condition evaluation in condition.go. It evaluates at cook/pour time to include or exclude steps based on formula variables.

Supported formats:

  • "{{var}}" - truthy check (non-empty, non-"false", non-"0")
  • "!{{var}}" - negated truthy check (include if var is falsy)
  • "{{var}} == value" - equality check
  • "{{var}} != value" - inequality check

Package formula provides parsing and validation for .formula.json files.

Formulas are high-level workflow templates that compile down to proto beads. They support:

  • Variable definitions with defaults and validation
  • Step definitions that become issue hierarchies
  • Composition rules for bonding formulas together
  • Inheritance via extends

Example .formula.json:

{
  "formula": "mol-feature",
  "description": "Standard feature workflow",
  "version": 1,
  "type": "workflow",
  "vars": {
    "component": {
      "description": "Component name",
      "required": true
    }
  },
  "steps": [
    {"id": "design", "title": "Design {{component}}", "type": "task"},
    {"id": "implement", "title": "Implement {{component}}", "depends_on": ["design"]}
  ]
}

Index

Constants

View Source
const (
	FormulaExtTOML = ".formula.toml"
	FormulaExtJSON = ".formula.json"
	FormulaExt     = FormulaExtJSON // Legacy alias for backwards compatibility
)

Formula file extensions. TOML is preferred, JSON is legacy fallback.

View Source
const DefaultMaxExpansionDepth = 5

DefaultMaxExpansionDepth is the maximum depth for recursive template expansion. This prevents runaway nested expansions while still allowing substantial work generation. The limit applies to template children, not to expansion rules.

Variables

This section is empty.

Functions

func ApplyDefaults

func ApplyDefaults(formula *Formula, values map[string]string) map[string]string

ApplyDefaults returns a new map with default values filled in.

func EvaluateExpr

func EvaluateExpr(expr string, vars map[string]string) (int, error)

EvaluateExpr evaluates a mathematical expression with variable substitution. Supports: + - * / ^ (power) and parentheses. Variables use {name} syntax.

func EvaluateStepCondition

func EvaluateStepCondition(condition string, vars map[string]string) (bool, error)

EvaluateStepCondition evaluates a step's condition against variable values. Returns true if the step should be included, false if it should be skipped.

Condition formats:

  • "" (empty) - always include
  • "{{var}}" - include if var is truthy (non-empty, non-"false", non-"0")
  • "!{{var}}" - include if var is NOT truthy (negated)
  • "{{var}} == value" - include if var equals value
  • "{{var}} != value" - include if var does not equal value

func ExtractVariables

func ExtractVariables(formula *Formula) []string

ExtractVariables finds all {{variable}} references in a formula.

func MatchAnyPointcut

func MatchAnyPointcut(pointcuts []*Pointcut, step *Step) bool

MatchAnyPointcut checks if a step matches any pointcut in the list.

func MatchGlob

func MatchGlob(pattern, stepID string) bool

MatchGlob checks if a step ID matches a glob pattern. Supported patterns:

  • "exact" - exact match
  • "*.suffix" - ends with .suffix
  • "prefix.*" - starts with prefix.
  • "*" - matches everything
  • "prefix.*.suffix" - starts with prefix. and ends with .suffix

func MatchPointcut

func MatchPointcut(pc *Pointcut, step *Step) bool

MatchPointcut checks if a step matches a pointcut.

func MaterializeExpansion

func MaterializeExpansion(f *Formula, targetID string, vars map[string]string) error

MaterializeExpansion converts a standalone expansion formula into a cookable form by expanding its Template into Steps. A synthetic target step is created using targetID as the step ID and the formula's own name/description for {target.title} and {target.description} placeholders.

This enables expansion formulas to be directly instantiated via wisp/pour without requiring a Compose wrapper (bd-qzb).

No-op if the formula is not an expansion type, has no Template, or already has Steps.

func SetSourceInfo

func SetSourceInfo(formula *Formula)

SetSourceInfo sets SourceFormula and SourceLocation on all steps in a formula. Called after parsing to enable source tracing during cooking (gt-8tmz.18).

func StringPtr

func StringPtr(s string) *string

StringPtr returns a pointer to s. Useful for constructing VarDef literals.

func Substitute

func Substitute(s string, vars map[string]string) string

Substitute replaces {{variable}} placeholders with values.

func ValidateRange

func ValidateRange(expr string) error

ValidateRange validates a range expression without evaluating it. Useful for syntax checking during formula validation.

func ValidateVars

func ValidateVars(formula *Formula, values map[string]string) error

ValidateVars checks that all required variables are provided and all values pass their constraints.

Types

type AdviceRule

type AdviceRule struct {
	// Target is a glob pattern matching step IDs to apply advice to.
	// Examples: "*.implement", "design", "shiny.*"
	Target string `json:"target"`

	// Before inserts a step before the target.
	Before *AdviceStep `json:"before,omitempty"`

	// After inserts a step after the target.
	After *AdviceStep `json:"after,omitempty"`

	// Around wraps the target with before and after steps.
	Around *AroundAdvice `json:"around,omitempty"`
}

AdviceRule defines a step transformation rule. Advice operators insert steps before, after, or around matching targets.

type AdviceStep

type AdviceStep struct {
	// ID is the step identifier. Supports {step.id} substitution.
	ID string `json:"id"`

	// Title is the step title. Supports {step.id} substitution.
	Title string `json:"title,omitempty"`

	// Description is the step description.
	Description string `json:"description,omitempty"`

	// Type is the issue type (task, bug, etc).
	Type string `json:"type,omitempty"`

	// Args are additional context passed to the step.
	Args map[string]string `json:"args,omitempty"`

	// Output defines expected outputs from this step.
	Output map[string]string `json:"output,omitempty"`
}

AdviceStep defines a step to insert via advice.

type AroundAdvice

type AroundAdvice struct {
	// Before is a list of steps to insert before the target.
	Before []*AdviceStep `json:"before,omitempty"`

	// After is a list of steps to insert after the target.
	After []*AdviceStep `json:"after,omitempty"`
}

AroundAdvice wraps a target with before and after steps.

type BondPoint

type BondPoint struct {
	// ID is the unique identifier for this bond point.
	ID string `json:"id"`

	// Description explains what should be attached here.
	Description string `json:"description,omitempty"`

	// AfterStep is the step ID after which to attach.
	// Mutually exclusive with BeforeStep.
	AfterStep string `json:"after_step,omitempty" toml:"after_step,omitempty"`

	// BeforeStep is the step ID before which to attach.
	// Mutually exclusive with AfterStep.
	BeforeStep string `json:"before_step,omitempty" toml:"before_step,omitempty"`

	// Parallel makes attached steps run in parallel with the anchor step.
	Parallel bool `json:"parallel,omitempty"`
}

BondPoint is a named attachment site for composition.

type BranchRule

type BranchRule struct {
	// From is the step ID that precedes the parallel paths.
	// All branch steps will depend on this step.
	From string `json:"from"`

	// Steps are the step IDs that run in parallel.
	// These steps will all depend on From.
	Steps []string `json:"steps"`

	// Join is the step ID that follows all parallel paths.
	// This step will depend on all Steps completing.
	Join string `json:"join"`
}

BranchRule defines parallel execution paths that rejoin. Creates a fork-join pattern: from -> [parallel steps] -> join.

type ComposeRules

type ComposeRules struct {
	// BondPoints are named locations where other formulas can attach.
	BondPoints []*BondPoint `json:"bond_points,omitempty" toml:"bond_points,omitempty"`

	// Hooks are automatic attachments triggered by labels or conditions.
	Hooks []*Hook `json:"hooks,omitempty"`

	// Expand applies an expansion template to a single target step.
	// The target step is replaced by the expanded template steps.
	Expand []*ExpandRule `json:"expand,omitempty"`

	// Map applies an expansion template to all steps matching a pattern.
	// Each matching step is replaced by the expanded template steps.
	Map []*MapRule `json:"map,omitempty"`

	// Branch defines fork-join parallel execution patterns.
	// Each rule creates dependencies for parallel paths that rejoin.
	Branch []*BranchRule `json:"branch,omitempty"`

	// Gate defines conditional waits before steps.
	// Each rule adds a condition that must be satisfied at runtime.
	Gate []*GateRule `json:"gate,omitempty"`

	// Aspects lists aspect formula names to apply to this formula.
	// Aspects are applied after expansions, adding before/after/around
	// steps to matching targets based on the aspect's advice rules.
	// Example: ["security-audit", "logging"]
	Aspects []string `json:"aspects,omitempty"`
}

ComposeRules define how formulas can be bonded together.

type Condition

type Condition struct {
	// Raw is the original condition string.
	Raw string

	// Type is the condition type: field, aggregate, external.
	Type ConditionType

	// For field conditions:
	StepRef  string   // Step ID reference (e.g., "review", "step" for current)
	Field    string   // Field path (e.g., "status", "output.approved")
	Operator Operator // Comparison operator
	Value    string   // Expected value

	// For aggregate conditions:
	AggregateFunc string // Function: all, any, count
	AggregateOver string // What to aggregate: children, descendants, steps

	// For external conditions:
	ExternalType string // file.exists, env
	ExternalArg  string // Argument (path or env var name)
}

Condition represents a parsed condition expression.

func ParseCondition

func ParseCondition(expr string) (*Condition, error)

ParseCondition parses a condition string into a Condition struct.

func (*Condition) Evaluate

func (c *Condition) Evaluate(ctx *ConditionContext) (*ConditionResult, error)

Evaluate evaluates the condition against the given context.

type ConditionContext

type ConditionContext struct {
	// Steps maps step ID to step state.
	Steps map[string]*StepState

	// CurrentStep is the step being gated (for relative references).
	CurrentStep string

	// Vars are the formula variables (for variable substitution).
	Vars map[string]string
}

ConditionContext provides the evaluation context for conditions.

type ConditionResult

type ConditionResult struct {
	// Satisfied is true if the condition is met.
	Satisfied bool

	// Reason explains why the condition is satisfied or not.
	Reason string
}

ConditionResult represents the result of evaluating a condition.

func EvaluateCondition

func EvaluateCondition(expr string, ctx *ConditionContext) (*ConditionResult, error)

EvaluateCondition is a convenience function that parses and evaluates a condition.

type ConditionType

type ConditionType string

ConditionType categorizes conditions.

const (
	ConditionTypeField     ConditionType = "field"
	ConditionTypeAggregate ConditionType = "aggregate"
	ConditionTypeExternal  ConditionType = "external"
)

type ExpandRule

type ExpandRule struct {
	// Target is the step ID to expand.
	Target string `json:"target"`

	// With is the name of the expansion formula to apply.
	With string `json:"with"`

	// Vars are variable overrides for the expansion.
	Vars map[string]string `json:"vars,omitempty"`
}

ExpandRule applies an expansion template to a single target step.

type Formula

type Formula struct {
	// Formula is the unique identifier/name for this formula.
	// Convention: mol-<name> for molecules, exp-<name> for expansions.
	Formula string `json:"formula"`

	// Description explains what this formula does.
	Description string `json:"description,omitempty"`

	// Version is the schema version (currently 1).
	Version int `json:"version"`

	// Type categorizes the formula: workflow, expansion, or aspect.
	Type FormulaType `json:"type"`

	// Extends is a list of parent formulas to inherit from.
	// The child formula inherits all vars, steps, and compose rules.
	// Child definitions override parent definitions with the same ID.
	Extends []string `json:"extends,omitempty"`

	// Vars defines template variables with defaults and validation.
	Vars map[string]*VarDef `json:"vars,omitempty"`

	// Steps defines the work items to create.
	Steps []*Step `json:"steps,omitempty"`

	// Template defines expansion template steps (for TypeExpansion formulas).
	// Template steps use {target} and {target.description} placeholders
	// that get substituted when the expansion is applied to a target step.
	Template []*Step `json:"template,omitempty"`

	// Compose defines composition/bonding rules.
	Compose *ComposeRules `json:"compose,omitempty"`

	// Advice defines step transformations (before/after/around).
	// Applied during cooking to insert steps around matching targets.
	Advice []*AdviceRule `json:"advice,omitempty"`

	// Pointcuts defines target patterns for aspect formulas.
	// Used with TypeAspect to specify which steps the aspect applies to.
	Pointcuts []*Pointcut `json:"pointcuts,omitempty"`

	// Phase indicates the recommended instantiation phase: "liquid" (pour) or "vapor" (wisp).
	// If "vapor", bd pour will warn and suggest using bd mol wisp instead.
	// Patrol and release workflows should typically use "vapor" since they're operational.
	Phase string `json:"phase,omitempty"`

	// Pour controls whether steps are materialized as individual child issues.
	// If true, each step becomes a DB row with dependency tracking (checkpoint recovery).
	// If false (default), only the root issue is created; steps are read inline at prime time.
	// Reserve pour=true for critical, infrequent work (e.g. releases) where step-level
	// tracking is worth the DB overhead. Patrol formulas should NOT set this.
	Pour bool `json:"pour,omitempty"`

	// Source tracks where this formula was loaded from (set by parser).
	Source string `json:"source,omitempty"`
}

Formula is the root structure for .formula.json files.

func (*Formula) GetBondPoint

func (f *Formula) GetBondPoint(id string) *BondPoint

GetBondPoint finds a bond point by ID.

func (*Formula) GetRequiredVars

func (f *Formula) GetRequiredVars() []string

GetRequiredVars returns the names of all required variables.

func (*Formula) GetStepByID

func (f *Formula) GetStepByID(id string) *Step

GetStepByID finds a step by its ID (searches recursively).

func (*Formula) Validate

func (f *Formula) Validate() error

Validate checks the formula for structural errors.

type FormulaType

type FormulaType string

FormulaType categorizes formulas by their purpose.

const (
	// TypeWorkflow is a standard workflow template (sequence of steps).
	TypeWorkflow FormulaType = "workflow"

	// TypeExpansion is a macro that expands into multiple steps.
	// Used for common patterns like "test + lint + build".
	TypeExpansion FormulaType = "expansion"

	// TypeAspect is a cross-cutting concern that can be applied to other formulas.
	// Examples: add logging steps, add approval gates.
	TypeAspect FormulaType = "aspect"
)

func (FormulaType) IsValid

func (t FormulaType) IsValid() bool

IsValid checks if the formula type is recognized.

type Gate

type Gate struct {
	// Type is the condition type: gh:run, gh:pr, timer, human, mail.
	Type string `json:"type"`

	// ID is the condition identifier (e.g., workflow name for gh:run).
	ID string `json:"id,omitempty"`

	// Timeout is how long to wait before escalation (e.g., "1h", "24h").
	Timeout string `json:"timeout,omitempty"`
}

Gate defines an async wait condition for formula steps. When a step has a Gate, bd cook creates a gate issue that blocks the step. The gate must be closed (manually or via watchers) to unblock the step.

type GateRule

type GateRule struct {
	// Before is the step ID that the gate applies to.
	// The condition must be satisfied before this step can start.
	Before string `json:"before"`

	// Condition is the expression to evaluate.
	// Format matches condition evaluator syntax (e.g., "tests.status == 'complete'").
	Condition string `json:"condition"`
}

GateRule defines a condition that must be satisfied before a step proceeds. Gates are evaluated at runtime by the patrol executor.

type Hook

type Hook struct {
	// Trigger is what activates this hook.
	// Formats: "label:security", "type:bug", "priority:0-1".
	Trigger string `json:"trigger"`

	// Attach is the formula to attach when triggered.
	Attach string `json:"attach"`

	// At is the bond point to attach at (default: end).
	At string `json:"at,omitempty"`

	// Vars are variable overrides for the attached formula.
	Vars map[string]string `json:"vars,omitempty"`
}

Hook defines automatic formula attachment based on conditions.

type LoopSpec

type LoopSpec struct {
	// Count is the fixed number of iterations.
	// When set, the loop body is expanded Count times.
	Count int `json:"count,omitempty"`

	// Until is a condition that ends the loop.
	// Format matches condition evaluator syntax (e.g., "step.status == 'complete'").
	Until string `json:"until,omitempty"`

	// Max is the maximum iterations for conditional loops.
	// Required when Until is set, to prevent unbounded loops.
	Max int `json:"max,omitempty"`

	// Range specifies a computed range for iteration.
	// Format: "start..end" where start and end can be:
	//   - Integers: "1..10"
	//   - Expressions: "1..2^{disks}" (evaluated at cook time)
	//   - Variables: "{start}..{count}" (substituted from Vars)
	// Supports: + - * / ^ (power) and parentheses.
	Range string `json:"range,omitempty"`

	// Var is the variable name exposed to body steps.
	// For Range loops, this is set to the current iteration value.
	// Example: var: "move_num" with range: "1..7" exposes {move_num}=1,2,...,7
	Var string `json:"var,omitempty"`

	// Body contains the steps to repeat.
	Body []*Step `json:"body"`
}

LoopSpec defines iteration over a body of steps. One of Count, Until, or Range must be specified.

type MapRule

type MapRule struct {
	// Select is a glob pattern matching step IDs to expand.
	// Examples: "*.implement", "shiny.*"
	Select string `json:"select"`

	// With is the name of the expansion formula to apply.
	With string `json:"with"`

	// Vars are variable overrides for the expansion.
	Vars map[string]string `json:"vars,omitempty"`
}

MapRule applies an expansion template to all matching steps.

type OnCompleteSpec

type OnCompleteSpec struct {
	// ForEach is the path to the iterable collection in step output.
	// Format: "output.<field>" or "output.<field>.<nested>"
	// The collection must be an array at runtime.
	ForEach string `json:"for_each,omitempty" toml:"for_each,omitempty"`

	// Bond is the formula to instantiate for each item.
	// A new molecule is created for each element in the ForEach collection.
	Bond string `json:"bond,omitempty"`

	// Vars are variable bindings for each iteration.
	// Supports placeholders:
	//   - {item} - the current item value (for primitives)
	//   - {item.field} - a field from the current item (for objects)
	//   - {index} - the zero-based iteration index
	Vars map[string]string `json:"vars,omitempty"`

	// Parallel runs all bonded molecules concurrently (default behavior).
	// Set to true to make this explicit.
	Parallel bool `json:"parallel,omitempty"`

	// Sequential runs bonded molecules one at a time.
	// Each molecule starts only after the previous one completes.
	// Mutually exclusive with Parallel.
	Sequential bool `json:"sequential,omitempty"`
}

OnCompleteSpec defines actions triggered when a step completes. Used for runtime expansion over step output (the for-each construct).

Example YAML:

step: survey-workers
on_complete:
  for_each: output.polecats
  bond: mol-polecat-arm
  vars:
    polecat_name: "{item.name}"
    rig: "{item.rig}"
  parallel: true

type Operator

type Operator string

Operator represents a comparison operator.

const (
	OpEqual        Operator = "=="
	OpNotEqual     Operator = "!="
	OpGreater      Operator = ">"
	OpGreaterEqual Operator = ">="
	OpLess         Operator = "<"
	OpLessEqual    Operator = "<="
)

type Parser

type Parser struct {
	// contains filtered or unexported fields
}

Parser handles loading and resolving formulas.

NOTE: Parser is NOT thread-safe. Create a new Parser per goroutine or synchronize access externally. The cache and resolving maps have no internal synchronization.

func NewParser

func NewParser(searchPaths ...string) *Parser

NewParser creates a new formula parser. searchPaths are directories to search for formulas when resolving extends. Default paths are: .beads/formulas, ~/.beads/formulas, $GT_ROOT/.beads/formulas

func (*Parser) LoadByName

func (p *Parser) LoadByName(name string) (*Formula, error)

LoadByName loads a formula by name from search paths. This is the public API for loading formulas used by expansion operators.

func (*Parser) Parse

func (p *Parser) Parse(data []byte) (*Formula, error)

Parse parses a formula from JSON bytes.

func (*Parser) ParseFile

func (p *Parser) ParseFile(path string) (*Formula, error)

ParseFile parses a formula from a file path. Detects format from extension: .formula.toml or .formula.json

func (*Parser) ParseTOML

func (p *Parser) ParseTOML(data []byte) (*Formula, error)

ParseTOML parses a formula from TOML bytes.

func (*Parser) Resolve

func (p *Parser) Resolve(formula *Formula) (*Formula, error)

Resolve fully resolves a formula, processing extends and expansions. Returns a new formula with all inheritance applied.

type Pointcut

type Pointcut struct {
	// Glob is a glob pattern to match step IDs.
	// Examples: "*.implement", "shiny.*", "review"
	Glob string `json:"glob,omitempty"`

	// Type matches steps by their type field.
	// Examples: "task", "bug", "epic"
	Type string `json:"type,omitempty"`

	// Label matches steps that have a specific label.
	Label string `json:"label,omitempty"`
}

Pointcut defines a target pattern for advice application. Used in aspect formulas to specify which steps the advice applies to.

type RangeSpec

type RangeSpec struct {
	Start int // Evaluated start value (inclusive)
	End   int // Evaluated end value (inclusive)
}

RangeSpec represents a parsed range expression.

func ParseRange

func ParseRange(expr string, vars map[string]string) (*RangeSpec, error)

ParseRange parses a range expression and evaluates it using the given variables. Returns the start and end values of the range.

Examples:

ParseRange("1..10", nil)           -> {1, 10}
ParseRange("1..2^3", nil)          -> {1, 8}
ParseRange("1..2^{n}", {"n":"3"})  -> {1, 8}

type Step

type Step struct {
	// ID is the unique identifier within this formula.
	// Used for dependency references and bond points.
	ID string `json:"id"`

	// Title is the issue title (supports {{variable}} substitution).
	Title string `json:"title"`

	// Description is the issue description (supports substitution).
	Description string `json:"description,omitempty"`

	// Notes are additional notes for the issue (supports substitution).
	Notes string `json:"notes,omitempty"`

	// Type is the issue type: task, bug, feature, epic, chore.
	Type string `json:"type,omitempty"`

	// Priority is the issue priority (0-4).
	Priority *int `json:"priority,omitempty"`

	// Labels are applied to the created issue.
	Labels []string `json:"labels,omitempty"`

	// DependsOn lists step IDs this step blocks on (within the formula).
	DependsOn []string `json:"depends_on,omitempty" toml:"depends_on,omitempty"`

	// Needs is a simpler alias for DependsOn - lists sibling step IDs that must complete first.
	// Either Needs or DependsOn can be used; they are merged during cooking.
	Needs []string `json:"needs,omitempty" toml:"needs,omitempty"`

	// WaitsFor specifies a fanout gate type for this step.
	// Values: "all-children" (wait for all dynamic children) or "any-children" (wait for first).
	// When set, the cooked issue gets a "gate:<value>" label.
	WaitsFor string `json:"waits_for,omitempty" toml:"waits_for,omitempty"`

	// Assignee is the default assignee (supports substitution).
	Assignee string `json:"assignee,omitempty"`

	// Expand references an expansion formula to inline here.
	// When set, this step is replaced by the expansion's template steps.
	// See ApplyInlineExpansions in expand.go for implementation.
	Expand string `json:"expand,omitempty"`

	// ExpandVars are variable overrides for the expansion.
	// Merged with the expansion formula's default vars during inline expansion.
	ExpandVars map[string]string `json:"expand_vars,omitempty" toml:"expand_vars,omitempty"`

	// Condition makes this step optional based on a variable.
	// Format: "{{var}}" (truthy), "!{{var}}" (negated), "{{var}} == value", "{{var}} != value".
	// Evaluated at cook/pour time via FilterStepsByCondition.
	Condition string `json:"condition,omitempty"`

	// Children are nested steps (for creating epic hierarchies).
	Children []*Step `json:"children,omitempty"`

	// Gate defines an async wait condition for this step.
	// When set, bd cook creates a gate issue that blocks this step.
	// Close the gate issue (bd close bd-xxx.gate-stepid) to unblock.
	Gate *Gate `json:"gate,omitempty"`

	// Loop defines iteration for this step.
	// When set, the step becomes a container that expands its body.
	Loop *LoopSpec `json:"loop,omitempty"`

	// OnComplete defines actions triggered when this step completes.
	// Used for runtime expansion over step output (the for-each construct).
	OnComplete *OnCompleteSpec `json:"on_complete,omitempty" toml:"on_complete,omitempty"`

	// SourceFormula is the formula name where this step was defined.
	// For inherited steps, this is the parent formula, not the final composed formula.
	SourceFormula string `json:"-"` // Internal only, not serialized to JSON

	// SourceLocation is the path within the source formula.
	// Format: "steps[0]", "steps[2].children[1]", "advice[0].after", "loop.body[0]"
	SourceLocation string `json:"-"` // Internal only, not serialized to JSON
}

Step defines a work item to create when the formula is instantiated.

func ApplyAdvice

func ApplyAdvice(steps []*Step, advice []*AdviceRule) []*Step

ApplyAdvice transforms a formula's steps by applying advice rules. Returns a new steps slice with advice steps inserted. The original steps slice is not modified.

Self-matching prevention: Advice only matches steps that existed BEFORE this call. Steps inserted by advice (before/after/around) are not matched, preventing infinite recursion.

func ApplyBranches

func ApplyBranches(steps []*Step, compose *ComposeRules) ([]*Step, error)

ApplyBranches wires fork-join dependency patterns. For each branch rule:

  • All branch steps depend on the 'from' step
  • The 'join' step depends on all branch steps

Returns a new steps slice with dependencies added. The original steps slice is not modified.

func ApplyControlFlow

func ApplyControlFlow(steps []*Step, compose *ComposeRules) ([]*Step, error)

ApplyControlFlow applies all control flow operators in the correct order: 1. Loops (expand iterations) 2. Branches (wire fork-join dependencies) 3. Gates (add condition labels)

Returns a new steps slice. The original steps slice is not modified.

func ApplyExpansions

func ApplyExpansions(steps []*Step, compose *ComposeRules, parser *Parser) ([]*Step, error)

ApplyExpansions applies all expand and map rules to a formula's steps. Returns a new steps slice with expansions applied. The original steps slice is not modified.

The parser is used to load referenced expansion formulas by name. If parser is nil, no expansions are applied.

func ApplyGates

func ApplyGates(steps []*Step, compose *ComposeRules) ([]*Step, error)

ApplyGates adds gate conditions to steps. For each gate rule:

  • The target step gets a "gate:condition" label
  • At runtime, the patrol executor evaluates the condition

Returns a new steps slice with gate labels added. The original steps slice is not modified.

func ApplyInlineExpansions

func ApplyInlineExpansions(steps []*Step, parser *Parser) ([]*Step, error)

ApplyInlineExpansions applies Step.Expand fields to inline expansions. 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 ApplyLoops

func ApplyLoops(steps []*Step) ([]*Step, error)

ApplyLoops expands loop bodies in a formula's steps. Fixed-count loops expand the body N times with indexed step IDs. Conditional loops expand once and add a "loop:until" label for runtime evaluation. Returns a new steps slice with loops expanded.

func FilterStepsByCondition

func FilterStepsByCondition(steps []*Step, vars map[string]string) ([]*Step, error)

FilterStepsByCondition filters a list of steps based on their Condition field. Steps with conditions that evaluate to false are excluded from the result. Children of excluded steps are also excluded.

Parameters:

  • steps: the steps to filter
  • vars: variable values for condition evaluation

Returns the filtered steps and any error encountered during evaluation.

func UpdateDependenciesForExpansion

func UpdateDependenciesForExpansion(steps []*Step, expandedID string, lastExpandedStepID string) []*Step

UpdateDependenciesForExpansion updates dependency references after expansion. When step X is expanded into X.draft, X.refine-1, etc., any step that depended on X should now depend on the last step in the expansion.

type StepState

type StepState struct {
	// ID is the step identifier.
	ID string

	// Status is the step status: pending, in_progress, complete, failed.
	Status string

	// Output is the structured output from the step (if complete).
	// Keys are dot-separated paths, values are the output values.
	Output map[string]interface{}

	// Children are the child step states (for aggregate conditions).
	Children []*StepState
}

StepState represents the runtime state of a step for condition evaluation.

type VarDef

type VarDef struct {
	// Description explains what this variable is for.
	Description string `json:"description,omitempty"`

	// Default is the value to use if not provided.
	// nil means no default (variable must be provided if referenced).
	// Non-nil (including &"") means the variable has an explicit default.
	Default *string `json:"default,omitempty"`

	// Required indicates the variable must be provided (no default).
	Required bool `json:"required,omitempty"`

	// Enum lists the allowed values (if non-empty).
	Enum []string `json:"enum,omitempty"`

	// Pattern is a regex pattern the value must match.
	Pattern string `json:"pattern,omitempty"`

	// Type is the expected value type: string (default), int, bool.
	Type string `json:"type,omitempty"`
}

VarDef defines a template variable with optional validation.

func (*VarDef) UnmarshalTOML

func (v *VarDef) UnmarshalTOML(data interface{}) error

UnmarshalTOML implements toml.Unmarshaler for VarDef. This allows vars to be defined as either simple strings or tables:

[vars]
wisp_type = "patrol"           # simple string -> Default = "patrol"

[vars.component]               # table with full definition
description = "Component name"
required = true

type WaitsForSpec

type WaitsForSpec struct {
	// Gate is the gate type: "all-children" or "any-children"
	Gate string
	// SpawnerID is the step ID whose children to wait for.
	// Empty means infer from context (typically first step in needs).
	SpawnerID string
}

WaitsForSpec holds the parsed waits_for field.

func ParseWaitsFor

func ParseWaitsFor(value string) *WaitsForSpec

ParseWaitsFor parses a waits_for value into its components. Returns nil if the value is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL