package sdlc import ( "fmt" "strings" ) // DefaultRules returns all classifier rules in priority order. // First matching rule wins. func DefaultRules() []Rule { return []Rule{ blockedDependencyRule(), needsSpecRule(), specNeedsApprovalRule(), specApprovedRule(), needsDesignRule(), designNeedsApprovalRule(), needsTasksRule(), tasksNeedApprovalRule(), needsQAPlanRule(), qaPlanNeedsApprovalRule(), planningCompleteRule(), needsBranchRule(), readyToImplementRule(), implementNextTaskRule(), implementationCompleteRule(), needsReviewRule(), reviewHasIssuesRule(), reviewPassedRule(), needsAuditRule(), auditHasIssuesRule(), auditPassedRule(), needsQARule(), qaHasFailuresRule(), qaPassedRule(), needsMergeRule(), archiveFeatureRule(), } } func blockedDependencyRule() Rule { return Rule{ ID: "blocked-dependency", Condition: func(ctx *EvalContext) bool { return ctx.Feature.IsBlocked() }, Action: ActionBlocked, Message: func(ctx *EvalContext) string { return fmt.Sprintf("Feature blocked by: %s", strings.Join(ctx.Feature.Blockers, ", ")) }, } } func needsSpecRule() Rule { return Rule{ ID: "needs-spec", Condition: func(ctx *EvalContext) bool { if ctx.Feature.Phase != PhaseDraft { return false } art := ctx.Feature.GetArtifact(ArtifactSpec) return art == nil || art.Status == StatusPending }, Action: ActionCreateSpec, NextCommand: func(ctx *EvalContext) string { return "/spec-feature " + ctx.Feature.Slug }, OutputPath: func(ctx *EvalContext) string { return ".sdlc/features/" + ctx.Feature.Slug + "/spec.md" }, } } func specNeedsApprovalRule() Rule { return Rule{ ID: "spec-needs-approval", Condition: func(ctx *EvalContext) bool { if ctx.Feature.Phase != PhaseDraft { return false } art := ctx.Feature.GetArtifact(ArtifactSpec) return art != nil && art.Status == StatusDraft }, Action: ActionAwaitApproval, Message: func(_ *EvalContext) string { return "Specification requires user approval" }, } } func specApprovedRule() Rule { return Rule{ ID: "spec-approved", Condition: func(ctx *EvalContext) bool { if ctx.Feature.Phase != PhaseDraft { return false } art := ctx.Feature.GetArtifact(ArtifactSpec) return art != nil && art.Status == StatusApproved }, Action: ActionTransition, TransitionTo: PhaseSpecified, } } func needsDesignRule() Rule { return Rule{ ID: "needs-design", Condition: func(ctx *EvalContext) bool { if ctx.Feature.Phase != PhaseSpecified { return false } art := ctx.Feature.GetArtifact(ArtifactDesign) return art == nil || art.Status == StatusPending }, Action: ActionCreateDesign, NextCommand: func(ctx *EvalContext) string { return "/design-feature " + ctx.Feature.Slug }, OutputPath: func(ctx *EvalContext) string { return ".sdlc/features/" + ctx.Feature.Slug + "/design.md" }, } } func designNeedsApprovalRule() Rule { return Rule{ ID: "design-needs-approval", Condition: func(ctx *EvalContext) bool { if ctx.Feature.Phase != PhaseSpecified { return false } art := ctx.Feature.GetArtifact(ArtifactDesign) return art != nil && (art.Status == StatusDraft || art.Status == StatusRejected) }, Action: ActionAwaitApproval, Message: func(_ *EvalContext) string { return "Design document requires user approval" }, } } func needsTasksRule() Rule { return Rule{ ID: "needs-tasks", Condition: func(ctx *EvalContext) bool { if ctx.Feature.Phase != PhaseSpecified { return false } design := ctx.Feature.GetArtifact(ArtifactDesign) if design == nil || design.Status != StatusApproved { return false } tasks := ctx.Feature.GetArtifact(ArtifactTasks) return tasks == nil || tasks.Status == StatusPending }, Action: ActionCreateTasks, NextCommand: func(ctx *EvalContext) string { return "/breakdown-feature " + ctx.Feature.Slug }, OutputPath: func(ctx *EvalContext) string { return ".sdlc/features/" + ctx.Feature.Slug + "/tasks.md" }, } } func tasksNeedApprovalRule() Rule { return Rule{ ID: "tasks-need-approval", Condition: func(ctx *EvalContext) bool { if ctx.Feature.Phase != PhaseSpecified { return false } tasks := ctx.Feature.GetArtifact(ArtifactTasks) return tasks != nil && (tasks.Status == StatusDraft || tasks.Status == StatusRejected) }, Action: ActionAwaitApproval, Message: func(_ *EvalContext) string { return "Task breakdown requires user approval" }, } } func needsQAPlanRule() Rule { return Rule{ ID: "needs-qa-plan", Condition: func(ctx *EvalContext) bool { if ctx.Feature.Phase != PhaseSpecified { return false } tasks := ctx.Feature.GetArtifact(ArtifactTasks) if tasks == nil || tasks.Status != StatusApproved { return false } qa := ctx.Feature.GetArtifact(ArtifactQAPlan) return qa == nil || qa.Status == StatusPending }, Action: ActionCreateQAPlan, NextCommand: func(ctx *EvalContext) string { return "/create-qa-plan " + ctx.Feature.Slug }, OutputPath: func(ctx *EvalContext) string { return ".sdlc/features/" + ctx.Feature.Slug + "/qa-plan.md" }, } } func qaPlanNeedsApprovalRule() Rule { return Rule{ ID: "qa-plan-needs-approval", Condition: func(ctx *EvalContext) bool { if ctx.Feature.Phase != PhaseSpecified { return false } qa := ctx.Feature.GetArtifact(ArtifactQAPlan) return qa != nil && (qa.Status == StatusDraft || qa.Status == StatusRejected) }, Action: ActionAwaitApproval, Message: func(_ *EvalContext) string { return "QA plan requires user approval" }, } } func planningCompleteRule() Rule { return Rule{ ID: "planning-complete", Condition: func(ctx *EvalContext) bool { if ctx.Feature.Phase != PhaseSpecified { return false } qa := ctx.Feature.GetArtifact(ArtifactQAPlan) return qa != nil && qa.Status == StatusApproved }, Action: ActionTransition, TransitionTo: PhasePlanned, } } func needsBranchRule() Rule { return Rule{ ID: "needs-branch", Condition: func(ctx *EvalContext) bool { if ctx.Feature.Phase != PhasePlanned { return false } if ctx.Config == nil || !ctx.Config.Compliance.RequireBranch { return false } return ctx.Feature.Branch == "" }, Action: ActionCreateBranch, Message: func(_ *EvalContext) string { return "Feature branch required before implementation" }, NextCommand: func(ctx *EvalContext) string { return "sdlc branch create " + ctx.Feature.Slug }, } } func readyToImplementRule() Rule { return Rule{ ID: "ready-to-implement", Condition: func(ctx *EvalContext) bool { // Only trigger transition for PhasePlanned -> PhaseReady // PhaseReady should proceed to implementNextTaskRule, not re-transition return ctx.Feature.Phase == PhasePlanned }, Action: ActionTransition, TransitionTo: PhaseReady, Message: func(_ *EvalContext) string { return "Ready for implementation: transitioning to ready phase" }, } }