rdev/internal/sdlc/rules_execution.go
jordan f22b220c6d feat: add SDLC branch management, merge, archive, and orchestrator APIs
Add branch lifecycle commands (branch, merge, archive) to the SDLC CLI.
Introduce orchestrator handler and service for multi-step SDLC workflows.
Expand skeleton template with 15 Claude commands covering the full feature
lifecycle. Extend classifier rules, error types, and executor port for
branch operations. Split rules.go and classifier_test.go to stay within
500-line limit.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 12:30:03 -07:00

230 lines
5.5 KiB
Go

package sdlc
import "fmt"
// Execution phase rules: review, audit, QA, merge, archive.
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
},
}
}
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,
}
}