fix(squad): 区分未注入对话的原因并附上采集统计
占位说明不再把开关关闭、项目未绑定和本轮没有对话包混成一句,避免把未采集误写成对话已被滤掉。
This commit is contained in:
@@ -13,16 +13,20 @@ func TestPrepareWorkspaceWritesDialogueContext(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
engine := squad.NewWorkspaceEngine()
|
||||
ws, err := engine.PrepareWorkspace(squad.WorkspacePrepareRequest{
|
||||
WorkDir: dir,
|
||||
TaskKey: "system_reqs",
|
||||
StageTitle: "系统需求",
|
||||
ProjectName: "Demo",
|
||||
WorkDir: dir,
|
||||
TaskKey: "system_reqs",
|
||||
StageTitle: "系统需求",
|
||||
ProjectName: "Demo",
|
||||
CustomerName: "Acme",
|
||||
DialoguePackage: &squad.DialoguePackage{
|
||||
DigestMD: "# 项目对话纪要\n\n- **2026-09-24**: 张工: 器械类型眼科OCT\n",
|
||||
TurnsMD: "# turns\n\n[2026-09-24 15:00] 张工(engineer/member) 用户: 器械类型:眼科OCT\n",
|
||||
FactsJSON: []byte(`[{"key":"device_type","value":"眼科OCT"}]`),
|
||||
ConfirmationsMD: "# 裁决\n\n无冲突\n",
|
||||
DigestMD: "# 项目对话纪要\n\n- **2026-09-24**: 张工: 器械类型眼科OCT\n",
|
||||
TurnsMD: "# turns\n\n[2026-09-24 15:00] 张工(engineer/member) 用户: 器械类型:眼科OCT\n",
|
||||
FactsJSON: []byte(`[{"key":"device_type","value":"眼科OCT"}]`),
|
||||
ConfirmationsMD: "# 裁决\n\n无冲突\n",
|
||||
TurnCount: 2,
|
||||
ExcludedPrivate: 1,
|
||||
ExcludedNoAccess: 0,
|
||||
ExcludedNoise: 3,
|
||||
},
|
||||
WorkflowSteps: []squad.StepSpec{{
|
||||
ID: "s1", Title: "整理", Role: "organizer", Mode: "write",
|
||||
@@ -45,8 +49,63 @@ func TestPrepareWorkspaceWritesDialogueContext(t *testing.T) {
|
||||
if !strings.Contains(string(readme), "collab_dialogue") {
|
||||
t.Fatalf("README missing collab_dialogue: %s", readme)
|
||||
}
|
||||
dlgReadme, _ := os.ReadFile(filepath.Join(dir, "context", "dialogue", "README.md"))
|
||||
if !strings.Contains(string(dlgReadme), "采集统计:保留 2 轮;滤除私密 1、阶段不可见 0、噪声 3") {
|
||||
t.Fatalf("dialogue README missing collection stats: %s", dlgReadme)
|
||||
}
|
||||
taskMD, _ := os.ReadFile(filepath.Join(dir, "tasks", "01_organizer", "TASK.md"))
|
||||
if !strings.Contains(string(taskMD), "context/dialogue/") {
|
||||
t.Fatalf("TASK.md missing dialogue nav: %s", taskMD)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrepareWorkspaceExplainsMissingDialogue(t *testing.T) {
|
||||
t.Setenv("DIALOGUE_CTX_INJECTION", "")
|
||||
unbound := prepareDialogueWorkspace(t, squad.WorkspacePrepareRequest{})
|
||||
readme, err := os.ReadFile(filepath.Join(unbound, "context", "dialogue", "README.md"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(string(readme), "任务未绑定项目") {
|
||||
t.Fatalf("unbound readme=%s", readme)
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(unbound, "context", "dialogue", "turns.md")); err == nil {
|
||||
t.Fatal("unbound project must not pretend turns were filtered")
|
||||
}
|
||||
|
||||
bound := prepareDialogueWorkspace(t, squad.WorkspacePrepareRequest{ProjectID: 6022})
|
||||
readme, err = os.ReadFile(filepath.Join(bound, "context", "dialogue", "README.md"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(string(readme), "没有对话包") {
|
||||
t.Fatalf("bound readme=%s", readme)
|
||||
}
|
||||
|
||||
t.Setenv("DIALOGUE_CTX_INJECTION", "off")
|
||||
disabled := prepareDialogueWorkspace(t, squad.WorkspacePrepareRequest{ProjectID: 6022})
|
||||
readme, err = os.ReadFile(filepath.Join(disabled, "context", "dialogue", "README.md"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(string(readme), "开关已关闭") {
|
||||
t.Fatalf("disabled readme=%s", readme)
|
||||
}
|
||||
}
|
||||
|
||||
func prepareDialogueWorkspace(t *testing.T, req squad.WorkspacePrepareRequest) string {
|
||||
t.Helper()
|
||||
req.WorkDir = t.TempDir()
|
||||
req.TaskKey = "research"
|
||||
req.StageTitle = "调研报告"
|
||||
req.ProjectName = "测试项目"
|
||||
req.CustomerName = "客户"
|
||||
req.WorkflowSteps = []squad.StepSpec{{
|
||||
ID: "s1", Title: "整理", Role: "organizer", Mode: "write",
|
||||
SubWorkspace: "tasks/01_organizer", Status: "pending",
|
||||
}}
|
||||
if _, err := squad.NewWorkspaceEngine().PrepareWorkspace(req); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return req.WorkDir
|
||||
}
|
||||
|
||||
@@ -41,10 +41,14 @@ type WorkspacePrepareRequest struct {
|
||||
|
||||
// DialoguePackage is the squad-facing dialogue injection payload.
|
||||
type DialoguePackage struct {
|
||||
DigestMD string
|
||||
TurnsMD string
|
||||
FactsJSON []byte
|
||||
ConfirmationsMD string
|
||||
DigestMD string
|
||||
TurnsMD string
|
||||
FactsJSON []byte
|
||||
ConfirmationsMD string
|
||||
TurnCount int
|
||||
ExcludedPrivate int
|
||||
ExcludedNoAccess int
|
||||
ExcludedNoise int
|
||||
}
|
||||
|
||||
// Workspace 表示已初始化的工作区句柄
|
||||
|
||||
@@ -182,7 +182,7 @@ func (e *WorkspaceEngine) buildContextSystem(workDir string, req WorkspacePrepar
|
||||
})
|
||||
|
||||
// 8. dialogue/ — project member dialogue (digest + turns + facts)
|
||||
if err := e.writeDialogueContext(ctxDir, req.DialoguePackage, &contextItems); err != nil {
|
||||
if err := e.writeDialogueContext(ctxDir, req.ProjectID, req.DialoguePackage, &contextItems); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -210,13 +210,13 @@ func (e *WorkspaceEngine) buildContextSystem(workDir string, req WorkspacePrepar
|
||||
return os.WriteFile(filepath.Join(ctxDir, "README.md"), []byte(readmeContent), 0o644)
|
||||
}
|
||||
|
||||
func (e *WorkspaceEngine) writeDialogueContext(ctxDir string, pkg *DialoguePackage, items *[]ContextItem) error {
|
||||
func (e *WorkspaceEngine) writeDialogueContext(ctxDir string, projectID int64, pkg *DialoguePackage, items *[]ContextItem) error {
|
||||
dlgDir := filepath.Join(ctxDir, "dialogue")
|
||||
if err := os.MkdirAll(dlgDir, 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
if pkg == nil {
|
||||
_ = os.WriteFile(filepath.Join(dlgDir, "README.md"), []byte("# 项目对话说明\n\n本任务未注入项目成员对话上下文(开关关闭或无可共享对话)。\n"), 0o644)
|
||||
if reason := dialogueOmitReason(projectID, pkg); reason != "" {
|
||||
_ = os.WriteFile(filepath.Join(dlgDir, "README.md"), []byte(reason), 0o644)
|
||||
return nil
|
||||
}
|
||||
digest := pkg.DigestMD
|
||||
@@ -239,7 +239,9 @@ func (e *WorkspaceEngine) writeDialogueContext(ctxDir string, pkg *DialoguePacka
|
||||
_ = os.WriteFile(filepath.Join(dlgDir, "turns.md"), []byte(turns), 0o644)
|
||||
_ = os.WriteFile(filepath.Join(dlgDir, "facts.json"), facts, 0o644)
|
||||
_ = os.WriteFile(filepath.Join(dlgDir, "CONFIRMATIONS.md"), []byte(confirmations), 0o644)
|
||||
_ = os.WriteFile(filepath.Join(dlgDir, "README.md"), []byte("# 项目成员对话上下文\n\n- `digest.md`:按日纪要\n- `turns.md`:近窗带元数据原文\n- `facts.json`:对话沉淀事实候选\n- `CONFIRMATIONS.md`:冲突裁决审计\n\n权威度 `collab_dialogue`:低于黑板与已批准文档。\n"), 0o644)
|
||||
readme := "# 项目成员对话上下文\n\n- `digest.md`:按日纪要\n- `turns.md`:近窗带元数据原文\n- `facts.json`:对话沉淀事实候选\n- `CONFIRMATIONS.md`:冲突裁决审计\n\n权威度 `collab_dialogue`:低于黑板与已批准文档。\n"
|
||||
readme += dialogueCollectionStats(pkg)
|
||||
_ = os.WriteFile(filepath.Join(dlgDir, "README.md"), []byte(readme), 0o644)
|
||||
|
||||
*items = append(*items, ContextItem{
|
||||
File: "context/dialogue/",
|
||||
@@ -249,3 +251,37 @@ func (e *WorkspaceEngine) writeDialogueContext(ctxDir string, pkg *DialoguePacka
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func dialogueInjectionEnabled() bool {
|
||||
switch strings.TrimSpace(strings.ToLower(os.Getenv("DIALOGUE_CTX_INJECTION"))) {
|
||||
case "off", "false", "0", "no":
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// dialogueOmitReason 区分未注入的三种原因。已有对话包时返回空,调用方写入正文。
|
||||
func dialogueOmitReason(projectID int64, pkg *DialoguePackage) string {
|
||||
if pkg != nil {
|
||||
return ""
|
||||
}
|
||||
if !dialogueInjectionEnabled() {
|
||||
return "# 项目对话说明\n\n未注入项目成员对话:对话注入开关已关闭(DIALOGUE_CTX_INJECTION)。\n"
|
||||
}
|
||||
if projectID <= 0 {
|
||||
return "# 项目对话说明\n\n未注入项目成员对话:任务未绑定项目,无法收集个人会话与项目成员共享对话。\n"
|
||||
}
|
||||
return "# 项目对话说明\n\n未注入项目成员对话:项目已绑定,但本轮没有对话包(采集失败或调用方未传入)。这不表示对话已被判定为无用信息。\n"
|
||||
}
|
||||
|
||||
func dialogueCollectionStats(pkg *DialoguePackage) string {
|
||||
if pkg == nil {
|
||||
return ""
|
||||
}
|
||||
if pkg.TurnCount == 0 && pkg.ExcludedPrivate == 0 && pkg.ExcludedNoAccess == 0 && pkg.ExcludedNoise == 0 {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("\n采集统计:保留 %d 轮;滤除私密 %d、阶段不可见 %d、噪声 %d。仅当这些过滤后仍有内容时才写入 turns.md。\n",
|
||||
pkg.TurnCount, pkg.ExcludedPrivate, pkg.ExcludedNoAccess, pkg.ExcludedNoise)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user