feat(context): implement self-describing context system with authority guide
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,192 @@
|
||||
package squad
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ContextManager 负责上下文资产的读写、索引管理与提示词注入
|
||||
type ContextManager struct{}
|
||||
|
||||
func NewContextManager() *ContextManager {
|
||||
return &ContextManager{}
|
||||
}
|
||||
|
||||
// LoadContextIndex 读取工作区中的 context/index.json
|
||||
func (cm *ContextManager) LoadContextIndex(wsDir string) (*ContextIndex, error) {
|
||||
idxPath := filepath.Join(wsDir, "context", "index.json")
|
||||
data, err := os.ReadFile(idxPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read context/index.json: %w", err)
|
||||
}
|
||||
var idx ContextIndex
|
||||
if err := json.Unmarshal(data, &idx); err != nil {
|
||||
return nil, fmt.Errorf("unmarshal context/index.json: %w", err)
|
||||
}
|
||||
return &idx, nil
|
||||
}
|
||||
|
||||
// FormatPromptContext 生成注入给智能体 Prompt 的上下文导航与权威等级说明
|
||||
func (cm *ContextManager) FormatPromptContext(wsDir string, role string) (string, error) {
|
||||
var b strings.Builder
|
||||
|
||||
b.WriteString("## 📚 任务上下文资源与权威等级\n\n")
|
||||
b.WriteString("本工作区已为你准备好完整、分级的上下文资源,请务必先查阅对应文件:\n\n")
|
||||
|
||||
// 1. 读取 index.json
|
||||
idx, err := cm.LoadContextIndex(wsDir)
|
||||
if err == nil && len(idx.Items) > 0 {
|
||||
b.WriteString("| 资源路径 | 权威等级 | 用途说明 | 使用与引用指导 |\n")
|
||||
b.WriteString("|---|---|---|---|\n")
|
||||
for _, item := range idx.Items {
|
||||
b.WriteString(fmt.Sprintf("| `%s` | **%s** | %s | %s |\n",
|
||||
item.File, item.AuthorityLevel, item.Description, item.Guidance))
|
||||
}
|
||||
b.WriteString("\n")
|
||||
}
|
||||
|
||||
// 2. 注入全局项目基线摘要
|
||||
projPath := filepath.Join(wsDir, "context", "project.md")
|
||||
if projData, err := os.ReadFile(projPath); err == nil {
|
||||
b.WriteString("### 📌 项目基线要点(不可推翻)\n")
|
||||
b.WriteString(string(projData) + "\n\n")
|
||||
}
|
||||
|
||||
// 3. 注入材料清单提示
|
||||
matDir := filepath.Join(wsDir, "context", "materials_text")
|
||||
if entries, err := os.ReadDir(matDir); err == nil && len(entries) > 0 {
|
||||
b.WriteString("### 📄 可用输入材料(核心原始事实证据)\n")
|
||||
for _, e := range entries {
|
||||
if e.Name() == "_all.txt" {
|
||||
continue
|
||||
}
|
||||
b.WriteString(fmt.Sprintf("- `context/materials_text/%s`\n", e.Name()))
|
||||
}
|
||||
b.WriteString("> ⚠️ 引用材料技术参数或客户原话时,必须标注出处:`〔材料: 文件名 §章节 p.页码〕`。\n\n")
|
||||
}
|
||||
|
||||
// 4. 注入法规知识库命中快照提示
|
||||
kbPath := filepath.Join(wsDir, "context", "knowledge", "hits.md")
|
||||
if kbData, err := os.ReadFile(kbPath); err == nil && len(strings.TrimSpace(string(kbData))) > 0 {
|
||||
b.WriteString("### ⚖️ 法规与标准快照(参考依据)\n")
|
||||
lines := strings.Split(string(kbData), "\n")
|
||||
for _, l := range lines {
|
||||
if strings.HasPrefix(l, "### [") {
|
||||
b.WriteString("- " + strings.TrimPrefix(l, "### ") + "\n")
|
||||
}
|
||||
}
|
||||
b.WriteString("> 详情请直接阅读 `context/knowledge/hits.md`。涉及强制标准须注明标准号与条款。\n\n")
|
||||
}
|
||||
|
||||
// 5. 注入前序文档追溯提示
|
||||
priorIndexPath := filepath.Join(wsDir, "context", "prior_docs", "index.md")
|
||||
if priorData, err := os.ReadFile(priorIndexPath); err == nil && len(strings.TrimSpace(string(priorData))) > 0 {
|
||||
b.WriteString("### 🔗 前序已批准文档(前后闭环追溯)\n")
|
||||
b.WriteString(string(priorData) + "\n")
|
||||
b.WriteString("> 本阶段生成内容需与前序文档结论保持自洽。\n\n")
|
||||
}
|
||||
|
||||
return b.String(), nil
|
||||
}
|
||||
|
||||
// InjectKnowledgeHits 向现有工作区动态注入或追加法规知识库条目
|
||||
func (cm *ContextManager) InjectKnowledgeHits(wsDir string, hits []KnowledgeHit) error {
|
||||
if len(hits) == 0 {
|
||||
return nil
|
||||
}
|
||||
kbDir := filepath.Join(wsDir, "context", "knowledge")
|
||||
if err := os.MkdirAll(kbDir, 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
hitsPath := filepath.Join(kbDir, "hits.md")
|
||||
|
||||
var b strings.Builder
|
||||
existing, _ := os.ReadFile(hitsPath)
|
||||
if len(existing) > 0 {
|
||||
b.Write(existing)
|
||||
b.WriteString("\n\n")
|
||||
} else {
|
||||
b.WriteString("# 法规与标准检索条文摘录\n\n")
|
||||
}
|
||||
|
||||
for _, hit := range hits {
|
||||
b.WriteString(fmt.Sprintf("### [%s] %s %s\n\n%s\n\n> 来源: %s\n\n---\n\n",
|
||||
strings.ToUpper(hit.Category), hit.DocCode, hit.Title, hit.Content, hit.Source))
|
||||
}
|
||||
|
||||
if err := os.WriteFile(hitsPath, []byte(b.String()), 0o644); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return cm.addContextItem(wsDir, ContextItem{
|
||||
File: "context/knowledge/hits.md",
|
||||
Description: "法规知识库中命中的医疗器械分类、国家标准与指导原则",
|
||||
AuthorityLevel: "reference_standard",
|
||||
Guidance: "合规与技术论据源。引用必须带标准号;涉及审批结论需标注'需向药监局审评中心确认'",
|
||||
})
|
||||
}
|
||||
|
||||
// InjectMaterials 向现有工作区注入上传的新材料
|
||||
func (cm *ContextManager) InjectMaterials(wsDir string, materials []MaterialItem) error {
|
||||
if len(materials) == 0 {
|
||||
return nil
|
||||
}
|
||||
matDir := filepath.Join(wsDir, "context", "materials_text")
|
||||
if err := os.MkdirAll(matDir, 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, m := range materials {
|
||||
name := filepath.Base(m.Filename)
|
||||
if name == "" || name == "." {
|
||||
name = "material.txt"
|
||||
}
|
||||
p := filepath.Join(matDir, name)
|
||||
if err := os.WriteFile(p, m.Content, 0o644); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Also write to root materials_text/ for backward compatibility
|
||||
rootMatDir := filepath.Join(wsDir, "materials_text")
|
||||
_ = os.MkdirAll(rootMatDir, 0o755)
|
||||
_ = os.WriteFile(filepath.Join(rootMatDir, name), m.Content, 0o644)
|
||||
|
||||
_ = cm.addContextItem(wsDir, ContextItem{
|
||||
File: filepath.ToSlash(filepath.Join("context", "materials_text", name)),
|
||||
Description: fmt.Sprintf("输入材料文本: %s", name),
|
||||
AuthorityLevel: "primary_evidence",
|
||||
Guidance: "核心原始证据。提取参数需标明出处;材料内部矛盾需在矛盾表中并列呈现",
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cm *ContextManager) addContextItem(wsDir string, item ContextItem) error {
|
||||
idxPath := filepath.Join(wsDir, "context", "index.json")
|
||||
var idx ContextIndex
|
||||
if data, err := os.ReadFile(idxPath); err == nil {
|
||||
_ = json.Unmarshal(data, &idx)
|
||||
}
|
||||
|
||||
found := false
|
||||
for i, existing := range idx.Items {
|
||||
if existing.File == item.File {
|
||||
idx.Items[i] = item
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
idx.Items = append(idx.Items, item)
|
||||
}
|
||||
|
||||
data, err := json.MarshalIndent(idx, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(idxPath, data, 0o644)
|
||||
}
|
||||
Reference in New Issue
Block a user