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(), 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 readyToImplementRule() Rule { return Rule{ ID: "ready-to-implement", Condition: func(ctx *EvalContext) bool { return ctx.Feature.Phase == PhasePlanned || ctx.Feature.Phase == PhaseReady }, Action: ActionTransition, TransitionTo: PhaseReady, Message: func(ctx *EvalContext) string { if ctx.Feature.Phase == PhasePlanned { return "Ready for implementation: transition to ready, then implementation" } return "Ready for implementation" }, } } func implementNextTaskRule() Rule { return Rule{ ID: "implement-next-task", Condition: func(ctx *EvalContext) bool { if ctx.Feature.Phase != PhaseImplementation { return false } next := NextTask(ctx.Feature.Tasks) return next != nil }, Action: ActionImplementTask, NextCommand: func(ctx *EvalContext) string { next := NextTask(ctx.Feature.Tasks) if next == nil { return "" } return fmt.Sprintf("/implement-task %s %s", ctx.Feature.Slug, next.ID) }, OutputPath: func(ctx *EvalContext) string { return ".sdlc/features/" + ctx.Feature.Slug + "/tasks.md" }, TaskID: func(ctx *EvalContext) string { next := NextTask(ctx.Feature.Tasks) if next == nil { return "" } return next.ID }, } } func implementationCompleteRule() Rule { return Rule{ ID: "implementation-complete", Condition: func(ctx *EvalContext) bool { return ctx.Feature.Phase == PhaseImplementation && AllTasksComplete(ctx.Feature.Tasks) }, Action: ActionTransition, TransitionTo: PhaseReview, } } func needsReviewRule() Rule { return Rule{ ID: "needs-review", Condition: func(ctx *EvalContext) bool { if ctx.Feature.Phase != PhaseReview { return false } art := ctx.Feature.GetArtifact(ArtifactReview) return art == nil || art.Status == StatusPending }, Action: ActionReviewCode, NextCommand: func(ctx *EvalContext) string { return "/review-feature " + ctx.Feature.Slug }, OutputPath: func(ctx *EvalContext) string { return ".sdlc/features/" + ctx.Feature.Slug + "/review.md" }, } } func reviewHasIssuesRule() Rule { return Rule{ ID: "review-has-issues", Condition: func(ctx *EvalContext) bool { if ctx.Feature.Phase != PhaseReview { return false } art := ctx.Feature.GetArtifact(ArtifactReview) return art != nil && art.Status == StatusNeedsFix }, Action: ActionFixReviewIssues, NextCommand: func(ctx *EvalContext) string { return "/fix-review-issues " + ctx.Feature.Slug }, } } func reviewPassedRule() Rule { return Rule{ ID: "review-passed", Condition: func(ctx *EvalContext) bool { if ctx.Feature.Phase != PhaseReview { return false } art := ctx.Feature.GetArtifact(ArtifactReview) return art != nil && art.Status == StatusPassed }, Action: ActionTransition, TransitionTo: PhaseAudit, } } func needsAuditRule() Rule { return Rule{ ID: "needs-audit", Condition: func(ctx *EvalContext) bool { if ctx.Feature.Phase != PhaseAudit { return false } art := ctx.Feature.GetArtifact(ArtifactAudit) return art == nil || art.Status == StatusPending }, Action: ActionAuditCode, NextCommand: func(ctx *EvalContext) string { return "/audit-feature " + ctx.Feature.Slug }, OutputPath: func(ctx *EvalContext) string { return ".sdlc/features/" + ctx.Feature.Slug + "/audit.md" }, } } func auditHasIssuesRule() Rule { return Rule{ ID: "audit-has-issues", Condition: func(ctx *EvalContext) bool { if ctx.Feature.Phase != PhaseAudit { return false } art := ctx.Feature.GetArtifact(ArtifactAudit) return art != nil && art.Status == StatusNeedsFix }, Action: ActionRemediateAudit, NextCommand: func(ctx *EvalContext) string { return "/remediate-audit " + ctx.Feature.Slug }, } } func auditPassedRule() Rule { return Rule{ ID: "audit-passed", Condition: func(ctx *EvalContext) bool { if ctx.Feature.Phase != PhaseAudit { return false } art := ctx.Feature.GetArtifact(ArtifactAudit) return art != nil && art.Status == StatusPassed }, Action: ActionTransition, TransitionTo: PhaseQA, } } func needsQARule() Rule { return Rule{ ID: "needs-qa", Condition: func(ctx *EvalContext) bool { if ctx.Feature.Phase != PhaseQA { return false } art := ctx.Feature.GetArtifact(ArtifactQAResults) return art == nil || art.Status == StatusPending }, Action: ActionRunQA, NextCommand: func(ctx *EvalContext) string { return "/run-qa " + ctx.Feature.Slug }, OutputPath: func(ctx *EvalContext) string { return ".sdlc/features/" + ctx.Feature.Slug + "/qa-results.md" }, } } func qaHasFailuresRule() Rule { return Rule{ ID: "qa-has-failures", Condition: func(ctx *EvalContext) bool { if ctx.Feature.Phase != PhaseQA { return false } art := ctx.Feature.GetArtifact(ArtifactQAResults) return art != nil && art.Status == StatusFailed }, Action: ActionFixQAFailures, NextCommand: func(ctx *EvalContext) string { return "/fix-qa-failures " + ctx.Feature.Slug }, } } func qaPassedRule() Rule { return Rule{ ID: "qa-passed", Condition: func(ctx *EvalContext) bool { if ctx.Feature.Phase != PhaseQA { return false } art := ctx.Feature.GetArtifact(ArtifactQAResults) return art != nil && art.Status == StatusPassed }, Action: ActionTransition, TransitionTo: PhaseMerge, } } func needsMergeRule() Rule { return Rule{ ID: "needs-merge", Condition: func(ctx *EvalContext) bool { return ctx.Feature.Phase == PhaseMerge }, Action: ActionMergeFeature, NextCommand: func(ctx *EvalContext) string { return "/merge-feature " + ctx.Feature.Slug }, } } func archiveFeatureRule() Rule { return Rule{ ID: "archive-feature", Condition: func(ctx *EvalContext) bool { return ctx.Feature.Phase == PhaseReleased }, Action: ActionArchive, NextCommand: func(ctx *EvalContext) string { return "/archive-feature " + ctx.Feature.Slug }, } }