# GEO 样例文章 E2E skill 工作流设计

## 目标

设计一个未来可以包装成 Codex skill 的自动验收工作流。用户启动该 skill 后，系统使用 `samples/articles/*.json` 中的测试文章，打开当前网页，按真实用户路径完成一键流式优化，并生成可复查的测试报告。

默认模式必须使用当前真实 LLM 配置，不使用 mock，不静默降级。本工作流的定位是“近似人工验收”的自动化，而不是快速单元测试或只测 API 的冒烟测试。

## 当前实现依据

当前首页主路径是一键流式优化：

- 用户填写 `访问密钥`。
- 用户在文章输入区填写 `文章内容`、`图片描述或图片链接`、`目标平台`、`用户要求`。
- 用户点击 `开始优化`。
- 前端调用 `POST /api/jobs/optimize-stream`，读取 NDJSON 流式事件。
- 页面依次展示事实卡、草稿或终稿、质量报告、导出入口。
- 终稿到达后显示 `optimized.md`、`optimized.docx`、`qa_report.json` 三个导出链接。

现有 `tests/e2e/mvp.spec.ts` 仍保留旧的“分析文章 -> 确认事实卡 -> 开始优化”路径，后续实现时应新增或替换为当前一键流式路径。

样例文件形状为：

```json
{
  "name": "Sample name",
  "input": {
    "title": "",
    "body": "",
    "image_lines": "",
    "platform": "official_site",
    "user_instructions": ""
  },
  "expectedHardFailures": [],
  "expectedWarnings": []
}
```

## 工作流范围

本设计覆盖：

- 本地启动或复用 Next.js 开发服务。
- 读取 `samples/articles` 下的 JSON 样例。
- 使用 Playwright 打开网页并完成当前 UI 主流程。
- 使用真实 LLM 跑事实提取、文章优化、QA 和必要的定向修复。
- 验证页面结果、质量报告和导出文件。
- 为每篇样例保存截图、日志、耗时和结构化结论。
- 输出汇总报告，供用户判断当前实现是否可交付或需要排查。

本设计不覆盖：

- 自动修复失败。
- token 级模型流式输出验证。
- 线上 Cloudflare 环境压测。
- 多用户协作、权限矩阵或发布平台 API。
- 把测试报告提交进 Git。

## 总体架构

建议把能力拆成三层。

第一层是仓库内 runner，作为真实执行入口。它负责读取配置、启动服务、调用 Playwright、收集报告。未来可以通过 `npm run test:e2e:samples` 或等价命令运行。

第二层是 Playwright 测试和辅助库。它负责页面操作、等待流式结果、下载导出文件、截图和 trace 采集。

第三层是 Codex skill 包装器。skill 本身保持轻薄，只做前置说明、参数解析、运行仓库命令、总结结果。仓库脚本仍是行为来源，避免把项目选择器和业务断言硬编码进用户级 skill。

## 默认执行模式

默认模式为 `live`：

- 从 `.env.local` 和当前 shell 环境读取配置。
- 要求 `API_ACCESS_KEY` 可用，除非 `API_AUTH_DISABLED=true`。
- 要求当前 `LLM_PROVIDER` 对应的真实 API key 已配置。
- 不安装 mock handler。
- 不把 LLM/provider/schema 错误转换成通过。
- 每篇样例顺序执行，默认并发为 1，避免真实 LLM 请求互相干扰或触发限流。

可选模式可以后续增加：

- `--sample <name-or-glob>`：只跑指定样例。
- `--limit <n>`：只跑前 n 篇样例。
- `--headed`：打开可见浏览器，方便人工观察。
- `--reuse-server`：复用已有服务。
- `--mock`：开发调试用 mock 模式，但不是默认入口。

## 服务启动策略

默认策略是启动专用本地服务：

1. 读取 `.env.local`，合并当前 shell 环境。
2. 为本次运行创建报告目录。
3. 设置 `APP_DATA_DIR=<reportDir>/app-data`，隔离 SQLite 数据和导出文件。
4. 找一个可用端口，优先 `3000`，被占用时使用下一个可用端口。
5. 运行 `npm run dev -- --port <port>`。
6. 用 `curl` 或 Playwright request 确认首页返回 `200 OK`。
7. 捕获服务 stdout/stderr，保存到报告目录。

