chore(docs): 归档 ISS-001 session-dedup-knowledge-map + ISS-002 mvp 文档

- 移动 session-dedup-knowledge-map OpenSpec 到 archive 目录
- 提交 ISS-001 遗留的 devflow 档案文件
- 更新 ISS-002 状态为已修复
- 新增 mvp/architecture/action-memory-relevance.md 设计文档
- 更新 mvp/README.md 文档导航
- 更新 devflow/index.md OpenSpec 链接指向 archive
This commit is contained in:
zhuyongxin
2026-07-01 18:27:04 +08:00
parent e438df4355
commit a1c896ebda
14 changed files with 834 additions and 3 deletions
@@ -0,0 +1,87 @@
# Functional Spec: session-dedup-knowledge-map
## REQ-01:工具层去重(Part A)
**触发**:`LookupKnowledgeTool.lookupKnowledge(query)` 被调用
**行为**:
1. 从 `SessionContextHolder.getSessionId()` 获取当前 sessionId;若为 null(非会话上下文)跳过去重逻辑,正常检索
2. L0+L1 检索完成后,将结果中已在 `RetrievedDocTracker` 中标记的 filePath 过滤掉
3. 若过滤后 L0 结果为空、L1 结果也为空(全部已召回),返回 `LookupResult.found=false`,并在结果中附带提示文本:"以下文档已在本会话中检索过:[列表],无需重复召回"
4. 未被过滤的文档正常返回后,将其 filePath 写入 `RetrievedDocTracker`
5. `SessionContextHolder.clear()` 调用时,`RetrievedDocTracker.clearSession(sessionId)` 同步清理
**验收**:
- 同一 session 内同一文档第二次命中时,返回去重提示而非完整文档内容
- 不同 session 之间互不影响
- sessionId 为 null 时不影响正常检索流程
---
## REQ-02:文档级 LLM 字段生成(Part B - 文档级)
**触发**:`DocumentManagementService.uploadDocument()` 完成 frontmatter 解析后
**行为**:
1. 若文档 frontmatter 中已包含 `covers` 和 `whenToRetrieve`,跳过 LLM 生成(作者手动填写优先)
2. 否则,调用 LLM,输入为文档 title + summary + 正文前 1000 字符
3. Prompt 要求 LLM 返回 JSON:`{"covers": [...], "whenToRetrieve": "..."}`
4. 解析结果,回填到 `Frontmatter` 对象
5. 序列化存入 `api_document.metadata`;同步更新 `KnowledgeEntry` 写入 L0 索引
6. LLM 调用失败时,`covers` 置为空列表,`whenToRetrieve` 置为 summary(降级),不阻断上传流程
**验收**:
- 上传后 `api_document.metadata` 中包含 `covers` 和 `whenToRetrieve` 字段
- frontmatter 已有这两个字段时不覆盖
- LLM 调用异常时文档仍上传成功,字段降级填充
---
## REQ-03:域级聚合与存储(Part B - 域级)
**触发**:文档上传成功后;文档删除后;`loadIndex()` 时发现某域在 `knowledge_domain` 表无记录
**行为**:
1. `KnowledgeDomainService.onDocumentChange(category)` 读取该 category 下所有 `KnowledgeEntry` 的 title + covers + whenToRetrieve
2. 调用 LLM,生成域级 `when_to_retrieve`(要求 LLM 识别域内文档边界,输出含区分语义的路由描述)
3. 写入 `knowledge_domain` 表(upsert by domain_id),同时更新 `document_count`
4. LLM 调用失败时,`domain.when_to_retrieve` 保留上次 DB 记录;若无历史记录则置为空字符串
**验收**:
- 上传文档后,对应 category 的 `knowledge_domain` 记录被更新
- 删除文档后,对应 category 的 `document_count` 减少,`when_to_retrieve` 重新生成
- `loadIndex()` 时无 DB 记录的域自动触发生成
---
## REQ-04:knowledge map 注入 Planner(Part B - 注入)
**触发**:`ChatService.buildChatPlannerAgent()` 调用时
**行为**:
1. 调用 `KnowledgeDomainService.buildKnowledgeMap()` 生成 YAML 文本
2. YAML 结构:域列表,每个域包含 domain_id、description、when_to_retrieve、documents(title + covers)
3. 若 knowledge_domain 表为空(无任何域记录),跳过注入,不修改 prompt
4. 注入位置:Planner system prompt 末尾,独立区块
**Planner prompt 附加规则**:
- 制定步骤时,先查看 `available_knowledge_domains`,按 `when_to_retrieve` 判断是否需要检索该域
- 每个域最多指示 Executor 检索一次;已检索过的域不再安排检索步骤
**验收**:
- Planner prompt 包含 `available_knowledge_domains` 区块
- 无域记录时 prompt 不包含该区块(不注入空结构)
- knowledge map 文本长度 < 1000 字符(6 个文档场景下)
---
## REQ-05:Frontmatter 字段扩展
**行为**:
- `Frontmatter.java` 新增 `List<String> covers` 和 `String whenToRetrieve`
- `KnowledgeEntry.java` 新增同名字段
- `FrontmatterParser.java` 解析 `covers`(YAML 数组)和 `when_to_retrieve`(YAML 字符串)
**验收**:
- 现有文档(无新字段)上传/解析不报错,字段为 null 或空列表
- 含新字段的文档正确解析