KTD1 fifth step (behavior preserved):
- squad.AssetProvider{DocTemplate,RenderDoc,AgentInstruction,
AgentInstructionFile} injected via Options.Assets into Service,
WorkspaceEngine and AdaptivePlanner
- bizdev templateAssetProvider maps port to internal/template (assembly side)
- nil-provider paths degrade to the same fallbacks the old error branches
used (placeholder template, role header) — fail-visible, no guessing
- squad -> template import eliminated; arch edge moved bizdev->template
- verified: build/vet/arch green, squad+bizdev short tests pass
185 lines
5.2 KiB
Go
185 lines
5.2 KiB
Go
package squad
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestRDevCatalogSizeAndKinds(t *testing.T) {
|
|
if len(RDevCatalog) != 60 {
|
|
t.Fatalf("want 60 catalog entries, got %d", len(RDevCatalog))
|
|
}
|
|
var doc, rev, up int
|
|
for _, e := range RDevCatalog {
|
|
switch e.Kind {
|
|
case RDevKindDocument:
|
|
doc++
|
|
case RDevKindReviewMinutes:
|
|
rev++
|
|
case RDevKindUploadIndex:
|
|
up++
|
|
default:
|
|
t.Fatalf("unknown kind %q for %s", e.Kind, e.Key)
|
|
}
|
|
}
|
|
if doc != 47 || rev != 6 || up != 7 {
|
|
t.Fatalf("kind counts document=%d review=%d upload=%d", doc, rev, up)
|
|
}
|
|
if len(SupportedTasks) < 69 {
|
|
t.Fatalf("SupportedTasks should include base+rdev, got %d", len(SupportedTasks))
|
|
}
|
|
}
|
|
|
|
func TestRDevWorkflowStepsByKind(t *testing.T) {
|
|
docSteps := DefaultWorkflowSteps("rd_proposal")
|
|
if len(docSteps) < 4 {
|
|
t.Fatalf("document steps want >=4, got %d", len(docSteps))
|
|
}
|
|
revSteps := DefaultWorkflowSteps("rd_planning_review")
|
|
if len(revSteps) != 3 {
|
|
t.Fatalf("review_minutes want 3 steps, got %d", len(revSteps))
|
|
}
|
|
upSteps := DefaultWorkflowSteps("rd_prototype_photos")
|
|
if len(upSteps) != 4 {
|
|
t.Fatalf("upload_index want 4 steps, got %d", len(upSteps))
|
|
}
|
|
agents := DefaultSquadAgents("rd_proposal")
|
|
foundLeader := false
|
|
for _, a := range agents {
|
|
if a.Role == "leader" && a.Name == "研发设计控制负责人" {
|
|
foundLeader = true
|
|
}
|
|
}
|
|
if !foundLeader {
|
|
t.Fatal("leader display name missing")
|
|
}
|
|
}
|
|
|
|
func TestRDevRegulatoryFollowsComplexityTier(t *testing.T) {
|
|
if !RDevNeedsRegulatory("rd_sw_plan") {
|
|
t.Fatal("unset tier must keep the static regulatory list")
|
|
}
|
|
if !RDevNeedsRegulatoryAt("rd_sw_plan", "high") {
|
|
t.Fatal("high tier must keep regulatory for listed tasks")
|
|
}
|
|
if RDevNeedsRegulatoryAt("rd_sw_plan", "medium") || RDevNeedsRegulatoryAt("rd_sw_plan", "low") {
|
|
t.Fatal("medium and low must not inject regulatory")
|
|
}
|
|
if RDevNeedsRegulatoryAt("rd_proposal", "high") {
|
|
t.Fatal("tasks outside the list stay without regulatory even at high")
|
|
}
|
|
|
|
planner := NewAdaptivePlanner(nil, nil)
|
|
low, err := planner.Plan(PlannerRequest{
|
|
TaskKey: "rd_sw_plan",
|
|
Fields: map[string]string{"complexity_tier": "low"},
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, step := range low.Workflow.Steps {
|
|
if step.Role == "regulatory" {
|
|
t.Fatalf("low tier plan must not include regulatory step: %+v", low.Workflow.Steps)
|
|
}
|
|
}
|
|
high, err := planner.Plan(PlannerRequest{
|
|
TaskKey: "rd_sw_plan",
|
|
Fields: map[string]string{"complexity_tier": "high"},
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
found := false
|
|
for _, step := range high.Workflow.Steps {
|
|
if step.Role == "regulatory" {
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
t.Fatal("high tier plan must include regulatory step")
|
|
}
|
|
}
|
|
|
|
func TestRDevModelFromEnv(t *testing.T) {
|
|
t.Setenv("SQUAD_MODEL_ID", "")
|
|
t.Setenv("MINIMAX_MODEL_ID", "")
|
|
if got := rdevModel(); got != "MiniMax-M3" {
|
|
t.Fatalf("default model want MiniMax-M3 got %s", got)
|
|
}
|
|
t.Setenv("MINIMAX_MODEL_ID", "MiniMax-M2.7-highspeed")
|
|
if got := rdevModel(); got != "MiniMax-M2.7-highspeed" {
|
|
t.Fatalf("MINIMAX_MODEL_ID want highspeed got %s", got)
|
|
}
|
|
t.Setenv("SQUAD_MODEL_ID", "custom-squad")
|
|
if got := rdevModel(); got != "custom-squad" {
|
|
t.Fatalf("SQUAD_MODEL_ID must win, got %s", got)
|
|
}
|
|
}
|
|
|
|
func TestRDevStageOverrideAndAliases(t *testing.T) {
|
|
for _, item := range RDevCatalog {
|
|
if item.Hint == "" || len(item.Aliases) == 0 {
|
|
t.Fatalf("%s missing hint or alias", item.Key)
|
|
}
|
|
}
|
|
got, ok := LookupRDevTask("项目建议书")
|
|
if !ok || got.Key != "rd_proposal" {
|
|
t.Fatalf("alias lookup got %+v ok=%v", got, ok)
|
|
}
|
|
|
|
roles := func(task, tier string) []string {
|
|
t.Helper()
|
|
var out []string
|
|
for _, step := range rdevWorkflowSteps(task, tier) {
|
|
out = append(out, step.Role)
|
|
}
|
|
return out
|
|
}
|
|
want := func(task, tier string, expect ...string) {
|
|
t.Helper()
|
|
got := roles(task, tier)
|
|
if len(got) != len(expect) {
|
|
t.Fatalf("%s tier=%q roles %v want %v", task, tier, got, expect)
|
|
}
|
|
for i := range expect {
|
|
if got[i] != expect[i] {
|
|
t.Fatalf("%s tier=%q roles %v want %v", task, tier, got, expect)
|
|
}
|
|
}
|
|
}
|
|
|
|
want("rd_design_vv", "low", "organizer", "regulatory", "engineer", "writer", "leader")
|
|
want("rd_user_needs", "high", "organizer", "engineer", "writer", "leader")
|
|
want("rd_emc_safety", "high", "organizer", "engineer", "writer", "leader")
|
|
want("rd_regs_stds", "", "organizer", "searcher", "engineer", "regulatory", "writer", "leader")
|
|
want("rd_sw_sys_test", "", "organizer", "regulatory", "engineer", "writer", "leader")
|
|
want("rd_sw_sys_test", "low", "organizer", "engineer", "writer", "leader")
|
|
want("rd_system_reqs", "", "organizer", "engineer", "writer", "leader")
|
|
want("rd_system_reqs", "high", "organizer", "engineer", "regulatory", "writer", "leader")
|
|
|
|
steps := rdevWorkflowSteps("rd_proposal", "")
|
|
foundHint := false
|
|
for _, step := range steps {
|
|
if step.Role == "writer" {
|
|
for _, line := range step.Accept {
|
|
if strings.Contains(line, "项目建议书") {
|
|
foundHint = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if !foundHint {
|
|
t.Fatal("writer accept must carry the stage hint")
|
|
}
|
|
agents := rdevSquadAgents("rd_regs_stds", "")
|
|
foundSearcher := false
|
|
for _, agent := range agents {
|
|
if agent.Role == "searcher" {
|
|
foundSearcher = true
|
|
}
|
|
}
|
|
if !foundSearcher {
|
|
t.Fatal("regs_stds squad must include searcher")
|
|
}
|
|
}
|