受控小队任务服务端启用修复预算;否定性自检引用不再误报;quality_failed 保持失败且不激活未过门禁稿。 Co-authored-by: Cursor <cursoragent@cursor.com>
1104 lines
41 KiB
Go
1104 lines
41 KiB
Go
package squad
|
||
|
||
import (
|
||
"encoding/json"
|
||
"fmt"
|
||
"os"
|
||
"path/filepath"
|
||
"strings"
|
||
"time"
|
||
|
||
"chat-agent-system/internal/template"
|
||
)
|
||
|
||
// WorkspacePrepareRequest 工作区准备请求
|
||
type WorkspacePrepareRequest struct {
|
||
WorkDir string
|
||
SessionID FlexSessionID
|
||
ProjectID int64
|
||
TaskKey string
|
||
StageTitle string
|
||
ProjectName string
|
||
CustomerName string
|
||
DeviceType string
|
||
IntendedUse string
|
||
TargetMarket string
|
||
Backend string
|
||
TemplateContent string
|
||
Materials []MaterialItem
|
||
KnowledgeHits []KnowledgeHit
|
||
PriorDocs []PriorDocItem
|
||
UserContext string
|
||
ProjectContext string
|
||
AgentContext string
|
||
MemoryBankFiles map[string]string
|
||
CustomAgents []AgentSpec
|
||
WorkflowSteps []StepSpec
|
||
InitialFields map[string]string
|
||
Metadata map[string]string
|
||
}
|
||
|
||
// Workspace 表示已初始化的工作区句柄
|
||
type Workspace struct {
|
||
WorkDir string
|
||
Meta WorkspaceMetadata
|
||
Squad SquadConfig
|
||
Workflow WorkflowPlan
|
||
}
|
||
|
||
// WorkspaceEngine 工作区生命周期与沙箱构建引擎
|
||
type WorkspaceEngine struct{}
|
||
|
||
func NewWorkspaceEngine() *WorkspaceEngine {
|
||
return &WorkspaceEngine{}
|
||
}
|
||
|
||
// PrepareWorkspace 初始化自包含工作区目录
|
||
func (e *WorkspaceEngine) PrepareWorkspace(req WorkspacePrepareRequest) (*Workspace, error) {
|
||
workDir := req.WorkDir
|
||
if workDir == "" {
|
||
ts := time.Now().UnixNano()
|
||
workDir = filepath.Join("data", "dev", "workspaces", "bizdev", fmt.Sprintf("task_%s_%d", req.TaskKey, ts))
|
||
}
|
||
if err := os.MkdirAll(workDir, 0o755); err != nil {
|
||
return nil, fmt.Errorf("create workspace root dir: %w", err)
|
||
}
|
||
|
||
now := time.Now()
|
||
wsID := fmt.Sprintf("ws_%s_%d", req.TaskKey, now.UnixNano())
|
||
if req.Metadata != nil && req.Metadata["id"] != "" {
|
||
wsID = req.Metadata["id"]
|
||
}
|
||
|
||
// 1. 初始化 workspace.json
|
||
meta := WorkspaceMetadata{
|
||
ID: wsID,
|
||
SessionID: req.SessionID,
|
||
ProjectID: req.ProjectID,
|
||
TaskKey: req.TaskKey,
|
||
StageTitle: req.StageTitle,
|
||
ProjectName: req.ProjectName,
|
||
CustomerName: req.CustomerName,
|
||
DeviceType: req.DeviceType,
|
||
IntendedUse: req.IntendedUse,
|
||
TargetMarket: req.TargetMarket,
|
||
Status: "created",
|
||
Backend: req.Backend,
|
||
WorkDir: workDir,
|
||
CreatedAt: now,
|
||
UpdatedAt: now,
|
||
Metadata: req.Metadata,
|
||
}
|
||
if err := writeJSON(filepath.Join(workDir, "workspace.json"), meta); err != nil {
|
||
return nil, fmt.Errorf("write workspace.json: %w", err)
|
||
}
|
||
|
||
// 2. 初始化自适应小队与工作流规划
|
||
var squadCfg SquadConfig
|
||
var wf WorkflowPlan
|
||
tmplContent := req.TemplateContent
|
||
|
||
if len(req.WorkflowSteps) > 0 {
|
||
agents := req.CustomAgents
|
||
if len(agents) == 0 {
|
||
agents = DefaultSquadAgents(req.TaskKey)
|
||
}
|
||
squadCfg = SquadConfig{
|
||
SquadName: fmt.Sprintf("%s_squad", req.TaskKey),
|
||
Version: "1.0.0",
|
||
Agents: agents,
|
||
}
|
||
steps := req.WorkflowSteps
|
||
for idx := range steps {
|
||
if steps[idx].Status == "" {
|
||
steps[idx].Status = "pending"
|
||
}
|
||
}
|
||
wf = WorkflowPlan{
|
||
WorkflowID: fmt.Sprintf("wf_%s_%d", req.TaskKey, now.UnixNano()),
|
||
TaskKey: req.TaskKey,
|
||
Title: req.StageTitle,
|
||
Status: "pending",
|
||
CurrentStepIndex: 0,
|
||
Steps: steps,
|
||
}
|
||
} else {
|
||
planner := NewAdaptivePlanner(nil)
|
||
pRes, pErr := planner.Plan(PlannerRequest{
|
||
TaskKey: req.TaskKey,
|
||
Title: req.StageTitle,
|
||
ProjectName: req.ProjectName,
|
||
CustomerName: req.CustomerName,
|
||
DeviceType: req.DeviceType,
|
||
IntendedUse: req.IntendedUse,
|
||
TargetMarket: req.TargetMarket,
|
||
Materials: req.Materials,
|
||
KnowledgeHits: req.KnowledgeHits,
|
||
Fields: req.InitialFields,
|
||
CustomSteps: req.WorkflowSteps,
|
||
})
|
||
if pErr == nil && pRes != nil {
|
||
squadCfg = pRes.Squad
|
||
wf = pRes.Workflow
|
||
if strings.TrimSpace(tmplContent) == "" {
|
||
tmplContent = pRes.TemplateContent
|
||
}
|
||
} else {
|
||
agents := req.CustomAgents
|
||
if len(agents) == 0 {
|
||
agents = DefaultSquadAgents(req.TaskKey)
|
||
}
|
||
squadCfg = SquadConfig{
|
||
SquadName: fmt.Sprintf("%s_squad", req.TaskKey),
|
||
Version: "1.0.0",
|
||
Agents: agents,
|
||
}
|
||
steps := req.WorkflowSteps
|
||
if len(steps) == 0 {
|
||
steps = DefaultWorkflowSteps(req.TaskKey)
|
||
}
|
||
wf = WorkflowPlan{
|
||
WorkflowID: fmt.Sprintf("wf_%s_%d", req.TaskKey, now.UnixNano()),
|
||
Status: "pending",
|
||
CurrentStepIndex: 0,
|
||
Steps: steps,
|
||
}
|
||
}
|
||
}
|
||
|
||
if err := writeJSON(filepath.Join(workDir, "squad.json"), squadCfg); err != nil {
|
||
return nil, fmt.Errorf("write squad.json: %w", err)
|
||
}
|
||
if err := writeJSON(filepath.Join(workDir, "workflow.json"), wf); err != nil {
|
||
return nil, fmt.Errorf("write workflow.json: %w", err)
|
||
}
|
||
|
||
// 4. 写入自描述模板 template.md
|
||
if strings.TrimSpace(tmplContent) == "" {
|
||
var err error
|
||
tmplContent, err = template.GetDocTemplate(req.TaskKey)
|
||
if err != nil {
|
||
tmplContent = fmt.Sprintf("# %s\n\n## 1. 结论\n\n待确认\n\n## 2. 正文\n\n待确认\n", req.StageTitle)
|
||
}
|
||
}
|
||
vars := map[string]string{
|
||
"project_name": req.ProjectName,
|
||
"product_name": req.ProjectName,
|
||
"customer_name": req.CustomerName,
|
||
"device_type": req.DeviceType,
|
||
"intended_use": req.IntendedUse,
|
||
"target_market": req.TargetMarket,
|
||
"generated_at": now.Format("2006-01-02 15:04:05"),
|
||
"snapshot_id": wsID,
|
||
}
|
||
for k, v := range req.InitialFields {
|
||
vars[k] = v
|
||
}
|
||
renderedTmpl := template.RenderDoc(tmplContent, vars)
|
||
if err := os.WriteFile(filepath.Join(workDir, "template.md"), []byte(renderedTmpl), 0o644); err != nil {
|
||
return nil, fmt.Errorf("write template.md: %w", err)
|
||
}
|
||
|
||
// 5. 初始化 blackboard.json
|
||
bb := BlackboardState{
|
||
Facts: make([]FactItem, 0),
|
||
Gaps: make([]GapItem, 0),
|
||
Implications: make([]ImplicationItem, 0),
|
||
Assumptions: make([]AssumptionItem, 0),
|
||
Fields: make(map[string]string),
|
||
UpdatedAt: now,
|
||
}
|
||
if req.CustomerName != "" {
|
||
bb.Fields["customer_name"] = req.CustomerName
|
||
bb.Facts = append(bb.Facts, FactItem{
|
||
ID: "fact_01",
|
||
Claim: "客户名称为 " + req.CustomerName,
|
||
Source: "用户输入",
|
||
Verified: true,
|
||
})
|
||
}
|
||
if req.ProjectName != "" {
|
||
bb.Fields["project_name"] = req.ProjectName
|
||
}
|
||
if req.DeviceType != "" {
|
||
bb.Fields["device_type"] = req.DeviceType
|
||
}
|
||
if req.IntendedUse != "" {
|
||
bb.Fields["intended_use"] = req.IntendedUse
|
||
}
|
||
if req.TargetMarket != "" {
|
||
bb.Fields["target_market"] = req.TargetMarket
|
||
}
|
||
for k, v := range req.InitialFields {
|
||
bb.Fields[k] = v
|
||
}
|
||
if err := writeJSON(filepath.Join(workDir, "blackboard.json"), bb); err != nil {
|
||
return nil, fmt.Errorf("write blackboard.json: %w", err)
|
||
}
|
||
|
||
// 6. 构造自描述上下文体系 context/
|
||
if err := e.buildContextSystem(workDir, req); err != nil {
|
||
return nil, fmt.Errorf("build context system: %w", err)
|
||
}
|
||
|
||
// 7. 创建子工作区 tasks/<sub_dir> 并注入 TASK.md 与 AGENTS.md
|
||
if err := e.buildSubWorkspaces(workDir, wf, squadCfg, renderedTmpl); err != nil {
|
||
return nil, fmt.Errorf("build sub workspaces: %w", err)
|
||
}
|
||
|
||
// 8. 创建 artifacts/ 与基础日志
|
||
_ = os.MkdirAll(filepath.Join(workDir, "artifacts"), 0o755)
|
||
logEntry := fmt.Sprintf("[%s] 工作区创建完成: ID=%s, Task=%s\n", now.Format(time.RFC3339), wsID, req.TaskKey)
|
||
_ = os.WriteFile(filepath.Join(workDir, "execution.log"), []byte(logEntry), 0o644)
|
||
|
||
return &Workspace{
|
||
WorkDir: workDir,
|
||
Meta: meta,
|
||
Squad: squadCfg,
|
||
Workflow: wf,
|
||
}, nil
|
||
}
|
||
|
||
// buildContextSystem 构建具有权威度说明的 context/ 体系
|
||
func (e *WorkspaceEngine) buildContextSystem(workDir string, req WorkspacePrepareRequest) error {
|
||
ctxDir := filepath.Join(workDir, "context")
|
||
_ = os.MkdirAll(filepath.Join(ctxDir, "materials_text"), 0o755)
|
||
_ = os.MkdirAll(filepath.Join(ctxDir, "knowledge"), 0o755)
|
||
_ = os.MkdirAll(filepath.Join(ctxDir, "prior_docs"), 0o755)
|
||
_ = os.MkdirAll(filepath.Join(ctxDir, "memory"), 0o755)
|
||
|
||
var contextItems []ContextItem
|
||
|
||
// 1. user.md
|
||
userContent := req.UserContext
|
||
if strings.TrimSpace(userContent) == "" {
|
||
userContent = fmt.Sprintf("# 用户角色与偏好\n\n- 用户角色: 商务与技术负责人\n- 决策风格: 严谨可追溯,注重四段式论据\n- 授权边界: 预算与商业承诺必须经由用户拍板\n")
|
||
}
|
||
_ = os.WriteFile(filepath.Join(ctxDir, "user.md"), []byte(userContent), 0o644)
|
||
contextItems = append(contextItems, ContextItem{
|
||
File: "context/user.md",
|
||
Description: "操作用户的业务角色、决策授权边界与协同偏好",
|
||
AuthorityLevel: "authoritative",
|
||
Guidance: "必须尊重用户设定的目标市场与边界,用户未决事项一律列入待确认清单,严禁越权替用户拍板",
|
||
})
|
||
|
||
// 2. project.md
|
||
projContent := req.ProjectContext
|
||
if strings.TrimSpace(projContent) == "" {
|
||
projContent = fmt.Sprintf("# 项目全局基线\n\n- 项目名称: %s\n- 客户名称: %s\n- 产品类型: %s\n- 预期用途: %s\n- 目标市场: %s\n",
|
||
req.ProjectName, req.CustomerName, req.DeviceType, req.IntendedUse, req.TargetMarket)
|
||
}
|
||
_ = os.WriteFile(filepath.Join(ctxDir, "project.md"), []byte(projContent), 0o644)
|
||
contextItems = append(contextItems, ContextItem{
|
||
File: "context/project.md",
|
||
Description: "项目立项章程与全局事实基线",
|
||
AuthorityLevel: "authoritative",
|
||
Guidance: "本项目核心前提。所有后续分析论证不得推翻已锁定的项目名称与核心预期用途",
|
||
})
|
||
|
||
// 3. agent.md
|
||
agentContent := req.AgentContext
|
||
if strings.TrimSpace(agentContent) == "" {
|
||
agentContent = "# 智能体小队协同通用规范\n\n- 事实闭环: 严禁凭空捏造未提供之参数或法规\n- 引用规范: 引用必须标注出处〔材料:...〕或〔法规:...〕\n- 负向红线: 禁止敷衍写作;各章节须实质展开,不得以篇幅或省略写法跳过应写内容\n"
|
||
}
|
||
_ = os.WriteFile(filepath.Join(ctxDir, "agent.md"), []byte(agentContent), 0o644)
|
||
contextItems = append(contextItems, ContextItem{
|
||
File: "context/agent.md",
|
||
Description: "智能体通用协同纪律与引用要求",
|
||
AuthorityLevel: "authoritative",
|
||
Guidance: "所有小队角色必须严格执行的专业守则与写作禁令",
|
||
})
|
||
|
||
// 4. materials_text/
|
||
if len(req.Materials) > 0 {
|
||
var allBuilder strings.Builder
|
||
for _, m := range req.Materials {
|
||
name := filepath.Base(m.Filename)
|
||
if name == "" || name == "." {
|
||
name = "material.txt"
|
||
}
|
||
|
||
// 自动提取纯文本并注入页码锚点 <!-- page N -->
|
||
textContent, err := ExtractTextWithAnchors(name, m.Content)
|
||
if err != nil || strings.TrimSpace(textContent) == "" {
|
||
textContent = string(m.Content)
|
||
}
|
||
textBytes := []byte(textContent)
|
||
|
||
p := filepath.Join(ctxDir, "materials_text", name)
|
||
_ = os.WriteFile(p, textBytes, 0o644)
|
||
// 若原文件扩展名不是 .txt / .md,同时生成一份 .txt 方便智能体直接阅读
|
||
if !strings.HasSuffix(name, ".txt") && !strings.HasSuffix(name, ".md") {
|
||
_ = os.WriteFile(filepath.Join(ctxDir, "materials_text", name+".txt"), textBytes, 0o644)
|
||
}
|
||
|
||
// Also write to root materials_text/ for backward compatibility
|
||
rootMatDir := filepath.Join(workDir, "materials_text")
|
||
_ = os.MkdirAll(rootMatDir, 0o755)
|
||
_ = os.WriteFile(filepath.Join(rootMatDir, name), textBytes, 0o644)
|
||
if !strings.HasSuffix(name, ".txt") && !strings.HasSuffix(name, ".md") {
|
||
_ = os.WriteFile(filepath.Join(rootMatDir, name+".txt"), textBytes, 0o644)
|
||
}
|
||
|
||
allBuilder.WriteString(fmt.Sprintf("\n\n=== 文件: %s ===\n\n", name))
|
||
allBuilder.WriteString(textContent)
|
||
|
||
contextItems = append(contextItems, ContextItem{
|
||
File: filepath.ToSlash(filepath.Join("context", "materials_text", name)),
|
||
Description: fmt.Sprintf("用户上传的输入材料文本: %s", name),
|
||
AuthorityLevel: "primary_evidence",
|
||
Guidance: "核心原始证据。提取参数需标明出处;材料内部矛盾需在矛盾表中并列呈现",
|
||
})
|
||
}
|
||
_ = os.WriteFile(filepath.Join(ctxDir, "materials_text", "_all.txt"), []byte(allBuilder.String()), 0o644)
|
||
_ = os.WriteFile(filepath.Join(ctxDir, "materials_text", "README.md"), []byte("# 原始材料清单\n\n已汇总在 `context/materials_text/_all.txt`,亦可单独查阅各文件。\n"), 0o644)
|
||
} else {
|
||
_ = os.WriteFile(filepath.Join(ctxDir, "materials_text", "README.md"), []byte("# 原始材料说明\n\n本项目当前未提供独立的附加文本材料,所有事实依据以 `context/project.md`(项目全局基线)、`context/user.md`(用户要求与偏好)以及 `context/knowledge/hits.md`(法规标准知识库)为准。\n"), 0o644)
|
||
}
|
||
|
||
// 5. knowledge/
|
||
_ = os.WriteFile(filepath.Join(ctxDir, "knowledge", "WEB_CROSSCHECK.md"), []byte("# 法规检索与交叉核对原则\n\n1. 知识库条目(hits.md)为首要法规事实依据。\n2. 如需查询外部信息,以国家药监局官方分类目录为主。\n"), 0o644)
|
||
if len(req.KnowledgeHits) > 0 {
|
||
var kbBuilder strings.Builder
|
||
kbBuilder.WriteString("# 权威法规与技术标准引用库 (Regulatory Knowledge Hits)\n\n")
|
||
kbBuilder.WriteString("以下为针对本项目研发与注册需求命中的权威法规、国家标准(GB)与行业标准(YY)条款。\n")
|
||
kbBuilder.WriteString("在输出交付物时,请结合上下文使用,并显式标注【引用标记 (CiteKey)】标明出处。\n\n")
|
||
for _, hit := range req.KnowledgeHits {
|
||
kbBuilder.WriteString(fmt.Sprintf("### [%s] %s %s\n\n", strings.ToUpper(hit.Category), hit.DocCode, hit.Title))
|
||
if hit.HeadingPath != "" {
|
||
kbBuilder.WriteString(fmt.Sprintf("- **章节路径**: %s\n", hit.HeadingPath))
|
||
}
|
||
if hit.CiteKey != "" {
|
||
kbBuilder.WriteString(fmt.Sprintf("- **引用标记 (CiteKey)**: `%s`\n", hit.CiteKey))
|
||
}
|
||
kbBuilder.WriteString(fmt.Sprintf("- **来源**: %s\n\n", hit.Source))
|
||
kbBuilder.WriteString(hit.Content + "\n\n---\n\n")
|
||
}
|
||
_ = os.WriteFile(filepath.Join(ctxDir, "knowledge", "hits.md"), []byte(kbBuilder.String()), 0o644)
|
||
contextItems = append(contextItems, ContextItem{
|
||
File: "context/knowledge/hits.md",
|
||
Description: "法规知识库中命中的医疗器械分类、国家标准与指导原则",
|
||
AuthorityLevel: "reference_standard",
|
||
Guidance: "合规与技术论据源。引用必须标注标准号与引用标记 (CiteKey);涉及审批结论需标注'需向药监局审评中心确认'",
|
||
})
|
||
}
|
||
|
||
// 6. prior_docs/
|
||
if len(req.PriorDocs) > 0 {
|
||
var indexBuilder strings.Builder
|
||
indexBuilder.WriteString("# 前序阶段交付物索引\n\n")
|
||
for _, doc := range req.PriorDocs {
|
||
filename := doc.Filename
|
||
if filename == "" {
|
||
filename = doc.DocKey + ".md"
|
||
}
|
||
p := filepath.Join(ctxDir, "prior_docs", filename)
|
||
_ = os.WriteFile(p, []byte(doc.Content), 0o644)
|
||
indexBuilder.WriteString(fmt.Sprintf("- [%s] `%s`: %s (阶段: %s)\n", doc.DocKey, filename, doc.Title, doc.Stage))
|
||
|
||
contextItems = append(contextItems, ContextItem{
|
||
File: filepath.ToSlash(filepath.Join("context", "prior_docs", filename)),
|
||
Description: fmt.Sprintf("前序已归档文档: %s (%s)", doc.Title, doc.DocKey),
|
||
AuthorityLevel: "primary_evidence",
|
||
Guidance: "前序阶段已批准的结论基准。本阶段内容应与前序结论保持自洽",
|
||
})
|
||
}
|
||
_ = os.WriteFile(filepath.Join(ctxDir, "prior_docs", "index.md"), []byte(indexBuilder.String()), 0o644)
|
||
} else {
|
||
_ = os.WriteFile(filepath.Join(ctxDir, "prior_docs", "README.md"), []byte("# 前序交付物说明\n\n本项目当前处于启动阶段,暂无已归档的前序阶段交付物。\n"), 0o644)
|
||
}
|
||
|
||
if req.TemplateContent != "" {
|
||
_ = os.WriteFile(filepath.Join(ctxDir, "template.md"), []byte(req.TemplateContent), 0o644)
|
||
}
|
||
|
||
// 7. memory/
|
||
if len(req.MemoryBankFiles) > 0 {
|
||
for k, v := range req.MemoryBankFiles {
|
||
p := filepath.Join(ctxDir, "memory", k)
|
||
_ = os.WriteFile(p, []byte(v), 0o644)
|
||
}
|
||
} else {
|
||
// 默认四大核心资产骨架
|
||
_ = os.WriteFile(filepath.Join(ctxDir, "memory", "projectbrief.md"), []byte(fmt.Sprintf("# 项目章程\n- 项目: %s\n- 客户: %s\n", req.ProjectName, req.CustomerName)), 0o644)
|
||
_ = os.WriteFile(filepath.Join(ctxDir, "memory", "activeContext.md"), []byte(fmt.Sprintf("# 当前焦点\n- 阶段: %s\n- 时间: %s\n", req.TaskKey, time.Now().Format("2006-01-02 15:04"))), 0o644)
|
||
_ = os.WriteFile(filepath.Join(ctxDir, "memory", "systemPatterns.md"), []byte("# 规范与模式\n- 遵循四段式结构与严格角色边界\n- 单一真实源原则\n"), 0o644)
|
||
_ = os.WriteFile(filepath.Join(ctxDir, "memory", "progress.md"), []byte(fmt.Sprintf("# 进度\n- 当前正在进行: %s\n", req.StageTitle)), 0o644)
|
||
}
|
||
contextItems = append(contextItems, ContextItem{
|
||
File: "context/memory/",
|
||
Description: "长期项目记忆银行 (ActiveContext, Progress, ProjectBrief, SystemPatterns)",
|
||
AuthorityLevel: "supplementary",
|
||
Guidance: "协同上下文背景参考,防止上下文遗忘",
|
||
})
|
||
|
||
// 8. 写入 context/index.json
|
||
ctxIndex := ContextIndex{Items: contextItems}
|
||
if err := writeJSON(filepath.Join(ctxDir, "index.json"), ctxIndex); err != nil {
|
||
return err
|
||
}
|
||
|
||
// 9. 写入 context/README.md 指引
|
||
readmeContent := `# 上下文使用指引 (Context Reading Guide)
|
||
|
||
本目录为当前智能体任务的自包含上下文沙箱。所有智能体专员在执行任务时,必须按照以下权威度等级(Authority Level)采纳信息:
|
||
|
||
| 权威度级别 | 适用文件类别 | 采信与处理规则 |
|
||
|---|---|---|
|
||
| **authoritative** | ` + "`user.md`" + `, ` + "`project.md`" + `, ` + "`agent.md`" + ` | **最高权威**。必须绝对遵循;严禁越权替用户拍板,未决项列入待确认清单 |
|
||
| **primary_evidence** | ` + "`materials_text/*`" + `, ` + "`prior_docs/*`" + ` | **原始事实证据**。引用必须标明出处条文;若材料间存在矛盾,并列呈现 |
|
||
| **reference_standard** | ` + "`knowledge/hits.md`" + ` | **标准与法规依据**。必须指明法规/标准代码;涉及监管裁定标注需窗口核实 |
|
||
| **supplementary** | ` + "`memory/*`" + ` | **补充记忆背景**。仅供背景了解,不作为唯一支撑证据 |
|
||
|
||
---
|
||
`
|
||
return os.WriteFile(filepath.Join(ctxDir, "README.md"), []byte(readmeContent), 0o644)
|
||
}
|
||
|
||
// buildSubWorkspaces 为 workflow 中的各个子任务构建独立沙箱
|
||
func (e *WorkspaceEngine) buildSubWorkspaces(workDir string, wf WorkflowPlan, squadCfg SquadConfig, renderedTmpl string) error {
|
||
agentMap := make(map[string]AgentSpec)
|
||
for _, a := range squadCfg.Agents {
|
||
agentMap[a.Role] = a
|
||
}
|
||
|
||
for _, step := range wf.Steps {
|
||
subDir := filepath.Join(workDir, filepath.FromSlash(step.SubWorkspace))
|
||
if err := os.MkdirAll(subDir, 0o755); err != nil {
|
||
return err
|
||
}
|
||
|
||
// 1. 生成 AGENTS.md
|
||
var agentsBuilder strings.Builder
|
||
inst, err := template.GetAgentInstruction(step.Role)
|
||
if err == nil {
|
||
agentsBuilder.WriteString(inst)
|
||
} else {
|
||
spec := agentMap[step.Role]
|
||
agentsBuilder.WriteString(fmt.Sprintf("# 角色: %s (%s)\n\n", spec.Name, spec.Role))
|
||
if len(spec.Responsibilities) > 0 {
|
||
agentsBuilder.WriteString("## 核心职责\n")
|
||
for _, r := range spec.Responsibilities {
|
||
agentsBuilder.WriteString(fmt.Sprintf("- %s\n", r))
|
||
}
|
||
agentsBuilder.WriteString("\n")
|
||
}
|
||
if len(spec.ForbiddenActions) > 0 {
|
||
agentsBuilder.WriteString("## 严禁行为(红线)\n")
|
||
for _, f := range spec.ForbiddenActions {
|
||
agentsBuilder.WriteString(fmt.Sprintf("- ❌ %s\n", f))
|
||
}
|
||
agentsBuilder.WriteString("\n")
|
||
}
|
||
}
|
||
_ = os.WriteFile(filepath.Join(subDir, "AGENTS.md"), []byte(agentsBuilder.String()), 0o644)
|
||
_ = os.WriteFile(filepath.Join(subDir, "INSTRUCTIONS.md"), []byte(agentsBuilder.String()), 0o644)
|
||
|
||
// 2. 生成 TASK.md
|
||
deliverableName := "output.md"
|
||
if step.Mode == "plan" || step.Mode == "replan" {
|
||
deliverableName = "output_plan.md"
|
||
} else if step.Mode == "review_plan" {
|
||
deliverableName = "review_notes.md"
|
||
}
|
||
|
||
var taskBuilder strings.Builder
|
||
taskBuilder.WriteString(fmt.Sprintf("# 任务执行指令: %s\n\n", step.Title))
|
||
taskBuilder.WriteString(fmt.Sprintf("- **步骤 ID**: `%s`\n", step.ID))
|
||
taskBuilder.WriteString(fmt.Sprintf("- **分配角色**: `%s`\n", step.Role))
|
||
taskBuilder.WriteString(fmt.Sprintf("- **执行模式**: `%s`\n", step.Mode))
|
||
taskBuilder.WriteString(fmt.Sprintf("- **交付产物**: `%s`\n\n", deliverableName))
|
||
|
||
taskBuilder.WriteString("## 环境与输入导航\n")
|
||
taskBuilder.WriteString("请仔细查阅上级目录资源:\n")
|
||
taskBuilder.WriteString("- `../../context/index.json` 及 `../../context/README.md`:阅读上下文指引\n")
|
||
taskBuilder.WriteString("- `../../template.md`:自描述交付模板(嵌入四段式与格式约束)\n")
|
||
taskBuilder.WriteString("- `../../blackboard.json`:前序专员抽取的结构化事实与缺口\n")
|
||
taskBuilder.WriteString("- `../../context/project.md`:项目全局立项基线与产品定义\n")
|
||
taskBuilder.WriteString("- `../../context/user.md`:用户个性化工作偏好与决策约束\n")
|
||
taskBuilder.WriteString("- `../../context/materials_text/README.md`:原始材料全文或基线说明\n")
|
||
taskBuilder.WriteString("- `../../context/knowledge/hits.md`:法规与标准命中快照 (若存在)\n\n")
|
||
|
||
if len(step.Needs) > 0 {
|
||
taskBuilder.WriteString("## 前序依赖产出\n")
|
||
for _, n := range step.Needs {
|
||
taskBuilder.WriteString(fmt.Sprintf("- `../../%s`\n", filepath.ToSlash(n)))
|
||
}
|
||
taskBuilder.WriteString("\n")
|
||
}
|
||
|
||
if len(step.Accept) > 0 {
|
||
taskBuilder.WriteString("## 验收标准(Checklist)\n")
|
||
for _, a := range step.Accept {
|
||
taskBuilder.WriteString(fmt.Sprintf("- [ ] %s\n", a))
|
||
}
|
||
taskBuilder.WriteString("\n")
|
||
}
|
||
|
||
taskBuilder.WriteString("## 交付契约与写作纪律\n")
|
||
taskBuilder.WriteString(fmt.Sprintf("请在当前目录下生成完整交付物 `%s`:\n", deliverableName))
|
||
taskBuilder.WriteString("1. 严格四段式展开(结论句 → 表格/列表 → 解读 → 支撑出处)。\n")
|
||
taskBuilder.WriteString("2. 各章节必须实质展开;禁止以篇幅借口或省略写法跳过应写细节(自检时勿复述禁用原词)。\n")
|
||
taskBuilder.WriteString("3. 关键事实必须标注出处,未知项列入待确认清单。\n")
|
||
if step.Role == "organizer" {
|
||
taskBuilder.WriteString("4. 从材料提取的客观字段,请在当前工作目录下输出 `fields.json`。\n")
|
||
}
|
||
|
||
_ = os.WriteFile(filepath.Join(subDir, "TASK.md"), []byte(taskBuilder.String()), 0o644)
|
||
|
||
// 3. 写入 input_refs.json
|
||
inputRefs := map[string]any{
|
||
"step_id": step.ID,
|
||
"role": step.Role,
|
||
"needs": step.Needs,
|
||
"target_file": deliverableName,
|
||
}
|
||
_ = writeJSON(filepath.Join(subDir, "input_refs.json"), inputRefs)
|
||
|
||
// 4. 创建便捷软链接,使得当前目录下无论是读 template.md、context/ 还是 ../../ 均可访问
|
||
_ = os.Symlink(filepath.Join("..", "..", "template.md"), filepath.Join(subDir, "template.md"))
|
||
_ = os.Symlink(filepath.Join("..", "..", "context"), filepath.Join(subDir, "context"))
|
||
_ = os.Symlink(filepath.Join("..", "..", "blackboard.json"), filepath.Join(subDir, "blackboard.json"))
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// DefaultSquadAgents 根据任务类型返回默认小队成员编制
|
||
func DefaultSquadAgents(taskKey string) []AgentSpec {
|
||
switch taskKey {
|
||
case "intake":
|
||
return []AgentSpec{
|
||
{
|
||
Role: "organizer",
|
||
Name: "材料整理专员",
|
||
Model: "claude-3-7-sonnet",
|
||
Tools: []string{"read", "write", "edit"},
|
||
BashPermission: "deny",
|
||
Responsibilities: []string{"材料事实抽取", "缺口识别", "字段结构化回填"},
|
||
ForbiddenActions: []string{"严禁测算价格", "严禁出具法规终结裁定", "严禁撰写正文终稿"},
|
||
},
|
||
{
|
||
Role: "writer",
|
||
Name: "文档撰写专员",
|
||
Model: "claude-3-7-sonnet",
|
||
Tools: []string{"read", "write", "edit"},
|
||
BashPermission: "deny",
|
||
Responsibilities: []string{"严格四段式成文", "依据材料出处引用", "模板各章节写满"},
|
||
ForbiddenActions: []string{"严禁以省略写法跳过应写章节", "严禁随意篡改前序事实"},
|
||
},
|
||
{
|
||
Role: "leader",
|
||
Name: "商务对接负责人",
|
||
Model: "claude-3-7-sonnet",
|
||
Tools: []string{"read", "write", "edit"},
|
||
BashPermission: "deny",
|
||
Responsibilities: []string{"计划审核", "正文终审", "待确认清单汇总", "对外汇报"},
|
||
ForbiddenActions: []string{"严禁代用户做商业拍板", "严禁直接交付未审核初稿"},
|
||
},
|
||
}
|
||
case "research":
|
||
return []AgentSpec{
|
||
{
|
||
Role: "searcher",
|
||
Name: "资料检索专员",
|
||
Model: "claude-3-7-sonnet",
|
||
Tools: []string{"read", "write", "edit"},
|
||
BashPermission: "deny",
|
||
Responsibilities: []string{"公开资料检索", "法规标准核对", "出处与日期标注"},
|
||
ForbiddenActions: []string{"严禁编造URL与标准号", "未检索到须标明尝试路径"},
|
||
},
|
||
{
|
||
Role: "market",
|
||
Name: "市场与竞品分析师",
|
||
Model: "claude-3-7-sonnet",
|
||
Tools: []string{"read", "write", "edit"},
|
||
BashPermission: "deny",
|
||
Responsibilities: []string{"竞品同维度对比", "市场规模测算", "机会与风险分析"},
|
||
ForbiddenActions: []string{"严禁编造市场份额与价格", "必须采用统一对比维度"},
|
||
},
|
||
{
|
||
Role: "regulatory",
|
||
Name: "医疗器械法规专员",
|
||
Model: "claude-3-7-sonnet",
|
||
Tools: []string{"read", "write", "edit"},
|
||
BashPermission: "deny",
|
||
Responsibilities: []string{"器械分类初筛", "注册路径比对", "关键标准提炼"},
|
||
ForbiddenActions: []string{"严禁代做行政终局裁定", "必须标注需向窗口确认"},
|
||
},
|
||
{
|
||
Role: "writer",
|
||
Name: "文档撰写专员",
|
||
Model: "claude-3-7-sonnet",
|
||
Tools: []string{"read", "write", "edit"},
|
||
BashPermission: "deny",
|
||
Responsibilities: []string{"按计划合成调研报告", "全面四段式展开"},
|
||
ForbiddenActions: []string{"严禁以省略写法跳过应写章节"},
|
||
},
|
||
{
|
||
Role: "leader",
|
||
Name: "商务对接负责人",
|
||
Model: "claude-3-7-sonnet",
|
||
Tools: []string{"read", "write", "edit"},
|
||
BashPermission: "deny",
|
||
Responsibilities: []string{"审核撰写计划", "终审报告", "质量把关"},
|
||
ForbiddenActions: []string{"严禁代用户做商业拍板"},
|
||
},
|
||
}
|
||
case "system_reqs":
|
||
return []AgentSpec{
|
||
{
|
||
Role: "organizer",
|
||
Name: "需求梳理专员",
|
||
Model: "claude-3-7-sonnet",
|
||
Tools: []string{"read", "write", "edit"},
|
||
BashPermission: "deny",
|
||
Responsibilities: []string{"用户需求抽取", "产品定位基线对齐", "UR需求池建立"},
|
||
ForbiddenActions: []string{"严禁随意篡改原始用户需求"},
|
||
},
|
||
{
|
||
Role: "engineer",
|
||
Name: "系统工程架构师",
|
||
Model: "claude-3-7-sonnet",
|
||
Tools: []string{"read", "write", "edit"},
|
||
BashPermission: "deny",
|
||
Responsibilities: []string{"系统需求分解", "验证方式定义", "接口定义", "requirements.json 导出"},
|
||
ForbiddenActions: []string{"严禁遗漏验证方式", "严禁需求编号重复"},
|
||
},
|
||
{
|
||
Role: "regulatory",
|
||
Name: "法规合规审查专员",
|
||
Model: "claude-3-7-sonnet",
|
||
Tools: []string{"read", "write", "edit"},
|
||
BashPermission: "deny",
|
||
Responsibilities: []string{"医疗器械强标比对", "适用性与理由论证"},
|
||
ForbiddenActions: []string{"严禁引用虚构标准号"},
|
||
},
|
||
{
|
||
Role: "writer",
|
||
Name: "系统需求文档编制专员",
|
||
Model: "claude-3-7-sonnet",
|
||
Tools: []string{"read", "write", "edit"},
|
||
BashPermission: "deny",
|
||
Responsibilities: []string{"严格按 system_reqs.md 模板成文", "正文结构完整", "填满追溯表"},
|
||
ForbiddenActions: []string{"严禁残留占位符", "严禁以省略写法跳过应写章节"},
|
||
},
|
||
{
|
||
Role: "leader",
|
||
Name: "智能产品经理主管",
|
||
Model: "claude-3-7-sonnet",
|
||
Tools: []string{"read", "write", "edit"},
|
||
BashPermission: "deny",
|
||
Responsibilities: []string{"系统需求规格书终审", "追溯一致性把关", "质量门禁验收"},
|
||
ForbiddenActions: []string{"严禁放行未覆盖验证方式的需求"},
|
||
},
|
||
}
|
||
case "test_manager":
|
||
return []AgentSpec{
|
||
{
|
||
Role: "organizer",
|
||
Name: "需求追溯分析员",
|
||
Model: "claude-3-7-sonnet",
|
||
Tools: []string{"read", "write", "edit"},
|
||
BashPermission: "deny",
|
||
Responsibilities: []string{"上游系统需求解析", "需求全集锁定", "追溯关系初始化"},
|
||
ForbiddenActions: []string{"严禁遗漏任一条上游需求"},
|
||
},
|
||
{
|
||
Role: "engineer",
|
||
Name: "系统测试工程师",
|
||
Model: "claude-3-7-sonnet",
|
||
Tools: []string{"read", "write", "edit"},
|
||
BashPermission: "deny",
|
||
Responsibilities: []string{"系统验证用例设计", "判据与接受准则", "test_cases.json 导出"},
|
||
ForbiddenActions: []string{"严禁测试方法泛化为仅写标准号", "严禁无判定准则"},
|
||
},
|
||
{
|
||
Role: "regulatory",
|
||
Name: "试验标准审查员",
|
||
Model: "claude-3-7-sonnet",
|
||
Tools: []string{"read", "write", "edit"},
|
||
BashPermission: "deny",
|
||
Responsibilities: []string{"检验方法符合性评估", "检测设备与环境要求审查"},
|
||
ForbiddenActions: []string{"严禁使用过期作废标准"},
|
||
},
|
||
{
|
||
Role: "writer",
|
||
Name: "系统测试方案编制专员",
|
||
Model: "claude-3-7-sonnet",
|
||
Tools: []string{"read", "write", "edit"},
|
||
BashPermission: "deny",
|
||
Responsibilities: []string{"严格按 test_manager.md 模板成文", "用例主表翔实完整"},
|
||
ForbiddenActions: []string{"严禁残留占位符", "严禁以省略写法跳过应写章节"},
|
||
},
|
||
{
|
||
Role: "leader",
|
||
Name: "智能测试经理主管",
|
||
Model: "claude-3-7-sonnet",
|
||
Tools: []string{"read", "write", "edit"},
|
||
BashPermission: "deny",
|
||
Responsibilities: []string{"系统验证方案终审", "全覆盖无静默遗漏门禁把关", "质量签署"},
|
||
ForbiddenActions: []string{"严禁放行覆盖率不足 100% 的方案"},
|
||
},
|
||
}
|
||
default:
|
||
return []AgentSpec{
|
||
{
|
||
Role: "organizer",
|
||
Name: "材料整理专员",
|
||
Model: "claude-3-7-sonnet",
|
||
Tools: []string{"read", "write", "edit"},
|
||
BashPermission: "deny",
|
||
Responsibilities: []string{"事实整理与基线对齐"},
|
||
},
|
||
{
|
||
Role: "writer",
|
||
Name: "文档撰写专员",
|
||
Model: "claude-3-7-sonnet",
|
||
Tools: []string{"read", "write", "edit"},
|
||
BashPermission: "deny",
|
||
Responsibilities: []string{"正文撰写"},
|
||
},
|
||
{
|
||
Role: "leader",
|
||
Name: "商务对接负责人",
|
||
Model: "claude-3-7-sonnet",
|
||
Tools: []string{"read", "write", "edit"},
|
||
BashPermission: "deny",
|
||
Responsibilities: []string{"终审与把关"},
|
||
},
|
||
}
|
||
}
|
||
}
|
||
|
||
// DefaultWorkflowSteps 根据任务类型返回最佳实践编排步骤
|
||
func DefaultWorkflowSteps(taskKey string) []StepSpec {
|
||
switch taskKey {
|
||
case "intake":
|
||
return []StepSpec{
|
||
{
|
||
ID: "step_01_organize",
|
||
Role: "organizer",
|
||
Title: "抽取材料事实并回填字段",
|
||
SubWorkspace: "tasks/01_organize",
|
||
Mode: "execute",
|
||
Accept: []string{
|
||
"抽取材料中的客户身份与产品定义",
|
||
"回填 fields.json 结构化数据",
|
||
"向 blackboard 写入 facts 与 gaps",
|
||
},
|
||
},
|
||
{
|
||
ID: "step_02_draft",
|
||
Role: "writer",
|
||
Title: "撰写用户初始需求收集表初稿",
|
||
SubWorkspace: "tasks/02_draft",
|
||
Mode: "execute",
|
||
Needs: []string{"tasks/01_organize/output.md"},
|
||
Accept: []string{
|
||
"按 template.md 各章节严格四段式展开",
|
||
"正文翔实,各节须实质展开",
|
||
"文末集中附录字段快照与待确认清单",
|
||
},
|
||
},
|
||
{
|
||
ID: "step_03_review",
|
||
Role: "leader",
|
||
Title: "审核并定稿交付",
|
||
SubWorkspace: "tasks/03_review",
|
||
Mode: "execute",
|
||
Needs: []string{"tasks/02_draft/output.md"},
|
||
Accept: []string{
|
||
"核验全文逻辑一致性与出处标注",
|
||
"汇编待确认清单并给出下一步建议",
|
||
"交付终稿 output.md",
|
||
},
|
||
},
|
||
}
|
||
case "research":
|
||
return []StepSpec{
|
||
{
|
||
ID: "step_01_search",
|
||
Role: "searcher",
|
||
Title: "公开资料与标准检索",
|
||
SubWorkspace: "tasks/01_search",
|
||
Mode: "execute",
|
||
Parallel: true,
|
||
Accept: []string{
|
||
"检索纪要附带明确出处或未检索到说明",
|
||
"对本项目含义逐条阐述",
|
||
},
|
||
},
|
||
{
|
||
ID: "step_02_market",
|
||
Role: "market",
|
||
Title: "市场格局与竞品同维度对比",
|
||
SubWorkspace: "tasks/02_market",
|
||
Mode: "execute",
|
||
Parallel: true,
|
||
Accept: []string{
|
||
"多维度竞品对比表",
|
||
"机会与风险对称分析",
|
||
},
|
||
},
|
||
{
|
||
ID: "step_03_regulatory",
|
||
Role: "regulatory",
|
||
Title: "医疗器械法规与指导原则初筛",
|
||
SubWorkspace: "tasks/03_regulatory",
|
||
Mode: "execute",
|
||
Parallel: true,
|
||
Accept: []string{
|
||
"分类依据条目与建议类别",
|
||
"明确标注需向审评窗口确认",
|
||
},
|
||
},
|
||
{
|
||
ID: "step_04_plan",
|
||
Role: "writer",
|
||
Title: "撰写调研报告段落计划",
|
||
SubWorkspace: "tasks/04_plan",
|
||
Mode: "plan",
|
||
Needs: []string{
|
||
"tasks/01_search/output.md",
|
||
"tasks/02_market/output.md",
|
||
"tasks/03_regulatory/output.md",
|
||
},
|
||
Accept: []string{"各章节论点与引用规划"},
|
||
},
|
||
{
|
||
ID: "step_05_review_plan",
|
||
Role: "leader",
|
||
Title: "审核调研报告撰写计划",
|
||
SubWorkspace: "tasks/05_review_plan",
|
||
Mode: "review_plan",
|
||
Needs: []string{"tasks/04_plan/output_plan.md"},
|
||
Accept: []string{"出具审核意见 review_notes.md"},
|
||
},
|
||
{
|
||
ID: "step_06_draft",
|
||
Role: "writer",
|
||
Title: "按计划合成调研报告正文",
|
||
SubWorkspace: "tasks/06_draft",
|
||
Mode: "execute",
|
||
Needs: []string{
|
||
"tasks/04_plan/output_plan.md",
|
||
"tasks/05_review_plan/review_notes.md",
|
||
},
|
||
Accept: []string{"四段式完整正文,无省略套话"},
|
||
},
|
||
{
|
||
ID: "step_07_review",
|
||
Role: "leader",
|
||
Title: "终审并定稿",
|
||
SubWorkspace: "tasks/07_review",
|
||
Mode: "execute",
|
||
Needs: []string{"tasks/06_draft/output.md"},
|
||
Accept: []string{"质量把关并通过交付"},
|
||
},
|
||
}
|
||
case "feasibility_reg":
|
||
return []StepSpec{
|
||
{
|
||
ID: "step_01_regulatory",
|
||
Role: "regulatory",
|
||
Title: "分析法规分类与标准条款",
|
||
SubWorkspace: "tasks/01_regulatory",
|
||
Mode: "execute",
|
||
Accept: []string{
|
||
"研判医疗器械管理类别与分类依据",
|
||
"结合知识库 hits.md 梳理适用的强制与推荐标准",
|
||
"给出型式检验与主要检测项目清单",
|
||
},
|
||
},
|
||
{
|
||
ID: "step_02_draft",
|
||
Role: "writer",
|
||
Title: "撰写注册可行性评估报告初稿",
|
||
SubWorkspace: "tasks/02_draft",
|
||
Mode: "execute",
|
||
Needs: []string{"tasks/01_regulatory/output.md"},
|
||
Accept: []string{
|
||
"严格按 template.md 结构展开",
|
||
"必须显式标注引用标准与条款 CiteKey",
|
||
"文末附待确认清单与风险警示",
|
||
},
|
||
},
|
||
{
|
||
ID: "step_03_review",
|
||
Role: "leader",
|
||
Title: "审核定稿交付",
|
||
SubWorkspace: "tasks/03_review",
|
||
Mode: "execute",
|
||
Needs: []string{"tasks/02_draft/output.md"},
|
||
Accept: []string{
|
||
"核验法规条款引用的准确性与完整性",
|
||
"质量门禁达标交付",
|
||
},
|
||
},
|
||
}
|
||
case "system_reqs":
|
||
return []StepSpec{
|
||
{
|
||
ID: "step_01_organize",
|
||
Role: "organizer",
|
||
Title: "解析用户需求并抽取产品功能点",
|
||
SubWorkspace: "tasks/01_organize",
|
||
Mode: "execute",
|
||
Accept: []string{
|
||
"抽取用户需求或项目事实黑板中的关键需求条目",
|
||
"提取系统功能、非功能指标及环境约束",
|
||
"向 blackboard 写入 facts 与 gaps",
|
||
},
|
||
},
|
||
{
|
||
ID: "step_02_engineer",
|
||
Role: "engineer",
|
||
Title: "系统技术规格与性能指标分解",
|
||
SubWorkspace: "tasks/02_engineer",
|
||
Mode: "execute",
|
||
Needs: []string{"tasks/01_organize/output.md"},
|
||
Accept: []string{
|
||
"定义子系统性能、电气/软件/机械指标与参数",
|
||
"规范接口协议与环境要求",
|
||
},
|
||
},
|
||
{
|
||
ID: "step_03_regulatory",
|
||
Role: "regulatory",
|
||
Title: "医疗器械强标比对与合规性论证",
|
||
SubWorkspace: "tasks/03_regulatory",
|
||
Mode: "execute",
|
||
Needs: []string{"tasks/01_organize/output.md"},
|
||
Accept: []string{
|
||
"审查适用医疗器械强制性标准及条款要求",
|
||
"输出安全、可靠性与合规性约束条目",
|
||
},
|
||
},
|
||
{
|
||
ID: "step_04_draft",
|
||
Role: "writer",
|
||
Title: "编制系统需求规格书全文",
|
||
SubWorkspace: "tasks/04_draft",
|
||
Mode: "execute",
|
||
Needs: []string{
|
||
"tasks/01_organize/output.md",
|
||
"tasks/02_engineer/output.md",
|
||
"tasks/03_regulatory/output.md",
|
||
},
|
||
Accept: []string{
|
||
"按 template.md 结构展开,包含系统需求清单与接口要求",
|
||
"每条系统需求必须具备明确唯一的 REQ- 编号与验证方式",
|
||
"禁止敷衍套话与未替换占位符",
|
||
},
|
||
},
|
||
{
|
||
ID: "step_05_review",
|
||
Role: "leader",
|
||
Title: "终审需求可测性与可追溯性定稿",
|
||
SubWorkspace: "tasks/05_review",
|
||
Mode: "execute",
|
||
Needs: []string{"tasks/04_draft/output.md"},
|
||
Accept: []string{
|
||
"全面核查需求条目可验证性与无歧义性",
|
||
"核查追溯关系完整性并交付终稿",
|
||
},
|
||
},
|
||
}
|
||
case "test_manager":
|
||
return []StepSpec{
|
||
{
|
||
ID: "step_01_trace",
|
||
Role: "organizer",
|
||
Title: "解析上游系统需求并锁定全量追溯矩阵",
|
||
SubWorkspace: "tasks/01_trace",
|
||
Mode: "execute",
|
||
Accept: []string{
|
||
"全量读取上游系统需求规格书的所有 REQ- 编号条目",
|
||
"建立 100% 覆盖的测试需求追溯矩阵底表",
|
||
},
|
||
},
|
||
{
|
||
ID: "step_02_cases",
|
||
Role: "engineer",
|
||
Title: "设计系统测试用例与判定准则",
|
||
SubWorkspace: "tasks/02_cases",
|
||
Mode: "execute",
|
||
Needs: []string{"tasks/01_trace/output.md"},
|
||
Accept: []string{
|
||
"为每条需求设计至少一个具备明确合格判定准则的测试用例 TC-",
|
||
"导出 test_cases.json 结构化用例清单至 artifacts/",
|
||
},
|
||
},
|
||
{
|
||
ID: "step_03_regulatory",
|
||
Role: "regulatory",
|
||
Title: "检验规程与检测环境合规审查",
|
||
SubWorkspace: "tasks/03_regulatory",
|
||
Mode: "execute",
|
||
Needs: []string{"tasks/02_cases/output.md"},
|
||
Accept: []string{
|
||
"核验测试方法与引用的医疗器械检验标准条款一致性",
|
||
"审查仪器设备与测试环境要求",
|
||
},
|
||
},
|
||
{
|
||
ID: "step_04_draft",
|
||
Role: "writer",
|
||
Title: "编制系统验证方案及计划全文",
|
||
SubWorkspace: "tasks/04_draft",
|
||
Mode: "execute",
|
||
Needs: []string{
|
||
"tasks/01_trace/output.md",
|
||
"tasks/02_cases/output.md",
|
||
"tasks/03_regulatory/output.md",
|
||
},
|
||
Accept: []string{
|
||
"严格按 test_manager.md 模板生成正式方案文本",
|
||
"嵌入测试用例清单与覆盖率统计,严禁遗漏上游需求",
|
||
},
|
||
},
|
||
{
|
||
ID: "step_05_review",
|
||
Role: "leader",
|
||
Title: "全覆盖门禁终审与定稿交付",
|
||
SubWorkspace: "tasks/05_review",
|
||
Mode: "execute",
|
||
Needs: []string{"tasks/04_draft/output.md"},
|
||
Accept: []string{
|
||
"验证需求覆盖率达到 100%",
|
||
"签署验证结论并交付",
|
||
},
|
||
},
|
||
}
|
||
default:
|
||
return []StepSpec{
|
||
{
|
||
ID: "step_01_draft",
|
||
Role: "writer",
|
||
Title: "撰写初稿",
|
||
SubWorkspace: "tasks/01_draft",
|
||
Mode: "execute",
|
||
Accept: []string{"按模板四段式展开"},
|
||
},
|
||
{
|
||
ID: "step_02_review",
|
||
Role: "leader",
|
||
Title: "审核定稿",
|
||
SubWorkspace: "tasks/02_review",
|
||
Mode: "execute",
|
||
Needs: []string{"tasks/01_draft/output.md"},
|
||
Accept: []string{"质量门禁通过"},
|
||
},
|
||
}
|
||
}
|
||
}
|
||
|
||
func writeJSON(path string, val any) error {
|
||
b, err := json.MarshalIndent(val, "", " ")
|
||
if err != nil {
|
||
return err
|
||
}
|
||
return os.WriteFile(path, b, 0o644)
|
||
}
|