case-lite:小需求用例生成
定位
面向单一功能点的小需求,核心理念是精准输入:用户自己选择相关章节,避免读入无关信息影响判断和浪费 token。
- 无模块拆分、无自动选章、无质量门禁
- 用户主导章节选择,AI 专注生成
- 可独立分发,不依赖 skills-migration 其他模块
环境要求
进入 Step 1 之前,先做轻量 MCP 可用性检查。 如果工具都可用,直接进入 Step 1,不要展开安装流程;只有依赖缺失、版本过旧或用户明确要求安装时,才进入“首次使用依赖向导”。这是渐进式披露:普通用例生成不应该被完整安装链路打断。
首次使用依赖向导(仅在缺依赖时)
优先使用内置脚本输出诊断报告:
python case-lite/scripts/setup_mcp.py --agent claude-code
python case-lite/scripts/setup_mcp.py --agent codex
根据当前 Agent 选择 claude-code 或 codex。若无法判断 Agent 类型,先询问用户。
诊断报告展示后,必须询问用户是否同意自动写入全局 MCP 配置。用户同意后,再运行:
python case-lite/scripts/setup_mcp.py --agent <claude-code|codex> --fix
setup_mcp.py 会在写入前再次确认,写入前备份原配置,并只写缺失/过期的 MCP server。它只支持 Claude Code / Codex 的全局配置;其他 Agent 继续展示手动配置提示。写入注意:
- 配置路径不确定时不要盲写:若报告提示同时存在
~/.claude/.claude.json与~/.claude.json(旧版 cc-switch 可能覆盖生效路径),先和用户确认哪个是生效路径,再用--config <路径>指定;--fix --yes在路径不确定时会拒绝写入。 - 默认不覆盖已有
feishu-docx-blocks:已配置时脚本默认保留,仅在用户确认升级或显式加--replace-feishu时才替换(例如从源码安装切换到uvx@latest)。 - Claude Code 写入前请退出 Claude Code,避免运行中并发写回覆盖本次修改。
凭证安全:不要在 skill 中写入默认凭证,也不要在对话或日志中输出
FEISHU_APP_SECRET明文。FEISHU_APP_ID/FEISHU_APP_SECRET由用户从内部文档获取,并通过环境变量或脚本交互输入提供。默认凭证文档:https://gaotuedu.feishu.cn/wiki/CNBZwz8rwiew8dkXHt1cRIAAn8g#share-DrNhdQPiToYWMXxyC6nciHlknJh完整安装说明仅在需要时读取:references/install-mcp.md。
飞书文档工具(必需)
对 Docx/Wiki 尝试调用 get_child_documents、parse_document_id 或 extract_document_structure。当用户提供 /file/TOKEN,或 Wiki 节点解析为 file 时,必须确认 get_markdown_file_sections 可用。缺少本任务所需工具时进入首次使用依赖向导;如果已经配置但不是 feishu-docx-blocks@latest,询问用户是否升级。
{
"mcpServers": {
"feishu-docx-blocks": {
"command": "uvx",
"args": ["feishu-docx-blocks@latest"],
"env": {
"FEISHU_APP_ID": "<用户提供>",
"FEISHU_APP_SECRET": "<用户提供>"
}
}
}
}
首次调用工具或授权过期时,
feishu-docx-blocks会自动拉起浏览器完成飞书授权。get_child_documents依赖feishu-docx-blocks最新版及wiki:node:retrieve权限。原生 Markdown 文件还需要get_markdown_file_sections和drive:file:download授权。若所需工具缺失,提示用户重启 MCP / 重新授权 / 确认uvx feishu-docx-blocks@latest已生效;无法立即升级时,只能处理当前 MCP 已支持的文档类型。
搬山测试平台工具(推荐)
尝试调用 testCaseDetail。如果工具不存在,进入首次使用依赖向导,或提示用户手动添加:
{
"mcpServers": {
"Banshan": {
"type": "streamable-http",
"url": "https://tech.baijia.com/mcp-server/banshan/mcp"
}
}
}
搬山 MCP 用于获取参考用例(Step 3a)。写回功能由内置脚本直接调用 HTTP 完成,不依赖此配置。 如果用户无法安装,Step 3a 的参考用例获取降级为手动粘贴 markdown,其余流程不受影响。
产物目录
产物根目录由是否提供迭代信息决定。后续所有步骤中的产物路径都相对于「产物根目录」,本文档统一用 {产物根目录} 指代:
| 场景 | 产物根目录 |
|---|---|
| 未提供迭代信息(默认) | case-lite-output/{需求slug}/ |
| 提供了迭代信息 | case-lite-output/{迭代slug}/{需求slug}/ |
{需求slug} 由需求名称生成,如 ai-audit-model;{迭代slug} 由迭代名称生成,如 ai-search-202609。两者规则一致:小写英文 kebab-case,中文按语义译成英文,日期/版本号保留数字(详见 Step 1)。
产物根目录内部结构在两种场景下完全一致:
{产物根目录}/
├── chapters/
│ └── {docKey}-chapters.md ← 章节树展示(供用户选章)
│ └── child-documents.md ← 子文档发现结果与用户纳入选择
├── corpus/
│ ├── selected-corpus.md ← 用户选定章节的拼接语料
│ └── extra-context.md ← 用户补充信息(含 Review 阶段追加内容)
├── style-ref/
│ └── reference-cases.md ← 参考用例或默认风格说明
├── structure.md ← 场景结构(用户审核)
├── full.md ← 完整用例(用户审核)
├── review.md ← 用例检查结果(用户决定是否采纳)
└── writeback/
├── node-tree.json ← writeback.py 生成的节点树
└── writeback-log.json ← 写回日志
提供迭代信息时,迭代目录下额外维护一份跨需求索引:
case-lite-output/{迭代slug}/
├── iteration-index.md ← 本迭代需求清单与进度(跨需求,追加维护)
├── {需求slug-A}/
└── {需求slug-B}/
向后兼容:不提供迭代信息时,行为与旧版完全一致,已有的扁平产物目录无需迁移。
产物落盘约束
- 每一步结束前,必须确认该步骤对应的 markdown 产物已经写入磁盘,而不是只在对话中展示
- 若某一步没有实际内容,也要写入占位说明,避免后续流程判断不清。例如:
- 无补充信息 →
corpus/extra-context.md写明“当前无补充信息” - 跳过参考用例 →
style-ref/reference-cases.md写明“本次未提供参考用例,使用默认风格规则” - 跳过自检 →
review.md写明“用户选择跳过用例自检,直接进入回填”
- 无补充信息 →
- 任务完成后不要删除这些产物。
chapters/、corpus/、structure.md、full.md、review.md、writeback/都应保留,便于复盘和二次编辑 - 提供了迭代信息时,
iteration-index.md位于迭代目录(需求目录的上一级),维护方式是按需求行追加或更新,绝不整份重写覆盖其他需求的记录 - 进入下一步前,先检查上一步产物文件存在且内容已更新;若不存在,先补写文件再继续
- 如果后续步骤修订了前序结论,应更新对应 markdown 文件,而不是只修改终态文件
执行流程
Step 1:收集输入
从用户消息中提取:
- 需求名称(必须)→ 用于生成
{需求slug}和产物目录 - 迭代名称(可选)→ 用于生成
{迭代slug},决定产物目录是否多一层。一个迭代可包含多个需求 - 文档链接(必须)→ 飞书文档 URL 列表
- 文档类型标签(可选)→ 需求 / 前端 / 后端 / 客户端 / 算法
引导话术:
请提供以下信息:
1. 需求名称(如:AI审核模型变更)
2. 迭代名称(可选,一个迭代可包含多个需求,如:AI搜索9月迭代)
3. 相关文档链接(飞书链接,可多个)
4. 文档类型(可选,如:需求文档、后端技术方案等)
示例:
- 需求名称:AI审核模型变更
- 迭代名称:AI搜索9月迭代
- 后端技术方案:https://xxx.feishu.cn/docx/TOKEN1
- 需求文档:https://xxx.feishu.cn/wiki/TOKEN2
迭代名称是可选项。用户未提供时不要额外追问,直接按无迭代处理。
1a. 确定产物根目录
生成 slug:需求名称和迭代名称都按同一规则转成 slug——小写英文 kebab-case;中文按语义译成英文(如「AI搜索9月迭代」→
ai-search-202609);日期、版本号保留数字;不使用空格、中文或特殊字符。拼出产物根目录:
- 无迭代名称 →
case-lite-output/{需求slug}/ - 有迭代名称 →
case-lite-output/{迭代slug}/{需求slug}/
- 无迭代名称 →
复用已有迭代目录:若
case-lite-output/{迭代slug}/已存在,直接复用,不要新建变体目录名。若存在语义相同但拼写不同的迭代目录(如ai-search-2026-09vsai-search-202609),先列给用户确认用哪一个。旧扁平目录冲突检测(仅在提供了迭代名称时执行):如果
case-lite-output/{需求slug}/已作为扁平目录存在,不要自动移动,也不要静默新建,先提示用户并等待回复:检测到已存在扁平产物目录:case-lite-output/{需求slug}/ 本次指定了迭代「{迭代名称}」,目标目录为 case-lite-output/{迭代slug}/{需求slug}/ 请选择: - 回复「迁移」→ 将旧目录移动到迭代目录下,在已有产物基础上继续 - 回复「新建」→ 保留旧目录不动,在迭代目录下新建一份产物用户选择「迁移」后再执行移动;用户未明确回复前不要动任何已有产物。
1b. 初始化产物
创建产物根目录,并初始化以下产物(如文件不存在则创建):
corpus/extra-context.mdstyle-ref/reference-cases.mdreview.mdchapters/child-documents.md
初始化内容可使用简短占位说明,后续步骤再覆盖或追加。
提供了迭代名称时,还要维护 case-lite-output/{迭代slug}/iteration-index.md:
- 文件不存在则按下方格式创建
- 文件已存在则追加或更新本需求所在行,不要重写其他需求的行
- Step 1b 写入本需求行时,caseId 填
—,状态填进行中 - 后续步骤中状态变化时回来更新对应行(Step 4 完成 →
用例已生成;Step 6 写回成功 → 填入 caseId 并置为已写回)
# 迭代:{迭代名称}({迭代slug})
| 需求 | 产物目录 | 搬山 caseId | 状态 | 更新时间 |
|---|---|---|---|---|
| AI审核模型变更 | ai-audit-model/ | 22841 | 已写回 | 2026-09-03 |
| 新用户欢迎引导 | new-user-welcome-guide/ | — | 用例已生成 | 2026-09-03 |
状态取值:进行中 / 用例已生成 / 已写回。
1c. 递归发现 wiki 子文档 [HITL]
在进入章节浏览前,对 Wiki/Docx 链接尝试发现子文档;直接 /file/TOKEN 的原生 Markdown 文件没有 Docx 子文档树,跳过发现并在 chapters/child-documents.md 记录“不适用”:
- 调用子文档工具:对每个原始链接调用:
只保留get_child_documents(url="{url}", fetch_all=true, include_non_docx=false)obj_type == "docx"的子文档。普通 docx 不在知识库节点树中时,工具会返回空children,这是正常情况。 - 递归展开:如果返回的子文档
has_child == true,继续对该子文档的url调用get_child_documents(fetch_all=true, include_non_docx=false),直到没有更下级子文档。递归过程中用node_token(无则用url)去重,避免重复或循环。 - 只收集链接和元数据:本步骤只读取
title、url、obj_token、obj_type、has_child等元数据,不读取正文内容,不调用get_document_blocks。 - 展示发现结果并等待用户确认纳入:如果发现任意子文档,将其按层级列给用户,并说明“默认作为父文档的同类文档纳入”。用户可以选择全部纳入、按编号纳入、全部跳过,或指定个别子文档改为其他类型。
展示格式示例:
检测到「后端技术方案」下存在子文档:
1. 后端技术方案 / 接口设计
- 类型:默认同父文档(后端)
- 链接:https://xxx.feishu.cn/wiki/CHILD1
2. 后端技术方案 / 接口设计 / 错误码说明
- 类型:默认同父文档(后端)
- 链接:https://xxx.feishu.cn/wiki/CHILD2
是否将这些子文档作为同类文档一起读取?
- 回复“全部纳入”
- 回复编号,如“1,2”
- 回复“跳过”
- 如需改类型,可回复“1=后端, 2=需求”
- 扩展文档列表:用户确认纳入的子文档加入后续 Step 2 的文档列表;默认继承父文档的文档类型标签(需求 / 前端 / 后端 / 客户端 / 算法等),除非用户显式改类型。
- 落盘记录:将完整发现树、用户确认纳入的子文档、继承/覆盖后的文档类型写入
chapters/child-documents.md。如果没有发现子文档,也写明“未发现可纳入的 docx 子文档”。
关键约束:发现子文档不等于自动读取内容。必须经过用户确认纳入后,才进入 Step 2 的章节浏览与选章。
Step 2:章节浏览与选择 [HITL]
2a. 逐文档浏览章节并完成选章
对 Step 1 和 Step 1c 确认后的文档列表依次先进行类型分流,同一任务中允许同时包含原生 Markdown 和 Docx:
- 原生 Markdown 分流:
/file/TOKENURL:调用get_markdown_file_sections(url="{url}", max_level=4, preview_chars=0);首轮只返回章节元数据,不能读取正文。/wiki/TOKENURL:先以preview_chars=0调用同一工具。若其成功返回,则该 Wiki 节点底层是原生 Markdownfile,继续使用此分流;若返回“不是原生飞书 Markdown 文件”,才进入下方 Docx 分流。- 不要对原生 Markdown 调用
parse_document_id、extract_document_structure或get_document_blocks。
- Docx 分流:调用
parse_document_id(url)获取document_id,再调用extract_document_structure(document_id, max_level=4, output_format="json")。 - 处理无章节的文档:若对应工具返回空章节列表(Markdown 指代码围栏外没有 ATX 标题;Docx 指没有 H1-H4),告知用户并让其选择:
📄 文档 "{文档标题}" 未解析出任何章节标题,可能是纯文本/列表格式。 请选择处理方式: 1. 全量导入该文档内容(适合短文档) 2. 跳过该文档 3. 我手动指定需要的内容- 选 1:Docx 调用
get_document_blocks(document_id, fetch_all=true)获取全文;Markdown 仅在用户确认短文件可全文导入后调用get_markdown_file_sections(..., include_full_content=true)获取原始 Markdown - 选 2:跳过该文档,继续处理下一个
- 选 3:按用户指示获取部分内容(如用
search_document_content搜索关键词定位)
- 选 1:Docx 调用
- 格式化展示(正常有章节时):将对应工具返回的章节树转为用户友好的编号列表。Markdown 使用
id、section_path、range.start_line/end_line;Docx 继续使用position。
展示格式示例:
## 📄 UOS 相关需求 后端反讲(后端文档)
1. 一、变更历史
2. 二、背景
2.1 需求
2.2 关联方
3. 三、整体设计
3.1 Apollo 新增/变更配置
3.1.1 新增模型连接配置
3.1.2 变更业务模型路由配置
3.2 需求六(账号类型字段)
4. 四、详细设计
4.1 AI审核模型变更
4.1.1 整体流程图
4.1.2 核心实现
4.1.3 关键设计检查
保存章节树:写入
chapters/{docKey}-chapters.md引导用户选章:
请选择需要纳入用例生成的章节(可多选):
选择方式:
- 按编号:3, 4.1, 4.2(支持整章或子章节)
- 按关键词:整体设计, AI审核
- 混合:3, "核心实现"
输入 "全选" 选择该文档所有章节。
多个文档请分别选择。
- 记录选择:在
chapters/{docKey}-chapters.md末尾追加选章结果,格式:
## 用户选章结果
- {章节标题} | pos:{start}-{end}
- {章节标题} | pos:{start}-{end}
Docx 的 position range 从 extract_document_structure 的 JSON 返回值中读取。Markdown 记录 line:{start}-{end} 和 MCP 返回的 section_id。每个文档单独追加到其对应的 chapters 文件。
完成所有文档的章节展示与选章后,再统一进入补充信息确认。
飞书工具详细用法:见 references/feishu-tools-guide.md
2b. 统一收集补充信息 [HITL]
用户的补充内容可选,但询问本身不可省略。
必须在所有文档选章完成后,统一发出询问并等待用户明确回复,方可进入 Step 3。 不得因“文档已全选”、“用户最初没有主动提供补充信息”或“已有部分文档处理完成”而跳过此步骤。
如果用户在 Step 1 输入阶段已经提供了补充信息,也要先落盘到 corpus/extra-context.md,并在此处继续确认:
- 这些信息是否就是全部补充信息
- 是否还有新增内容需要纳入
已选定的文档章节会作为用例生成的主要依据。
是否还有补充信息需要纳入?例如:
- TAPD/Jira 上的需求描述或验收标准
- 产品口头沟通的额外规则或约束
- 接口文档、字段说明等技术细节
- 其他背景信息
可以直接粘贴文本,也可以回复”没有了”继续。
如果用户提供了补充信息,保存到 corpus/extra-context.md,在后续 Step 3 生成时与选定语料一同作为输入。
如果用户回复”没有了”,也要在 corpus/extra-context.md 中写明当前无补充信息。
Step 2 完成检查点(进入 Step 3 前必须确认):
- 所有文档的章节树文件
chapters/{docKey}-chapters.md已写入 ✓- 用户已完成所有文档的选章,且选择结果已记录 ✓
- 补充信息询问已统一发出,并收到用户明确回复(补充内容或”没有了”均可),
corpus/extra-context.md已写入 ✓三项均满足后,方可进入 Step 3。
Step 3:生成场景结构 [HITL]
3a. 收集参考用例(可选,不阻塞)
是否有可以参考的优秀用例?(用于学习文本风格和覆盖度)
- 输入搬山用例 ID(如:20612)
- 粘贴 markdown 格式的用例片段
- 回复"跳过"使用默认风格
- 搬山 caseId → 调用
testCaseDetail(caseId)获取,保存到style-ref/reference-cases.md - markdown 片段 → 直接保存到
style-ref/reference-cases.md - 跳过 → 使用内置 assets/case-learning.md 默认风格规则
如果用户跳过参考用例,也要在 style-ref/reference-cases.md 中写明本次使用默认风格规则,避免该产物缺失。
参考用例学习规则(必须遵守):
- 只学习文本风格:学习参考用例中场景/测试点/步骤/结果的文本措辞和描述粒度
- 不学习节点层级:无论参考用例的结构是什么样的(可能有前置条件节点、可能有多层嵌套),生成时始终严格按本 skill 的 full.md 格式规范输出
- 不学习优先级:即使参考用例有 P0/P1/P2 标记,也不要在 full.md 中生成优先级标记(agent 判断的优先级不准,由用户事后标注)
3b. 拉取选定语料
⛔ 严禁改写 —
selected-corpus.md必须原文写入get_document_blocks返回的文本,包括表格、代码块、JSON 示例、curl 命令、字段说明。不得摘要、精简、改写任何内容。 唯一允许的处理是添加章节分隔注释<!-- SOURCE: ... -->。
对每个选定章节,按文档类型走对应分支,并在每个文档完成后立即追加写入 corpus/selected-corpus.md:
原生 Markdown 文件分支
- 对同一文件的用户选章 ID 一次调用:
get_markdown_file_sections(url="{url}", section_ids=["1", "2.1"]) - 工具返回的
selected_sections[].content是原始 Markdown。按返回顺序逐段落盘,格式:<!-- SOURCE: {docKey} | {section_title} | line:{start}-{end} | native-markdown --> {原始 Markdown,不改写} <!-- END SOURCE --> - 不下载图片:Markdown 内的图片、链接和附件引用作为原始文本保留。不要调用
download_image_blocks或download_board_as_image,因为它们只适用于 Docx blocks。
Docx 文档分支
对每个选定章节,按顺序执行以下三步,每章完成后立即追加写入 corpus/selected-corpus.md,不等所有章节拉完再统一写盘:
Step 3b-1:拉取文本和媒体元数据
get_document_blocks(document_id, start_position=X, end_position=Y)
返回章节的文本内容和媒体元数据。注意:图片/画板只返回 block_id 和 token,不包含实际图片。
Step 3b-2:下载图片(如果上一步返回了图片元数据)
download_image_blocks(document_id, image_block_ids=["block_id_1", "block_id_2"])
将实际图片下载到本地,返回可视化的图片内容。
- 下载成功:在 corpus 对应位置插入
[📷 图片: {上下文描述}] - 下载失败:插入
[📷 图片下载失败: block_id={id},跳过],不阻塞后续流程
如果章节包含画板(流程图、架构图等),额外调用:
download_board_as_image(board_tokens=["token_1"], document_id=document_id, board_block_ids=["block_id_1"])
Step 3b-3:立即追加写入
完成上两步后,立即将本章内容追加到 corpus/selected-corpus.md,格式:
<!-- SOURCE: {docKey} | {section_title} | pos:{start}-{end} -->
{章节文本内容,原文逐字写入,不做任何处理}
[📷 图片: {image_description_or_context}]
<!-- END SOURCE -->
所有章节处理完毕后,corpus/selected-corpus.md 应包含每个章节各自的 <!-- SOURCE --> 注释块。
关键:文档中的流程图、接口说明图、交互稿等视觉信息对用例生成至关重要。 如果跳过图片下载,生成的用例可能遗漏图中描述的分支逻辑和交互细节。
Step 3b 完成检查点(进入 3c 前必须确认):
corpus/selected-corpus.md已存在,且包含所有选定章节各自的<!-- SOURCE -->注释头 ✓- 文件内容包含原始代码块、JSON 示例、表格等,未被摘要替代 ✓
3c. 生成场景结构
基于以下输入生成 structure.md:
- 选定语料(
selected-corpus.md) - 补充信息(
extra-context.md,如有) - 风格参考(参考用例或默认规则)
- 需求名称
生成 prompt 要点:
- 按功能/流程拆分场景
- 默认单层场景。只有满足「何时拆子场景」的条件时才引入子场景,并遵守「场景分层规则」(同一场景下不能混排子场景和测试点)
- 每个场景列出测试点,标注优先级(P0/P1/P2)
- 覆盖维度:正常流程、异常处理、边界值、安全/权限
- 命名格式:操作对象 + 操作 + 结果/场景(不用"验证"/"测试"前缀)
- 在输出
structure.md前先做一次测试点去重:如果两个测试点覆盖目标、核心操作和断言对象本质相同,只是表述不同,优先合并,避免重复测试点先进入审核流
生成后输出 structure.md 并请用户审核:
场景结构已生成,请审核:
[展示 structure.md 内容]
请确认或提出修改意见。确认后将生成完整用例。
Step 4:生成完整用例 [HITL]
基于已审核的 structure.md + selected-corpus.md 生成 full.md。
生成 prompt 要点:
- 严格按照 structure.md 的场景和测试点展开,编号必须与 structure.md 完全一致(编号即层级路径,改编号等于改树结构)
- 每个测试点只包含:执行步骤、预期结果(不生成"前置条件"section)
- 如有前置条件,将其精简后融入执行步骤的第一步(如"1. 已登录管理后台,进入XX页面")
- 执行步骤精确到字段名/按钮名/接口路径
- 预期结果多层验证(UI/交互/接口/数据层),断言可量化
- 不扩写 structure.md 中没有的场景
- 不生成优先级标记:full.md 的场景和测试点标题中不要包含 P0/P1/P2(优先级由用户事后标注)
- 即使参考用例中包含"前置条件"节点或优先级标记,也不要模仿
- 生成时先做一次去重检查:如果不同场景下的测试点本质上是同一组前置条件 + 操作 + 断言,只是表述不同,优先合并或收敛,避免 full.md 出现重复用例
生成后输出 full.md 并请用户审核:
完整用例已生成,请审核:
[展示 full.md 内容或告知文件路径]
确认后,可选择先做 agent 自检,再决定是否进入回填。
用户审核通过后,如果本次提供了迭代信息,将 iteration-index.md 中本需求的状态更新为 用例已生成。
Step 5:用例检查 [HITL]
在 full.md 生成并完成用户初审后,先主动询问:
完整用例已生成。写回搬山前,是否需要我先自检这份用例?
如果你还有补充信息,也可以现在一并发我,例如:
- 边界规则或异常处理要求
- 产品/研发口头补充的约束
- 线上问题、历史缺陷、埋点或权限要求
- 其他刚想到但前面没写进文档的内容
- 回复"否"或"直接回填":跳过自检;如果同时附带补充信息,则仅归档到 markdown 产物,不修改当前用例,随后进入回填搬山
- 回复"是":先做用例检查
- 也可以在回复里附带补充信息
5a. 用户选择否
如果用户在“否/直接回填”时附带了补充信息:
- 追加保存到
corpus/extra-context.md - 在
review.md记录“用户补充了信息,但本轮未启用自检或改稿,直接回填” - 不要修改
structure.md或full.md
如果用户没有补充信息,也要在 review.md 写明“用户选择跳过用例自检,直接进入回填”。
直接进入 Step 6 回填搬山。
5b. 用户选择是
先检查当前会话的可用 skills 列表,判断是否有可辅助 Review 的 skill
优先检查当前会话的 available skills 中是否存在精确名称case-design-strategy-skill,其次再看其他覆盖度评审 / 用例设计策略类 skill。- 这里检查的是当前会话已加载的 skill 列表,不要靠文件系统路径扫描,也不要依赖记忆中的旧别名
- 如果当前会话可用 skills 中存在
case-design-strategy-skill,则必须显式读取并使用它,将当前步骤视为 coverage review / 边界异常覆盖评审 - 如果当前会话中不存在该 skill,即使磁盘上已安装,也应明确告知“当前窗口未加载到该 skill”,随后直接进行 inline Review
- 若用户希望使用该 skill 但当前窗口未加载到,可建议用户在新窗口 / 新会话重试
如果用户在此时提供了补充信息
将其追加保存到corpus/extra-context.md(如新增一个## Review 补充信息段落),后续如用户允许修改文档,则将这些补充信息一并纳入。Review 重点
重点检查:- 边界与异常覆盖是否缺失
- 权限、状态流转、重复提交、幂等、空值/非法值等是否遗漏
- 文档原文、补充信息与现有用例之间是否存在冲突
- 是否存在内容重复或高度重合的测试点,尤其是"正常场景"与后补的"边界场景"之间
Review 产出约束
- 将检查结论保存到
review.md - 不要在 Review 完成后直接修改
full.md - 先向用户简要汇报结果,再由用户决定是否允许修改
full.md
- 将检查结论保存到
建议汇报格式:
用例检查已完成,结论如下:
1. 缺失覆盖:...
2. 重复/重合:...
3. 其他风险:...
是否需要我根据这些建议修改 full.md?
5c. 用户允许修改 full.md
修改前先判断补充信息的影响范围:
仅影响已有测试点的步骤、断言或细节补充
例如补充异常返回码、埋点校验、权限断言、边界值断言,但不新增独立场景或测试点。
这种情况:- 不改
structure.md - 直接对
full.md做增量补充 - 只修改受影响的测试点,避免整份
full.md重生成
- 不改
引入新的测试点,但场景结构变化较小
例如在某个既有场景下补充 1-2 个边界/异常测试点。
这种情况:- 先更新
structure.md - 将变更后的
structure.md给用户确认 - 用户确认后,默认只对
structure.md中新增或变更的测试点增量补充full.md - 不重写未受影响的已有测试点
- 先更新
引入新的场景,或造成结构性变化
例如场景拆分/合并、测试点大范围重排、编号体系明显变化、覆盖策略整体调整。
这种情况:- 先更新
structure.md - 将变更后的
structure.md给用户重新审核 - 用户确认后,再全量重生成
full.md
- 先更新
无论是哪一种,修改前都先做去重判断,再补充内容:
- 如果拟新增的测试点与现有测试点在前置条件、核心操作、断言目标上本质相同,只是换了说法,不要新增重复用例
- 优先选择:
- 合并到已有测试点
- 在已有测试点的执行步骤 / 预期结果中补充缺失断言
- 仅在确实新增了独立覆盖目标时,再新增测试点
修改完成后:
- 更新对应产物文件(
structure.md/full.md/review.md) - 向用户展示变更摘要或变更后的相关片段
- 待用户确认后进入 Step 6
5d. 用户不允许修改 full.md
保留当前 full.md 不变,直接进入 Step 6。
Step 6:回填搬山 [HITL]
建议:本步骤为纯机械操作,优先使用
writeback.py脚本完成全部处理(解析 → 写入 → 验证),避免 LLM 读取 full.md 浪费 token。 如果脚本执行失败,允许 agent 读取 full.md 排查原因并修复格式问题后重试。
收集 caseId:
请提供搬山用例 ID(caseId),用于写回搬山平台。 如还未创建用例,请先在搬山创建后提供 ID。定位 writeback.py:在本 skill 安装目录的
scripts/writeback.py。用 Glob 查找:Glob("**/case-lite/scripts/writeback.py")dry-run 验证(必须先执行,不可跳过):
python {writeback.py路径} {产物根目录}/full.md \ --case-id {caseId} --dry-run脚本会执行以下检查:
- 格式验证:检测未被识别的
##/###标题(格式漂移) - 数量校验:对比 markdown 原文与解析出的场景/测试点数
- 完整性检查:检测无执行步骤的测试点
如果有 ⚠ 警告,必须先修复 full.md 再写回。 不要带警告强行写入。 展示场景概览和节点数给用户确认。
- 格式验证:检测未被识别的
用户确认后,执行写回:
python {writeback.py路径} {产物根目录}/full.md \ --case-id {caseId} --modifier case-lite脚本自动完成:
- 重复写入检测:若 caseId 已有 AI 节点则提醒并中止,请用户先在搬山平台清空用例后重试
- 解析 markdown → 构建节点树 → 调用搬山 batchAddNode → testCaseDetail 验证
agent 只需读取终端输出摘要并告知用户结果。
产物:
writeback/node-tree.json— 节点树 JSONwriteback/writeback-log.json— 写回日志
更新迭代索引(仅在提供了迭代信息时):写回成功后,将
iteration-index.md中本需求行的搬山 caseId填为本次 caseId,状态置为已写回,更新时间置为当天。写回失败或中止时不要改动索引。
脚本源码:scripts/writeback.py。零外部依赖,纯 Python 标准库。
MCP 端点可通过环境变量 BANSHAN_MCP_ENDPOINT 覆盖。
structure.md 格式规范
编号即层级:场景3.1 是 场景3 的子场景,测试点3.1.2 挂在 场景3.1 下。
### 场景1:场景标题
- 测试点1.1:测试点标题(P0)
- 测试点1.2:测试点标题(P1)
### 场景2:场景标题
- 测试点2.1:测试点标题(P0)
- 测试点2.2:测试点标题(P2)
### 场景3:场景标题(含子场景,本层不直接挂测试点)
#### 场景3.1:子场景标题
- 测试点3.1.1:测试点标题(P0)
- 测试点3.1.2:测试点标题(P1)
#### 场景3.2:子场景标题
- 测试点3.2.1:测试点标题(P0)
- 不拆子场景时,结构与旧版完全一致
- 一个场景要么直接挂测试点,要么只挂子场景,不能两者混排(见下方「场景分层规则」)
full.md 格式规范
核心约定:编号即路径
writeback.py 用编号段数决定树的层级,不看标题的 # 级别。
| 写法 | 编号路径 | 在树中的位置 |
|---|---|---|
场景1:登录 |
(1) |
顶层场景 |
场景3:支付 |
(3) |
顶层场景 |
场景3.1:微信支付 |
(3,1) |
场景3 的子场景 |
测试点1.1:正常登录 |
(1,1) |
场景1 下的测试点 |
测试点3.1.2:余额不足 |
(3,1,2) |
场景3.1 下的测试点 |
推论(很重要):
- 标题级别只影响阅读观感,不影响解析结果。写错级别不会导致节点丢失或错位,写错编号才会。
执行步骤/预期结果的标记与深度解耦:#### 执行步骤、##### 执行步骤、**执行步骤**都能被识别。推荐统一用####,不必随嵌套深度往下顺延。- 建议标题级别 = 场景深度 + 1(
场景1→##,场景3.1→###,场景3.1.1→####),纯粹为了好读。
单层场景(最常见,与旧版完全一致)
## 场景1:场景标题
### 测试点1.1:测试点标题
#### 执行步骤
1. 前置:已登录XX系统,进入XX页面(前置条件融入第一步)
2. 操作步骤(精确到字段/按钮/接口级)
#### 预期结果
1. 期望结果(可量化断言)
多级场景
## 场景3:支付
### 场景3.1:微信支付
#### 测试点3.1.1:余额充足时支付成功
#### 执行步骤
1. 已登录且微信账户余额 ≥ 订单金额,进入收银台
2. 选择「微信支付」,点击「确认支付」
#### 预期结果
1. 调起微信收银台,订单状态由「待支付」变为「已支付」
2. POST /order/pay 返回 code=0
#### 测试点3.1.2:余额不足时支付失败
#### 执行步骤
1. 已登录且微信账户余额 < 订单金额,进入收银台
2. 选择「微信支付」,点击「确认支付」
#### 预期结果
1. 提示「余额不足」,订单保持「待支付」
### 场景3.2:支付宝支付
#### 测试点3.2.1:免密支付直接扣款
#### 执行步骤
1. 已开通支付宝免密,进入收银台
2. 选择「支付宝」,点击「确认支付」
#### 预期结果
1. 无需二次确认直接扣款成功
对应写入搬山后的树形:
场景3:支付
├── 场景3.1:微信支付
│ ├── 测试点3.1.1:余额充足时支付成功
│ │ └── 执行步骤 → 预期结果
│ └── 测试点3.1.2:余额不足时支付失败
│ └── 执行步骤 → 预期结果
└── 场景3.2:支付宝支付
└── 测试点3.2.1:免密支付直接扣款
└── 执行步骤 → 预期结果
场景分层规则(硬约束,违反会被 writeback.py 拒绝)
- 不允许混搭:任一场景的直接子节点,要么全是子场景,要么全是测试点,不能两者共存。
- ✗
场景1下同时有测试点1.1和场景1.1 - ✓ 想加子场景,就把
测试点1.1也下沉到某个子场景里
- ✗
- 编号不能断层:出现
场景3.1之前必须先有场景3,且父场景要写在子场景前面 - 编号不能重复:同一个
场景N/测试点N.M编号只能出现一次 - 测试点至少两段编号:
测试点1.1✓,测试点1✗(测试点不能挂在根节点下) - 场景嵌套建议不超过 3 层:超过只提示不阻塞,但通常说明该需求应拆成多个搬山用例。 另有 20 层硬上限用于挡住畸形输入,正常写法碰不到
何时拆子场景
该拆(满足任一条):
- 单个场景下测试点超过 8 个,且这些测试点天然沿某个维度聚成几堆
- 存在正交维度,不拆就要在每个测试点标题里重复维度名:端(iOS/Android/H5)× 功能、角色(管理员/普通用户)× 操作、支付渠道 × 结果
- 子集之间前置条件差异大:一组需要「已开通免密」,另一组需要「未开通」,混在一层会让每个测试点第一步都在重复铺环境
- 需求文档本身就是两级结构(模块 → 子功能),且这个结构对测试有意义
不该拆(满足任一条就别拆):
- 场景下测试点 ≤ 5 个 —— 拆了只是多一层点击
- 拆出来的子场景只有 1 个测试点 —— 等于没拆,反而加深了树
- 只是为了对齐文档目录结构,测试上没有区分意义
- 需要拆到第 4 层 —— 说明这个需求本身该拆成多个搬山用例
拆分维度优先级:前置条件差异 > 端/角色等正交维度 > 功能子模块 > 正常流/异常流。 按前置条件拆收益最大,因为它直接减少每个测试点里的环境铺垫重复。
真实示例(来自参考用例 22328,writeback.py 可正确解析)
## 场景1:速搭端-下发重置密码链接
### 测试点1.1:未输入userId或重置原因时执行,不出现链接
#### 执行步骤
1. 打开「飞途速搭-操作类-账号密码重置」页面
2. 保持userId输入框为空,重置原因输入框为空,点击「执行」按钮
3. 分别验证仅填写userId不填重置原因、仅填写重置原因不填userId两种情况
#### 预期结果
1. 三种情况下页面均不出现重置密码链接
2. 不触发下发链接请求
### 测试点1.2:填写userId和重置原因后点击执行,生成重置密码链接
#### 执行步骤
1. 打开「飞途速搭-操作类-账号密码重置」页面
2. 在userId输入框中输入目标用户的userId(如6819696814)
3. 在重置原因输入框中输入原因(如"无法收到手机验证码")
4. 点击「执行」按钮
#### 预期结果
1. 请求 POST /v1/user/visitor/adminReset/sendLink 接口,入参包含targetUserId、reason、employeeId
2. 接口返回有效的重置密码链接
3. 页面出现蓝色可点击链接
4. 后台记录客服操作日志
- 执行步骤 / 预期结果:
####级标题 + 编号列表 - 不生成
**前置条件**section,前置条件融入执行步骤第一步
full.md 写回解析约束(必须严格遵守)
writeback.py 用正则逐行解析 full.md,以下格式偏差会导致节点丢失:
writeback.py 用正则逐行解析 full.md,并把编号解释为树路径。违反规则的后果分三档:阻塞错误(拒绝写回)、警告(dry-run 拒绝放行)、建议(只提示)。
阻塞错误 —— 结构不合法,dry-run 和写回都会中止(exit 2)
| 规则 | 正确 | 错误 |
|---|---|---|
| 场景直接子节点不混搭 | 场景1 下全是测试点,或全是子场景 |
场景1 下同时有 测试点1.1 和 场景1.1 |
| 编号不断层,父在子前 | 先 场景3,再 场景3.1 |
出现 场景3.1 但没有 场景3 |
| 编号不重复(场景和测试点都算) | 每个编号只出现一次 | 两个 ## 场景1:,或两个 ### 测试点1.1: |
| 测试点至少两段编号 | 测试点1.1:标题 |
测试点1:标题 |
| 场景嵌套不超过 20 层(硬上限) | 正常写法远低于此值 | 畸形输入,如 20 层以上嵌套 |
警告 —— 内容可能丢失,dry-run 会拒绝放行
| 规则 | 正确 | 错误(会被跳过) |
|---|---|---|
| 场景/测试点用阿拉伯数字 + 冒号 | 场景1:标题、场景1:标题 |
场景一:标题、场景1 标题(无冒号) |
| 标题必须是这四类之一 | 场景N / 测试点N.M / 执行步骤 / 预期结果 | 自创的 ### 补充说明 等标题 |
步骤/结果内容用编号列表;编号步骤下可用 - 子项列举输入数据 |
1. 分别输入以下内容: + - 输入A |
纯 bullet 作为主步骤,缺少编号父步骤 |
| 每个测试点必须有执行步骤 | 先 执行步骤 再 预期结果 |
只有预期结果无执行步骤(结果会被丢弃) |
| 场景不能为空 | 每个场景至少有一个测试点或子场景 | 空场景(写进去是无意义节点) |
| 编号连续不跳号 | 测试点1.1 测试点1.2 测试点1.3 |
测试点1.1 测试点1.3(通常是生成时被截断) |
| 同级编号按升序书写 | 测试点1.1 写在 测试点1.2 前面 |
先写 测试点1.2 再写 测试点1.1(写入搬山后的节点次序由书写顺序决定,与编号不一致会造成误读) |
| 每段编号不超过 6 位 | 场景12:标题 |
场景{一长串数字}:标题(不会被识别为场景) |
建议 —— 只打印,不阻塞
| 提示 | 含义 |
|---|---|
| 场景嵌套超过 3 层 | 平台支持,但通常说明该拆成多个搬山用例(20 层是硬上限,见阻塞错误) |
| 某场景只有 1 个子场景 | 拆了等于没拆,可合并回上层 |
| 某场景下测试点超过 10 个 | 可考虑按维度拆子场景 |
不再是问题的(相比旧版放宽)
| 项 | 说明 |
|---|---|
标题的 # 级别 |
解析时忽略,写错级别不会导致节点丢失或错位 |
#### 执行步骤 vs **执行步骤** |
两种写法都能识别,任意 # 级别也都能识别 |
--- 分割线 |
已明确跳过,不干扰解析 |
仍然保留:不生成
**前置条件**section,其内容会被解析器跳过、不写入搬山,前置条件应融入执行步骤第一步。生成 full.md 后必须跑
writeback.py --dry-run。它会打印完整的场景/测试点树形概览,逐层核对比数数字更可靠。
工具依赖
| 工具 | 用途 | 阶段 | 必需 |
|---|---|---|---|
| feishu-docx-blocks MCP | 文档解析、章节获取、语料拉取 | Step 2, 3 | 是 |
| Banshan MCP | 通过 caseId 获取参考用例 | Step 3a | 推荐(可降级) |
| scripts/writeback.py | full.md 解析 + 节点树构建 + HTTP 写回搬山 | Step 6 | 内置,无需安装 |
风格规则
详见 assets/case-learning.md。