diff --git a/internal/squad/quality_gate.go b/internal/squad/quality_gate.go index d3249fa7..7d75f6cc 100644 --- a/internal/squad/quality_gate.go +++ b/internal/squad/quality_gate.go @@ -54,8 +54,16 @@ func (q *QualityGate) Validate(wsDir string, taskKey string, content string) (*Q tmplData, err := os.ReadFile(tmplPath) if err == nil { expectedHeadings := extractHeadings(string(tmplData)) + actualHeadings := extractHeadings(content) for _, h := range expectedHeadings { - if !strings.Contains(content, h) { + found := false + for _, act := range actualHeadings { + if strings.Contains(act, h) || strings.Contains(h, act) { + found = true + break + } + } + if !found && !strings.Contains(content, h) { report.MissingSections = append(report.MissingSections, h) report.Violations = append(report.Violations, fmt.Sprintf("缺少必填章节: %s", h)) report.Score -= 15.0 @@ -269,12 +277,43 @@ func extractHeadings(md string) []string { for _, line := range lines { trimmed := strings.TrimSpace(line) if strings.HasPrefix(trimmed, "## ") { - headings = append(headings, trimmed) + raw := strings.TrimPrefix(trimmed, "## ") + clean := cleanHeadingTitle(raw) + if clean != "" { + headings = append(headings, clean) + } } } return headings } +func cleanHeadingTitle(title string) string { + // 去除前缀序号如 "一、", "二、", "1.", "1.1", "(一)" 等 + t := strings.TrimSpace(title) + runes := []rune(t) + // 去掉中文序号如 "一、", "二、", "三、", "四、", "五、", "六、", "七、", "八、", "九、", "十、" + chineseNums := []string{"一、", "二、", "三、", "四、", "五、", "六、", "七、", "八、", "九、", "十、"} + for _, cn := range chineseNums { + if strings.HasPrefix(t, cn) { + t = strings.TrimPrefix(t, cn) + break + } + } + // 去掉数字序号如 "1. ", "1、" + for len(runes) > 0 && ((runes[0] >= '0' && runes[0] <= '9') || runes[0] == '.' || runes[0] == '、' || runes[0] == ' ') { + t = strings.TrimSpace(string(runes[1:])) + runes = []rune(t) + } + // 去除可能括号后缀 + if idx := strings.Index(t, "("); idx > 0 { + t = strings.TrimSpace(t[:idx]) + } + if idx := strings.Index(t, "("); idx > 0 { + t = strings.TrimSpace(t[:idx]) + } + return strings.TrimSpace(t) +} + func computeSHA256(filePath string) (string, error) { f, err := os.Open(filePath) if err != nil { diff --git a/internal/squad/workflow.go b/internal/squad/workflow.go index bc876ac9..2c91e22b 100644 --- a/internal/squad/workflow.go +++ b/internal/squad/workflow.go @@ -348,6 +348,17 @@ func (d *WorkflowDriver) runOneStep( } stepStart := time.Now() + // 若当前步骤为 leader 审核定稿,且依赖前序专员稿件,先预置 baseline 草稿,供审查与润色 + if step.Role == "leader" && deliverableName == "output.md" { + targetFile := filepath.Join(subDir, deliverableName) + if _, err := os.Stat(targetFile); os.IsNotExist(err) && len(step.Needs) > 0 { + srcFile := filepath.Join(wsDir, filepath.FromSlash(step.Needs[0])) + if srcData, err := os.ReadFile(srcFile); err == nil && len(srcData) > 0 { + _ = os.WriteFile(targetFile, srcData, 0o644) + } + } + } + // 1. 读取 TASK.md 与 AGENTS.md taskData, _ := os.ReadFile(filepath.Join(subDir, "TASK.md")) agentsData, _ := os.ReadFile(filepath.Join(subDir, "AGENTS.md"))