diff --git a/docs/superpowers/specs/2026-07-01-geo-sample-e2e-skill-design.md b/docs/superpowers/specs/2026-07-01-geo-sample-e2e-skill-design.md new file mode 100644 index 0000000..a3cbf32 --- /dev/null +++ b/docs/superpowers/specs/2026-07-01-geo-sample-e2e-skill-design.md @@ -0,0 +1,329 @@ +# 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 `:只跑指定样例。 +- `--limit `:只跑前 n 篇样例。 +- `--headed`:打开可见浏览器,方便人工观察。 +- `--reuse-server`:复用已有服务。 +- `--mock`:开发调试用 mock 模式,但不是默认入口。 + +## 服务启动策略 + +默认策略是启动专用本地服务: + +1. 读取 `.env.local`,合并当前 shell 环境。 +2. 为本次运行创建报告目录。 +3. 设置 `APP_DATA_DIR=/app-data`,隔离 SQLite 数据和导出文件。 +4. 找一个可用端口,优先 `3000`,被占用时使用下一个可用端口。 +5. 运行 `npm run dev -- --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 可能较慢,超时值必须可通过参数覆盖。 + +## 导出验证 + +当前导出链接是页面上的普通 ``,但导出 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`。 + +如果未来产品要求“用户直接点击即可下载”,应另加一个点击下载断言。按当前实现,该断言预期会暴露访问密钥无法通过 `` 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// +``` + +目录结构: + +```text +summary.json +summary.md +server.log +samples/ + / + 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。 +- 成功样例展示事实卡、优化结果、质量报告和三个导出入口。 +- 导出文件通过带密钥请求验证可读。 +- 失败样例保留可复查证据,而不是只返回一行超时或错误。 +- 最终报告能回答三个问题:哪些样例跑通了,失败在哪里,当前实现是否值得人工继续验收。