# Case Lite

> 小需求测试用例生成 skill。用户提供飞书 Docx、Wiki 或云空间原生 Markdown 文件链接 → 浏览并手动选择章节 → 生成场景结构 → 生成完整用例 → 可选 agent 自检补漏 → 写回搬山。不含模块拆分和自动选章，适用于单一功能点的小需求。当用户说"小需求用例"、"简单需求生成用例"、"case-lite"，或明确只有 1-2 篇文档且无需模块拆分时触发。

- Skill: `ex-imanity/case-lite` (Agent Skill, multi-file: 12 files)
- Install (CLI): `npx skillmds@latest add ex-imanity/case-lite`
- Raw SKILL.md: https://api.skillmd.com/api/skills/ex-imanity/case-lite/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: Ex-imanity (https://skillmd.com/u/ex-imanity)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/ex-imanity/case-lite

---


# case-lite：小需求用例生成

## 定位

面向**单一功能点的小需求**，核心理念是**精准输入**：用户自己选择相关章节，避免读入无关信息影响判断和浪费 token。

- 无模块拆分、无自动选章、无质量门禁
- 用户主导章节选择，AI 专注生成
- 可独立分发，不依赖 skills-migration 其他模块

## 环境要求

**进入 Step 1 之前，先做轻量 MCP 可用性检查。** 如果工具都可用，直接进入 Step 1，不要展开安装流程；只有依赖缺失、版本过旧或用户明确要求安装时，才进入“首次使用依赖向导”。这是渐进式披露：普通用例生成不应该被完整安装链路打断。

### 首次使用依赖向导（仅在缺依赖时）

优先使用内置脚本输出诊断报告：

