package sdlc import "time" // Classification is the output of the classifier engine. type Classification struct { Timestamp time.Time `json:"timestamp" yaml:"timestamp"` Feature string `json:"feature" yaml:"feature"` CurrentPhase FeaturePhase `json:"current_phase" yaml:"current_phase"` RuleMatched string `json:"rule_matched" yaml:"rule_matched"` Action ActionType `json:"action" yaml:"action"` Message string `json:"message" yaml:"message"` NextCommand string `json:"next_command,omitempty" yaml:"next_command,omitempty"` OutputPath string `json:"output_path,omitempty" yaml:"output_path,omitempty"` TransitionTo FeaturePhase `json:"transition_to,omitempty" yaml:"transition_to,omitempty"` TaskID string `json:"task_id,omitempty" yaml:"task_id,omitempty"` } // EvalContext provides all the state needed for rule evaluation. type EvalContext struct { State *State Feature *Feature Config *Config Root string } // Rule is a single classifier rule with a condition and resulting action. type Rule struct { ID string Condition func(ctx *EvalContext) bool Action ActionType Message func(ctx *EvalContext) string NextCommand func(ctx *EvalContext) string OutputPath func(ctx *EvalContext) string TransitionTo FeaturePhase TaskID func(ctx *EvalContext) string } // Classifier evaluates rules in priority order, returning the first match. type Classifier struct { rules []Rule } // NewClassifier creates a classifier with the default rules. func NewClassifier() *Classifier { return &Classifier{rules: DefaultRules()} } // NewClassifierWithRules creates a classifier with custom rules. func NewClassifierWithRules(rules []Rule) *Classifier { return &Classifier{rules: rules} } // Classify evaluates all rules against the context and returns the first match. func (c *Classifier) Classify(ctx *EvalContext) *Classification { for _, rule := range c.rules { if rule.Condition(ctx) { cl := &Classification{ Timestamp: time.Now().UTC(), Feature: ctx.Feature.Slug, CurrentPhase: ctx.Feature.Phase, RuleMatched: rule.ID, Action: rule.Action, TransitionTo: rule.TransitionTo, } if rule.Message != nil { cl.Message = rule.Message(ctx) } if rule.NextCommand != nil { cl.NextCommand = rule.NextCommand(ctx) } if rule.OutputPath != nil { cl.OutputPath = rule.OutputPath(ctx) } if rule.TaskID != nil { cl.TaskID = rule.TaskID(ctx) } return cl } } // Default: nothing to do return &Classification{ Timestamp: time.Now().UTC(), Feature: ctx.Feature.Slug, CurrentPhase: ctx.Feature.Phase, RuleMatched: "nothing-to-do", Action: ActionIdle, Message: "No actionable work found", } }