Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@
- 本会话用户已授权本地真实模型/浏览器联调,不需要再次索要 `.env` 或确认这类验收。新会话按其授权范围执行;不得把旧默认限制当成本会话的额外审批。`git commit` / `git push` / 对外发布仍未授权。
- 不改 `benchmark/*/gold.json`、`human-labels.json`、`held-out/`、`rubric/`;不把它们放进任何提示词。
- 不让自愈改 `oracle`。
- 动 `packages/harness-testing/src/casegen/prompts.ts` 或 `plugins/testpilot/skills/**` 时:`node scripts/check-drift.mjs` 必须绿,`plugins/testpilot/plugin.json` 的 `skillVersions` 跳版本。
- 动 `packages/harness-testing/src/casegen/prompts.ts` 或 `plugins/testpilot/skills/**` 时:`pnpm check:drift`(等价于 `node scripts/check-drift.mjs`)必须绿,`plugins/testpilot/plugin.json` 的 `skillVersions` 跳版本。
改完 skill 记得 `node scripts/build-claude-plugin.mjs` 与 `build-codex-plugin.mjs` 重新生成宿主副本——检查会拦,但拦住之前先想着它:2026-09-14 那次,改过的 skill 有四个文件整整两天没到宿主 agent 手里。

## 验收命令
```bash
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"server:dev": "pnpm --filter testpilot-server dev",
"check:i18n": "node scripts/check-i18n.mjs",
"test:hooks": "node --test \"plugins/testpilot/hooks/test/*.test.mjs\"",
"check:drift": "node scripts/check-drift.mjs && node scripts/build-claude-plugin.mjs --check && node scripts/build-codex-plugin.mjs --check",
"check:drift": "node scripts/check-drift.mjs",
"build:claude-plugin": "node scripts/build-claude-plugin.mjs",
"cost:report": "node scripts/cost-report.mjs",
"doctor": "node scripts/testpilot-setup.mjs doctor",
Expand Down
6 changes: 3 additions & 3 deletions plugins/testpilot-claude/.claude-plugin/testpilot.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
"skillVersions": {
"testpilot-generate": "2026-09-10.1",
"testpilot-explore": "2026-09-03.1",
"testpilot-stories": "2026-09-10.1",
"testpilot-design": "2026-09-12.1",
"testpilot-stories": "2026-09-14.1",
"testpilot-design": "2026-09-13.1",
"testpilot-run-c": "2026-09-12.3",
"testpilot-evaluation": "2026-09-03.1",
"testpilot-benchmark-design": "2026-09-04.1",
Expand All @@ -16,7 +16,7 @@
"skillDigests": {
"testpilot-generate": "12ee15239b74568a",
"testpilot-explore": "49cd2ccdaf361117",
"testpilot-stories": "078a845773d390a4",
"testpilot-stories": "0e71db20690374b0",
"testpilot-design": "17734a1342ef759e",
"testpilot-run-c": "dc8f6953b1b9efd9",
"testpilot-evaluation": "3d1bc1c2aaeae65e",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,88 +1,66 @@
# 合约交易前端的不变量(领域 REFERENCE)
# 领域不变量怎么变成屏幕判据(领域 REFERENCE)

> 这一份对应 `prompts.ts` 里的 `DOMAIN_PERP`。它是**可选的一段**:在 skill 世界里,「可选」= 这份文件在不在。
> 装上它是一臂,卸掉它是另一臂(`ablate: ["domain-perp"]`)。它存在的目的是被比较:
> 对着观察写的用例只能发现产品**变了**,对着这些规则写的用例才有资格说产品**错了**。
>
> 只写**做过合约前端的人才知道、而且能变成判据**的规则。通用测试方法不在这里(那是 `REFERENCE.md`)。
> 每条规则附一个判据示例,**全部打在屏幕上**:出现了哪一行、那一行里是什么值、哪个数必须不变、
> 前端的拒绝文案。这个产品产出的是端到端 UI 测试——去问被测产品自己的接口,判的就不是用户
> 看得见的那件事(2026-09-12 用户口径,见仓库 CLAUDE.md)。
> **没有一条钉在易变读数上**——有测试扫这份文件(`domain-perp.test.ts`)。
> 对着观察写的用例只能发现产品**变了**,对着领域不变量写的用例才有资格说产品**错了**。

被测对象是合约交易页(下单面板、持仓、挂单)时,设计用例前先读它;不是就跳过。
## 2026-09-13:不变量的正本在规则包里,不在这份文件里

## 规则与判据
这份文件原来自己列着十条合约交易的不变量(步长、80% 价格带、杠杆分档、平仓后消失…)。
那是**领域业务知识放错了地方**:

### 1. 先确认数量单位、步长及该产品的输入归一化规则
只有规格明确要求截断的前端(如本仓库 tier4-demo 演示契约),0.001 步长输入 0.0016 才应提交 0.001。交易所接受的精度限制不能证明前端必须截断;有的入口会直接拒绝非整步数量。以下示例仅用于明确的截断契约。0.001 到 0.002 是翻倍,不能描述成十倍。
```json
{"expected": "仓位表里出现 BTC 的一行,数量列显示 0.001", "oracle": {"kind": "count", "value": "BTC", "op": "eq", "n": 1}}
```
- 它对每个项目都下发,换一个产品(电商、后台)收到的是一段用不上的合约散文;
- 它和规则包里的 `R-SIZE-PRECISION` / `R-PRICE-PRECISION` / `R-LEVERAGE` 说同一批事,两处会各自漂移;
- **最要命的是它以权威口吻写**。同样的内容搬进规则包之后,校验当场拦了两次:
「domain-reference 这类来源只能支撑假设,撑不起要求」,以及「没有产品来源的假设不能自称 P0」。
于是那五条进去时只能是 `claimType: hypothesis`、不带 `riskFloor`——
而假设在下游有明确待遇:**只能变成开放问题,永远不能变成验收标准**。
它们在这份散文里被当成事实用了很久。

### 2. 价格按 tickSize 对齐;离参考价超过交易所的带宽(Hyperliquid:80%)的限价单在前端就被拒
80% 是历史演示契约,不是所有交易所或当前所有产品的固定规则。先获取被测版本的带宽与拒绝文案;下面只有在规格明确该文案时适用。
```json
{"expected": "页面显示「Order price cannot be more than 80% away from the reference price」", "oracle": {"kind": "text", "value": "Order price cannot be more than 80% away from the reference price"}}
```
**所以:这个产品的领域不变量,看你这个单元材料里的 `rules`。** 每条规则自带
`claimType`(要求 / 观察 / 假设)、`sourceRefs` 与 `riskFloor`——那三样决定了你能拿它做什么:

### 3. 杠杆上限随名义价值分档;账户最终的杠杆以仓位行显示的为准
```json
{"expected": "合约头部的杠杆徽标显示 5x,且仓位行的杠杆与它一致", "oracle": {"kind": "text", "value": "5x"}}
```
| claimType | 你能用它做什么 |
|---|---|
| `normative` | 可以写成验收标准与用例的期望;`riskFloor` 是这条用例优先级的下限 |
| `observed` | 可以写成用例,但期望只能是「观察到的那样」,不能说产品「应该」如此 |
| `hypothesis` | **只能变成开放问题**。不要把它写成一条会判失败的断言——那是在拿没证实的东西判产品有罪 |

### 4. 逐仓 / 全仓切换:模式与余额重算是独立验收点
下面仅证明模式已切换;余额/强平价重算需要冻结行情、计算规格和独立判据。模式通过不能证明重算正确。
```json
{"expected": "下单面板的保证金模式控件显示 Isolated", "oracle": {"kind": "text", "value": "Isolated"}}
```
## 剩下的部分:怎么把一条领域规则变成屏幕上的判据

规则说的是产品**应该**怎样,判据说的是**屏幕上**能看到什么。这中间的翻译有固定几招:

### 5. 触发单类型与价格关系分别验证
相对入场价的方向规则仅在该产品规格要求时适用;盈利后移动止损等场景不能套用统一方向。下面只证明触发单类型存在,触发价必须另有精确判据。
### 1. 「某件事发生了」→ 出现了哪一行
```json
{"expected": "当前委托表里出现一行 Take Profit Market,市场列是 BTC", "oracle": {"kind": "count", "value": "Take Profit Market", "op": "eq", "n": 1}}
{"expected": "仓位表里出现 BTC 的一行,数量列显示 0.001", "oracle": {"kind": "count", "value": "BTC", "op": "eq", "n": 1}}
```

### 6. 一键平仓后该币种从仓位表里消失
### 2. 「某件事被撤销了」→ 那一行不见了
```json
{"expected": "平仓后仓位表不再列出 BTC(它是唯一持仓时显示空态文案)", "oracle": {"kind": "noText", "value": "BTC-USD"}}
```

### 7. 超过可用保证金的下单被拒,名义仓位不变(tier 2:两次读数的关系)
### 3. 「产品应该拒绝」→ 拒绝文案逐字出现
拒绝文案是产品自己说的话,**逐字抄**,不要翻译也不要自己编一句更顺口的。
```json
{"expected": "下单被拒,当前委托表的行数在提交前后不变", "oracle": {"kind": "delta", "value": "当前委托", "direction": "unchanged"}}
{"expected": "页面显示「Order must have minimum value of $10.」", "oracle": {"kind": "text", "value": "Order must have minimum value of $10."}}
```

### 8. 撤单后挂单从当前委托表里消失
### 4. 「这一步不该改变什么」→ 两次读数的关系(tier 2)
```json
{"expected": "撤单后当前委托表里没有 BTC 的挂单", "oracle": {"kind": "noText", "value": "BTC-USD"}}
{"expected": "下单被拒,当前委托表的行数在提交前后不变", "oracle": {"kind": "delta", "value": "当前委托", "direction": "unchanged"}}
```

### 9. 资金费率、倒计时、24h 量、标记价 / 预言机价是**易变读数**,禁止钉在判据里
能断言的只有「字段存在」或一个关系。
### 5. 易变读数只能断言存在或关系,不能钉住数值
标记价、预言机价、资金费率、倒计时、24h 量都在动。钉住一个数的用例下一分钟就红,
而它红的时候产品什么事都没有。
```json
{"expected": "账户权益字段在页面上存在(只断言存在,不钉数值)", "oracle": {"kind": "count", "value": "Account Equity", "op": "gte", "n": 1}}
{"expected": "标头显示 Funding / Countdown 字段", "oracle": {"kind": "text", "value": "Funding / Countdown"}}
```

### 10. 断线重连后挂单列表与重连前一致
下面的 exists 仅是接口存在性检查,**不能证明重连后 UI/API 一致**。一致性需按同一订单 ID 比较快照;当前单一 oracle 无法组合时标为未覆盖,不能用存在性替代。
### 6. 判决永远从屏幕读,不去问被测产品自己的接口
接口说下单成功而屏幕上没有那一行,这条用例会通过,而产品其实是坏的。
会动的数字不是去问接口的理由——那恰恰是上面第 4、5 条的理由。
```json
{"expected": "重连后当前委托表仍列出同样多的行", "oracle": {"kind": "delta", "value": "当前委托", "direction": "unchanged"}}
{"expected": "屏幕显示「Limit order placed.」", "oracle": {"kind": "text", "value": "Limit order placed."}}
```

## 写法约束

- 涉及资金或仓位状态的用例**必须**带 `api` 判据;`text` 只用于前端的拒绝文案。
- 接口地址与账户地址走 `${env.*}` 占位符,不写字面量。
- 数组元素按字段选(`[position.coin=BTC]`),不按下标——下标随账户里有几个仓位而变。
- `settleMs` 给 2–4 秒:交易所接口在下单后有传播延迟。

## 适用范围与依据(2026-09-09 核验)

这是一组测试设计候选规则,不是跨交易所统一交易规则。优先级:被测版本需求/契约 → 该交易所官方规格 → 待确认建议。每个 sourceRef 存在只证明定位成功,不证明它支持结论;人工审核需核对规则族、参数、单位和版本。

- [Hyperliquid tick and lot size](https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/tick-and-lot-size) 定义精度限制,不定义所有前端如何修改用户输入。
- [Hyperliquid order book](https://hyperliquid.gitbook.io/hyperliquid-docs/trading/order-book) 定义 tick/lot 对齐。
- [Binance Spot filters](https://github.com/binance/binance-spot-api-docs/blob/master/filters.md) 是现货过滤器资料;不能未经核对推广至合约。

金额/数量优先使用十进制字符串,API 判据精确比较,不自动引入容差。数组需唯一币种/订单 ID;多项匹配不可观测。`unit: {path,value}` 可验证单位;`freshness: {timestampPath,maxAgeMs}` 检查毫秒时间戳或 ISO 时间。前后关系必须在相同币种、账户、环境复位条件下取样。等待固定时间不证明状态已传播;过期或缺失时间应记录未观测。
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,15 @@
},
"risk": { "impact": "funds-and-exposure", "reason": "数量取整错误会改变实际敞口", "ruleRefs": ["R-SIZE-PRECISION"] },
"testData": { "fixtureRef": "perp-deterministic-v1", "values": [{ "name": "size", "value": "0.001", "unit": "BTC", "source": "R-SIZE-PRECISION" }] },
"assertions": [{ "id": "A-SIZE", "statement": "接口里的持仓数量等于 0.001 BTC", "ruleRefs": ["R-SIZE-PRECISION"], "oracle": { "kind": "api", "url": "${env.HL_INFO}", "method": "POST", "body": "…", "path": "…szi", "op": "eq", "value": "0.001" } }],
"assertions": [{ "id": "A-SIZE", "statement": "仓位表里 BTC 那一行的数量列显示 0.001", "ruleRefs": ["R-SIZE-PRECISION"], "oracle": { "kind": "text", "value": "0.001" } }],
"readiness": { "design": "candidate", "execution": "requires-fixture", "reason": "缺可控持仓 fixture" }
}
```