如果用户显式传入 `--reuse-server`，runner 可以复用已有服务，但报告必须标记：

- `server_mode: "reused"`
- 无法保证数据隔离。
- 无法完整证明 LLM 日志来自本次运行，除非用户同时提供服务日志。

## LLM 参与证明

由于默认是 live 模式，runner 必须做两类证明。

前置证明：

- `LLM_PROVIDER` 有效。
- DeepSeek 模式下 `DEEPSEEK_API_KEY` 存在。
- OpenAI 模式下 `OPENAI_API_KEY` 存在。
- 报告只记录 provider 和 model，不记录密钥。

运行证明：

- 专用服务模式下，从服务日志中提取 `[llm:start]`、`[llm:response]`、`[llm:validated]`、`[llm:error]`。
- 每篇成功样例至少应看到 `fact_extractor`、`article_optimizer`、`quality_inspector` 任务。
- `targeted_rewriter` 只在 QA 失败触发修复时出现，不作为所有样例的必需任务。
- 如果 LLM 报错或 schema 校验失败，该样例失败，但报告保留真实错误和阶段。

## 样例读取

runner 按文件名排序读取 `samples/articles/*.json`，保证运行顺序稳定。

每个样例先做静态校验：

- `input.body` 必须是非空字符串。
- `input.platform` 必须是当前系统支持的发布平台。
- `input.image_lines` 和 `input.user_instructions` 缺失时按空字符串处理。
- `expectedHardFailures` 和 `expectedWarnings` 缺失时按空数组处理。

无效样例不进入浏览器流程，直接记为 `sample_invalid`。

## 浏览器操作流程

每篇样例使用新的页面，减少状态串扰。

页面操作按当前实现执行：

1. 打开首页。
2. 如果存在 `访问密钥` 输入框，填写 API key。
3. 填写文章输入框。当前标签是 `文章内容`，后续实现可兼容 `粘贴文章`。
4. 填写 `图片描述或图片链接`。
5. 选择 `目标平台`。
6. 填写 `用户要求`。
7. 点击 `开始优化`。
8. 等待事实卡区从待生成状态进入可见事实卡状态。
9. 等待结果区出现草稿、终稿或完成提示。
10. 等待 `质量报告` 出现。
11. 等待 `optimized.md`、`optimized.docx`、`qa_report.json` 三个导出入口出现。

等待终稿的默认超时建议为每篇 10 分钟。长文和真实 LLM 可能较慢，超时值必须可通过参数覆盖。

## 导出验证

当前导出链接是页面上的普通 `<a>`，但导出 API 仍需要 `x-api-key`。普通浏览器点击无法附加自定义 header。因此测试拆成两层：

- UI 层：确认页面显示三个导出入口。
- API 层：用 Playwright request 或 Node fetch 携带 `x-api-key` 请求导出 URL，确认文件可读。

导出断言：

- `optimized.md` 返回 200，正文非空，并包含优化标题或正文片段。
- `optimized.docx` 返回 200，content-type 或文件头符合 Word 文档预期。
- `qa_report.json` 返回 200，可解析为 JSON，并包含 `overall_status` 和 `checks`。

如果未来产品要求“用户直接点击即可下载”，应另加一个点击下载断言。按当前实现，该断言预期会暴露访问密钥无法通过 `<a>` header 传递的问题。

## QA 期望对照

`expectedHardFailures` 和 `expectedWarnings` 用于校准样例意图，但第一版不把它们作为硬性失败条件。

原因是 live LLM 输出会波动，而且优化后文章可能已经修复了原文缺陷。测试报告应记录：

- 样例预期的 hard failures。
- 样例预期的 warnings。
- 实际 QA 中 fail/warn/pass 的 rule_id。
- 预期缺陷是否被命中。
- 预期缺陷是否被修复或未复现。

这些差异在 `summary.md` 中作为“质量对照”呈现，不影响浏览器全流程是否通过。

## 失败分类

每篇样例只能有一个主失败分类，方便汇总：

