Files
Agent-System/internal/squad/quality_gate_v2.go
T
weichenandCursor b550911667 feat(squad): load rdev overlays and enforce document gates
Each rdev step now gets a role-filtered overlay so writing discipline stays out of the template. User needs, system requirements, review minutes, and upload indexes fail the quality gate when IDs, decisions, or attachment indexes are incomplete.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-19 10:12:10 +00:00

97 lines
2.7 KiB
Go

package squad
import (
"encoding/json"
"os"
"path/filepath"
"strings"
)
// ValidateQualityGateV2 按 quality_gate_key 执行结构化门禁。
func ValidateQualityGateV2(gateKey, wsDir string) []string {
switch gateKey {
case "quality.system_reqs.v2":
return validateRequirementsJSON(wsDir)
case "quality.test_manager.v2":
return validateTestCasesJSON(wsDir)
case "quality.rd_user_needs.v1":
body, _ := os.ReadFile(filepath.Join(wsDir, "output.md"))
return rdevRequirementIssues(wsDir, string(body), "UR")
case "quality.rd_system_reqs.v1":
body, _ := os.ReadFile(filepath.Join(wsDir, "output.md"))
return rdevRequirementIssues(wsDir, string(body), "RS")
case "quality.rd_review_minutes.v1":
body, _ := os.ReadFile(filepath.Join(wsDir, "output.md"))
return rdevReviewMinutesIssues(string(body))
case "quality.rd_upload_index.v1":
body, _ := os.ReadFile(filepath.Join(wsDir, "output.md"))
return rdevUploadIndexIssues(string(body))
default:
return nil
}
}
func validateRequirementsJSON(wsDir string) []string {
path := filepath.Join(wsDir, "artifacts", "requirements.json")
raw, err := os.ReadFile(path)
if err != nil {
return []string{"requirements.json 为必需产物"}
}
var reqs []map[string]any
if err := json.Unmarshal(raw, &reqs); err != nil {
return []string{"requirements.json 格式无效"}
}
ids := map[string]bool{}
issues := make([]string, 0)
for _, r := range reqs {
id, _ := r["id"].(string)
if id == "" {
issues = append(issues, "需求缺少 id")
continue
}
if ids[id] {
issues = append(issues, "重复需求 id: "+id)
}
ids[id] = true
for _, field := range []string{"verification_method", "acceptance_criteria"} {
if v, _ := r[field].(string); strings.TrimSpace(v) == "" {
issues = append(issues, id+": 缺少 "+field)
}
}
}
return issues
}
func validateTestCasesJSON(wsDir string) []string {
path := filepath.Join(wsDir, "artifacts", "test_cases.json")
raw, err := os.ReadFile(path)
if err != nil {
return []string{"test_cases.json 为必需产物"}
}
var cases []map[string]any
if err := json.Unmarshal(raw, &cases); err != nil {
return []string{"test_cases.json 格式无效"}
}
reqPath := filepath.Join(wsDir, "artifacts", "requirements.json")
reqRaw, err := os.ReadFile(reqPath)
if err != nil {
return []string{"缺少上游 requirements.json 无法验证覆盖率"}
}
var reqs []map[string]any
_ = json.Unmarshal(reqRaw, &reqs)
covered := map[string]bool{}
for _, tc := range cases {
if rid, _ := tc["requirement_id"].(string); rid != "" {
covered[rid] = true
}
}
issues := make([]string, 0)
for _, r := range reqs {
id, _ := r["id"].(string)
if id != "" && !covered[id] {
issues = append(issues, "需求未覆盖: "+id)
}
}
return issues
}