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/ 并注入 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" } // 自动提取纯文本并注入页码锚点 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 if step.InputLayout == InputLayoutSnapshot { if err := SnapshotInputsIntoSubdir(workDir, subDir, step.Needs, deliverableName); err != nil { return err } taskText := BuildContainedTaskMarkdown(step, deliverableName) if TaskMarkdownEscapesParent(taskText) { return fmt.Errorf("snapshot TASK.md must not reference parent paths") } if err := writeRegularFile(filepath.Join(subDir, "TASK.md"), []byte(taskText)); err != nil { return err } inputRefs := map[string]any{ "step_id": step.ID, "role": step.Role, "needs": step.Needs, "target_file": deliverableName, "input_layout": InputLayoutSnapshot, "forbid_parent_paths": true, } _ = writeJSON(filepath.Join(subDir, "input_refs.json"), inputRefs) continue } 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) }