Stage-specific squad steps now win over the kind default, including always-on regulatory and searcher roles. Each of the 60 tasks carries its writing hint and Chinese alias so lookup and the writer accept line use them. Co-authored-by: Cursor <cursoragent@cursor.com>
383 lines
12 KiB
Go
383 lines
12 KiB
Go
package squad
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
)
|
|
|
|
const rdevDHFAccept = "模板写满;结论先行;追溯表必填;缺项标「待确认」;不编造数据/签字/附件"
|
|
|
|
func rdevModel() string {
|
|
if v := strings.TrimSpace(os.Getenv("SQUAD_MODEL_ID")); v != "" {
|
|
return v
|
|
}
|
|
if v := strings.TrimSpace(os.Getenv("MINIMAX_MODEL_ID")); v != "" {
|
|
return v
|
|
}
|
|
return "MiniMax-M3"
|
|
}
|
|
|
|
func rdevComplexityTier(fields map[string]string) string {
|
|
if fields == nil {
|
|
return ""
|
|
}
|
|
switch strings.ToLower(strings.TrimSpace(fields["complexity_tier"])) {
|
|
case "high", "medium", "low":
|
|
return strings.ToLower(strings.TrimSpace(fields["complexity_tier"]))
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
func rdevSquadAgents(taskKey, tier string) []AgentSpec {
|
|
if ov, ok := rdevResolvedOverride(taskKey, tier); ok {
|
|
return rdevAgentsFromOverride(ov)
|
|
}
|
|
meta, _ := LookupRDevTask(taskKey)
|
|
agents := []AgentSpec{
|
|
{
|
|
Role: "organizer",
|
|
Name: "材料整理专员",
|
|
Model: rdevModel(),
|
|
Tools: []string{"read", "write", "edit"},
|
|
BashPermission: "deny",
|
|
Responsibilities: []string{"材料事实抽取", "适用性与缺口识别", "字段回填"},
|
|
ForbiddenActions: []string{"严禁编造试验数据", "严禁伪造签字"},
|
|
},
|
|
}
|
|
switch meta.Kind {
|
|
case RDevKindReviewMinutes:
|
|
agents = append(agents,
|
|
AgentSpec{
|
|
Role: "writer",
|
|
Name: "文档撰写专员",
|
|
Model: rdevModel(),
|
|
Tools: []string{"read", "write", "edit"},
|
|
BashPermission: "deny",
|
|
Responsibilities: []string{"按评审记录模板成文", "问题-决议-行动项闭环"},
|
|
},
|
|
rdevLeaderAgent(),
|
|
)
|
|
case RDevKindUploadIndex:
|
|
agents = append(agents,
|
|
AgentSpec{
|
|
Role: "engineer",
|
|
Name: "技术专员",
|
|
Model: rdevModel(),
|
|
Tools: []string{"read", "write", "edit"},
|
|
BashPermission: "deny",
|
|
Responsibilities: []string{"附件索引完整性", "缺失影响分析"},
|
|
},
|
|
AgentSpec{
|
|
Role: "writer",
|
|
Name: "文档撰写专员",
|
|
Model: rdevModel(),
|
|
Tools: []string{"read", "write", "edit"},
|
|
BashPermission: "deny",
|
|
Responsibilities: []string{"按附件清单模板成文", "标明待上传项"},
|
|
},
|
|
rdevLeaderAgent(),
|
|
)
|
|
default:
|
|
agents = append(agents,
|
|
AgentSpec{
|
|
Role: "engineer",
|
|
Name: "技术专员",
|
|
Model: rdevModel(),
|
|
Tools: []string{"read", "write", "edit"},
|
|
BashPermission: "deny",
|
|
Responsibilities: []string{"技术内容与追溯", "设计控制论证"},
|
|
},
|
|
)
|
|
if RDevNeedsRegulatoryAt(taskKey, tier) {
|
|
agents = append(agents, AgentSpec{
|
|
Role: "regulatory",
|
|
Name: "法规专员",
|
|
Model: rdevModel(),
|
|
Tools: []string{"read", "write", "edit"},
|
|
BashPermission: "deny",
|
|
Responsibilities: []string{"法规/标准适用性", "软件与风险管理合规要点"},
|
|
})
|
|
}
|
|
agents = append(agents,
|
|
AgentSpec{
|
|
Role: "writer",
|
|
Name: "文档撰写专员",
|
|
Model: rdevModel(),
|
|
Tools: []string{"read", "write", "edit"},
|
|
BashPermission: "deny",
|
|
Responsibilities: []string{"按 RD 模板成文", "结论先行与待确认清单"},
|
|
},
|
|
rdevLeaderAgent(),
|
|
)
|
|
}
|
|
return agents
|
|
}
|
|
|
|
func rdevLeaderAgent() AgentSpec {
|
|
return AgentSpec{
|
|
Role: "leader",
|
|
Name: "研发设计控制负责人",
|
|
Model: rdevModel(),
|
|
Tools: []string{"read", "write", "edit"},
|
|
BashPermission: "deny",
|
|
InstructionsFile: "configs/agents/rdev_leader.md",
|
|
Responsibilities: []string{"DHF 完整性终审", "不适用理由审核", "禁止编造检查"},
|
|
ForbiddenActions: []string{"严禁代签", "严禁伪造附件内容"},
|
|
}
|
|
}
|
|
|
|
func rdevWorkflowSteps(taskKey, tier string) []StepSpec {
|
|
meta, ok := LookupRDevTask(taskKey)
|
|
kind := RDevKindDocument
|
|
title := taskKey
|
|
if ok {
|
|
kind = meta.Kind
|
|
title = meta.Title
|
|
}
|
|
if ov, found := rdevResolvedOverride(taskKey, tier); found {
|
|
return rdevAttachStageHint(taskKey, rdevStepsFromOverride(ov))
|
|
}
|
|
|
|
switch kind {
|
|
case RDevKindReviewMinutes:
|
|
return rdevAttachStageHint(taskKey, []StepSpec{
|
|
{
|
|
ID: "step_01_organize", Role: "organizer", Title: "整理评审输入与议题",
|
|
SubWorkspace: "tasks/01_organize", Mode: "execute",
|
|
Accept: []string{"汇总上游批准文档要点", "列出待评审议题与缺口", rdevDHFAccept},
|
|
},
|
|
{
|
|
ID: "step_02_draft", Role: "writer", Title: "撰写评审记录",
|
|
SubWorkspace: "tasks/02_draft", Mode: "execute",
|
|
Needs: []string{"tasks/01_organize/output.md"},
|
|
Accept: []string{"按模板写满评审记录", "问题-决议-行动项可追溯", rdevDHFAccept},
|
|
},
|
|
{
|
|
ID: "step_03_review", Role: "leader", Title: "研发设计控制终审",
|
|
SubWorkspace: "tasks/03_review", Mode: "execute",
|
|
Needs: []string{"tasks/02_draft/output.md"},
|
|
Accept: []string{"完整性与粒度达标", "无编造签字/数据", rdevDHFAccept},
|
|
},
|
|
})
|
|
case RDevKindUploadIndex:
|
|
return rdevAttachStageHint(taskKey, []StepSpec{
|
|
{
|
|
ID: "step_01_organize", Role: "organizer", Title: "整理附件材料线索",
|
|
SubWorkspace: "tasks/01_organize", Mode: "execute",
|
|
Accept: []string{"列出已知附件与缺失项", rdevDHFAccept},
|
|
},
|
|
{
|
|
ID: "step_02_engineer", Role: "engineer", Title: "完善附件索引与影响分析",
|
|
SubWorkspace: "tasks/02_engineer", Mode: "execute",
|
|
Needs: []string{"tasks/01_organize/output.md"},
|
|
Accept: []string{"索引表完整", "缺失影响已说明", rdevDHFAccept},
|
|
},
|
|
{
|
|
ID: "step_03_draft", Role: "writer", Title: "撰写附件清单",
|
|
SubWorkspace: "tasks/03_draft", Mode: "execute",
|
|
Needs: []string{"tasks/02_engineer/output.md"},
|
|
Accept: []string{"按 upload_index 模板成文", "禁止伪造附件正文", rdevDHFAccept},
|
|
},
|
|
{
|
|
ID: "step_04_review", Role: "leader", Title: "研发设计控制终审",
|
|
SubWorkspace: "tasks/04_review", Mode: "execute",
|
|
Needs: []string{"tasks/03_draft/output.md"},
|
|
Accept: []string{"索引与待上传结论一致", rdevDHFAccept},
|
|
},
|
|
})
|
|
default:
|
|
return rdevAttachStageHint(taskKey, rdevDocumentSteps(taskKey, title, tier))
|
|
}
|
|
}
|
|
|
|
func rdevDocumentSteps(taskKey, title, tier string) []StepSpec {
|
|
steps := []StepSpec{
|
|
{
|
|
ID: "step_01_organize", Role: "organizer", Title: "抽取材料事实并回填",
|
|
SubWorkspace: "tasks/01_organize", Mode: "execute",
|
|
Accept: []string{"facts/gaps/implications 入黑板", "适用性字段回填", rdevDHFAccept},
|
|
},
|
|
{
|
|
ID: "step_02_engineer", Role: "engineer", Title: "技术内容与追溯编制",
|
|
SubWorkspace: "tasks/02_engineer", Mode: "execute",
|
|
Needs: []string{"tasks/01_organize/output.md"},
|
|
Accept: []string{"技术论证完整", "追溯表可填则填", rdevDHFAccept},
|
|
},
|
|
}
|
|
draftNeeds := "tasks/02_engineer/output.md"
|
|
if RDevNeedsRegulatoryAt(taskKey, tier) {
|
|
steps = append(steps, StepSpec{
|
|
ID: "step_03_regulatory", Role: "regulatory", Title: "法规与风险合规要点",
|
|
SubWorkspace: "tasks/03_regulatory", Mode: "execute",
|
|
Needs: []string{"tasks/02_engineer/output.md"},
|
|
Accept: []string{"法规适用性明确", "风险类引用可追溯", rdevDHFAccept},
|
|
})
|
|
draftNeeds = "tasks/03_regulatory/output.md"
|
|
steps = append(steps,
|
|
StepSpec{
|
|
ID: "step_04_draft", Role: "writer", Title: "撰写《" + title + "》",
|
|
SubWorkspace: "tasks/04_draft", Mode: "execute",
|
|
Needs: []string{draftNeeds},
|
|
Accept: []string{"严格按 rd_ 模板成文", "结论先行", rdevDHFAccept},
|
|
},
|
|
StepSpec{
|
|
ID: "step_05_review", Role: "leader", Title: "研发设计控制终审",
|
|
SubWorkspace: "tasks/05_review", Mode: "execute",
|
|
Needs: []string{"tasks/04_draft/output.md"},
|
|
Accept: []string{"DHF 粒度达标", "无编造", rdevDHFAccept},
|
|
},
|
|
)
|
|
return steps
|
|
}
|
|
steps = append(steps,
|
|
StepSpec{
|
|
ID: "step_03_draft", Role: "writer", Title: "撰写《" + title + "》",
|
|
SubWorkspace: "tasks/03_draft", Mode: "execute",
|
|
Needs: []string{draftNeeds},
|
|
Accept: []string{"严格按 rd_ 模板成文", "结论先行", rdevDHFAccept},
|
|
},
|
|
StepSpec{
|
|
ID: "step_04_review", Role: "leader", Title: "研发设计控制终审",
|
|
SubWorkspace: "tasks/04_review", Mode: "execute",
|
|
Needs: []string{"tasks/03_draft/output.md"},
|
|
Accept: []string{"DHF 粒度达标", "无编造", rdevDHFAccept},
|
|
},
|
|
)
|
|
return steps
|
|
}
|
|
|
|
func rdevIncludeConditionalRegulatory(taskKey, tier string) bool {
|
|
switch strings.ToLower(strings.TrimSpace(tier)) {
|
|
case "medium", "low":
|
|
return false
|
|
case "high":
|
|
return true
|
|
default:
|
|
return RDevNeedsRegulatory(taskKey)
|
|
}
|
|
}
|
|
|
|
func rdevResolvedOverride(taskKey, tier string) ([]rdevOverrideStep, bool) {
|
|
raw, ok := rdevStageOverrides[taskKey]
|
|
if !ok {
|
|
return nil, false
|
|
}
|
|
out := make([]rdevOverrideStep, 0, len(raw))
|
|
for _, step := range raw {
|
|
if step.WhenHigh && !rdevIncludeConditionalRegulatory(taskKey, tier) {
|
|
continue
|
|
}
|
|
out = append(out, step)
|
|
}
|
|
return out, true
|
|
}
|
|
|
|
func rdevAgentsFromOverride(steps []rdevOverrideStep) []AgentSpec {
|
|
seen := map[string]bool{}
|
|
var agents []AgentSpec
|
|
for _, step := range steps {
|
|
if seen[step.Role] {
|
|
continue
|
|
}
|
|
seen[step.Role] = true
|
|
agents = append(agents, rdevAgentForRole(step.Role))
|
|
}
|
|
return agents
|
|
}
|
|
|
|
func rdevAgentForRole(role string) AgentSpec {
|
|
switch role {
|
|
case "writer":
|
|
return AgentSpec{
|
|
Role: "writer", Name: "文档撰写专员", Model: rdevModel(),
|
|
Tools: []string{"read", "write", "edit"}, BashPermission: "deny",
|
|
Responsibilities: []string{"按 RD 模板成文", "结论先行与待确认清单"},
|
|
}
|
|
case "engineer":
|
|
return AgentSpec{
|
|
Role: "engineer", Name: "技术专员", Model: rdevModel(),
|
|
Tools: []string{"read", "write", "edit"}, BashPermission: "deny",
|
|
Responsibilities: []string{"技术内容与追溯", "设计控制论证"},
|
|
}
|
|
case "regulatory":
|
|
return AgentSpec{
|
|
Role: "regulatory", Name: "法规专员", Model: rdevModel(),
|
|
Tools: []string{"read", "write", "edit"}, BashPermission: "deny",
|
|
Responsibilities: []string{"法规/标准适用性", "软件与风险管理合规要点"},
|
|
}
|
|
case "searcher":
|
|
return AgentSpec{
|
|
Role: "searcher", Name: "资料检索专员", Model: rdevModel(),
|
|
Tools: []string{"read", "write", "edit"}, BashPermission: "deny",
|
|
Responsibilities: []string{"公开资料检索", "出处与日期标注"},
|
|
ForbiddenActions: []string{"严禁编造URL与标准号"},
|
|
}
|
|
case "leader":
|
|
return rdevLeaderAgent()
|
|
default:
|
|
return AgentSpec{
|
|
Role: "organizer", Name: "材料整理专员", Model: rdevModel(),
|
|
Tools: []string{"read", "write", "edit"}, BashPermission: "deny",
|
|
Responsibilities: []string{"材料事实抽取", "适用性与缺口识别", "字段回填"},
|
|
ForbiddenActions: []string{"严禁编造试验数据", "严禁伪造签字"},
|
|
}
|
|
}
|
|
}
|
|
|
|
func rdevStepsFromOverride(steps []rdevOverrideStep) []StepSpec {
|
|
out := make([]StepSpec, 0, len(steps))
|
|
var parallelOutputs []string
|
|
prev := ""
|
|
for i, step := range steps {
|
|
id := fmt.Sprintf("step_%02d_%s", i+1, step.Role)
|
|
sub := fmt.Sprintf("tasks/%02d_%s", i+1, step.Role)
|
|
spec := StepSpec{
|
|
ID: id, Role: step.Role, Title: step.Title,
|
|
SubWorkspace: sub, Mode: "execute", Parallel: step.Parallel,
|
|
Accept: []string{rdevDHFAccept},
|
|
}
|
|
if step.Delegate {
|
|
spec.Accept = append([]string{"可委托检索,未命中须写检索局限"}, spec.Accept...)
|
|
}
|
|
if i > 0 {
|
|
if step.Parallel {
|
|
if prev != "" {
|
|
spec.Needs = []string{prev}
|
|
}
|
|
} else if len(parallelOutputs) > 0 {
|
|
spec.Needs = append([]string(nil), parallelOutputs...)
|
|
parallelOutputs = nil
|
|
} else if prev != "" {
|
|
spec.Needs = []string{prev}
|
|
}
|
|
}
|
|
out = append(out, spec)
|
|
file := sub + "/output.md"
|
|
if step.Parallel {
|
|
parallelOutputs = append(parallelOutputs, file)
|
|
} else {
|
|
prev = file
|
|
parallelOutputs = nil
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func rdevAttachStageHint(taskKey string, steps []StepSpec) []StepSpec {
|
|
meta, ok := LookupRDevTask(taskKey)
|
|
if !ok {
|
|
return steps
|
|
}
|
|
for i := range steps {
|
|
if steps[i].Role == "writer" && meta.Hint != "" {
|
|
steps[i].Accept = append(steps[i].Accept, meta.Hint)
|
|
}
|
|
if steps[i].Role == "organizer" && meta.SkipHint != "" {
|
|
steps[i].Accept = append(steps[i].Accept, meta.SkipHint)
|
|
}
|
|
}
|
|
return steps
|
|
}
|