```bash
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 配置。用户同意后，再运行：

```bash
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](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`，询问用户是否升级。

```json
{
  "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`。如果工具不存在，进入首次使用依赖向导，或提示用户手动添加：

```json
{
  "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：收集输入

从用户消息中提取：

1. **需求名称**（必须）→ 用于生成 `{需求slug}` 和产物目录
2. **迭代名称**（可选）→ 用于生成 `{迭代slug}`，决定产物目录是否多一层。一个迭代可包含多个需求
3. **文档链接**（必须）→ 飞书文档 URL 列表
4. **文档类型标签**（可选）→ 需求 / 前端 / 后端 / 客户端 / 算法

引导话术：

```
请提供以下信息：
1. 需求名称（如：AI审核模型变更）
2. 迭代名称（可选，一个迭代可包含多个需求，如：AI搜索9月迭代）
3. 相关文档链接（飞书链接，可多个）
4. 文档类型（可选，如：需求文档、后端技术方案等）

示例：
- 需求名称：AI审核模型变更
- 迭代名称：AI搜索9月迭代
- 后端技术方案：https://xxx.feishu.cn/docx/TOKEN1
- 需求文档：https://xxx.feishu.cn/wiki/TOKEN2
```

> 迭代名称是**可选项**。用户未提供时不要额外追问，直接按无迭代处理。

#### 1a. 确定产物根目录

1. **生成 slug**：需求名称和迭代名称都按同一规则转成 slug——小写英文 kebab-case；中文按语义译成英文（如「AI搜索9月迭代」→ `ai-search-202609`）；日期、版本号保留数字；不使用空格、中文或特殊字符。
2. **拼出产物根目录**：
   - 无迭代名称 → `case-lite-output/{需求slug}/`
   - 有迭代名称 → `case-lite-output/{迭代slug}/{需求slug}/`
3. **复用已有迭代目录**：若 `case-lite-output/{迭代slug}/` 已存在，直接复用，不要新建变体目录名。若存在语义相同但拼写不同的迭代目录（如 `ai-search-2026-09` vs `ai-search-202609`），先列给用户确认用哪一个。
4. **旧扁平目录冲突检测**（仅在提供了迭代名称时执行）：如果 `case-lite-output/{需求slug}/` 已作为扁平目录存在，**不要自动移动，也不要静默新建**，先提示用户并等待回复：

   ```
   检测到已存在扁平产物目录：case-lite-output/{需求slug}/
   本次指定了迭代「{迭代名称}」，目标目录为 case-lite-output/{迭代slug}/{需求slug}/

   请选择：
   - 回复「迁移」→ 将旧目录移动到迭代目录下，在已有产物基础上继续
   - 回复「新建」→ 保留旧目录不动，在迭代目录下新建一份产物
   ```

   用户选择「迁移」后再执行移动；用户未明确回复前不要动任何已有产物。

#### 1b. 初始化产物

创建产物根目录，并初始化以下产物（如文件不存在则创建）：

- `corpus/extra-context.md`
- `style-ref/reference-cases.md`
- `review.md`
- `chapters/child-documents.md`

初始化内容可使用简短占位说明，后续步骤再覆盖或追加。

**提供了迭代名称时**，还要维护 `case-lite-output/{迭代slug}/iteration-index.md`：

- 文件不存在则按下方格式创建
- 文件已存在则**追加或更新**本需求所在行，不要重写其他需求的行
- Step 1b 写入本需求行时，caseId 填 `—`，状态填 `进行中`
- 后续步骤中状态变化时回来更新对应行（Step 4 完成 → `用例已生成`；Step 6 写回成功 → 填入 caseId 并置为 `已写回`）

```markdown
# 迭代：{迭代名称}（{迭代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` 记录“不适用”：

1. **调用子文档工具**：对每个原始链接调用：
   ```
   get_child_documents(url="{url}", fetch_all=true, include_non_docx=false)
   ```
   只保留 `obj_type == "docx"` 的子文档。普通 docx 不在知识库节点树中时，工具会返回空 `children`，这是正常情况。
2. **递归展开**：如果返回的子文档 `has_child == true`，继续对该子文档的 `url` 调用 `get_child_documents(fetch_all=true, include_non_docx=false)`，直到没有更下级子文档。递归过程中用 `node_token`（无则用 `url`）去重，避免重复或循环。
3. **只收集链接和元数据**：本步骤只读取 `title`、`url`、`obj_token`、`obj_type`、`has_child` 等元数据，不读取正文内容，不调用 `get_document_blocks`。
4. **展示发现结果并等待用户确认纳入**：如果发现任意子文档，将其按层级列给用户，并说明“默认作为父文档的同类文档纳入”。用户可以选择全部纳入、按编号纳入、全部跳过，或指定个别子文档改为其他类型。

展示格式示例：

```markdown
检测到「后端技术方案」下存在子文档：

1. 后端技术方案 / 接口设计
   - 类型：默认同父文档（后端）
   - 链接：https://xxx.feishu.cn/wiki/CHILD1
2. 后端技术方案 / 接口设计 / 错误码说明
   - 类型：默认同父文档（后端）
   - 链接：https://xxx.feishu.cn/wiki/CHILD2

是否将这些子文档作为同类文档一起读取？
- 回复“全部纳入”
- 回复编号，如“1,2”
- 回复“跳过”
- 如需改类型，可回复“1=后端, 2=需求”
```

5. **扩展文档列表**：用户确认纳入的子文档加入后续 Step 2 的文档列表；默认继承父文档的文档类型标签（需求 / 前端 / 后端 / 客户端 / 算法等），除非用户显式改类型。
6. **落盘记录**：将完整发现树、用户确认纳入的子文档、继承/覆盖后的文档类型写入 `chapters/child-documents.md`。如果没有发现子文档，也写明“未发现可纳入的 docx 子文档”。

> **关键约束**：发现子文档不等于自动读取内容。必须经过用户确认纳入后，才进入 Step 2 的章节浏览与选章。

### Step 2：章节浏览与选择 [HITL]

#### 2a. 逐文档浏览章节并完成选章

对 Step 1 和 Step 1c 确认后的文档列表依次先进行**类型分流**，同一任务中允许同时包含原生 Markdown 和 Docx：

1. **原生 Markdown 分流**：
   - `/file/TOKEN` URL：调用 `get_markdown_file_sections(url="{url}", max_level=4, preview_chars=0)`；首轮只返回章节元数据，不能读取正文。
   - `/wiki/TOKEN` URL：先以 `preview_chars=0` 调用同一工具。若其成功返回，则该 Wiki 节点底层是原生 Markdown `file`，继续使用此分流；若返回“不是原生飞书 Markdown 文件”，才进入下方 Docx 分流。
   - **不要**对原生 Markdown 调用 `parse_document_id`、`extract_document_structure` 或 `get_document_blocks`。
2. **Docx 分流**：调用 `parse_document_id(url)` 获取 `document_id`，再调用 `extract_document_structure(document_id, max_level=4, output_format="json")`。
3. **处理无章节的文档**：若对应工具返回空章节列表（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` 搜索关键词定位）
4. **格式化展示**（正常有章节时）：将对应工具返回的章节树转为用户友好的编号列表。Markdown 使用 `id`、`section_path`、`range.start_line/end_line`；Docx 继续使用 `position`。

展示格式示例：

```markdown
## 📄 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 关键设计检查
```

5. **保存章节树**：写入 `chapters/{docKey}-chapters.md`

6. **引导用户选章**：

```
请选择需要纳入用例生成的章节（可多选）：

选择方式：
- 按编号：3, 4.1, 4.2（支持整章或子章节）
- 按关键词：整体设计, AI审核
- 混合：3, "核心实现"

输入 "全选" 选择该文档所有章节。
多个文档请分别选择。
```

7. **记录选择**：在 `chapters/{docKey}-chapters.md` 末尾追加选章结果，格式：

```markdown
## 用户选章结果
- {章节标题} | 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](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](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 文件分支**

1. 对同一文件的用户选章 ID 一次调用：
   ```
   get_markdown_file_sections(url="{url}", section_ids=["1", "2.1"])
   ```
2. 工具返回的 `selected_sections[].content` 是**原始 Markdown**。按返回顺序逐段落盘，格式：
   ```markdown
   <!-- SOURCE: {docKey} | {section_title} | line:{start}-{end} | native-markdown -->
   {原始 Markdown，不改写}
   <!-- END SOURCE -->
   ```
3. **不下载图片**：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`，格式：
```markdown
<!-- 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. 用户选择是

1. **先检查当前会话的可用 skills 列表，判断是否有可辅助 Review 的 skill**  
   优先检查当前会话的 available skills 中是否存在**精确名称** `case-design-strategy-skill`，其次再看其他覆盖度评审 / 用例设计策略类 skill。  
   - 这里检查的是**当前会话已加载的 skill 列表**，不要靠文件系统路径扫描，也不要依赖记忆中的旧别名
   - 如果当前会话可用 skills 中存在 `case-design-strategy-skill`，则**必须显式读取并使用它**，将当前步骤视为 coverage review / 边界异常覆盖评审
   - 如果当前会话中不存在该 skill，即使磁盘上已安装，也应明确告知“当前窗口未加载到该 skill”，随后直接进行 inline Review
   - 若用户希望使用该 skill 但当前窗口未加载到，可建议用户在新窗口 / 新会话重试

2. **如果用户在此时提供了补充信息**  
   将其追加保存到 `corpus/extra-context.md`（如新增一个 `## Review 补充信息` 段落），后续如用户允许修改文档，则将这些补充信息一并纳入。

3. **Review 重点**  
   重点检查：
   - 边界与异常覆盖是否缺失
   - 权限、状态流转、重复提交、幂等、空值/非法值等是否遗漏
   - 文档原文、补充信息与现有用例之间是否存在冲突
   - 是否存在内容重复或高度重合的测试点，尤其是"正常场景"与后补的"边界场景"之间

4. **Review 产出约束**  
   - 将检查结论保存到 `review.md`
   - **不要在 Review 完成后直接修改 `full.md`**
   - 先向用户简要汇报结果，再由用户决定是否允许修改 `full.md`

建议汇报格式：

```markdown
用例检查已完成，结论如下：
1. 缺失覆盖：...
2. 重复/重合：...
3. 其他风险：...

是否需要我根据这些建议修改 full.md？
```

#### 5c. 用户允许修改 `full.md`

修改前先判断补充信息的影响范围：

1. **仅影响已有测试点的步骤、断言或细节补充**  
   例如补充异常返回码、埋点校验、权限断言、边界值断言，但不新增独立场景或测试点。  
   这种情况：
   - 不改 `structure.md`
   - 直接对 `full.md` 做**增量补充**
   - 只修改受影响的测试点，避免整份 `full.md` 重生成

2. **引入新的测试点，但场景结构变化较小**  
   例如在某个既有场景下补充 1-2 个边界/异常测试点。  
   这种情况：
   - 先更新 `structure.md`
   - 将变更后的 `structure.md` 给用户确认
   - 用户确认后，默认只对 `structure.md` 中**新增或变更的测试点**增量补充 `full.md`
   - 不重写未受影响的已有测试点

3. **引入新的场景，或造成结构性变化**  
   例如场景拆分/合并、测试点大范围重排、编号体系明显变化、覆盖策略整体调整。  
   这种情况：
   - 先更新 `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 排查原因并修复格式问题后重试。

1. **收集 caseId**：
   ```
   请提供搬山用例 ID（caseId），用于写回搬山平台。
   如还未创建用例，请先在搬山创建后提供 ID。
   ```

2. **定位 writeback.py**：在本 skill 安装目录的 `scripts/writeback.py`。用 Glob 查找：
   ```
   Glob("**/case-lite/scripts/writeback.py")
   ```

3. **dry-run 验证**（**必须先执行，不可跳过**）：
   ```bash
   python {writeback.py路径} {产物根目录}/full.md \
     --case-id {caseId} --dry-run
   ```
   脚本会执行以下检查：
   - 格式验证：检测未被识别的 `##` / `###` 标题（格式漂移）
   - 数量校验：对比 markdown 原文与解析出的场景/测试点数
   - 完整性检查：检测无执行步骤的测试点
   
   **如果有 ⚠ 警告，必须先修复 full.md 再写回。** 不要带警告强行写入。
   展示场景概览和节点数给用户确认。

4. **用户确认后，执行写回**：
   ```bash
   python {writeback.py路径} {产物根目录}/full.md \
     --case-id {caseId} --modifier case-lite
   ```
   脚本自动完成：
   - 重复写入检测：若 caseId 已有 AI 节点则提醒并中止，请用户先在搬山平台清空用例后重试
   - 解析 markdown → 构建节点树 → 调用搬山 batchAddNode → testCaseDetail 验证
   
   agent 只需读取终端输出摘要并告知用户结果。

5. **产物**：
   - `writeback/node-tree.json` — 节点树 JSON
   - `writeback/writeback-log.json` — 写回日志

6. **更新迭代索引**（仅在提供了迭代信息时）：写回成功后，将 `iteration-index.md` 中本需求行的 `搬山 caseId` 填为本次 caseId，状态置为 `已写回`，更新时间置为当天。写回失败或中止时不要改动索引。

脚本源码：[scripts/writeback.py](scripts/writeback.py)。零外部依赖，纯 Python 标准库。
MCP 端点可通过环境变量 `BANSHAN_MCP_ENDPOINT` 覆盖。

---

## structure.md 格式规范

编号即层级：`场景3.1` 是 `场景3` 的子场景，`测试点3.1.2` 挂在 `场景3.1` 下。

```markdown
### 场景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` → `####`），纯粹为了好读。

### 单层场景（最常见，与旧版完全一致）

```markdown
## 场景1：场景标题

### 测试点1.1：测试点标题

#### 执行步骤
1. 前置：已登录XX系统，进入XX页面（前置条件融入第一步）
2. 操作步骤（精确到字段/按钮/接口级）

#### 预期结果
1. 期望结果（可量化断言）
```

### 多级场景

```markdown
## 场景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.1` 也下沉到某个子场景里
2. **编号不能断层**：出现 `场景3.1` 之前必须先有 `场景3`，且父场景要写在子场景前面
3. **编号不能重复**：同一个 `场景N` / `测试点N.M` 编号只能出现一次
4. **测试点至少两段编号**：`测试点1.1` ✓，`测试点1` ✗（测试点不能挂在根节点下）
5. **场景嵌套建议不超过 3 层**：超过只提示不阻塞，但通常说明该需求应拆成多个搬山用例。
   另有 20 层硬上限用于挡住畸形输入，正常写法碰不到

### 何时拆子场景

**该拆**（满足任一条）：

- 单个场景下测试点超过 8 个，且这些测试点天然沿某个维度聚成几堆
- 存在正交维度，不拆就要在每个测试点标题里重复维度名：端（iOS/Android/H5）× 功能、角色（管理员/普通用户）× 操作、支付渠道 × 结果
- 子集之间**前置条件差异大**：一组需要「已开通免密」，另一组需要「未开通」，混在一层会让每个测试点第一步都在重复铺环境
- 需求文档本身就是两级结构（模块 → 子功能），且这个结构对测试有意义

**不该拆**（满足任一条就别拆）：

- 场景下测试点 ≤ 5 个 —— 拆了只是多一层点击
- 拆出来的子场景只有 1 个测试点 —— 等于没拆，反而加深了树
- 只是为了对齐文档目录结构，测试上没有区分意义
- 需要拆到第 4 层 —— 说明这个需求本身该拆成多个搬山用例

**拆分维度优先级**：前置条件差异 > 端/角色等正交维度 > 功能子模块 > 正常流/异常流。
按前置条件拆收益最大，因为它直接减少每个测试点里的环境铺垫重复。

### 真实示例（来自参考用例 22328，writeback.py 可正确解析）

```markdown
## 场景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](assets/case-learning.md)。

