Files
Agent-System/internal/squad/workspace.go
T
weichenandCursor 900af79e14 fix(docgov): quality gate self-pollution, server repair budget, quality_failed UI
受控小队任务服务端启用修复预算;否定性自检引用不再误报;quality_failed 保持失败且不激活未过门禁稿。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-13 15:00:50 +00:00

1104 lines
41 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)
}