- 在JWT中添加permver权限版本号claim,用于实时权限变更检测 - 实现PermissionVersionMiddleware中间件,在认证后授权前校验权限版本 - 修改AssignPermissionsCommandHandler,角色权限变更时自增相关用户权限版本号 - 修改AssignRolesCommandHandler,用户角色变更时自增权限版本号强制重登录 - 集成Redis存储权限版本镜像,支持分布式环境下的版本同步 - 添加PermissionVersion字段到User实体,用于存储权限版本号 - 实现分布式登录失败计数与账号锁定功能,增强安全防护 - 配置文件中新增AccessTokenMinutes和RefreshTokenDays可配置项 - 更新CORS策略配置,添加127.0.0.1本地访问支持 - 完善异常处理,统一使用ErrorCode枚举进行错误码管理 - 添加SOP测试单元设计文档,规范端到端测试流程
354 lines
18 KiB
Markdown
354 lines
18 KiB
Markdown
# SOP 测试单元工厂设计 · TDD + SDD 三驱动融合,推送飞书
|
||
|
||
- **状态**:已批准(Approved)
|
||
- **日期**:2026-07-07
|
||
- **作者**:brainstorming session
|
||
- **影响范围**:全 6 后端模块(POC 阶段聚焦 `rag`);产出物推送至飞书知识库 `7659208807054117857` 的"产品"目录
|
||
|
||
---
|
||
|
||
## 一、目标与背景
|
||
|
||
把 BuildFlow"现在的功能"拆解为**可执行、可追踪、可复制的最小 SOP 测试单元**,通过三位一体的工作流落地到飞书(文档 + 任务联动),实现端到端场景的标准化验证。
|
||
|
||
### 三驱动定义(与用户对齐)
|
||
|
||
| 驱动 | 全称 | 在本项目中的具体含义 |
|
||
|------|------|----------------------|
|
||
| **TDD-文档** | Technical Design Document | 技术设计文档:架构、接口契约、数据模型、时序图、边界条件 |
|
||
| **TDD-测试** | Test-Driven Development | 测试驱动开发:先写 xUnit 测试(红)→ 实现(绿)→ 重构 |
|
||
| **SDD** | Skill-Driven Development | 技能驱动开发:复用项目已有的 `.agents/skills/rag-coding-api` 编码规范(MediatR CQRS、测试规范、模块边界) |
|
||
|
||
### 现状(已通过代码探索确认)
|
||
|
||
| 维度 | 现状 | 与本设计的关系 |
|
||
|------|------|----------------|
|
||
| 测试体系 | 8 个 xUnit 测试项目,1472 源文件 vs 92 测试文件,FluentAssertions + EF InMemory | 成熟,但全部是单元/Handler 级,**无端到端 SOP 测试** |
|
||
| RAG 测试 | `BuildFlow.Rag.Tests` 仅 7 个文件,覆盖 sanitizer/parser/catalog/validator | **关键缺口**:无 ProcessDocumentCommandHandler、SendMessageCommandHandler、RagQueryCommandHandler、RAGStreamEndpoint 的集成测试 |
|
||
| 文档 | 仅 scripting/x6-workflow/CI-CD 三份设计文档,散落各子工程 | **无统一 PRD/SDD/SOP 体系** |
|
||
| 技能规范 | `.agents/skills/rag-coding-api`(27KB)已强制 MediatR CQRS、禁 service bridge、测试规范 | SDD 的载体已就绪,本设计直接复用 |
|
||
| 飞书 | 已有知识库 `7659208807054117857`,含"服务器配置""项目开发指南"两个根节点 | 本设计在其下新建"产品"目录承载 SOP 产出 |
|
||
|
||
---
|
||
|
||
## 二、核心决策(已与用户确认)
|
||
|
||
| # | 决策点 | 选择 | 理由 |
|
||
|---|--------|------|------|
|
||
| 1 | 功能范围 | 全部 6 后端模块 | rag/file/im/platform/workflow/scripting 全覆盖 |
|
||
| 2 | 推进策略 | POC 先行:RAG 跑通再复制 | 风险低、模式可复制、工作量可控 |
|
||
| 3 | 测试单元粒度 | 端到端场景 | 用户视角完整流程(非原子用例) |
|
||
| 4 | 飞书产出形式 | 飞书文档 + 飞书任务联动 | 文档沉淀设计,任务追踪执行 |
|
||
| 5 | RAG 场景 | A=知识库 RAG 问答全链路 + B=多轮对话流式问答 | 互补覆盖 RAG 两条主干(检索 vs 对话) |
|
||
| 6 | pgvector 隔离 | Stub 先行 + Testcontainers 集成测试补充 | POC 用 Stub 快速跑通,后续补真实召回验证 |
|
||
| 7 | 实现顺序 | 文档先行 → 测试后补 | 文档引导实现,飞书文档作为单一事实来源 |
|
||
| 8 | 模板化 | 8 段式文档骨架 + 5 任务结构 | Phase 2 可填模板复制到其余 5 模块 |
|
||
|
||
---
|
||
|
||
## 三、SOP 测试单元工厂架构
|
||
|
||
### 3.1 生产流水线
|
||
|
||
```
|
||
[SDD 规范] [TDD 设计] [TDD 实现]
|
||
rag-coding-api skill → 技术设计文档 → xUnit 测试先行
|
||
(MediatR CQRS / (架构/接口/数据模型/ (红色测试 →
|
||
测试规范) 时序/边界) 绿色实现 → 重构)
|
||
│ │ │
|
||
└──────────────────────┴───────────────────────┘
|
||
│
|
||
▼
|
||
┌─────────────────────────┐
|
||
│ 最小 SOP 测试单元 │
|
||
│ (端到端场景) │
|
||
└────────────┬────────────┘
|
||
│
|
||
┌─────────────┴─────────────┐
|
||
▼ ▼
|
||
┌─────────────────┐ ┌────────────────────┐
|
||
│ 飞书文档 │ │ 飞书任务 │
|
||
│ (SOP + 设计) │◄──────►│ (检查项可勾选) │
|
||
│ 评审/沉淀 │ deep │ 执行/追踪 │
|
||
│ │ link │ │
|
||
└─────────────────┘ └────────────────────┘
|
||
```
|
||
|
||
### 3.2 飞书知识库目录结构
|
||
|
||
```
|
||
📚 Wiki 空间 7659208807054117857
|
||
├── 📄 服务器配置 (已存在)
|
||
├── 📄 项目开发指南 (已存在)
|
||
└── 📁 产品/ ← 已创建 node_token=StjYwzzsSisql1ki2oqc5KFEnZd
|
||
├── 📄 产品概览 - BuildFlow 模块地图
|
||
└── 📁 SOP 测试单元/
|
||
├── 📁 RAG 模块/
|
||
│ ├── 📄 [SOP-RAG-01] 知识库 RAG 问答全链路
|
||
│ └── 📄 [SOP-RAG-02] 多轮对话流式问答
|
||
├── 📁 File 模块/ (Phase 2)
|
||
├── 📁 IM 模块/ (Phase 2)
|
||
├── 📁 Platform 模块/ (Phase 2)
|
||
├── 📁 Workflow 模块/ (Phase 2)
|
||
└── 📁 Scripting 模块/ (Phase 2)
|
||
```
|
||
|
||
---
|
||
|
||
## 四、SOP 测试单元标准模板
|
||
|
||
每个 SOP 测试单元(无论 RAG 还是后续模块)都遵循同一个 8 段式文档骨架 + 5 任务结构。这是 Phase 2 能快速复制的关键。
|
||
|
||
### 4.1 飞书文档骨架(8 段式)
|
||
|
||
```
|
||
# [SOP-XX] 场景名称
|
||
## 模块: rag | 场景类型: 端到端 | 状态: 草稿/评审/通过
|
||
|
||
## 1. 用户故事与价值
|
||
作为...我希望...以便...
|
||
|
||
## 2. 前置条件 (SDD 规范约束)
|
||
- 遵守 rag-coding-api skill: MediatR CQRS / 测试规范
|
||
- 依赖的外部服务及隔离方式 (Stub/Testcontainer)
|
||
|
||
## 3. 技术设计 (TDD 文档部分)
|
||
3.1 架构图 (mermaid 时序图)
|
||
3.2 涉及的 Command/Query/Handler 清单
|
||
3.3 数据模型变更 (实体/字段)
|
||
3.4 关键接口契约 (输入/输出)
|
||
|
||
## 4. SOP 执行步骤 (端到端)
|
||
Step 1...N: 每步含 API 调用 / Handler / 预期结果
|
||
|
||
## 5. 验收标准 (Definition of Done)
|
||
☐ 可勾选的检查项列表
|
||
|
||
## 6. 测试设计 (TDD 测试部分)
|
||
6.1 测试文件位置与命名
|
||
6.2 Stub/Fixture 设计
|
||
6.3 测试用例清单 (Given/When/Then)
|
||
|
||
## 7. 执行结果记录
|
||
- 测试运行命令与输出
|
||
- 覆盖率
|
||
- 已知限制
|
||
|
||
## 8. 变更日志
|
||
```
|
||
|
||
### 4.2 飞书任务结构(每个 SOP 单元 = 1 父任务 + 5 子任务)
|
||
|
||
```
|
||
父任务: [SOP-XX] 场景名称 - 端到端验证
|
||
├── ☐ T1: 编写 TDD 技术设计文档 (description 含飞书文档 deep link)
|
||
├── ☐ T2: 编写 xUnit 测试用例 (红色阶段)
|
||
├── ☐ T3: 实现/对齐 Handler 逻辑 (绿色阶段)
|
||
├── ☐ T4: 跑通端到端测试,记录结果 (链接到文档§7)
|
||
└── ☐ T5: 评审 + 标记场景状态为「通过」
|
||
```
|
||
|
||
任务描述嵌入飞书文档 deep link,实现**文档 ↔ 任务双向联动**。
|
||
|
||
---
|
||
|
||
## 五、RAG 模块 POC 场景详细设计
|
||
|
||
### 5.1 场景 A:[SOP-RAG-01] 知识库 RAG 问答全链路
|
||
|
||
**用户故事**:作为知识工作者,我上传一份文档到知识库,提问后能获得基于该文档的准确回答和引用来源。
|
||
|
||
**覆盖链路**:建 KB → 上传文档 → 触发处理流水线(提取/分块/向量化/入库)→ RAG 检索问答。
|
||
|
||
**SOP 步骤(端到端)**:
|
||
|
||
| Step | API 调用 | MediatR | 预期结果 |
|
||
|------|----------|---------|----------|
|
||
| 1 | `POST /knowledge-bases` | CreateKnowledgeBaseCommand | KB 创建,配 RetrievalMode=Hybrid/TopK=5/阈值=0.7 |
|
||
| 2 | `POST /knowledge-bases/{id}/documents` (multipart) | UploadDocumentCommand | Document 落库 Status=Pending |
|
||
| 3 | `POST /documents/{id}/process` | ProcessDocumentCommand | 4 阶段流水线:提取→分块→向量化→入库,Status=Processed |
|
||
| 4 | `POST /rag/query` | RagQueryCommandHandler | 返回 {answer, sources[]},sources 非空 |
|
||
|
||
**涉及 Handler**:CreateKnowledgeBaseCommandHandler、UploadDocumentCommandHandler、ProcessDocumentCommandHandler、RagQueryCommandHandler、IRagRetrievalService、IAIChatAgent。
|
||
|
||
**验收标准(DoD)**:
|
||
- ☐ 文档 Status 从 Pending → Processing → Processed
|
||
- ☐ DocumentChunk 数量 = 预期分块数,Content 非空
|
||
- ☐ /rag/query 返回 sources[] 非空且 DocumentId 匹配
|
||
- ☐ answer 包含 LLM 响应文本
|
||
- ☐ 全程无未捕获异常
|
||
|
||
**测试隔离策略**:
|
||
|
||
| 外部依赖 | 处理方式 |
|
||
|----------|----------|
|
||
| pgvector | **POC**:Stub `IRagRetrievalService` 返回预设 sources;**补充**:Testcontainers + 真实 PG+pgvector 集成测试 |
|
||
| LM Studio | Stub `IAIChatAgent`(RunAsync 返回固定文本)+ Stub `IEmbeddingService`(返回固定 768 维向量) |
|
||
| 对象存储 | Stub `IFileStorageClient`(复用 `StubFileStorageClient` 模式) |
|
||
| 文档提取 | Stub `IDocumentExtractorFactory` + `IDocumentExtractor`(返回固定文本) |
|
||
|
||
**测试文件**:`tests/BuildFlow.Rag.Tests/RagQAEndToEndTests.cs`
|
||
|
||
---
|
||
|
||
### 5.1.1 分块策略详解(ChunkingMode 6 种模式)
|
||
|
||
分块在 `ProcessDocumentCommand` 阶段 2,由 `ITextChunker.Chunk(extracted, mode, size, overlap, separator)` 完成(实现 `Infrastructure/AI/TextChunker.cs`,设计参考 Dify):
|
||
|
||
| 模式 | 值 | 切分逻辑 |
|
||
|------|---|---------|
|
||
| **General** | 0(默认) | 段落感知:先按 `\n\n` 切段,超长段按句子标点二切,overlap 取最后 1-2 句 |
|
||
| **Heading** | 1 | 按 Markdown H1-H6 切,维护 headingStack,生成 HeadingPath 如 "第三章 > 3.1 概述" |
|
||
| **ParentChild** | 2 | 父块=整段(上下文,最大 chunkSize),子块=段落再切(chunkSize/3,精确匹配),**父子都向量化** |
|
||
| **Separator** | 3 | 按自定义分隔符(默认 `\n\n---\n\n`)切分,超长用 General 二切 |
|
||
| **ByPage** | 4 | PDF 每页/PPT 每张一块,页过长用 General 二切,子块共享 PageNumber |
|
||
| **Token** | 5 | 按 token 估算:charBudget = chunkSize × 1.5(注释称"中文=2/英文=1"但实际用固定 1.5 系数,**存在实现差异**) |
|
||
|
||
**ParentChild 回填机制**:先 SaveChanges 拿父块 Guid → 用 parentIdMap[Index→Guid] 反查 → 写入子块 ParentChunkId(两轮 SaveChanges)。
|
||
|
||
**参数优先级**:模式 `doc.ChunkingModeOverride ?? kb.ChunkingMode`(文档级覆盖优先);ChunkSize/ChunkOverlap/Separator 全部来自 kb,默认 500/50/null。
|
||
|
||
### 5.1.2 向量化策略详解
|
||
|
||
- **接口**:`IEmbeddingService.EmbedAsync(单条)` + `EmbedBatchAsync(批量)`,实现 `Infrastructure/AI/EmbeddingService.cs`,OpenAI 兼容协议
|
||
- **批处理**:`EmbeddingBatchSize = 64`,≤64 一次调用,否则按 64 步长切片按原顺序拼接(顺序必须与输入一致)
|
||
- **向量维度 = 768**(非 384/1536):默认模型 `embeddinggemma-300m` 输出 768 维,DB 列 `vector(768)`,**换模型必须同步改迁移**
|
||
- **写入方式**:EF 忽略 Embedding 列(`DocumentChunkConfiguration.Ignore`),逐块 raw SQL `UPDATE document_chunks SET embedding='[...]'::vector WHERE "Id"=...`(N 条 SQL,无批量优化)
|
||
- **TokenCount 粗估**:`Content.Length / 4`,非真实 tokenizer
|
||
|
||
### 5.1.3 召回与重排算法详解
|
||
|
||
`IRagRetrievalService.RetrieveAsync(kb, question)` → `List<RetrievalHit>`,4 步流水线:
|
||
|
||
1. **粗召回** `RecallAsync`:SQL `LIMIT RetrievalTopK`(默认 20)
|
||
2. **阈值过滤**:`VectorScore >= SimilarityThreshold`(默认 0.3),纯 Keyword 跳过
|
||
3. **内存重排**:按 mode 计算 finalScore,`Take(ContextTopK)`(默认 5)
|
||
4. **父块回填** `EnrichWithContextAsync`(当前空壳,见差异清单)
|
||
|
||
**Vector 召回 SQL**(pgvector 余弦距离):
|
||
```sql
|
||
SELECT ..., 1 - (dc.embedding <=> '{q}'::vector) AS similarity
|
||
FROM document_chunks dc JOIN documents d ON ...
|
||
WHERE d."KnowledgeBaseId" = '{kbId}' AND dc.embedding IS NOT NULL
|
||
AND dc."ParentChunkId" IS NULL -- 只召回父块/独立块
|
||
ORDER BY dc.embedding <=> '{q}'::vector LIMIT 20
|
||
```
|
||
|
||
**Keyword 召回**:`ExtractKeywords` 分词(英文 `[a-zA-Z]+`≥2,中文 ≤4 整体/>4 做 2-4 字 N-gram),每个关键词 `CASE WHEN Content ILIKE @kwN THEN 1 ELSE 0` 相加得命中数。
|
||
|
||
**Hybrid 重排加权**:`finalScore = VectorScore × VectorWeight + keywordScore × (1-VectorWeight)`,默认 VectorWeight=0.7(70% 向量 + 30% 关键词)。
|
||
|
||
### 5.1.4 设计意图 vs 当前实现差异清单(技术债务)
|
||
|
||
代码探索发现 4 处差异,POC 用 Stub 会绕过,但 Testcontainers 集成测试必须覆盖:
|
||
|
||
| # | 差异点 | 设计意图 | 当前实现 |
|
||
|---|--------|---------|---------|
|
||
| 1 | **RerankStrategy 未读取** | 枚举 None/Keyword/LLM 可切换重排 | 重排分支依据是 RetrievalMode,该字段从未被读取,LLM 重排未实现 |
|
||
| 2 | **Hybrid 非双路融合** | 向量召回 + 关键词召回融合(RRF/加权) | Hybrid 只执行向量召回 SQL,关键词仅在内存重排加权参与,未做关键词路独立召回 |
|
||
| 3 | **父块回填空壳** | 召回子块→回填父块获完整上下文 | `EnrichWithContextAsync` 无 DB 查询,ContextContent=Content;SQL `ParentChunkId IS NULL` 召回的是父块而非子块 |
|
||
| 4 | **表名不一致(潜在 bug)** | EF 表 `b_document_chunks` | raw SQL 写 `document_chunks`(无 `b_` 前缀),可能导致向量写入失败 |
|
||
|
||
---
|
||
|
||
### 5.2 场景 B:[SOP-RAG-02] 多轮对话流式问答
|
||
|
||
**用户故事**:作为用户,我与 AI 进行多轮对话,AI 能记住上下文并以流式方式逐字返回。
|
||
|
||
**覆盖链路**:建会话 → 流式提问 → 多轮历史拼装 → SSE 协议 → 缓存一致性。
|
||
|
||
**SOP 步骤(端到端)**:
|
||
|
||
| Step | API 调用 | 处理路径 | 预期结果 |
|
||
|------|----------|----------|----------|
|
||
| 1 | `POST /chat/conversations` | CreateConversationCommand | 会话创建(不绑 KB) |
|
||
| 2 | `POST /chat/conversations/{id}/stream` | StreamMessageEndpoint(直连) | SSE token 流,event:done 返回 messageId |
|
||
| 3 | `POST /chat/conversations/{id}/stream`(第二轮) | 同上 | 历史拼装含第一轮 user+assistant |
|
||
| 4 | 校验 | IChatMessageCache ↔ DB | cache 与 DB ChatMessage 一致 |
|
||
|
||
**涉及 Handler/Endpoint**:CreateConversationCommandHandler、StreamMessageEndpoint、IAIChatAgent.RunStreamingAsync、IChatMessageCache、AiChatOutputSanitizer。
|
||
|
||
**验收标准(DoD)**:
|
||
- ☐ 两轮 SSE 均以 event:done 结束并返回 messageId
|
||
- ☐ 第二轮请求时,历史拼装包含第一轮的 user+assistant 消息
|
||
- ☐ AiChatOutputSanitizer 生效(去开场白/think 标签)
|
||
- ☐ Redis cache 与 DB 消息记录一致
|
||
- ☐ 会话可删除(DELETE 返回成功)
|
||
|
||
**测试隔离策略**:
|
||
|
||
| 外部依赖 | 处理方式 |
|
||
|----------|----------|
|
||
| LM Studio | Stub `IAIChatAgent.RunStreamingAsync`(yield 固定 token 序列) |
|
||
| Redis | Stub `IChatMessageCache`(内存字典模拟)或用真实 Redis |
|
||
|
||
**测试文件**:`tests/BuildFlow.Rag.Tests/ChatStreamEndToEndTests.cs`
|
||
|
||
---
|
||
|
||
## 六、Stub/Fixture 设计(共享测试基础设施)
|
||
|
||
复用并扩展现有 `PreviewDocumentChunksQueryHandlerTests` 的 Stub 模式,新增以下共享测试基础设施(放 `tests/BuildFlow.Rag.Tests/Stub/`):
|
||
|
||
| Stub/Fixture | 实现接口 | 用途 |
|
||
|--------------|----------|------|
|
||
| `StubFileStorageClient` | `IFileStorageClient` | 提供文件流(内存 Stream),不调真实对象存储 |
|
||
| `StubDocumentExtractor` | `IDocumentExtractor` | 返回固定提取文本 |
|
||
| `StubDocumentExtractorFactory` | `IDocumentExtractorFactory` | 按扩展名分发 Stub |
|
||
| `StubEmbeddingService` | `IEmbeddingService` | 返回固定维度向量(如 384 维) |
|
||
| `StubAIChatAgent` | `IAIChatAgent` | RunAsync 返回固定文本;RunStreamingAsync yield 固定 token |
|
||
| `StubRagRetrievalService` | `IRagRetrievalService` | 返回预设 sources 列表(避开 pgvector) |
|
||
| `StubChatMessageCache` | `IChatMessageCache` | 内存字典模拟 Redis cache |
|
||
|
||
**测试 Host 搭建模式**:参考 `BuildFlow.IM.Integration.Tests` 的 `ImServerFixture`/`ImServerCollection` 模式,搭建 `RagEndToEndFixture`(注册 Stub 到 DI 容器 + EF InMemory + WebApplicationFactory)。
|
||
|
||
---
|
||
|
||
## 七、Phase 划分与里程碑
|
||
|
||
### Phase 1:RAG POC(本次实现)
|
||
|
||
| 里程碑 | 交付物 | 验收 |
|
||
|--------|--------|------|
|
||
| M1: 飞书落地 | 产品目录 + 产品概览 + RAG 2 份 SOP 文档 + 对应飞书任务 | 文档可访问,任务可勾选 |
|
||
| M2: 测试实现 | 2 个 E2E 测试文件 + 7 个 Stub 类 + RagEndToEndFixture | `dotnet test` 全绿 |
|
||
| M3: POC 验收 | 测试输出回填飞书文档§7,场景状态标记「通过」 | 模板固化,可复制 |
|
||
|
||
### Phase 2:复制到其余 5 模块(后续)
|
||
|
||
按 file → im → platform → workflow → scripting 顺序,每模块选 1-2 个端到端场景,套用 Phase 1 固化的模板。
|
||
|
||
---
|
||
|
||
## 八、风险与缓解
|
||
|
||
| 风险 | 缓解 |
|
||
|------|------|
|
||
| pgvector 无法 InMemory 测试 | POC 用 Stub RetrievalService;补充 Testcontainers 集成测试(需 Docker) |
|
||
| SSE 流式测试断言复杂 | 用 `HttpContent.ReadAsStreamAsync` 逐行解析 SSE,参考现有 `OpenAiCompatibleChatStreamParserTests` |
|
||
| StreamMessageEndpoint 直连不经 MediatR | 测试用 WebApplicationFactory 走真实 HTTP,而非直接调 Endpoint |
|
||
| Phase 2 模块差异大 | 8 段式模板的可变部分集中在§3 技术设计,其余段落复用 |
|
||
|
||
---
|
||
|
||
## 九、未来扩展(YAGNI - 本次不做)
|
||
|
||
- Web 前端端到端 SOP(Playwright,现有体系已支持)
|
||
- Portal/Client 端 SOP
|
||
- 飞书任务自动同步测试结果(CI/CD webhook)
|
||
- SOP 执行自动化(CLI 一键跑全部场景)
|
||
|
||
---
|
||
|
||
## 附录 A:关键文件索引
|
||
|
||
| 路径 | 作用 |
|
||
|------|------|
|
||
| `build-flow.api/src/modules/rag/Application/` | Command/Query/Handler 全清单 |
|
||
| `build-flow.api/src/modules/rag/Domain/Entities/` | KnowledgeBase/Document/DocumentChunk/Conversation/ChatMessage |
|
||
| `build-flow.api/src/modules/rag/Infrastructure/AI/RagRetrievalService.cs` | 向量检索核心 |
|
||
| `build-flow.api/src/BuildFlow.Host/Endpoints/Rag/` | HTTP 路由 |
|
||
| `build-flow.api/tests/BuildFlow.Rag.Tests/` | 现有测试 + 本次新增 E2E |
|
||
| `.agents/skills/rag-coding-api/SKILL.md` | SDD 规范来源 |
|
||
| 飞书 Wiki `7659208807054117857` / 产品节点 `StjYwzzsSisql1ki2oqc5KFEnZd` | SOP 产出落点 |
|