> `assertions[].oracle` 和顶层 `oracle` 是同一套判据,**同样只能从屏幕读**(CLAUDE.md 红线;
> 门禁规则 `oracle-offsite` 两边都查)。2026-09-13 之前这里的示例写的是 `kind: "api"`——
> 红线定了之后它没跟上,实测有用例照着它写出了断言被测站接口的判据,复核时被驳回。

| 字段 | 给了就会被核对的事 |
|---|---|
| `scenarioType` | 和 `designMethod` 分开:负例同样是用某种方法设计出来的。标了负向场景又标了方法,却不给 `design`,会被拒 |
Expand Down
5 changes: 5 additions & 0 deletions plugins/testpilot-claude/skills/testpilot-stories/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ rule bf648e58 When the budget of stories is smaller than the material, spr
- `acceptance` 写成 Given / When / Then,一条判据一项,界面文案**逐字引用规格自己的话**。
「Given 购物车里有一件商品 / When 用户点 Checkout / Then 页面是 Checkout: Your Information」。
**没有 When 的是描述,不是判据。**
- **一条长故事走完,就顺带走完了几条短故事——用 `subsumes` 说出来。**
展示型的短故事(「页头显示标记价」)单独立一条,下游只会长出「打开页面 + 看一眼」的两步用例,
而那种用例的判据在初始页面上就已经成立,它通过时什么都没证明。
一条真实旅程本来就会路过它:把它写进 `subsumes`,被覆盖的故事不再单独出用例,
它的判据改由这条长旅程路上的断言了结。只覆盖一层,别覆盖一条自己也在覆盖别人的故事。
- 规格有自己的 id(US-01 之类)就沿用;没有就编号 S-01、S-02……
- 规格明说不在范围内的东西,不要为它产故事。
- 材料可能是**好几份文档**,每份以 `===== 路径 =====` 开头。**每一份都要覆盖到。**
Expand Down
Loading
Loading