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>
144 lines
4.8 KiB
Go
144 lines
4.8 KiB
Go
package sdlc
|
|
|
|
import "testing"
|
|
|
|
// TestFullLifecycleClassification walks through the entire feature lifecycle.
|
|
func TestFullLifecycleClassification(t *testing.T) {
|
|
c := NewClassifier()
|
|
cfg := DefaultConfig("test")
|
|
state := DefaultState("test")
|
|
f := makeTestFeature(PhaseDraft)
|
|
|
|
// Phase: Draft
|
|
// Step 1: needs spec
|
|
cl := c.Classify(&EvalContext{State: state, Feature: f, Config: cfg})
|
|
if cl.Action != ActionCreateSpec {
|
|
t.Fatalf("step1: Action = %q, want CREATE_SPEC", cl.Action)
|
|
}
|
|
|
|
// Step 2: spec created -> needs approval
|
|
f.GetArtifact(ArtifactSpec).MarkDraft()
|
|
cl = c.Classify(&EvalContext{State: state, Feature: f, Config: cfg})
|
|
if cl.Action != ActionAwaitApproval {
|
|
t.Fatalf("step2: Action = %q, want AWAIT_APPROVAL", cl.Action)
|
|
}
|
|
|
|
// Step 3: spec approved -> transition to specified
|
|
f.GetArtifact(ArtifactSpec).Approve("user")
|
|
cl = c.Classify(&EvalContext{State: state, Feature: f, Config: cfg})
|
|
if cl.Action != ActionTransition || cl.TransitionTo != PhaseSpecified {
|
|
t.Fatalf("step3: Action = %q/%q", cl.Action, cl.TransitionTo)
|
|
}
|
|
|
|
// Phase: Specified
|
|
f.Transition(PhaseSpecified)
|
|
|
|
// Step 4: needs design
|
|
cl = c.Classify(&EvalContext{State: state, Feature: f, Config: cfg})
|
|
if cl.Action != ActionCreateDesign {
|
|
t.Fatalf("step4: Action = %q, want CREATE_DESIGN", cl.Action)
|
|
}
|
|
|
|
// Step 5: design approved -> needs tasks
|
|
f.GetArtifact(ArtifactDesign).Approve("user")
|
|
cl = c.Classify(&EvalContext{State: state, Feature: f, Config: cfg})
|
|
if cl.Action != ActionCreateTasks {
|
|
t.Fatalf("step5: Action = %q, want CREATE_TASKS", cl.Action)
|
|
}
|
|
|
|
// Step 6: tasks approved -> needs qa plan
|
|
f.GetArtifact(ArtifactTasks).Approve("user")
|
|
cl = c.Classify(&EvalContext{State: state, Feature: f, Config: cfg})
|
|
if cl.Action != ActionCreateQAPlan {
|
|
t.Fatalf("step6: Action = %q, want CREATE_QA_PLAN", cl.Action)
|
|
}
|
|
|
|
// Step 7: qa plan approved -> transition to planned
|
|
f.GetArtifact(ArtifactQAPlan).Approve("user")
|
|
cl = c.Classify(&EvalContext{State: state, Feature: f, Config: cfg})
|
|
if cl.Action != ActionTransition || cl.TransitionTo != PhasePlanned {
|
|
t.Fatalf("step7: Action = %q/%q", cl.Action, cl.TransitionTo)
|
|
}
|
|
|
|
// Phase: Planned -> needs branch -> Ready -> Implementation
|
|
f.Transition(PhasePlanned)
|
|
|
|
// Step 7b: needs branch (RequireBranch is true in DefaultConfig)
|
|
cl = c.Classify(&EvalContext{State: state, Feature: f, Config: cfg})
|
|
if cl.Action != ActionCreateBranch {
|
|
t.Fatalf("step7b: Action = %q, want CREATE_BRANCH", cl.Action)
|
|
}
|
|
|
|
// Branch created
|
|
f.Branch = "feature/auth"
|
|
|
|
// Step 7c: ready to implement
|
|
cl = c.Classify(&EvalContext{State: state, Feature: f, Config: cfg})
|
|
if cl.Action != ActionTransition || cl.TransitionTo != PhaseReady {
|
|
t.Fatalf("step7c: Action = %q/%q, want TRANSITION/ready", cl.Action, cl.TransitionTo)
|
|
}
|
|
|
|
f.Transition(PhaseReady)
|
|
f.Transition(PhaseImplementation)
|
|
|
|
// Add tasks
|
|
f.Tasks = AddTask(nil, "Create user model")
|
|
f.Tasks = AddTask(f.Tasks, "Add validation")
|
|
|
|
// Step 8: implement next task
|
|
cl = c.Classify(&EvalContext{State: state, Feature: f, Config: cfg})
|
|
if cl.Action != ActionImplementTask {
|
|
t.Fatalf("step8: Action = %q, want IMPLEMENT_TASK", cl.Action)
|
|
}
|
|
|
|
// Complete all tasks
|
|
f.Tasks, _ = StartTask(f.Tasks, "task-001")
|
|
f.Tasks, _ = CompleteTask(f.Tasks, "task-001")
|
|
f.Tasks, _ = StartTask(f.Tasks, "task-002")
|
|
f.Tasks, _ = CompleteTask(f.Tasks, "task-002")
|
|
|
|
// Step 9: implementation complete -> transition to review
|
|
cl = c.Classify(&EvalContext{State: state, Feature: f, Config: cfg})
|
|
if cl.Action != ActionTransition || cl.TransitionTo != PhaseReview {
|
|
t.Fatalf("step9: Action = %q/%q", cl.Action, cl.TransitionTo)
|
|
}
|
|
|
|
// Phase: Review
|
|
f.Transition(PhaseReview)
|
|
f.GetArtifact(ArtifactReview).MarkPassed()
|
|
cl = c.Classify(&EvalContext{State: state, Feature: f, Config: cfg})
|
|
if cl.TransitionTo != PhaseAudit {
|
|
t.Fatalf("review->audit: TransitionTo = %q", cl.TransitionTo)
|
|
}
|
|
|
|
// Phase: Audit
|
|
f.Transition(PhaseAudit)
|
|
f.GetArtifact(ArtifactAudit).MarkPassed()
|
|
cl = c.Classify(&EvalContext{State: state, Feature: f, Config: cfg})
|
|
if cl.TransitionTo != PhaseQA {
|
|
t.Fatalf("audit->qa: TransitionTo = %q", cl.TransitionTo)
|
|
}
|
|
|
|
// Phase: QA
|
|
f.Transition(PhaseQA)
|
|
f.GetArtifact(ArtifactQAResults).MarkPassed()
|
|
cl = c.Classify(&EvalContext{State: state, Feature: f, Config: cfg})
|
|
if cl.TransitionTo != PhaseMerge {
|
|
t.Fatalf("qa->merge: TransitionTo = %q", cl.TransitionTo)
|
|
}
|
|
|
|
// Phase: Merge
|
|
f.Transition(PhaseMerge)
|
|
cl = c.Classify(&EvalContext{State: state, Feature: f, Config: cfg})
|
|
if cl.Action != ActionMergeFeature {
|
|
t.Fatalf("merge: Action = %q, want MERGE_FEATURE", cl.Action)
|
|
}
|
|
|
|
// Phase: Released
|
|
f.Transition(PhaseReleased)
|
|
cl = c.Classify(&EvalContext{State: state, Feature: f, Config: cfg})
|
|
if cl.Action != ActionArchive {
|
|
t.Fatalf("released: Action = %q, want ARCHIVE", cl.Action)
|
|
}
|
|
}
|