- `preflight_failed`：环境、服务、密钥或 LLM 配置不满足。
- `sample_invalid`：样例 JSON 不符合最低要求。
- `page_flow_failed`：页面选择器、按钮、导航或 UI 状态异常。
- `stream_timeout`：超过单篇超时仍未出现终稿或失败提示。
- `stream_failed`：页面或捕获的流式事件返回失败。
- `llm_failed`：LLM provider、网络、额度、schema 校验等真实 LLM 错误。
- `export_failed`：导出入口缺失或带密钥请求导出文件失败。
- `console_error`：页面出现未允许的 console error 或 pageerror。
- `unknown_failed`：无法归类的异常。

失败时必须保留：

- 最后一张页面截图。
- 当前 URL。
- 控制台日志。
- 网络请求摘要。
- 服务日志片段。
- 如果能提取到，保留 LLM task 和错误文本。

## 报告输出

报告目录建议为：

```text
test-results/geo-sample-flow/<timestamp>/
```

目录结构：

```text
summary.json
summary.md
server.log
samples/
  <sample-slug>/
    result.json
    console.jsonl
    network.jsonl
    final.png
    failure.png
    trace.zip
    exports/
      optimized.md
      optimized.docx
      qa_report.json
```

`summary.json` 保存机器可读信息：

```json
{
  "started_at": "2026-07-01T00:00:00.000Z",
  "mode": "live",
  "provider": "deepseek",
  "model": "deepseek-v4-pro",
  "base_url": "http://127.0.0.1:3000",
  "totals": {
    "passed": 0,
    "failed": 0,
    "skipped": 0
  },
  "samples": []
}
```

每篇 `result.json` 保存：

```json
{
  "file": "samples/articles/title-quality.json",
  "name": "title quality sample",
  "status": "passed",
  "duration_ms": 180000,
  "job_id": "job_xxx",
  "qa_status": "warn",
  "qa_fail_rules": [],
  "qa_warn_rules": ["title_quality"],
  "expected_hard_failures": [],
  "expected_warnings": ["title_quality"],
  "exports": {
    "optimized.md": "passed",
    "optimized.docx": "passed",
    "qa_report.json": "passed"
  },
  "llm_tasks": ["fact_extractor", "article_optimizer", "quality_inspector"]
}
```

`summary.md` 面向人阅读，包含：

- 本次运行环境。
- 总通过率。
- 每篇样例的状态、耗时、QA 状态、失败原因。
- LLM 任务参与情况。
- 导出文件校验结果。
- 需要人工复查的问题列表。

## 安全和 Git 边界

报告目录可能包含模型输出、客户文章、服务日志和导出文件，不能提交进 Git。

后续实现时应确认：

- `test-results/` 被 `.gitignore` 忽略。
- 报告不写入真实 API key。
- 日志中的 `x-api-key`、Authorization header 和 provider key 必须脱敏。
- 不清理或覆盖用户已有 `data/`，除非 runner 启动了自己的隔离 `APP_DATA_DIR`。

## 后续实现建议

第一阶段：

- 新增 repo 内 sample runner 和 Playwright 辅助函数。
- 新增 `npm run test:e2e:samples`。
- 让 runner 默认 live 模式、顺序跑全部样例。
- 更新或替换旧的 `tests/e2e/mvp.spec.ts`，避免继续测试已下线的分步主路径。

第二阶段：

- 增加 `--sample`、`--limit`、`--headed`、`--reuse-server` 参数。
- 报告中加入 LLM 日志解析和 QA 期望对照表。
- 对失败样例生成更易读的 Markdown 复盘。

第三阶段：

- 创建 Codex skill。
- skill 读取本设计约定，调用 repo 命令。
- skill 在最终回复里摘要展示通过率、失败样例、报告路径和下一步排查入口。

## 验收标准

本设计实现后，用户启动 skill 应得到以下结果：

- 本地网页被真实打开。
- `samples/articles` 中的样例被逐篇填入页面。
- 每篇样例真实触发当前 LLM provider。
- 成功样例展示事实卡、优化结果、质量报告和三个导出入口。
- 导出文件通过带密钥请求验证可读。
- 失败样例保留可复查证据，而不是只返回一行超时或错误。
- 最终报告能回答三个问题：哪些样例跑通了，失败在哪里，当前实现是否值得人工继续验收。
