Ship Proof · 交付实证闸门
> Goal:把「做完了」这句话,变成一份能被别人复核的证据,并在这份证据成立之后才允许发布。
生成代码已经不是瓶颈了,验证才是。业界反复出现的失败是:162 个单元测试全绿,真机上 0 个主流程可用;
AI 自己写的测试编码了和代码相同的误解,于是永远通过;本地好好的,上线因为一个没打进产物的依赖而全挂。
本 skill 针对的正是这个缺口。它不帮你写代码,也不帮你接平台——它只做一件事:证明它真的能用。
与已有 Skill 的关系(不要混淆)
| 已有 Skill | 它做的事 | ship-proof 的位置 |
|---|---|---|
figma/github/gongfeng/cnb-connector |
接上某个平台,执行一次 API 操作 | 不涉及任何平台接入 |
computer-use |
操作桌面:点击、输入、截图 | 只做只读取证,不改动被测对象 |
发布为应用 |
把项目发上线,给一个链接 | 闸门通过后才调用它;发完还要复验线上 |
automation-task-manager |
管理定时任务 | 无关 |
一句话:发布为应用 负责发出去,ship-proof 负责证明发出去的东西能用。
两者串联,才是一条完整的交付链。
四条不可妥协的原则
- 证据必须独立于声明。 判定只看浏览器里的运行时行为(HTTP、控制台、网络、渲染、交互),
绝不读取或推断被测项目的源码来「确认它应该没问题」。一份由实现者自己写的测试,不是独立证据。 - 意图优先于技术指标。 先写清「用户最终要能做成什么事」(DoD),再取证。
没有断言的验证,只是在确认「代码做了代码做的事」。 - 阻断就是阻断。 存在 blocker 时返回非 0 退出码并拒绝发布。
不许把 blocker 重新表述成 warning 来让流程看起来顺利。 - 证明发布后的线上,而不只是本地。 路径、环境变量、CDN、缓存都会让「本地好的」在线上坏掉。
When to Use
- 用户要求验证 / 验收 / 确认能用 / 看看有没有问题 / 做完了吗。
- 用户要求交付 / 发布 / 上线,且存在一个可运行的 Web 产物。
- 你刚写完一个前端功能,需要在报告完成之前自证。
- 用户抱怨「上次说做好了结果不能用」——这说明缺的就是这道闸门。
When NOT to Use
- 纯文档、纯数据文件、无法在浏览器中运行的产物(改用对应的 lint / 校验)。
- 用户只是要你写代码,还没到交付环节——别在写代码的中途反复跑闸门,浪费时间。
- 用户明确说「不用验,直接发」——可以跳过闸门,但要说明你跳过了什么、风险是什么,
并且仍然执行发布后的线上复验(这一条不需要额外成本,且能兜住最坏情况)。
工作流
阶段 0 · 定义验收标准(不可跳过)
node <skill-dir>/scripts/dod.mjs --init "用户能在首页搜索商品并把结果加入购物车" --out dod.json
然后用真实的意图把它填实(模板里的选择器是占位的,必须替换成产物中真实存在的):
{
"schema": "ship-proof/dod@1",
"intent": "访客能在首页搜索『咖啡』,看到至少 3 条结果,并把第一条加入购物车后看到数量变为 1",
"checks": [
{ "id": "c1", "type": "visible", "selector": "#search-input", "desc": "搜索框在首屏可见" },
{ "id": "c2", "type": "flow", "desc": "完成一次搜索并加购", "steps": [
{ "action": "fill", "selector": "#search-input", "value": "咖啡" },
{ "action": "press", "key": "Enter" },
{ "action": "expect", "selector": ".result-item", "count": 3 },
{ "action": "click", "selector": ".result-item:first-child .add-btn" },
{ "action": "expect", "selector": "#cart-count", "text": "1" }
]},
{ "id": "c3", "type": "api", "desc": "搜索接口真实可用", "method": "GET",
"url": "/api/search?q=coffee", "expectStatus": 200, "expectJsonContains": { "total": 0 } }
],
"budget": { "maxConsoleErrors": 0, "maxFailedRequests": 0, "maxLoadMs": 5000, "maxOverflowPx": 20 }
}
写好后必须校验:
node <skill-dir>/scripts/dod.mjs --validate dod.json
> 断言写不出来,通常意味着需求本身还不清楚——这正是这一步的价值:
> 它会在你写代码之前就暴露「不知道要做成什么样」。
阶段 1 · 取证 + 闸门(本地)
node <skill-dir>/scripts/ship.mjs --dir ./dist --dod dod.json
- 静态目录:
--dir ./dist(自动起临时服务) - 动态服务:
--dir ./app --start-cmd "npm start" --port 3000 - 已有地址:
--url http://localhost:3000
产物落在 <项目>/.ship-proof/:evidence.local.json、report.local.md、shots/。
阶段 2 · 修复(有上限)
闸门未通过时:读 report.local.md 的阻断项,按优先级修:
page_error/console_error—— 未捕获异常会让其他断言连锁失败,先修它dod_fail/flow_fail—— 用户意图没达成,这是最严重的一类bad_status/failed_request/broken_image—— 产物或接口问题empty_shell/overflow—— 渲染与适配问题
最多自动修复 3 轮。 第 3 轮仍未通过就停下,如实告诉用户:
> 修了 3 轮仍未通过。剩余 N 项阻断,其中 X 项来自验收断言(意味着功能确实没做成)。
> 我判断再自动修下去是在猜,建议:① 确认需求是否是 A;② 让我定位到具体模块再看。
不要为了让报告变绿而放宽 budget 或删掉断言。 那是把体温计调低而不是退烧。
阶段 3 · 发布(需本轮显式授权)
node <skill-dir>/scripts/ship.mjs --dir ./dist --dod dod.json --publish
发布授权不跨轮次继承。 只有用户最近一条消息明确要求发布(出现
「发布 / 上线 / 部署 / 同步到线上 / publish / deploy」或明确要一个可分享链接),
才允许加 --publish。会话里早先同意过,不构成对本次的授权。
发布会覆盖别人此刻正在看的页面——绝不静默发布。
阶段 4 · 线上复验 + 汇报
脚本会自动对拿到的分享链接再跑一次完整取证,生成 report.online.md。
汇报时必须包含:
- 结论(通过 / 阻断)与健康分
- 分享链接(若已发布)
- 剩余阻断项逐条列出,不要说「基本没问题」
- 报告文件与截图路径
命令参考
| 脚本 | 作用 | 关键参数 |
|---|---|---|
dod.mjs |
生成/校验验收标准 | --init "意图" --out dod.json、--validate dod.json |
probe.mjs |
取证(可单独用) | --url / --dir、--dod、--flow、--api、--shots、--out |
gate.mjs |
判定 + 生成报告 | --evidence、--dod、--out report.md、--json verdict.json |
ship.mjs |
编排全流程 | --dir、--start-cmd、--port、--dod、--publish、--workdir |
退出码:0 = PASS,1 = BLOCK(有阻断项),2 = 用法/运行错误。
依赖:需要 playwright + chromium。缺失时按错误提示安装:
npm i -g playwright && npx playwright install chromium。
反模式(明确禁止)
| ❌ 不要做 | ✅ 应该做 |
|---|---|
| 读一遍源码就宣布「逻辑正确,没问题」 | 跑真实浏览器,用运行时证据说话 |
| 为了让流程继续,把 blocker 降级成 warning | 如实阻断,让问题暴露在发布之前 |
| 删掉过不去的断言 | 修实现;若断言本身写错了,改正断言并说明原因 |
| 只测桌面视口 | 默认必测 desktop(1440) + mobile(390),移动端是高发区 |
| 无限循环修下去 | 3 轮上限,超出即停下请用户确认方向 |
| 未经本轮授权就发布 | 严格按阶段 3 的授权规则执行 |
| 只验本地就宣布上线成功 | 发布后必须对线上链接复验 |
与其他工具的配合
- 需要真实点击复杂交互时,
probe的flow已覆盖常用动作
(goto/click/fill/press/hover/select/wait/expect/screenshot);
极少数需要人工判断的场景,才考虑agent-browser或computer-use。 - 发布能力复用
发布为应用的脚本,ship.mjs会自动查找并调用它。