Pin Agent-Native as a submodule so the module builds without the multi-repo sibling path. Co-authored-by: Cursor <cursoragent@cursor.com>
66 lines
1.7 KiB
Go
66 lines
1.7 KiB
Go
package squad
|
||
|
||
import (
|
||
"fmt"
|
||
"os"
|
||
"path/filepath"
|
||
"regexp"
|
||
"strings"
|
||
"time"
|
||
)
|
||
|
||
// Checker 交付检查器:工作流执行完后校验交付物。
|
||
// 默认 MinimalChecker:交付物存在且非空、无未替换占位符;产品可注入领域规则。
|
||
type Checker interface {
|
||
Check(req CheckRequest) *QualityReport
|
||
}
|
||
|
||
// CheckRequest 检查输入。
|
||
type CheckRequest struct {
|
||
RunID string
|
||
Workspace string
|
||
Deliverable string // 相对路径(如 output.md)
|
||
Plan *WorkflowPlan
|
||
Squad *SquadConfig
|
||
Template string
|
||
}
|
||
|
||
// MinimalChecker 默认最小检查器。
|
||
type MinimalChecker struct{}
|
||
|
||
// Check 执行最小检查:存在、非空、无占位符、含标题。
|
||
func (MinimalChecker) Check(req CheckRequest) *QualityReport {
|
||
report := &QualityReport{CheckedAt: time.Now(), Score: 100, Passed: true}
|
||
fail := func(msg string) {
|
||
report.Passed = false
|
||
report.Violations = append(report.Violations, msg)
|
||
report.Score -= 25
|
||
}
|
||
path := filepath.Join(req.Workspace, req.Deliverable)
|
||
data, err := os.ReadFile(path)
|
||
if err != nil {
|
||
fail(fmt.Sprintf("交付物缺失: %s (%v)", req.Deliverable, err))
|
||
return report
|
||
}
|
||
text := string(data)
|
||
if len(strings.TrimSpace(text)) < 50 {
|
||
fail("交付物内容过短(<50 字符)")
|
||
}
|
||
// 常见未替换占位符形态 {{var}} / «var» / <TODO>。
|
||
for _, re := range []string{`{{[^}]+}}`, `«[^»]+»`, `<TODO[^>]*>`} {
|
||
if hits := regexpFindAll(re, text); len(hits) > 0 {
|
||
report.Placeholders = append(report.Placeholders, hits...)
|
||
fail(fmt.Sprintf("存在未替换占位符: %v", hits))
|
||
}
|
||
}
|
||
if report.Passed {
|
||
report.Score = 100
|
||
}
|
||
return report
|
||
}
|
||
|
||
func regexpFindAll(pattern, s string) []string {
|
||
re := regexp.MustCompile(pattern)
|
||
return re.FindAllString(s, 8)
|
||
}
|